config: first version of storing pixel maps and configs in SPIFFS
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				ci/woodpecker/push/woodpecker Pipeline failed
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	ci/woodpecker/push/woodpecker Pipeline failed
				
			This commit is contained in:
		
							
								
								
									
										118
									
								
								src/Config.cpp
									
									
									
									
									
								
							
							
						
						
									
										118
									
								
								src/Config.cpp
									
									
									
									
									
								
							| @@ -1,8 +1,16 @@ | ||||
| #include "./Config.h" | ||||
| #include "./Static.h" | ||||
| #include "./Sequencer.h" | ||||
|  | ||||
| #include <ArduinoLog.h> | ||||
| #include <ArduinoJson.h> | ||||
| #include <EEPROM.h> | ||||
|  | ||||
| #include <LittleFS.h> | ||||
| #include <vector> | ||||
|  | ||||
| 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<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); | ||||
|     } | ||||
|  | ||||
|     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: | ||||
|   | ||||
							
								
								
									
										80
									
								
								src/Config.h
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								src/Config.h
									
									
									
									
									
								
							| @@ -1,78 +1,12 @@ | ||||
| #pragma once | ||||
| #include <Figments.h> | ||||
|  | ||||
| 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); | ||||
| }; | ||||
|   | ||||
| @@ -1,18 +1,29 @@ | ||||
| #include "Sequencer.h" | ||||
| #include <MainLoop.h> | ||||
| #include "Static.h" | ||||
|  | ||||
| Sequencer::Sequencer(std::vector<Sequencer::Scene> &&scenes) : | ||||
| Sequencer::Sequencer() : | ||||
|   Task("SceneSequencer"), | ||||
|   m_idx(0), | ||||
|   m_scenes(std::move(scenes)) | ||||
|   m_idx(0) | ||||
| { | ||||
| } | ||||
|  | ||||
| Sequencer::Sequencer(std::vector<Sequencer::Scene> &&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<Scene> &&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); | ||||
|   | ||||
| @@ -10,14 +10,16 @@ public: | ||||
|       public: | ||||
|         const char* name; | ||||
|         std::vector<const char*> patterns; | ||||
|         void start(); | ||||
|         void stop(); | ||||
|     }; | ||||
|  | ||||
|     Sequencer(std::vector<Scene> &&scenes); | ||||
|     Sequencer(std::vector<Scene> &&scenes, int startingIndex); | ||||
|     Sequencer(); | ||||
|  | ||||
|     void loop() override; | ||||
|     void onStart() override; | ||||
|     void handleEvent(const InputEvent& evt) override; | ||||
|     void setScenes(std::vector<Scene> &&scenes); | ||||
|  | ||||
|     const char* currentSceneName(); | ||||
|     const std::vector<Scene> scenes() const; | ||||
|   | ||||
							
								
								
									
										50
									
								
								src/main.cpp
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								src/main.cpp
									
									
									
									
									
								
							| @@ -12,17 +12,13 @@ | ||||
| #include "Static.h" | ||||
| #include "Config.h" | ||||
|  | ||||
| #include "Sequencer.h" | ||||
| #include "LogService.h" | ||||
|  | ||||
| #include <time.h> | ||||
|  | ||||
| #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<DrainAnimation>::instance(), /*&configDisplay,*/ &inputBlip, &power} | ||||
|     {Static<DrainAnimation>::instance(), /*&configDisplay,*/ Static<InputBlip>::instance(), &power} | ||||
| }; | ||||
|  | ||||
| // Cycle some random colors | ||||
|   | ||||
		Reference in New Issue
	
	Block a user