From f0abdc05676037770b81a17fc54b1d843293f458 Mon Sep 17 00:00:00 2001 From: Torrie Fischer Date: Sat, 18 Feb 2023 16:14:00 +0100 Subject: [PATCH] config: first version of storing pixel maps and configs in SPIFFS --- src/Config.cpp | 118 +++++++++++++++++++++++++++++++++++----------- src/Config.h | 80 ++++--------------------------- src/Sequencer.cpp | 66 ++++++++++++++++---------- src/Sequencer.h | 6 ++- src/main.cpp | 50 +------------------- 5 files changed, 144 insertions(+), 176 deletions(-) diff --git a/src/Config.cpp b/src/Config.cpp index 83293a0..b06c0a3 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -1,8 +1,16 @@ #include "./Config.h" #include "./Static.h" +#include "./Sequencer.h" + #include +#include #include +#include +#include + +StaticJsonDocument<256> jsonConfig; + constexpr uint16_t HardwareConfig::MAX_LED_NUM; HardwareConfig @@ -15,7 +23,7 @@ HardwareConfig::load() { #ifndef BOARD_TEENSY EEPROM.end(); #endif - Log.notice("Loaded config version %d, CRC %d", ret.version, ret.checksum); + Log.notice("config: Loaded SRAM config version %d, CRC %d", ret.version, ret.checksum); return ret; } @@ -33,18 +41,10 @@ HardwareConfig::save() { #endif } -LinearCoordinateMapping -HardwareConfig::toCoordMap() const -{ - auto pixelCount = min(HardwareConfig::MAX_LED_NUM, std::max((uint16_t)1, data.pixelCount)); - auto startPixel = min(pixelCount, std::max((uint16_t)1, data.startPixel)); - return LinearCoordinateMapping{pixelCount, startPixel}; -} - bool HardwareConfig::isValid() const { - return version == 2 && checksum == getCRC() && data.pixelCount <= MAX_LED_NUM; + return version == 3 && checksum == getCRC(); } uint8_t @@ -68,18 +68,82 @@ HardwareConfig::getCRC() const void ConfigService::onStart() { - Log.notice("Starting configuration service..."); + Log.notice("config: Starting configuration service..."); m_config = HardwareConfig::load(); if (m_config.isValid()) { - Log.notice("Configuration found!"); + Log.notice("config: Configuration found!"); } else { - Log.notice("No configuration found. Writing defaults..."); + Log.notice("config: No configuration found. Writing defaults..."); m_config = HardwareConfig{}; m_config.save(); } - m_coordMap = m_config.toCoordMap(); + if (strlen(m_config.data.loadedProfile) == 0) { + strcpy(m_config.data.loadedProfile, "default"); + } + loadProfile(m_config.data.loadedProfile); +} - Log.notice("Configured to use %d pixels, starting at %d", m_config.data.pixelCount, m_config.data.startPixel); +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 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()->dispatch(InputEvent{InputEvent::StartThing, taskList[i].as()}); + } + Log.notice("config: Loaded!"); + } else { + Log.warning("config: Could not load profile %s!", profileName); + } + + 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)); } void @@ -87,24 +151,22 @@ ConfigService::loop() { } +const char* +ConfigService::loadedProfile() const +{ + return m_config.data.loadedProfile; +} + void ConfigService::handleEvent(const InputEvent &evt) { switch(evt.intent) { - case InputEvent::SetDisplayLength: - //Log.info("Updating pixel count from %d to %d", m_coordMap.pixelCount, evt.asInt()); - m_config.data.pixelCount = evt.asInt(); - m_coordMap = m_config.toCoordMap(); - //Log.info("Count is now %d", m_coordMap.pixelCount); - break; - case InputEvent::SetDisplayOffset: - //Log.info("Updating pixel offset from %d to %d", m_coordMap.startPixel, evt.asInt()); - m_config.data.startPixel = evt.asInt(); - m_coordMap = m_config.toCoordMap(); - //Log.info("Offset is now %d", m_coordMap.startPixel); - break; + case InputEvent::LoadConfigurationByName: + Log.notice("Reloading configuration %s", evt.asString()); + strcpy(m_config.data.loadedProfile, evt.asString()); + loadProfile(evt.asString()); case InputEvent::SaveConfigurationRequest: - //Log.info("Saving configuration"); + Log.notice("Saving configuration"); m_config.save(); break; default: diff --git a/src/Config.h b/src/Config.h index c5bf96c..b158c8a 100644 --- a/src/Config.h +++ b/src/Config.h @@ -1,78 +1,12 @@ #pragma once #include - -struct MaskCoordinateMapping : CoordinateMapping { - struct Span { - int length = 0; - int x = 0; - int y = 0; - - Span(int length, int x, int y) : length(length), x(x), y(y) {} - }; - - Span displayMap[13] = { - {6, 0, 6}, - {6, 1, 6}, - {7, 2, 6}, - {9, 3, 4}, - {14, 4, 4}, - {17, 5, 0}, - {12, 6, 2}, - {18, 7, 0}, - {14, 8, 4}, - {9, 9, 5}, - {7, 10, 4}, - {6, 11, 5}, - {6, 12, 5} - }; - - VirtualCoordinates physicalToVirtualCoords(const PhysicalCoordinates localCoords) const override { - int offset = localCoords.x; - for(int i = 0; i < 12; i++) { - if (offset > displayMap[i].length) { - offset -= displayMap[i].length; - } else { - return VirtualCoordinates{i, offset}; - } - } - } - - PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const override { - const uint8_t spanIdx = scale8(12, virtualCoords.x); - const uint8_t spanOffset = scale8(17, virtualCoords.y); - return PhysicalCoordinates{spanIdx, spanOffset}; - } - - int physicalCoordsToIndex(const PhysicalCoordinates localCoords) const override { - uint8_t idx = 0; - bool inverse = false; - for(int i = 0; i < localCoords.x; i++) { - idx += displayMap[i].length; - inverse = !inverse; - } - if (inverse) { - idx += std::max(0, displayMap[localCoords.x].length - 1 - std::max(0, (int)localCoords.y - displayMap[localCoords.x].y)); - } else { - idx += std::min((int)displayMap[localCoords.x].length - 1, std::max(0, (int)localCoords.y - displayMap[localCoords.x].y)); - } - return idx; - } - - unsigned int physicalPixelCount() const override { - int total = 0; - for(int i = 0; i < 13; i++) { - total += displayMap[i].length; - } - return total; - } -}; +#include "JsonCoordinateMapping.h" struct HardwareConfig { uint8_t version = 3; uint8_t checksum = 0; struct Data { - uint16_t pixelCount = 255; - uint16_t startPixel = 0; + char loadedProfile[16] = {0}; uint8_t lastRed = 255; uint8_t lastGreen = 255; uint8_t lastBlue = 255; @@ -84,7 +18,6 @@ struct HardwareConfig { void save(); bool isValid() const; - LinearCoordinateMapping toCoordMap() const; static constexpr uint16_t MAX_LED_NUM = 255; private: @@ -101,10 +34,13 @@ struct ConfigService: public Task { void onStart(); void loop() override; void handleEvent(const InputEvent &evt) override; - const CoordinateMapping* coordMap() const { return /*&m_maskMap;*/ &m_coordMap; } + const CoordinateMapping* coordMap() const { return &m_jsonMap; } + const char* loadedProfile() const; private: HardwareConfig m_config; - MaskCoordinateMapping m_maskMap; - LinearCoordinateMapping m_coordMap; + JsonCoordinateMapping m_jsonMap; + + void loadProfile(const char* name); + void loadMap(const String& mapName); }; diff --git a/src/Sequencer.cpp b/src/Sequencer.cpp index c07d2cc..a62b9a2 100644 --- a/src/Sequencer.cpp +++ b/src/Sequencer.cpp @@ -1,18 +1,29 @@ #include "Sequencer.h" #include +#include "Static.h" -Sequencer::Sequencer(std::vector &&scenes) : +Sequencer::Sequencer() : Task("SceneSequencer"), - m_idx(0), - m_scenes(std::move(scenes)) + m_idx(0) { } -Sequencer::Sequencer(std::vector &&scenes, int startIndex) : - Task("SceneSequencer"), - m_idx(startIndex), - m_scenes(std::move(scenes)) +void +Sequencer::Scene::start() { + for(const char* pattern : patterns) { + Log.verbose("Starting pattern task %s", pattern); + MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern}); + } +} + +void +Sequencer::Scene::stop() +{ + for(const char* pattern : patterns) { + Log.verbose("Stopping pattern task %s", pattern); + MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, pattern}); + } } void @@ -35,32 +46,35 @@ Sequencer::onStart() { } +void +Sequencer::setScenes(std::vector &&scenes) +{ + Log.notice("Updated scenes"); + m_idx = 0; + m_scenes = scenes; +} + void Sequencer::handleEvent(const InputEvent& evt) { - if (evt.intent == InputEvent::ReadyToRoll) { - Log.notice("Starting pattern %s!", m_scenes[m_idx].name); - for(const char* pattern : m_scenes[m_idx].patterns) { - Log.verbose("Starting pattern task %s", pattern); - MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern}); - } - } - if (evt.intent == InputEvent::SetPattern && evt.asString() == m_scenes[m_idx].name) { + if (evt.intent == InputEvent::ReadyToRoll && !m_scenes.empty()) { + Log.notice("Starting pattern %s!", m_scenes[m_idx].name); + for(const char* pattern : m_scenes[m_idx].patterns) { + Log.verbose("Starting pattern task %s", pattern); + MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern}); + } + } else if (evt.intent == InputEvent::SetPattern && evt.asString() == m_scenes[m_idx].name) { return; - } - if (evt.intent == InputEvent::SetPattern || evt.intent == InputEvent::NextPattern || evt.intent == InputEvent::PreviousPattern) { + } else if (evt.intent == InputEvent::SetPattern || evt.intent == InputEvent::NextPattern || evt.intent == InputEvent::PreviousPattern) { Log.notice("Switching pattern!"); - for(const char* pattern : m_scenes[m_idx].patterns) { - Log.verbose("Stopping pattern task %s", pattern); - MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, pattern}); - } + + m_scenes[m_idx].stop(); if (evt.intent == InputEvent::NextPattern) { m_idx++; } else if (evt.intent == InputEvent::PreviousPattern) { m_idx--; } else { - //m_idx = evt.asInt(); for(m_idx = 0; m_idx < m_scenes.size(); m_idx++) { if (!strcmp(evt.asString(), m_scenes[m_idx].name)) { break; @@ -76,9 +90,9 @@ Sequencer::handleEvent(const InputEvent& evt) m_idx = 0; } - for(const char* pattern : m_scenes[m_idx].patterns) { - Log.verbose("Starting pattern task %s", pattern); - MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern}); - } + m_scenes[m_idx].start(); } } + +STATIC_ALLOC(Sequencer); +STATIC_TASK(Sequencer); diff --git a/src/Sequencer.h b/src/Sequencer.h index 8f5cdd3..52a9a27 100644 --- a/src/Sequencer.h +++ b/src/Sequencer.h @@ -10,14 +10,16 @@ public: public: const char* name; std::vector patterns; + void start(); + void stop(); }; - Sequencer(std::vector &&scenes); - Sequencer(std::vector &&scenes, int startingIndex); + Sequencer(); void loop() override; void onStart() override; void handleEvent(const InputEvent& evt) override; + void setScenes(std::vector &&scenes); const char* currentSceneName(); const std::vector scenes() const; diff --git a/src/main.cpp b/src/main.cpp index 17cabd1..81e3d9a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,17 +12,13 @@ #include "Static.h" #include "Config.h" -#include "Sequencer.h" #include "LogService.h" #include #include "animations/Power.h" -#include "animations/SolidAnimation.h" -#include "animations/Chimes.h" -#include "animations/Flashlight.h" #include "animations/Drain.h" -#include "animations/UpdateStatus.h" +#include "animations/InputBlip.h" #include "inputs/ColorCycle.h" #include "inputs/Buttons.h" @@ -65,32 +61,6 @@ REGISTER_TASK(power); } });*/ -class InputBlip: public Figment { -public: - InputBlip() : Figment("InputBlip", Task::Stopped) {} - - void handleEvent(const InputEvent& evt) override { - if (evt.intent != InputEvent::None) { - m_time = qadd8(m_time, 5); - } - } - void loop() override { - if (m_time > 0) { - m_time--; - } - } - void render(Display* dpy) const override { - if (m_time > 0) { - dpy->pixelAt(0) = CRGB(0, brighten8_video(ease8InOutApprox(m_time)), 0); - } - } -private: - uint8_t m_time = 0; -}; - -InputBlip inputBlip; -REGISTER_TASK(inputBlip); - InputFunc randomPulse([]() { static unsigned int pulse = 0; EVERY_N_MILLISECONDS(25) { @@ -131,22 +101,6 @@ InputMapper keyMap([](const InputEvent& evt) { REGISTER_TASK(keyMap); - -#ifndef DEFAULT_PATTERN_INDEX -#define DEFAULT_PATTERN_INDEX 0 -#endif - -Sequencer sequencer{{ - {"Idle", {"Solid", "MPU5060", "Pulse", "IdleColors", "CircadianRhythm"}}, - {"Acid", {"Chimes", "Pulse", "MPU5060", "IdleColors", "Rainbow"}}, - {"Solid", {"Solid", "MPU5060", "Pulse", "CircadianRhythm"}}, - {"Interactive", {"Drain", "MPU5060", "CircadianRhythm"}}, - {"Flashlight", {"Flashlight"}}, - {"Gay", {"Solid", "Pulse", "Rainbow"}}, -}, DEFAULT_PATTERN_INDEX}; - -REGISTER_TASK(sequencer); - class BPM : public InputSource { public: BPM() : InputSource("BPM") {} @@ -218,7 +172,7 @@ REGISTER_TASK(renderer); Renderer configRenderer{ {&dpy}, - {Static::instance(), /*&configDisplay,*/ &inputBlip, &power} + {Static::instance(), /*&configDisplay,*/ Static::instance(), &power} }; // Cycle some random colors