#include "./Config.h" #include "./Static.h" #include "./Sequencer.h" #include #include #include #include #include void ConfigTaskMixin::handleEvent(const InputEvent &evt) { if (evt.intent == InputEvent::ConfigurationChanged) { const JsonObject& cfg = *evt.as(); 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(&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 scenes; for(JsonPair pair : sceneList) { Log.notice("config: \tFound scene %s", pair.key().c_str()); std::vector patterns; for(const char* taskName : pair.value().as()) { patterns.push_back(taskName); } scenes.push_back(Sequencer::Scene{pair.key().c_str(), patterns}); } Static::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()}); } 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::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::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& ConfigService::commands() const { static const std::vector _commands = { {"save", doSave}, {"profile", doSetProfile}, {"maps", doMapList}, {"coordmap", doCoordMap} }; return _commands; } STATIC_ALLOC(ConfigService); STATIC_TASK(ConfigService);