renderbug-cpp/src/Config.cpp
2023-12-12 19:58:10 +01:00

311 lines
8.0 KiB
C++

#include "./Config.h"
#include "./Static.h"
#include "./Sequencer.h"
#include <ArduinoLog.h>
#include <ArduinoJson.h>
#include <EEPROM.h>
#include <LittleFS.h>
#include <vector>
void
ConfigTaskMixin::handleEvent(const InputEvent &evt)
{
if (evt.intent == InputEvent::ConfigurationChanged) {
const JsonObject& cfg = *evt.as<JsonObject>();
handleConfigChange(Configuration(cfg));
}
}
Configuration::Configuration(const JsonObject& data)
: m_json(data)
{
}
const char*
Configuration::get(const char* key, const char* defaultVal) const
{
if (m_json.containsKey(key)) {
return m_json[key];
} else {
return defaultVal;
}
}
int
Configuration::get(const char* key, int defaultVal) const
{
if (m_json.containsKey(key)) {
return m_json[key];
} else {
return defaultVal;
}
}
bool
Configuration::get(const char* key, bool defaultVal) const
{
if (m_json.containsKey(key)) {
return m_json[key];
} else {
return defaultVal;
}
}
StaticJsonDocument<2048> jsonConfig;
constexpr uint16_t HardwareConfig::MAX_LED_NUM;
HardwareConfig
HardwareConfig::load() {
HardwareConfig ret;
#ifndef BOARD_TEENSY
EEPROM.begin(sizeof(ret));
#endif
EEPROM.get(0, ret);
#ifndef BOARD_TEENSY
EEPROM.end();
#endif
Log.notice("config: Loaded SRAM config version %d, CRC %d", ret.version, ret.checksum);
return ret;
}
void
HardwareConfig::save() {
HardwareConfig dataCopy{*this};
dataCopy.checksum = getCRC();
#ifndef BOARD_TEENSY
EEPROM.begin(sizeof(dataCopy));
#endif
EEPROM.put(0, dataCopy);
#ifndef BOARD_TEENSY
EEPROM.commit();
EEPROM.end();
#endif
}
bool
HardwareConfig::isValid() const
{
return version == 3 && checksum == getCRC();
}
uint8_t
HardwareConfig::getCRC() const
{
const unsigned char* message = reinterpret_cast<const unsigned char*>(&data);
constexpr uint8_t length = sizeof(data);
unsigned char i, j, crc = 0;
for(i = 0; i < length; i++) {
crc ^= message[i];
for(j = 0; j < 8; j++) {
if (crc & 1) {
crc ^= CRC7_POLY;
}
crc >>= 1;
}
}
return crc;
}
void
ConfigService::onStart()
{
Log.notice("config: Starting configuration service...");
m_config = HardwareConfig::load();
if (m_config.isValid()) {
Log.notice("config: Configuration found!");
} else {
Log.notice("config: No configuration found. Writing defaults...");
m_config = HardwareConfig{};
m_config.save();
}
bool loaded = false;
if (m_overrideProfile != nullptr) {
loaded = loadProfile(m_overrideProfile);
}
if (!loaded && strlen(m_config.data.loadedProfile) > 0) {
loaded = loadProfile(m_config.data.loadedProfile);
}
if (!loaded && !loadProfile("default")) {
Log.fatal("Could not load default fallback profile! No tasks will be started.");
m_jsonMap.loadDefault();
}
}
void
ConfigService::overrideProfile(const char* profileName)
{
m_overrideProfile = profileName;
}
bool
ConfigService::loadMap(const String& mapName)
{
String fname = String("/maps/") + mapName + ".json";
if (LittleFS.exists(fname)) {
File configFile = LittleFS.open(fname, "r");
Log.notice("config: Loading coordinate map from %s", fname.c_str());
deserializeJson(jsonConfig, configFile);
configFile.close();
JsonArray strideList = jsonConfig["strides"];
m_jsonMap.load(strideList);
return true;
} else {
return false;
}
}
bool
ConfigService::loadProfile(const char* profileName)
{
Log.notice("config: Loading profile %s...", profileName);
String fname = String("/profiles/") + profileName + ".json";
LittleFS.begin();
if (LittleFS.exists(fname)) {
strncpy(m_config.data.loadedProfile, fname.c_str(), sizeof(m_config.data.loadedProfile));
File configFile = LittleFS.open(fname, "r");
jsonConfig.clear();
deserializeJson(jsonConfig, configFile);
configFile.close();
//int profileLogLevel = max(0, min(6, jsonConfig["logLevel"]));
//Log.setLevel(profileLogLevel);
//Log.trace("config: \t %d logging level");
JsonObject sceneList = jsonConfig["scenes"];
std::vector<Sequencer::Scene> scenes;
for(JsonPair pair : sceneList) {
Log.notice("config: \tFound scene %s", pair.key().c_str());
std::vector<const char*> patterns;
for(const char* taskName : pair.value().as<JsonArray>()) {
patterns.push_back(taskName);
}
scenes.push_back(Sequencer::Scene{pair.key().c_str(), patterns});
}
Static<Sequencer>::instance()->setScenes(std::move(scenes));
JsonArray taskList = jsonConfig["tasks"];
Log.notice("config: Starting %d tasks", taskList.size());
for(int i = 0; i < taskList.size();i++) {
MainLoop::instance()->dispatchSync(InputEvent{InputEvent::StartThing, taskList[i].as<const char*>()});
}
JsonObject defaults = jsonConfig["defaults"];
Log.notice("config: Loading %d app configurations", defaults.size());
MainLoop::instance()->dispatchSync(InputEvent{InputEvent::ConfigurationChanged, &defaults});
String mapName = jsonConfig["surfaceMap"];
jsonConfig.clear();
if (mapName.isEmpty()) {
Log.warning("config: No coordinate map defined! Defaulting to linear mapping.", mapName.c_str());
m_jsonMap.loadDefault();
} else if (!loadMap(mapName)) {
Log.warning("config: Couldn't load coordinate map %s!!! Defaulting to linear mapping.", mapName.c_str());
m_jsonMap.loadDefault();
}
Log.notice("config: Loaded!");
strcpy(m_config.data.loadedProfile, profileName);
} else {
Log.warning("config: Could not find profile json %s!", fname.c_str());
return false;
}
LittleFS.end();
Log.notice("config: Configured to use %d pixels", m_jsonMap.physicalPixelCount());
PhysicalCoordinates topLeft = m_jsonMap.virtualToPhysicalCoords({0, 0});
PhysicalCoordinates bottomRight = m_jsonMap.virtualToPhysicalCoords({255, 255});
Log.verbose(" (0,0) -> (%d, %d) -> %d", topLeft.x, topLeft.y, m_jsonMap.physicalCoordsToIndex(topLeft));
Log.verbose(" (255,255) -> (%d, %d) -> %d", bottomRight.x, bottomRight.y, m_jsonMap.physicalCoordsToIndex(bottomRight));
return true;
}
void
ConfigService::loop()
{
}
const char*
ConfigService::loadedProfile() const
{
return m_config.data.loadedProfile;
}
void
ConfigService::handleEvent(const InputEvent &evt)
{
switch(evt.intent) {
case InputEvent::LoadConfigurationByName:
Log.notice("Reloading configuration %s", evt.asString());
loadProfile(evt.asString());
case InputEvent::SaveConfigurationRequest:
Log.notice("Saving configuration");
m_config.save();
break;
default:
break;
}
}
void
doMapList(Args& args, Print& out)
{
static const auto conf = Static<ConfigService>::instance();
out.println("Available maps:");
LittleFS.begin();
for(auto it = conf->mapsBegin();it != conf->mapsEnd(); it++) {
out.println(*it);
}
LittleFS.end();
}
void
doSave(Args& args, Print& print)
{
MainLoop::instance()->dispatch(InputEvent::SaveConfigurationRequest);
}
static String s;
void
doSetProfile(Args& args, Print& out)
{
s = args[1];
MainLoop::instance()->dispatch(InputEvent{InputEvent::LoadConfigurationByName, s.c_str()});
}
void
doCoordMap(Args& args, Print& out)
{
VirtualCoordinates coords{atoi(args[1].c_str()), atoi(args[2].c_str())};
auto map = Static<ConfigService>::instance()->coordMap();
auto pPos = map->virtualToPhysicalCoords(coords);
auto idx = map->physicalCoordsToIndex(pPos);
char buf[32];
sprintf(buf, "(%d, %d) -> (%d, %d) -> %d", coords.x, coords.y, pPos.x, pPos.y, idx);
out.println(buf);
}
const std::vector<Command>&
ConfigService::commands() const
{
static const std::vector<Command> _commands = {
{"save", doSave},
{"profile", doSetProfile},
{"maps", doMapList},
{"coordmap", doCoordMap}
};
return _commands;
}
STATIC_ALLOC(ConfigService);
STATIC_TASK(ConfigService);