2021-03-29 08:10:55 +00:00
|
|
|
#include "./Config.h"
|
|
|
|
#include "./Static.h"
|
2023-02-18 15:14:00 +00:00
|
|
|
#include "./Sequencer.h"
|
|
|
|
|
2021-03-29 08:10:55 +00:00
|
|
|
#include <ArduinoLog.h>
|
2023-02-18 15:14:00 +00:00
|
|
|
#include <ArduinoJson.h>
|
2021-03-31 18:50:00 +00:00
|
|
|
#include <EEPROM.h>
|
2021-03-29 08:10:55 +00:00
|
|
|
|
2023-02-18 15:14:00 +00:00
|
|
|
#include <LittleFS.h>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
StaticJsonDocument<256> jsonConfig;
|
|
|
|
|
2021-03-29 08:10:55 +00:00
|
|
|
constexpr uint16_t HardwareConfig::MAX_LED_NUM;
|
|
|
|
|
|
|
|
HardwareConfig
|
|
|
|
HardwareConfig::load() {
|
|
|
|
HardwareConfig ret;
|
2022-06-11 09:02:27 +00:00
|
|
|
#ifndef BOARD_TEENSY
|
2021-04-10 18:10:25 +00:00
|
|
|
EEPROM.begin(sizeof(ret));
|
2022-06-11 09:02:27 +00:00
|
|
|
#endif
|
2021-03-29 08:10:55 +00:00
|
|
|
EEPROM.get(0, ret);
|
2022-06-11 09:02:27 +00:00
|
|
|
#ifndef BOARD_TEENSY
|
2021-04-10 18:10:25 +00:00
|
|
|
EEPROM.end();
|
2022-06-11 09:02:27 +00:00
|
|
|
#endif
|
2023-02-18 15:14:00 +00:00
|
|
|
Log.notice("config: Loaded SRAM config version %d, CRC %d", ret.version, ret.checksum);
|
2021-03-29 08:10:55 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
HardwareConfig::save() {
|
|
|
|
HardwareConfig dataCopy{*this};
|
|
|
|
dataCopy.checksum = getCRC();
|
2022-06-11 09:02:27 +00:00
|
|
|
#ifndef BOARD_TEENSY
|
2021-04-10 18:10:25 +00:00
|
|
|
EEPROM.begin(sizeof(dataCopy));
|
2022-06-11 09:02:27 +00:00
|
|
|
#endif
|
2021-03-29 08:10:55 +00:00
|
|
|
EEPROM.put(0, dataCopy);
|
2022-06-11 09:02:27 +00:00
|
|
|
#ifndef BOARD_TEENSY
|
2021-04-10 18:10:25 +00:00
|
|
|
EEPROM.commit();
|
|
|
|
EEPROM.end();
|
2022-06-11 09:02:27 +00:00
|
|
|
#endif
|
2021-03-29 08:10:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
HardwareConfig::isValid() const
|
|
|
|
{
|
2023-02-18 15:14:00 +00:00
|
|
|
return version == 3 && checksum == getCRC();
|
2021-03-29 08:10:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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()
|
|
|
|
{
|
2023-02-18 15:14:00 +00:00
|
|
|
Log.notice("config: Starting configuration service...");
|
2021-03-29 08:10:55 +00:00
|
|
|
m_config = HardwareConfig::load();
|
|
|
|
if (m_config.isValid()) {
|
2023-02-18 15:14:00 +00:00
|
|
|
Log.notice("config: Configuration found!");
|
2021-03-29 08:10:55 +00:00
|
|
|
} else {
|
2023-02-18 15:14:00 +00:00
|
|
|
Log.notice("config: No configuration found. Writing defaults...");
|
2021-03-29 08:10:55 +00:00
|
|
|
m_config = HardwareConfig{};
|
|
|
|
m_config.save();
|
|
|
|
}
|
2023-02-18 15:14:00 +00:00
|
|
|
if (strlen(m_config.data.loadedProfile) == 0) {
|
|
|
|
strcpy(m_config.data.loadedProfile, "default");
|
|
|
|
}
|
2023-02-18 16:15:21 +00:00
|
|
|
|
|
|
|
if (m_overrideProfile != nullptr) {
|
|
|
|
loadProfile(m_overrideProfile);
|
|
|
|
} else {
|
|
|
|
loadProfile(m_config.data.loadedProfile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ConfigService::overrideProfile(const char* profileName)
|
|
|
|
{
|
|
|
|
m_overrideProfile = profileName;
|
2023-02-18 15:14:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
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 %s", mapName.c_str());
|
|
|
|
deserializeJson(jsonConfig, configFile);
|
|
|
|
configFile.close();
|
|
|
|
JsonArray strideList = jsonConfig["strides"];
|
|
|
|
m_jsonMap.load(strideList);
|
|
|
|
} else {
|
|
|
|
Log.warning("config: Couldn't load coordinate map %s!!! Defaulting to linear mapping.", mapName.c_str());
|
|
|
|
m_jsonMap.loadDefault();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ConfigService::loadProfile(const char* profileName)
|
|
|
|
{
|
|
|
|
Log.notice("config: Loading profile %s...", profileName);
|
|
|
|
String fname = String("/profiles/") + profileName + ".json";
|
|
|
|
|
|
|
|
LittleFS.begin();
|
|
|
|
if (LittleFS.exists(fname)) {
|
|
|
|
File configFile = LittleFS.open(fname, "r");
|
|
|
|
jsonConfig.clear();
|
|
|
|
deserializeJson(jsonConfig, configFile);
|
|
|
|
configFile.close();
|
|
|
|
|
|
|
|
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()->dispatch(InputEvent{InputEvent::StartThing, taskList[i].as<const char*>()});
|
|
|
|
}
|
|
|
|
Log.notice("config: Loaded!");
|
|
|
|
} else {
|
|
|
|
Log.warning("config: Could not load profile %s!", profileName);
|
|
|
|
}
|
2021-03-29 08:10:55 +00:00
|
|
|
|
2023-02-18 15:14:00 +00:00
|
|
|
String configName = jsonConfig["surfaceMap"];
|
|
|
|
jsonConfig.clear();
|
|
|
|
loadMap(configName);
|
|
|
|
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));
|
2021-03-29 08:10:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ConfigService::loop()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2023-02-18 15:14:00 +00:00
|
|
|
const char*
|
|
|
|
ConfigService::loadedProfile() const
|
|
|
|
{
|
|
|
|
return m_config.data.loadedProfile;
|
|
|
|
}
|
|
|
|
|
2021-03-29 08:10:55 +00:00
|
|
|
void
|
|
|
|
ConfigService::handleEvent(const InputEvent &evt)
|
|
|
|
{
|
|
|
|
switch(evt.intent) {
|
2023-02-18 15:14:00 +00:00
|
|
|
case InputEvent::LoadConfigurationByName:
|
|
|
|
Log.notice("Reloading configuration %s", evt.asString());
|
|
|
|
strcpy(m_config.data.loadedProfile, evt.asString());
|
|
|
|
loadProfile(evt.asString());
|
2021-03-29 08:10:55 +00:00
|
|
|
case InputEvent::SaveConfigurationRequest:
|
2023-02-18 15:14:00 +00:00
|
|
|
Log.notice("Saving configuration");
|
2021-03-29 08:10:55 +00:00
|
|
|
m_config.save();
|
|
|
|
break;
|
2021-03-31 18:50:00 +00:00
|
|
|
default:
|
|
|
|
break;
|
2021-03-29 08:10:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
STATIC_ALLOC(ConfigService);
|
2022-06-11 09:02:27 +00:00
|
|
|
STATIC_TASK(ConfigService);
|