wip commit
This commit is contained in:
		| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "version": 1, |   "version": 1, | ||||||
|   "rotation": 3, |   "rotation": 2, | ||||||
|   "strides": [ |   "strides": [ | ||||||
|     {"x": 0, "y": 0, "pixels": 17}, |     {"x": 0, "y": 0, "pixels": 17}, | ||||||
|     {"x": 0, "y": 1, "pixels": 17}, |     {"x": 0, "y": 1, "pixels": 17}, | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ | |||||||
|   "version": 1, |   "version": 1, | ||||||
|   "tasks": [ |   "tasks": [ | ||||||
|     "Renderer", |     "Renderer", | ||||||
|     "U8Display", |  | ||||||
|     "WiFi", |     "WiFi", | ||||||
|     "MQTT", |     "MQTT", | ||||||
|     "ArduinoOTA", |     "ArduinoOTA", | ||||||
| @@ -10,7 +9,7 @@ | |||||||
|     "Serial" |     "Serial" | ||||||
|   ], |   ], | ||||||
|   "scenes": { |   "scenes": { | ||||||
|     "Idle": ["Solid", "MPU5060", "IdleColors", "CircadianRhythm"], |     "Idle": ["Solid", "IdleColors", "CircadianRhythm"], | ||||||
|     "Flashlight": ["Flashlight"] |     "Flashlight": ["Flashlight"] | ||||||
|   }, |   }, | ||||||
|   "surfaceMap": "default", |   "surfaceMap": "default", | ||||||
|   | |||||||
| @@ -5,20 +5,20 @@ | |||||||
|     "Renderer", |     "Renderer", | ||||||
|     "Serial" |     "Serial" | ||||||
|   ], |   ], | ||||||
|   "scenes": { |  | ||||||
|     "Rain": ["Rain", "Rainbow"], |  | ||||||
|     "Test": ["Test"], |  | ||||||
|     "Idle": ["Solid", "Pulse", "Rainbow", "CircadianRhythm"], |  | ||||||
|     "Acid": ["Chimes", "Pulse", "IdleColors", "Rainbow"], |  | ||||||
|     "Flashlight": ["Flashlight"] |  | ||||||
|   }, |  | ||||||
|   "surfaceMap": "djstrip", |   "surfaceMap": "djstrip", | ||||||
|   "defaults": { |   "defaults": { | ||||||
|     "mqtt.ip": "10.0.0.2", |     "mqtt.ip": "10.0.0.2", | ||||||
|     "power.volts": 0, |     "power.volts": 0, | ||||||
|     "power.milliamps": 0, |     "power.milliamps": 0, | ||||||
|     "power.useBPM": false, |     "power.useBPM": false, | ||||||
|     "bpm.idle": 45 |     "bpm.idle": 45, | ||||||
|  |     "scenes": { | ||||||
|  |       "Rain": ["Rain", "Rainbow"], | ||||||
|  |       "Test": ["Test"], | ||||||
|  |       "Idle": ["Solid", "Pulse", "Rainbow", "CircadianRhythm"], | ||||||
|  |       "Acid": ["Chimes", "Pulse", "IdleColors", "Rainbow"], | ||||||
|  |       "Flashlight": ["Flashlight"] | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,11 +7,12 @@ | |||||||
|     "MQTT", |     "MQTT", | ||||||
|     "ArduinoOTA", |     "ArduinoOTA", | ||||||
|     "UpdateStatusAnimation", |     "UpdateStatusAnimation", | ||||||
|     "Serial" |     "Serial", | ||||||
|  |     "U8Display" | ||||||
|   ], |   ], | ||||||
|   "scenes": { |   "scenes": { | ||||||
|     "Idle": ["Solid", "MPU5060", "Pulse", "IdleColors", "CircadianRhythm"], |     "Idle": ["Solid", "Rainbow", "CircadianRhythm"], | ||||||
|     "Acid": ["Chimes", "Pulse", "MPU5060", "IdleColors", "Rainbow"], |     "Acid": ["Chimes", "Rainbow"], | ||||||
|     "Flashlight": ["Flashlight"] |     "Flashlight": ["Flashlight"] | ||||||
|   }, |   }, | ||||||
|   "surfaceMap": "default", |   "surfaceMap": "default", | ||||||
|   | |||||||
| @@ -8,24 +8,40 @@ | |||||||
|     "UpdateStatusAnimation", |     "UpdateStatusAnimation", | ||||||
|     "Serial", |     "Serial", | ||||||
|     "Weather", |     "Weather", | ||||||
|     "CircadianRhythm" |     "Test", | ||||||
|  |     "CircadianRhythm", | ||||||
|  |     "IdleTimer" | ||||||
|   ], |   ], | ||||||
|   "scenes": { |  | ||||||
|     "Clear": ["Solid", "Rainbow"], |  | ||||||
|     "Rain": ["Rain"], |  | ||||||
|     "Drizzle": ["Rain"], |  | ||||||
|     "Mist": ["Rain"], |  | ||||||
|     "Snow": ["Solid"], |  | ||||||
|     "UnknownWeather": ["Solid", "IdleColors"], |  | ||||||
|     "Test": ["Test"], |  | ||||||
|     "Idle": ["Solid", "Rainbow"], |  | ||||||
|     "Acid": ["Chimes", "IdleColors", "Rainbow"], |  | ||||||
|     "Flashlight": ["Flashlight"] |  | ||||||
|   }, |  | ||||||
|   "surfaceMap": "ponder", |   "surfaceMap": "ponder", | ||||||
|   "defaults": { |   "defaults": { | ||||||
|     "mqtt.ip": "10.0.0.2", |     "mqtt.ip": "10.0.0.2", | ||||||
|     "power.volts": 0, |     "colors.sequences": { | ||||||
|     "power.milliamps": 0 |       "Rainbow": [[255, 0, 0], [255, 127, 0], [0, 255, 0], [0, 0, 255], [128, 0, 128]], | ||||||
|  |       "Idle": [[0, 123, 167], [80, 200, 120], [207, 113, 175], [128, 0, 128], [255, 255, 255], [0, 255, 255]], | ||||||
|  |       "Wet": [[3, 177, 252], [3, 78, 252], [126, 141, 242]], | ||||||
|  |       "Snowy": [[215, 219, 245], [255, 255, 255], [192, 250, 244]], | ||||||
|  |       "Sunny": [[213, 237, 95], [237, 180, 95], [245, 178, 10]] | ||||||
|  |     }, | ||||||
|  |     "colors.scenes": { | ||||||
|  |       "Clear": "Sunny", | ||||||
|  |       "Idle": "Rainbow", | ||||||
|  |       "Rain": "Wet", | ||||||
|  |       "Drizzle": "Wet", | ||||||
|  |       "Mist": "Wet", | ||||||
|  |       "Snow": "Snowy" | ||||||
|  |     }, | ||||||
|  |     "colors.secondsPerColor": 9, | ||||||
|  |     "scenes": { | ||||||
|  |       "Clear": ["Solid"], | ||||||
|  |       "Cloudy": ["Cloudy", "Solid"], | ||||||
|  |       "Rain": ["Rain", "Solid"], | ||||||
|  |       "Drizzle": ["Rain", "Cloudy", "Solid"], | ||||||
|  |       "Mist": ["Cloudy", "Solid"], | ||||||
|  |       "Snow": ["Solid"], | ||||||
|  |       "UnknownWeather": ["Thinking"], | ||||||
|  |       "Start": ["Thinking"], | ||||||
|  |       "Idle": ["Solid"], | ||||||
|  |       "Test": ["Test"] | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								data/profiles/pondertest.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								data/profiles/pondertest.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | { | ||||||
|  |   "version": 1, | ||||||
|  |   "tasks": [ | ||||||
|  |     "Renderer", | ||||||
|  |     "Serial" | ||||||
|  |   ] | ||||||
|  | } | ||||||
| @@ -6,6 +6,9 @@ class Args { | |||||||
|       String *str; |       String *str; | ||||||
|     public: |     public: | ||||||
|       Args(String *str) : str(str) {} |       Args(String *str) : str(str) {} | ||||||
|  |       operator const char*() const { | ||||||
|  |         return str->c_str(); | ||||||
|  |       } | ||||||
|       String operator[](int pos) { |       String operator[](int pos) { | ||||||
|         char buf[64]; |         char buf[64]; | ||||||
|         strncpy(buf, str->c_str(), sizeof(buf)); |         strncpy(buf, str->c_str(), sizeof(buf)); | ||||||
|   | |||||||
| @@ -108,7 +108,7 @@ struct InputEvent: public Variant { | |||||||
|     InputEvent() |     InputEvent() | ||||||
|       : Variant(), intent(None) {} |       : Variant(), intent(None) {} | ||||||
|  |  | ||||||
|     bool operator!=(const InputEvent::Intent& otherIntent) { |     bool operator!=(const InputEvent::Intent& otherIntent) const { | ||||||
|         return intent != otherIntent; |         return intent != otherIntent; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ Renderer::loop() | |||||||
| { | { | ||||||
|     uint16_t totalPower = 0; |     uint16_t totalPower = 0; | ||||||
|     for(Display* dpy : m_displays) { |     for(Display* dpy : m_displays) { | ||||||
|  |         dpy->clear(); | ||||||
|         totalPower += calculate_unscaled_power_mW(dpy->pixelBacking(), dpy->pixelCount()); |         totalPower += calculate_unscaled_power_mW(dpy->pixelBacking(), dpy->pixelCount()); | ||||||
|         for(Figment* figment : m_figments) { |         for(Figment* figment : m_figments) { | ||||||
|             if (figment->state == Task::Running) { |             if (figment->state == Task::Running) { | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								lib/Figments/Scheduler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								lib/Figments/Scheduler.h
									
									
									
									
									
										Normal file
									
								
							| @@ -43,6 +43,14 @@ Surface::operator+=(const CRGB& color) | |||||||
|     return *this; |     return *this; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Surface::blend(const CRGB& color, uint8_t pct) | ||||||
|  | { | ||||||
|  |     paintWith([&](CRGB& pixel) { | ||||||
|  |         nblend(pixel, color, pct); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
| void | void | ||||||
| Surface::paintWith(std::function<void(CRGB&)> func) | Surface::paintWith(std::function<void(CRGB&)> func) | ||||||
| { | { | ||||||
|   | |||||||
| @@ -31,6 +31,8 @@ public: | |||||||
|      */ |      */ | ||||||
|     Surface& operator+=(const CRGB& color); |     Surface& operator+=(const CRGB& color); | ||||||
|  |  | ||||||
|  |     void blend(const CRGB& color, uint8_t pct); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * OR operation that applies the given color to every pixel on the surface |      * OR operation that applies the given color to every pixel on the surface | ||||||
|      */ |      */ | ||||||
|   | |||||||
| @@ -11,21 +11,18 @@ | |||||||
| [env] | [env] | ||||||
| src_filter = +<*>, -<.git/>, -<.svn/>, -<platform/> | src_filter = +<*>, -<.git/>, -<.svn/>, -<platform/> | ||||||
| lib_ldf_mode = chain+ | lib_ldf_mode = chain+ | ||||||
| extra_scripts = pre:verify-configs.py, pre:build-hal.py | extra_scripts = post:verify-configs.py, pre:build-hal.py | ||||||
| check_flags = | check_flags = | ||||||
|   cppcheck: --inline-suppr --suppress=*:*/.pio/* |   cppcheck: --inline-suppr --suppress=*:*/.pio/* | ||||||
| board_build.filesystem = littlefs | board_build.filesystem = littlefs | ||||||
| upload_speed = 115200 | upload_speed = 115200 | ||||||
| monitor_speed = 115200 | monitor_speed = 115200 | ||||||
| build_type = debug |  | ||||||
| build_flags = | build_flags = | ||||||
|   -DFASTLED_ALL_PINS_HARDWARE_SPI |   -DFASTLED_ALL_PINS_HARDWARE_SPI | ||||||
| src_build_flags = | src_build_flags = | ||||||
|   -DFASTLED_ALL_PINS_HARDWARE_SPI |   -DFASTLED_ALL_PINS_HARDWARE_SPI | ||||||
|   -DRENDERBUG_VERSION=3 |   -DRENDERBUG_VERSION=3 | ||||||
|   -DRENDERBUG_LED_PIN=14 |   -DRENDERBUG_LED_PIN=14 | ||||||
|   -DRENDERBUG_LED_PACKING=RGB |  | ||||||
|   -DDEFAULT_PATTERN_INDEX=0 |  | ||||||
|   -fstack-protector |   -fstack-protector | ||||||
|   -Wall |   -Wall | ||||||
| lib_deps =  | lib_deps =  | ||||||
|   | |||||||
							
								
								
									
										153
									
								
								src/Colors.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								src/Colors.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,153 @@ | |||||||
|  | #include "Colors.h" | ||||||
|  | #include "Static.h" | ||||||
|  |  | ||||||
|  | Colors::Colors() | ||||||
|  |   : BufferedInputSource("Colors"), | ||||||
|  |     m_colorTimer(0), | ||||||
|  |     m_secondsPerColor(9) | ||||||
|  | { | ||||||
|  |   state = Task::Running; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Colors::loop() | ||||||
|  | { | ||||||
|  |   EVERY_N_SECONDS(1) { | ||||||
|  |     if (m_colorTimer > 0) { | ||||||
|  |       Log.verbose("colors: tick"); | ||||||
|  |       m_colorTimer -= 1; | ||||||
|  |  | ||||||
|  |       if (m_colorTimer == 0) { | ||||||
|  |         rotate(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ConfigTaskMixin::loop(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Colors::handleEvent(const InputEvent& evt) | ||||||
|  | { | ||||||
|  |   if (evt.intent == InputEvent::SetScene) { | ||||||
|  |     setScene(evt.asString()); | ||||||
|  |   } | ||||||
|  |   ConfigTaskMixin::handleEvent(evt); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Colors::setSequence(const char* seqName) | ||||||
|  | { | ||||||
|  |   bool found = false; | ||||||
|  |   int idx = 0; | ||||||
|  |   for(Sequence seq : m_sequences) { | ||||||
|  |     if (strcmp(seq.name.c_str(), seqName) == 0) { | ||||||
|  |       found = true; | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     idx++; | ||||||
|  |   } | ||||||
|  |   if (found) { | ||||||
|  |     Log.info("colors: Setting sequence to %s", seqName); | ||||||
|  |     m_idx = idx; | ||||||
|  |     rotate(); | ||||||
|  |   } else { | ||||||
|  |     Log.warning("colors: Unknown sequence %s", seqName); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Colors::rotate() | ||||||
|  | { | ||||||
|  |   if (!m_sequences.empty()) { | ||||||
|  |     Log.info("colors: rotate!") ; | ||||||
|  |     m_colorIdx += 1; | ||||||
|  |     m_colorIdx %= m_sequences[m_idx].colors.size(); | ||||||
|  |     m_colorTimer = m_secondsPerColor; | ||||||
|  |     MainLoop::instance()->dispatch(InputEvent{InputEvent::SetColor, m_sequences[m_idx].colors[m_colorIdx]}); | ||||||
|  |     //setEvent(InputEvent{InputEvent::SetColor, m_sequences[m_idx].colors[m_colorIdx]}); | ||||||
|  |   } else { | ||||||
|  |     Log.info("colors: No colors to rotate"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Colors::setScene(const char* sceneName) | ||||||
|  | { | ||||||
|  |   auto found = m_sceneMap.find(sceneName); | ||||||
|  |   if (found != m_sceneMap.end()) { | ||||||
|  |     Log.info("colors: Running scene %s", sceneName); | ||||||
|  |     setSequence(found->second); | ||||||
|  |   } else { | ||||||
|  |     Log.warning("colors: Unknown scene %s", sceneName); | ||||||
|  |     m_colorTimer = 0; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Colors::handleConfigChange(const Configuration& cfg) | ||||||
|  | { | ||||||
|  |   Log.info("colors: reloading config"); | ||||||
|  |   m_sequences.clear(); | ||||||
|  |   m_colorIdx = 0; | ||||||
|  |   m_idx = 0; | ||||||
|  |   m_secondsPerColor = cfg.get("colors.secondsPerColor", 9); | ||||||
|  |   m_colorTimer = 0; | ||||||
|  |   JsonObject seqJson = cfg.get("colors.sequences"); | ||||||
|  |   for(const auto seq : seqJson) { | ||||||
|  |     Log.info("colors: Found color sequence %s", seq.key().c_str()); | ||||||
|  |     std::vector<CRGB> colors; | ||||||
|  |     for(JsonArray rgb : seq.value().as<JsonArray>()) { | ||||||
|  |       colors.emplace_back(CRGB(rgb[0], rgb[1], rgb[2])); | ||||||
|  |     } | ||||||
|  |     m_sequences.emplace_back(Sequence{ | ||||||
|  |         seq.key().c_str(), | ||||||
|  |         std::move(colors) | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   m_sceneMap.clear(); | ||||||
|  |   JsonObject sceneJson = cfg.get("colors.scenes"); | ||||||
|  |   for(const auto scene : sceneJson) { | ||||||
|  |     Log.info("colors: Found scene %s", scene.key().c_str()); | ||||||
|  |     m_sceneMap[scene.key().c_str()] = scene.value().as<JsonString>().c_str(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   rotate(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Colors::doSequences(Args& args, Print& out) | ||||||
|  | { | ||||||
|  |   out.println("Color sequences:"); | ||||||
|  |   for(const Sequence& seq : m_sequences) { | ||||||
|  |     out.print("\t"); | ||||||
|  |     out.println(seq.name.c_str()); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Colors::doSequence(Args& args, Print& out) | ||||||
|  | { | ||||||
|  |   setSequence(args[1].c_str()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Colors::doRotate(Args& args, Print& out) | ||||||
|  | { | ||||||
|  |   rotate(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const std::vector<Command>& | ||||||
|  | Colors::commands() const | ||||||
|  | { | ||||||
|  |   static const std::vector<Command> _commands = { | ||||||
|  |     {"sequences", &Colors::doSequences}, | ||||||
|  |     {"sequence", &Colors::doSequence}, | ||||||
|  |     {"rotate", &Colors::doRotate} | ||||||
|  |   }; | ||||||
|  |   return _commands; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | STATIC_TASK(Colors); | ||||||
|  | STATIC_ALLOC(Colors); | ||||||
							
								
								
									
										36
									
								
								src/Colors.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/Colors.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <Figments.h> | ||||||
|  | #include "Config.h" | ||||||
|  | #include <map> | ||||||
|  |  | ||||||
|  | class Colors: public BufferedInputSource, ConfigTaskMixin { | ||||||
|  |   public: | ||||||
|  |     struct Sequence { | ||||||
|  |       String name; | ||||||
|  |       const std::vector<CRGB> colors; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     Colors(); | ||||||
|  |     void loop() override; | ||||||
|  |     void handleEvent(const InputEvent& evt) override; | ||||||
|  |     void handleConfigChange(const Configuration& cfg) override; | ||||||
|  |     const std::vector<Command>& commands() const override; | ||||||
|  |  | ||||||
|  |   private: | ||||||
|  |     uint16_t m_idx; | ||||||
|  |     uint16_t m_colorIdx; | ||||||
|  |     uint16_t m_colorTimer; | ||||||
|  |     uint16_t m_secondsPerColor; | ||||||
|  |     std::vector<Sequence> m_sequences; | ||||||
|  |     std::map<const char*, const char*> m_sceneMap; | ||||||
|  |  | ||||||
|  |     void setScene(const char* sceneName); | ||||||
|  |     void setSequence(const char* seqName); | ||||||
|  |  | ||||||
|  |     void doSequences(Args& args, Print& out); | ||||||
|  |     void doSequence(Args& args, Print& out); | ||||||
|  |     void doRotate(Args& args, Print& out); | ||||||
|  |  | ||||||
|  |     void rotate(); | ||||||
|  | }; | ||||||
| @@ -1,6 +1,5 @@ | |||||||
| #include "./Config.h" | #include "./Config.h" | ||||||
| #include "./Static.h" | #include "./Static.h" | ||||||
| #include "./Sequencer.h" |  | ||||||
|  |  | ||||||
| #include <ArduinoLog.h> | #include <ArduinoLog.h> | ||||||
| #include <ArduinoJson.h> | #include <ArduinoJson.h> | ||||||
| @@ -23,6 +22,12 @@ Configuration::Configuration(const JsonObject& data) | |||||||
| { | { | ||||||
| } | } | ||||||
|  |  | ||||||
|  | JsonVariant | ||||||
|  | Configuration::get(const char* key) const | ||||||
|  | { | ||||||
|  |     return m_json[key]; | ||||||
|  | } | ||||||
|  |  | ||||||
| const char* | const char* | ||||||
| Configuration::get(const char* key, const char* defaultVal) const | Configuration::get(const char* key, const char* defaultVal) const | ||||||
| { | { | ||||||
| @@ -53,8 +58,6 @@ Configuration::get(const char* key, bool defaultVal) const | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| StaticJsonDocument<2048> jsonConfig; |  | ||||||
|  |  | ||||||
| constexpr uint16_t HardwareConfig::MAX_LED_NUM; | constexpr uint16_t HardwareConfig::MAX_LED_NUM; | ||||||
|  |  | ||||||
| HardwareConfig | HardwareConfig | ||||||
| @@ -149,12 +152,15 @@ ConfigService::loadMap(const String& mapName) | |||||||
| { | { | ||||||
|   String fname = String("/maps/") + mapName + ".json"; |   String fname = String("/maps/") + mapName + ".json"; | ||||||
|   if (LittleFS.exists(fname)) { |   if (LittleFS.exists(fname)) { | ||||||
|  |     DynamicJsonDocument jsonConfig(2048); | ||||||
|     File configFile = LittleFS.open(fname, "r"); |     File configFile = LittleFS.open(fname, "r"); | ||||||
|     Log.notice("config: Loading coordinate map from %s", fname.c_str()); |     Log.notice("config: Loading coordinate map from %s", fname.c_str()); | ||||||
|     deserializeJson(jsonConfig, configFile); |     deserializeJson(jsonConfig, configFile); | ||||||
|     configFile.close(); |     configFile.close(); | ||||||
|     JsonArray strideList = jsonConfig["strides"]; |     JsonArray strideList = jsonConfig["strides"]; | ||||||
|  |     uint8_t rotation = jsonConfig["rotation"]; | ||||||
|     m_jsonMap.load(strideList); |     m_jsonMap.load(strideList); | ||||||
|  |     m_jsonMap.rotation = rotation; | ||||||
|     return true; |     return true; | ||||||
|   } else { |   } else { | ||||||
|     return false; |     return false; | ||||||
| @@ -171,25 +177,13 @@ ConfigService::loadProfile(const char* profileName) | |||||||
|     if (LittleFS.exists(fname)) { |     if (LittleFS.exists(fname)) { | ||||||
|       strncpy(m_config.data.loadedProfile, fname.c_str(), sizeof(m_config.data.loadedProfile)); |       strncpy(m_config.data.loadedProfile, fname.c_str(), sizeof(m_config.data.loadedProfile)); | ||||||
|       File configFile = LittleFS.open(fname, "r"); |       File configFile = LittleFS.open(fname, "r"); | ||||||
|       jsonConfig.clear(); |       DynamicJsonDocument jsonConfig(2048*2); | ||||||
|       deserializeJson(jsonConfig, configFile); |       auto err = deserializeJson(jsonConfig, configFile); | ||||||
|       configFile.close(); |       configFile.close(); | ||||||
|  |  | ||||||
|       //int profileLogLevel = max(0, min(6, jsonConfig["logLevel"])); |       if (err) { | ||||||
|       //Log.setLevel(profileLogLevel); |         Log.error("JSON failure: %s", err.c_str()); | ||||||
|       //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"]; |       JsonArray taskList = jsonConfig["tasks"]; | ||||||
|       Log.notice("config: Starting %d tasks", taskList.size()); |       Log.notice("config: Starting %d tasks", taskList.size()); | ||||||
| @@ -197,14 +191,8 @@ ConfigService::loadProfile(const char* profileName) | |||||||
|         MainLoop::instance()->dispatchSync(InputEvent{InputEvent::StartThing, taskList[i].as<const char*>()}); |         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"]; |       String mapName = jsonConfig["surfaceMap"]; | ||||||
|  |  | ||||||
|       jsonConfig.clear(); |  | ||||||
|  |  | ||||||
|       if (mapName.isEmpty()) { |       if (mapName.isEmpty()) { | ||||||
|         Log.warning("config: No coordinate map defined! Defaulting to linear mapping.", mapName.c_str()); |         Log.warning("config: No coordinate map defined! Defaulting to linear mapping.", mapName.c_str()); | ||||||
|         m_jsonMap.loadDefault(); |         m_jsonMap.loadDefault(); | ||||||
| @@ -212,6 +200,11 @@ ConfigService::loadProfile(const char* profileName) | |||||||
|         Log.warning("config: Couldn't load coordinate map %s!!! Defaulting to linear mapping.", mapName.c_str()); |         Log.warning("config: Couldn't load coordinate map %s!!! Defaulting to linear mapping.", mapName.c_str()); | ||||||
|         m_jsonMap.loadDefault(); |         m_jsonMap.loadDefault(); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |       JsonObject defaults = jsonConfig["defaults"]; | ||||||
|  |       Log.notice("config: Loading %d app configurations", defaults.size()); | ||||||
|  |       MainLoop::instance()->dispatchSync(InputEvent{InputEvent::ConfigurationChanged, &defaults}); | ||||||
|  |  | ||||||
|       Log.notice("config: Loaded!"); |       Log.notice("config: Loaded!"); | ||||||
|       strcpy(m_config.data.loadedProfile, profileName); |       strcpy(m_config.data.loadedProfile, profileName); | ||||||
|     } else { |     } else { | ||||||
| @@ -223,8 +216,8 @@ ConfigService::loadProfile(const char* profileName) | |||||||
|     Log.notice("config: Configured to use %d pixels", m_jsonMap.physicalPixelCount()); |     Log.notice("config: Configured to use %d pixels", m_jsonMap.physicalPixelCount()); | ||||||
|     PhysicalCoordinates topLeft = m_jsonMap.virtualToPhysicalCoords({0, 0}); |     PhysicalCoordinates topLeft = m_jsonMap.virtualToPhysicalCoords({0, 0}); | ||||||
|     PhysicalCoordinates bottomRight = m_jsonMap.virtualToPhysicalCoords({255, 255}); |     PhysicalCoordinates bottomRight = m_jsonMap.virtualToPhysicalCoords({255, 255}); | ||||||
|     Log.verbose("  (0,0) -> (%d, %d) -> %d", topLeft.x, topLeft.y, m_jsonMap.physicalCoordsToIndex(topLeft)); |     Log.info("  (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)); |     Log.info("  (255,255) -> (%d, %d) -> %d", bottomRight.x, bottomRight.y, m_jsonMap.physicalCoordsToIndex(bottomRight)); | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -293,6 +286,16 @@ ConfigService::doCoordMap(Args& args, Print& out) | |||||||
|   out.println(buf); |   out.println(buf); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | ConfigService::doRotate(Args& args, Print& out) | ||||||
|  | { | ||||||
|  |   out.print("Rotation: "); | ||||||
|  |   out.print(m_jsonMap.rotation); | ||||||
|  |   out.print(" "); | ||||||
|  |   m_jsonMap.rotation = atoi(args[1].c_str()); | ||||||
|  |   out.println(m_jsonMap.rotation); | ||||||
|  | } | ||||||
|  |  | ||||||
| const std::vector<Command>& | const std::vector<Command>& | ||||||
| ConfigService::commands() const | ConfigService::commands() const | ||||||
| { | { | ||||||
| @@ -300,7 +303,8 @@ ConfigService::commands() const | |||||||
|     {"save", &ConfigService::doSave}, |     {"save", &ConfigService::doSave}, | ||||||
|     {"profile", &ConfigService::doSetProfile}, |     {"profile", &ConfigService::doSetProfile}, | ||||||
|     {"maps", &ConfigService::doMapList}, |     {"maps", &ConfigService::doMapList}, | ||||||
|     {"coordmap", &ConfigService::doCoordMap} |     {"coordmap", &ConfigService::doCoordMap}, | ||||||
|  |     {"rotate", &ConfigService::doRotate} | ||||||
|   }; |   }; | ||||||
|   return _commands; |   return _commands; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ class Configuration { | |||||||
|     const char* get(const char* key, const char* defaultVal) const; |     const char* get(const char* key, const char* defaultVal) const; | ||||||
|     int get(const char* key, int defaultVal) const; |     int get(const char* key, int defaultVal) const; | ||||||
|     bool get(const char* key, bool defaultVal) const; |     bool get(const char* key, bool defaultVal) const; | ||||||
|  |     JsonVariant get(const char* name) const; | ||||||
|  |  | ||||||
|   private: |   private: | ||||||
|     const JsonObject& m_json; |     const JsonObject& m_json; | ||||||
| @@ -79,4 +80,5 @@ private: | |||||||
|     void doSetProfile(Args& args, Print& print); |     void doSetProfile(Args& args, Print& print); | ||||||
|     void doCoordMap(Args& args, Print& print); |     void doCoordMap(Args& args, Print& print); | ||||||
|     void doMapList(Args& args, Print& print); |     void doMapList(Args& args, Print& print); | ||||||
|  |     void doRotate(Args& args, Print& print); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -11,24 +11,33 @@ JsonCoordinateMapping::physicalPixelCount() const { | |||||||
|  |  | ||||||
| int | int | ||||||
| JsonCoordinateMapping::physicalCoordsToIndex(const PhysicalCoordinates localCoords) const { | JsonCoordinateMapping::physicalCoordsToIndex(const PhysicalCoordinates localCoords) const { | ||||||
|   uint8_t idx = 0; |   return displayMap[localCoords.x].physicalIdx + localCoords.y; | ||||||
|   bool inverse = false; | } | ||||||
|   for(int i = 0; i < localCoords.x; i++) {  |  | ||||||
|     idx += displayMap[i].length; | VirtualCoordinates | ||||||
|     inverse = !inverse; | JsonCoordinateMapping::rotate(const VirtualCoordinates coords, uint8_t rot) const | ||||||
|   } | { | ||||||
|   if (inverse) { |   if (rot == 0) { | ||||||
|     idx += std::max(0, displayMap[localCoords.x].length - 1 - std::max(0, (int)localCoords.y - displayMap[localCoords.x].y)); |     return coords; | ||||||
|  |   } else if (rot == 1) { | ||||||
|  |     return VirtualCoordinates{coords.y, coords.x}; | ||||||
|  |   } else if (rot == 2) { | ||||||
|  |     return VirtualCoordinates{255 - coords.y, 255 - coords.x}; | ||||||
|  |   } else if (rot == 3) { | ||||||
|  |     return VirtualCoordinates{255 - coords.x, 255 - coords.y}; | ||||||
|   } else { |   } else { | ||||||
|     idx += std::min((int)displayMap[localCoords.x].length - 1, std::max(0, (int)localCoords.y - displayMap[localCoords.x].y)); |     return coords; | ||||||
|   } |   } | ||||||
|   return idx; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| PhysicalCoordinates | PhysicalCoordinates | ||||||
| JsonCoordinateMapping::virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const { | JsonCoordinateMapping::virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const { | ||||||
|   const uint8_t spanIdx = scale8(strideCount-1, virtualCoords.x); |   // 0, 0 -> first pixel, first strand | ||||||
|   const uint8_t spanOffset = scale8(maxStrideSize - 1, virtualCoords.y); |   // 0, 255 -> first pixel, last strand | ||||||
|  |   // 255, 255 -> last pixel, last strand | ||||||
|  |   const VirtualCoordinates rotated = rotate(virtualCoords, rotation); | ||||||
|  |   const uint8_t spanIdx = scale8(strideCount-1, rotated.x); | ||||||
|  |   const uint8_t spanOffset = scale8(displayMap[spanIdx].length, rotated.y); | ||||||
|   return PhysicalCoordinates{spanIdx, spanOffset}; |   return PhysicalCoordinates{spanIdx, spanOffset}; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -48,23 +57,23 @@ JsonCoordinateMapping::physicalToVirtualCoords(const PhysicalCoordinates localCo | |||||||
| void | void | ||||||
| JsonCoordinateMapping::load(const JsonArray& strideList) | JsonCoordinateMapping::load(const JsonArray& strideList) | ||||||
| { | { | ||||||
|   maxStrideSize = 0; |  | ||||||
|   strideCount = strideList.size(); |   strideCount = strideList.size(); | ||||||
|  |   uint16_t idx = 0; | ||||||
|   for(int i = 0; i < strideList.size();i++) { |   for(int i = 0; i < strideList.size();i++) { | ||||||
|     JsonObject strideObj = strideList[i]; |     JsonObject strideObj = strideList[i]; | ||||||
|     int length = strideObj["pixels"]; |     int length = strideObj["pixels"]; | ||||||
|     int x = strideObj["x"]; |     int x = strideObj["x"]; | ||||||
|     int y = strideObj["y"]; |     int y = strideObj["y"]; | ||||||
|     displayMap[i] = Span{length, x, y}; |     bool reverse = strideObj["reverse"]; | ||||||
|  |     displayMap[i] = Span{length, x, y, reverse, idx}; | ||||||
|  |     idx += length; | ||||||
|     Log.info("config: Found stride (%d, %d): %d", x, y, length); |     Log.info("config: Found stride (%d, %d): %d", x, y, length); | ||||||
|     maxStrideSize = length > maxStrideSize ? length : maxStrideSize; |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void | void | ||||||
| JsonCoordinateMapping::loadDefault() | JsonCoordinateMapping::loadDefault() | ||||||
| { | { | ||||||
|     displayMap[0] = Span{255, 0, 0}; |     displayMap[0] = Span{255, 0, 0, false, 0}; | ||||||
|     maxStrideSize = 255; |  | ||||||
|     strideCount = 1; |     strideCount = 1; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,14 +7,18 @@ struct JsonCoordinateMapping : CoordinateMapping { | |||||||
|     int length = 0; |     int length = 0; | ||||||
|     int x = 0; |     int x = 0; | ||||||
|     int y = 0; |     int y = 0; | ||||||
|  |     bool reverse = false; | ||||||
|  |  | ||||||
|  |     uint16_t physicalIdx = 0; | ||||||
|  |  | ||||||
|     Span() {} |     Span() {} | ||||||
|     Span(int length, int x, int y) : length(length), x(x), y(y) {} |     Span(int length, int x, int y, bool rev, uint16_t idx) : length(length), x(x), y(y), reverse(rev), physicalIdx(idx) {} | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   uint8_t rotation = 0; | ||||||
|  |  | ||||||
|   Span displayMap[32]; |   Span displayMap[32]; | ||||||
|   int strideCount = 0; |   int strideCount = 0; | ||||||
|   int maxStrideSize = 0; |  | ||||||
|  |  | ||||||
|   void load(const JsonArray& strides); |   void load(const JsonArray& strides); | ||||||
|   void loadDefault(); |   void loadDefault(); | ||||||
| @@ -23,4 +27,6 @@ struct JsonCoordinateMapping : CoordinateMapping { | |||||||
|   PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const override; |   PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const override; | ||||||
|   int physicalCoordsToIndex(const PhysicalCoordinates localCoords) const override; |   int physicalCoordsToIndex(const PhysicalCoordinates localCoords) const override; | ||||||
|   unsigned int physicalPixelCount() const override; |   unsigned int physicalPixelCount() const override; | ||||||
|  |  | ||||||
|  |   VirtualCoordinates rotate(const VirtualCoordinates coords, uint8_t rotation) const; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ LogService::eventValue(const InputEvent& evt) | |||||||
| void | void | ||||||
| LogService::handleEvent(const InputEvent& evt) { | LogService::handleEvent(const InputEvent& evt) { | ||||||
|     if (evt.intent != InputEvent::None) { |     if (evt.intent != InputEvent::None) { | ||||||
|         if (evt.intent != m_lastEvent.intent) { |         if (true || evt.intent != m_lastEvent.intent) { | ||||||
|             if (m_duplicateEvents > 0) { |             if (m_duplicateEvents > 0) { | ||||||
|                 Log.trace("Suppressed reporting %d duplicate events.", m_duplicateEvents); |                 Log.trace("Suppressed reporting %d duplicate events.", m_duplicateEvents); | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ void printNewline(Print* logOutput, int logLevel) | |||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| int Platform::s_timezone = 0; | int Platform::s_timezone = 1; | ||||||
| Platform::TaskRegistration* Platform::firstTask = NULL; | Platform::TaskRegistration* Platform::firstTask = NULL; | ||||||
| Platform::TaskRegistration* Platform::lastTask = NULL; | Platform::TaskRegistration* Platform::lastTask = NULL; | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										33
									
								
								src/Scripts.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/Scripts.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | #include "Scripts.h" | ||||||
|  | #include "Static.h" | ||||||
|  |  | ||||||
|  | Scripts::Scripts() | ||||||
|  |   : Task("Scripts") | ||||||
|  | { | ||||||
|  |   state = Running; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Scripts::loop() | ||||||
|  | { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Scripts::doEval(Args& args, Print& out) | ||||||
|  | { | ||||||
|  |   String fullCmd((const char*)args); | ||||||
|  |   String fullScript(fullCmd.substring(fullCmd.indexOf(" ") + 1)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const std::vector<Command>& | ||||||
|  | Scripts::commands() const | ||||||
|  | { | ||||||
|  |   static const std::vector<Command> _commands{ | ||||||
|  |     {"eval", &Scripts::doEval} | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   return _commands; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | STATIC_ALLOC(Scripts); | ||||||
|  | STATIC_TASK(Scripts); | ||||||
							
								
								
									
										12
									
								
								src/Scripts.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/Scripts.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <Figments.h> | ||||||
|  |  | ||||||
|  | class Scripts : public Task { | ||||||
|  |   public: | ||||||
|  |     Scripts(); | ||||||
|  |     void loop() override; | ||||||
|  |     const std::vector<Command>& commands() const; | ||||||
|  |  | ||||||
|  |   private: | ||||||
|  |     void doEval(Args& args, Print& out); | ||||||
|  | }; | ||||||
| @@ -3,8 +3,9 @@ | |||||||
| #include "Static.h" | #include "Static.h" | ||||||
|  |  | ||||||
| Sequencer::Sequencer() : | Sequencer::Sequencer() : | ||||||
|   Task("SceneSequencer"), |   Task("Sequencer"), | ||||||
|   m_idx(0) |   m_idx(0), | ||||||
|  |   m_sceneBeforeIdle(-1) | ||||||
| { | { | ||||||
|   state = Task::Running; |   state = Task::Running; | ||||||
| } | } | ||||||
| @@ -12,28 +13,32 @@ Sequencer::Sequencer() : | |||||||
| void | void | ||||||
| Sequencer::Scene::start() | Sequencer::Scene::start() | ||||||
| { | { | ||||||
|     for(const char* pattern : patterns) { |     for(const auto& pattern : patterns) { | ||||||
|         Log.verbose("Starting pattern task %s", pattern); |         //pattern.getBytes(namebuf, sizeof(namebuf)); | ||||||
|         MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern}); |         Log.info("sequencer: Starting pattern task %s", pattern.c_str()); | ||||||
|  |         MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern.c_str()}); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| void | void | ||||||
| Sequencer::Scene::stop() | Sequencer::Scene::stop() | ||||||
| { | { | ||||||
|     for(const char* pattern : patterns) { |     for(const auto& pattern : patterns) { | ||||||
|         Log.verbose("Stopping pattern task %s", pattern); |         Log.info("sequencer: Stopping pattern task %s", pattern.c_str()); | ||||||
|         MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, pattern}); |         MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, pattern.c_str()}); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| void | void | ||||||
| Sequencer::loop() {} | Sequencer::loop() | ||||||
|  | { | ||||||
|  |   ConfigTaskMixin::loop(); | ||||||
|  | } | ||||||
|  |  | ||||||
| const char* | const char* | ||||||
| Sequencer::currentSceneName() | Sequencer::currentSceneName() | ||||||
| { | { | ||||||
|     return m_scenes[m_idx].name; |     return m_scenes[m_idx].name.c_str(); | ||||||
| } | } | ||||||
|  |  | ||||||
| const std::vector<Sequencer::Scene> | const std::vector<Sequencer::Scene> | ||||||
| @@ -48,30 +53,64 @@ Sequencer::onStart() | |||||||
| } | } | ||||||
|  |  | ||||||
| void | void | ||||||
| Sequencer::setScenes(std::vector<Scene> &&scenes) | Sequencer::handleEvent(const InputEvent& evt) | ||||||
| { | { | ||||||
|   m_idx = 0; |   if (!m_scenes.empty()) { | ||||||
|   m_scenes = scenes; |     if (evt.intent == InputEvent::ReadyToRoll) { | ||||||
|  |       Log.info("sequencer: Activating startup scene"); | ||||||
|  |       if (!changeToScene("Start")) { | ||||||
|  |         Log.info("sequencer: Start scene not found, falling back to first scene"); | ||||||
|  |         changeToScene(m_scenes[0].name.c_str()); | ||||||
|  |       } | ||||||
|  |     } else if (evt.intent == InputEvent::SetScene && m_scenes[m_idx].name == evt.asString()) { | ||||||
|  |         return; | ||||||
|  |     } else if (evt.intent == InputEvent::SetScene) { | ||||||
|  |       changeToScene(evt.asString()); | ||||||
|  |     } else if (evt.intent == InputEvent::IdleStart) { | ||||||
|  |       Log.info("sequencer: Activating idle scene"); | ||||||
|  |       m_sceneBeforeIdle = m_idx; | ||||||
|  |       MainLoop::instance()->dispatch(InputEvent{InputEvent::SetScene, "Idle"}); | ||||||
|  |     } else if (evt.intent == InputEvent::IdleStop && m_sceneBeforeIdle >= 0) { | ||||||
|  |       Log.info("sequencer: Resuming pre-idle scene"); | ||||||
|  |       //changeToScene(m_scenes[m_sceneBeforeIdle].name.c_str()); | ||||||
|  |       String sceneName = m_scenes[m_sceneBeforeIdle].name; | ||||||
|  |       m_sceneBeforeIdle = -1; | ||||||
|  |       MainLoop::instance()->dispatch(InputEvent{InputEvent::SetScene, sceneName.c_str()}); | ||||||
|  |     }  | ||||||
|  |   } | ||||||
|  |   ConfigTaskMixin::handleEvent(evt); | ||||||
| } | } | ||||||
|  |  | ||||||
| void | void | ||||||
| Sequencer::handleEvent(const InputEvent& evt) | Sequencer::handleConfigChange(const Configuration& cfg) | ||||||
| { | { | ||||||
|     if (evt.intent == InputEvent::ReadyToRoll && !m_scenes.empty()) { |   Log.notice("sequencer: Loading scenes"); | ||||||
|       Log.notice("Starting pattern %s!", m_scenes[m_idx].name); |  | ||||||
|       for(const char* pattern : m_scenes[m_idx].patterns) { |   JsonObject sceneList = cfg.get("scenes"); | ||||||
|           Log.verbose("Starting pattern task %s", pattern); |   m_idx = 0; | ||||||
|           MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern}); |   m_scenes.clear(); | ||||||
|  |   for(JsonPair pair : sceneList) { | ||||||
|  |     Log.notice("sequencer: Found scene %s", pair.key().c_str()); | ||||||
|  |     std::vector<String> patterns; | ||||||
|  |     for(const char* taskName : pair.value().as<JsonArray>()) { | ||||||
|  |       patterns.push_back(taskName); | ||||||
|     } |     } | ||||||
|     } else if (evt.intent == InputEvent::SetScene && evt.asString() == m_scenes[m_idx].name) { |     m_scenes.push_back(Scene{pair.key().c_str(), patterns}); | ||||||
|         return; |   } | ||||||
|     } else if (evt.intent == InputEvent::SetScene) { |   if (m_scenes.empty()) { | ||||||
|         Log.notice("Switching scene to %s!", evt.asString()); |     Log.warning("sequencer: Zero scenes loaded!"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool | ||||||
|  | Sequencer::changeToScene(const char* name) | ||||||
|  | { | ||||||
|  |     Log.notice("sequencer: Switching scene to %s!", name); | ||||||
|  |  | ||||||
|     int newIdx = 0; |     int newIdx = 0; | ||||||
|     bool found = false; |     bool found = false; | ||||||
|     for(newIdx = 0; newIdx < m_scenes.size(); newIdx++) { |     for(newIdx = 0; newIdx < m_scenes.size(); newIdx++) { | ||||||
|           if (!strcmp(evt.asString(), m_scenes[newIdx].name)) { |       if (!strcmp(name, m_scenes[newIdx].name.c_str())) { | ||||||
|           found = true; |           found = true; | ||||||
|           break; |           break; | ||||||
|       } |       } | ||||||
| @@ -81,9 +120,10 @@ Sequencer::handleEvent(const InputEvent& evt) | |||||||
|       m_scenes[m_idx].stop(); |       m_scenes[m_idx].stop(); | ||||||
|       m_idx = newIdx; |       m_idx = newIdx; | ||||||
|       m_scenes[m_idx].start(); |       m_scenes[m_idx].start(); | ||||||
|  |       return true; | ||||||
|     } else { |     } else { | ||||||
|           Log.notice("Scene not found!"); |       Log.notice("sequencer: Scene not found!"); | ||||||
|         } |       return false; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -92,7 +132,7 @@ Sequencer::doScenes(Args& args, Print& out) | |||||||
| { | { | ||||||
|   out.println("Available scenes: "); |   out.println("Available scenes: "); | ||||||
|   for (auto scene : scenes()) { |   for (auto scene : scenes()) { | ||||||
|     out.println(scene.name); |     out.println(scene.name.c_str()); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -101,8 +141,8 @@ static String s; | |||||||
| void | void | ||||||
| Sequencer::doScene(Args& args, Print& out) | Sequencer::doScene(Args& args, Print& out) | ||||||
| { | { | ||||||
|   s  = args[1]; |   String sceneName = args[1]; | ||||||
|   MainLoop::instance()->dispatch(InputEvent{InputEvent::SetScene, s.c_str()}); |   MainLoop::instance()->dispatch(InputEvent{InputEvent::SetScene, sceneName.c_str()}); | ||||||
| } | } | ||||||
|  |  | ||||||
| const std::vector<Command>& | const std::vector<Command>& | ||||||
|   | |||||||
| @@ -2,14 +2,15 @@ | |||||||
|  |  | ||||||
| #include <Figment.h> | #include <Figment.h> | ||||||
| #include <vector> | #include <vector> | ||||||
|  | #include "Config.h" | ||||||
|  |  | ||||||
| class Sequencer: public Task { | class Sequencer: public Task, ConfigTaskMixin { | ||||||
| public: | public: | ||||||
|  |  | ||||||
|     class Scene { |     class Scene { | ||||||
|       public: |       public: | ||||||
|         const char* name; |         String name; | ||||||
|         std::vector<const char*> patterns; |         std::vector<String> patterns; | ||||||
|         void start(); |         void start(); | ||||||
|         void stop(); |         void stop(); | ||||||
|     }; |     }; | ||||||
| @@ -19,7 +20,7 @@ public: | |||||||
|     void loop() override; |     void loop() override; | ||||||
|     void onStart() override; |     void onStart() override; | ||||||
|     void handleEvent(const InputEvent& evt) override; |     void handleEvent(const InputEvent& evt) override; | ||||||
|     void setScenes(std::vector<Scene> &&scenes); |     void handleConfigChange(const Configuration& cfg) override; | ||||||
|  |  | ||||||
|     const char* currentSceneName(); |     const char* currentSceneName(); | ||||||
|     const std::vector<Scene> scenes() const; |     const std::vector<Scene> scenes() const; | ||||||
| @@ -27,8 +28,12 @@ public: | |||||||
|  |  | ||||||
| private: | private: | ||||||
|     int m_idx; |     int m_idx; | ||||||
|  |     int m_sceneBeforeIdle; | ||||||
|  |     bool m_hasStartupScene; | ||||||
|     std::vector<Scene> m_scenes; |     std::vector<Scene> m_scenes; | ||||||
|  |  | ||||||
|  |     bool changeToScene(const char* name); | ||||||
|  |  | ||||||
|     void doScene(Args& args, Print& out); |     void doScene(Args& args, Print& out); | ||||||
|     void doScenes(Args& args, Print& out); |     void doScenes(Args& args, Print& out); | ||||||
| }; | }; | ||||||
|   | |||||||
							
								
								
									
										46
									
								
								src/animations/Cloudy.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/animations/Cloudy.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | #include "Cloudy.h" | ||||||
|  | #include "../Static.h" | ||||||
|  |  | ||||||
|  | Cloudy::Cloudy() | ||||||
|  |   : Figment("Cloudy") | ||||||
|  | { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Cloudy::loop() | ||||||
|  | { | ||||||
|  |   m_noiseOffset += 1; | ||||||
|  |   EVERY_N_MILLISECONDS(60) { | ||||||
|  |     m_hue.update(1); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Cloudy::render(Display* dpy) const | ||||||
|  | { | ||||||
|  |   Surface sfc = Surface(dpy, {0, 0}, {255, 128}); | ||||||
|  |   uint8_t noiseY = sin8(m_noiseOffset % 255); | ||||||
|  |   uint8_t noiseX = cos8(m_noiseOffset % 255); | ||||||
|  |   sfc.paintShader([=](CRGB& pixel, const VirtualCoordinates& coords, const PhysicalCoordinates, const VirtualCoordinates& surfaceCoords) { | ||||||
|  |       uint8_t brightness = inoise8(noiseX + surfaceCoords.x, noiseY + surfaceCoords.y); | ||||||
|  |       uint8_t saturation = inoise8(noiseY + surfaceCoords.y, noiseX + surfaceCoords.x); | ||||||
|  |       nblend(pixel, CHSV(m_hue, scale8(saturation, 128), brightness), 255 - surfaceCoords.y); | ||||||
|  |   }); | ||||||
|  |   Surface horizon = Surface(dpy, {0, 64}, {255, 128}); | ||||||
|  |   horizon.paintShader([=](CRGB& pixel, const VirtualCoordinates& coords, const PhysicalCoordinates, const VirtualCoordinates& surfaceCoords) { | ||||||
|  |       uint8_t brightness = inoise8(noiseX + surfaceCoords.x, noiseY + surfaceCoords.y); | ||||||
|  |       uint8_t saturation = inoise8(noiseY + surfaceCoords.y, noiseX + surfaceCoords.x); | ||||||
|  |       nblend(pixel, CHSV(m_hue + (saturation % 45), std::max((uint8_t)64, (uint8_t)saturation), brightness), surfaceCoords.y); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Cloudy::handleEvent(const InputEvent& evt) { | ||||||
|  |   if (evt.intent == InputEvent::SetColor) { | ||||||
|  |     CHSV next = rgb2hsv_approximate(evt.asRGB()); | ||||||
|  |     m_hue.set(next.h); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | STATIC_ALLOC(Cloudy); | ||||||
|  | STATIC_TASK(Cloudy); | ||||||
							
								
								
									
										13
									
								
								src/animations/Cloudy.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/animations/Cloudy.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <Figments.h> | ||||||
|  |  | ||||||
|  | class Cloudy: public Figment { | ||||||
|  |   private: | ||||||
|  |     uint16_t m_noiseOffset; | ||||||
|  |     AnimatedNumber m_hue; | ||||||
|  |   public: | ||||||
|  |     Cloudy(); | ||||||
|  |     void render(Display* dpy) const override; | ||||||
|  |     void loop() override; | ||||||
|  |     void handleEvent(const InputEvent& evt) override; | ||||||
|  | }; | ||||||
| @@ -8,11 +8,13 @@ RainAnimation::RainAnimation() : Figment("Rain") | |||||||
| void | void | ||||||
| RainAnimation::render(Display* dpy) const | RainAnimation::render(Display* dpy) const | ||||||
| { | { | ||||||
|   Surface sfc = Surface(dpy, {0, 0}, {255, 255}); |   Surface ground = Surface(dpy, {0, 64}, {255, 255}); | ||||||
|   uint8_t noiseY = sin8(m_noiseOffset % 255); |   uint8_t noiseY = sin8(m_noiseOffset % 255); | ||||||
|   uint8_t noiseX = cos8(m_noiseOffset % 255); |   uint8_t noiseX = cos8(m_noiseOffset % 255); | ||||||
|   sfc.paintShader([=](CRGB& pixel, const VirtualCoordinates& coords, const PhysicalCoordinates, const VirtualCoordinates& surfaceCoords) { |   ground.paintShader([=](CRGB& pixel, const VirtualCoordinates& coords, const PhysicalCoordinates, const VirtualCoordinates& surfaceCoords) { | ||||||
|       pixel = CHSV(m_hue, inoise8(noiseX + coords.x, coords.y), inoise8(m_noiseOffset + coords.x, noiseY + coords.y)); |       uint8_t brightness = inoise8(noiseX + surfaceCoords.x, noiseY + surfaceCoords.y); | ||||||
|  |       uint8_t saturation = inoise8(noiseY + surfaceCoords.y, noiseX + surfaceCoords.x); | ||||||
|  |       nblend(pixel, CHSV(m_hue, saturation, brightness), 255 - surfaceCoords.y); | ||||||
|   }); |   }); | ||||||
|   m_drops.render(dpy); |   m_drops.render(dpy); | ||||||
| } | } | ||||||
| @@ -20,7 +22,7 @@ RainAnimation::render(Display* dpy) const | |||||||
| void | void | ||||||
| RainAnimation::loop() | RainAnimation::loop() | ||||||
| { | { | ||||||
|   EVERY_N_MILLISECONDS(250) { |   EVERY_N_MILLISECONDS(120) { | ||||||
|     m_drops.update(); |     m_drops.update(); | ||||||
|   } |   } | ||||||
|   EVERY_N_MILLISECONDS(60) { |   EVERY_N_MILLISECONDS(60) { | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ | |||||||
| class RainAnimation: public Figment { | class RainAnimation: public Figment { | ||||||
| private: | private: | ||||||
|   struct Raindrop { |   struct Raindrop { | ||||||
|     int size = 20; |     int size = 10; | ||||||
|     int x = random(255); |     int x = random(255); | ||||||
|     int y = random(255); |     int y = random(255); | ||||||
|     CHSV fg{180, 255, 255}; |     CHSV fg{180, 255, 255}; | ||||||
|   | |||||||
| @@ -47,22 +47,20 @@ void SolidAnimation::loop() { | |||||||
|         }); |         }); | ||||||
|         m_blobs.update(); |         m_blobs.update(); | ||||||
|     } |     } | ||||||
|  |     m_noiseOffset += 1; | ||||||
| } | } | ||||||
|  |  | ||||||
| void SolidAnimation::render(Display* dpy) const { | void SolidAnimation::render(Display* dpy) const { | ||||||
|     CRGB color(m_red.value(), m_green.value(), m_blue.value()); |     Surface sfc = Surface(dpy, {0, 0}, {255, 255}); | ||||||
|     CRGB scaledPrev = m_prevColor; |     uint8_t noiseY = sin8(m_noiseOffset % 255); | ||||||
|     scaledPrev = color.nscale8(30); |     uint8_t noiseX = cos8(m_noiseOffset % 255); | ||||||
|     uint8_t frame = ease8InOutApprox(m_changePct); |     CHSV color(rgb2hsv_approximate(CRGB(m_red, m_green, m_blue))); | ||||||
|     if (F_LIKELY(frame == 255)) { |  | ||||||
|       Surface(dpy, {0, 0}, {255, 255}) = color.nscale8(10); |     sfc.paintShader([=](CRGB& pixel, const VirtualCoordinates& coords, const PhysicalCoordinates, const VirtualCoordinates& surfaceCoords) { | ||||||
|     } else { |         uint8_t brightness = inoise8(noiseX + surfaceCoords.x, noiseY + surfaceCoords.y); | ||||||
|       uint8_t cutoff = (frame / 2); |         uint8_t saturation = inoise8(noiseY + surfaceCoords.y, noiseX + surfaceCoords.x); | ||||||
|       uint8_t rotation = m_horizontal ? 0 : 128; |         nblend(pixel, CHSV(color.h, std::max((uint8_t)128, saturation), scale8(color.v, brightness)), 128); | ||||||
|       Surface(dpy, {0, 0}, {128 - cutoff, 255}, rotation) = scaledPrev; |     }); | ||||||
|       Surface(dpy, {128 - cutoff, 0}, {128 + cutoff, 255}, rotation) = scaledPrev; |  | ||||||
|       Surface(dpy, {128 + cutoff, 0}, {255, 255}, rotation) = scaledPrev; |  | ||||||
|     } |  | ||||||
|     m_blobs.render(dpy); |     m_blobs.render(dpy); | ||||||
| } | } | ||||||
| STATIC_ALLOC(SolidAnimation); | STATIC_ALLOC(SolidAnimation); | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ | |||||||
| class SolidAnimation: public Figment { | class SolidAnimation: public Figment { | ||||||
| private: | private: | ||||||
|     AnimatedNumber m_red, m_green, m_blue, m_changePct; |     AnimatedNumber m_red, m_green, m_blue, m_changePct; | ||||||
|  |     uint16_t m_noiseOffset; | ||||||
|     CRGB m_curColor; |     CRGB m_curColor; | ||||||
|     CRGB m_prevColor; |     CRGB m_prevColor; | ||||||
|     static constexpr int blobCount = 20; |     static constexpr int blobCount = 20; | ||||||
|   | |||||||
| @@ -7,25 +7,104 @@ TestAnimation::loop() | |||||||
| { | { | ||||||
|   m_x += 1; |   m_x += 1; | ||||||
|   m_y += 1; |   m_y += 1; | ||||||
|  |   m_hue += 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | TestAnimation::handleEvent(const InputEvent& evt) | ||||||
|  | { | ||||||
|  |   if (evt.intent == InputEvent::SetColor) { | ||||||
|  |     m_color = evt.asRGB(); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void | void | ||||||
| TestAnimation::render(Display* dpy) const | TestAnimation::render(Display* dpy) const | ||||||
| { | { | ||||||
|  |   CRGB drawColor = blend(m_color, CHSV(m_hue, 255, 255), 128); | ||||||
|  |   if (m_mode == Raw) { | ||||||
|     for(unsigned int i = 0; i < dpy->pixelCount(); i++) { |     for(unsigned int i = 0; i < dpy->pixelCount(); i++) { | ||||||
|     dpy->pixelAt(i) = CRGB{255, 255, 255}; |       dpy->pixelAt(i) = drawColor; | ||||||
|     } |     } | ||||||
|   return; |   } else if (m_mode == Top) { | ||||||
|   // Blank the canvas to white |     Surface{dpy, {0, 0}, {255, 0}} = drawColor; | ||||||
|   Surface{dpy, {0, 0}, {255, 255}} = CRGB{255, 255, 255}; |   } else if (m_mode == Left) { | ||||||
|  |     Surface{dpy, {0, 0}, {0, 255}} = drawColor; | ||||||
|  |   } else if (m_mode == Right) { | ||||||
|  |     Surface{dpy, {255, 0}, {255, 255}} = drawColor; | ||||||
|  |   } else if (m_mode == Bottom) { | ||||||
|  |     Surface{dpy, {0, 255}, {255, 255}} = drawColor; | ||||||
|  |   } else if (m_mode == Outline) { | ||||||
|     // Draw red line on top row |     // Draw red line on top row | ||||||
|     Surface{dpy, {0, 0}, {255, 0}} = CRGB{255, 0, 0}; |     Surface{dpy, {0, 0}, {255, 0}} = CRGB{255, 0, 0}; | ||||||
|     // Green line on first column |     // Green line on first column | ||||||
|     Surface{dpy, {0, 0}, {0, 255}} = CRGB{0, 255, 0}; |     Surface{dpy, {0, 0}, {0, 255}} = CRGB{0, 255, 0}; | ||||||
|  |   } else if (m_mode == White) { | ||||||
|  |     Surface{dpy, {0, 0}, {255, 255}} = CRGB{255, 255, 255}; | ||||||
|  |   } else if (m_mode == Red) { | ||||||
|  |     Surface{dpy, {0, 0}, {255, 255}} = CRGB{255, 0, 0}; | ||||||
|  |   } else if (m_mode == Green) { | ||||||
|  |     Surface{dpy, {0, 0}, {255, 255}} = CRGB{0, 255, 0}; | ||||||
|  |   } else if (m_mode == Blue) { | ||||||
|  |     Surface{dpy, {0, 0}, {255, 255}} = CRGB{0, 0, 255}; | ||||||
|  |   } else if (m_mode == HSV) { | ||||||
|  |     Surface{dpy, {0, 0}, {255, 255}}.paintShader([=](CRGB& pixel, const VirtualCoordinates& coords, const PhysicalCoordinates&, const VirtualCoordinates& surfaceCoords) { | ||||||
|  |         pixel = CHSV(coords.x, coords.y, 255); | ||||||
|  |     }); | ||||||
|  |   } else if (m_mode == RGB) { | ||||||
|  |     Surface{dpy, {0, 0}, {255, 255}}.paintShader([=](CRGB& pixel, const VirtualCoordinates& coords, const PhysicalCoordinates&, const VirtualCoordinates& surfaceCoords) { | ||||||
|  |         pixel = CRGB(coords.x, coords.y, (coords.x/2) + (coords.y/2)); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |   return; | ||||||
|  | } | ||||||
|  |  | ||||||
|   //Surface{dpy, {m_x, 0}, {m_x, 255}} = CRGB{255, 0, 0}; | void | ||||||
|   ///Surface{dpy, {0, m_y}, {255, m_y}} = CRGB{255, 0, 0}; | TestAnimation::doMode(Args& args, Print& out) | ||||||
|   //dpy->pixelAt(VirtualCoordinates{m_x, m_y}) = CRGB{255, 0, 255}; | { | ||||||
|  |   String s = args[1]; | ||||||
|  |   if (s == "") { | ||||||
|  |     out.println("Available modes: raw outline white red green blue hsv rgb none top left right bottom"); | ||||||
|  |     out.print("Current mode: "); | ||||||
|  |     out.println((int)m_mode); | ||||||
|  |   } else if (s == "raw") { | ||||||
|  |     m_mode = Raw; | ||||||
|  |   } else if (s == "outline") { | ||||||
|  |     m_mode = Outline; | ||||||
|  |   } else if (s == "white") { | ||||||
|  |     m_mode = White; | ||||||
|  |   } else if (s == "red") { | ||||||
|  |     m_mode = Red; | ||||||
|  |   } else if (s == "green") { | ||||||
|  |     m_mode = Green; | ||||||
|  |   } else if (s == "blue") { | ||||||
|  |     m_mode = Blue; | ||||||
|  |   } else if (s == "hsv") { | ||||||
|  |     m_mode = HSV; | ||||||
|  |   } else if (s == "rgb") { | ||||||
|  |     m_mode = RGB; | ||||||
|  |   } else if (s == "top") { | ||||||
|  |     m_mode = Top; | ||||||
|  |   } else if (s == "left") { | ||||||
|  |     m_mode = Left; | ||||||
|  |   } else if (s == "right") { | ||||||
|  |     m_mode = Right; | ||||||
|  |   } else if (s == "bottom") { | ||||||
|  |     m_mode = Bottom; | ||||||
|  |   } else if (s == "none") { | ||||||
|  |     m_mode = None; | ||||||
|  |   } else { | ||||||
|  |     out.println("Unknown test mode"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const std::vector<Command>& | ||||||
|  | TestAnimation::commands() const | ||||||
|  | { | ||||||
|  |     static const std::vector<Command> _commands{ | ||||||
|  |       {"test", &TestAnimation::doMode} | ||||||
|  |     }; | ||||||
|  |     return _commands; | ||||||
| } | } | ||||||
|  |  | ||||||
| STATIC_ALLOC(TestAnimation); | STATIC_ALLOC(TestAnimation); | ||||||
|   | |||||||
| @@ -5,8 +5,31 @@ public: | |||||||
|     TestAnimation() : Figment("Test") {} |     TestAnimation() : Figment("Test") {} | ||||||
|     void loop() override; |     void loop() override; | ||||||
|     void render(Display* dpy) const override; |     void render(Display* dpy) const override; | ||||||
|  |     void handleEvent(const InputEvent& evt) override; | ||||||
|  |     const std::vector<Command>& commands() const override; | ||||||
|  |  | ||||||
| private: | private: | ||||||
|     uint8_t m_x; |     uint8_t m_x; | ||||||
|     uint8_t m_y; |     uint8_t m_y; | ||||||
|  |     CRGB m_color; | ||||||
|  |     uint8_t m_hue; | ||||||
|  |  | ||||||
|  |     enum Mode { | ||||||
|  |       None, | ||||||
|  |       Raw, | ||||||
|  |       Outline, | ||||||
|  |       White, | ||||||
|  |       Red, | ||||||
|  |       Green, | ||||||
|  |       Blue, | ||||||
|  |       HSV, | ||||||
|  |       RGB, | ||||||
|  |       Top, | ||||||
|  |       Left, | ||||||
|  |       Right, | ||||||
|  |       Bottom | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     Mode m_mode = None; | ||||||
|  |     void doMode(Args&, Print&); | ||||||
| }; | }; | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								src/animations/Thinking.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/animations/Thinking.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | #include <Figments.h> | ||||||
|  | #include "../Static.h" | ||||||
|  |  | ||||||
|  | class Thinking: public Figment { | ||||||
|  |   public: | ||||||
|  |     Thinking() : Figment("Thinking") {} | ||||||
|  |     void render(Display* dpy) const override { | ||||||
|  |       Surface sfc = Surface(dpy, {0, 0}, {255, 255}); | ||||||
|  |       uint8_t noiseY = sin8(m_noiseOffset % 255); | ||||||
|  |       uint8_t noiseX = cos8(m_noiseOffset % 255); | ||||||
|  |       sfc.paintShader([=](CRGB& pixel, const VirtualCoordinates& coords, const PhysicalCoordinates, const VirtualCoordinates& surfaceCoords) { | ||||||
|  |           pixel = CHSV(inoise8(noiseX + surfaceCoords.x, noiseY + surfaceCoords.y), 255, inoise8(noiseY + surfaceCoords.y, noiseX + surfaceCoords.x)); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void loop() override { | ||||||
|  |       EVERY_N_MILLISECONDS(15) { | ||||||
|  |         m_noiseOffset += 1; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   private: | ||||||
|  |     int m_noiseOffset; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | STATIC_ALLOC(Thinking); | ||||||
|  | STATIC_TASK(Thinking); | ||||||
| @@ -131,10 +131,17 @@ SerialInput::doHelp(Args& args, Print& out) | |||||||
|   out.println("Available commands:"); |   out.println("Available commands:"); | ||||||
|   auto sched = MainLoop::instance()->scheduler; |   auto sched = MainLoop::instance()->scheduler; | ||||||
|   for(auto task : sched.tasks) { |   for(auto task : sched.tasks) { | ||||||
|  |     const auto taskCommands = task->commands(); | ||||||
|  |     if (!taskCommands.empty()) { | ||||||
|  |       out.print(task->name); | ||||||
|  |       out.println(":"); | ||||||
|  |       out.print("\t"); | ||||||
|       for(auto &command : task->commands()) { |       for(auto &command : task->commands()) { | ||||||
|         out.print(command.name); |         out.print(command.name); | ||||||
|         out.print(" "); |         out.print(" "); | ||||||
|       } |       } | ||||||
|  |       out.println(); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|   out.println(); |   out.println(); | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										147
									
								
								src/inputs/Weather.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								src/inputs/Weather.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,147 @@ | |||||||
|  | #include "Weather.h" | ||||||
|  | #include "../Static.h" | ||||||
|  |  | ||||||
|  | Weather::Weather() | ||||||
|  |   : BufferedInputSource("Weather") | ||||||
|  | { | ||||||
|  |   m_parser.setListener(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Weather::handleEvent(const InputEvent& evt) | ||||||
|  | { | ||||||
|  |   BufferedInputSource::handleEvent(evt); | ||||||
|  |   OnlineTaskMixin::handleEvent(evt); | ||||||
|  |   if (evt.intent == InputEvent::IdleStart) { | ||||||
|  |     doWeatherEvent(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Weather::loop() | ||||||
|  | { | ||||||
|  |   BufferedInputSource::loop(); | ||||||
|  |   OnlineTaskMixin::loop(); | ||||||
|  |   EVERY_N_SECONDS(3600) { | ||||||
|  |     update(); | ||||||
|  |   } | ||||||
|  |   if (m_fetching) { | ||||||
|  |     parse(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Weather::update() | ||||||
|  | { | ||||||
|  |   if (!m_fetching) { | ||||||
|  |     Log.info("Connecting to weather service"); | ||||||
|  |     m_parser.reset(); | ||||||
|  |     const String host = "api.openweathermap.org"; | ||||||
|  |     const uint8_t port = 80; | ||||||
|  | #ifdef ESP32 | ||||||
|  |     m_fetching = m_client.connect(host.c_str(), port); | ||||||
|  | #else | ||||||
|  |     m_fetching = m_client.connect(host, port); | ||||||
|  | #endif | ||||||
|  |     if (m_fetching) { | ||||||
|  |       Log.info("Ok!"); | ||||||
|  |       m_foundBody = false; | ||||||
|  |       m_client.print( | ||||||
|  |           "GET /data/2.5/weather?id=2950159&appid=f7aea43b54bcd27f542195a809c1463f&units=metric&lang=en HTTP/1.1\r\n" | ||||||
|  |           "Host: api.openweathermap.org\r\n" | ||||||
|  |           "Connection: close\r\n\r\n" | ||||||
|  |       ); | ||||||
|  |     } else { | ||||||
|  |       Log.info("No :("); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Weather::parse() | ||||||
|  | { | ||||||
|  |   if (m_client.available()) { | ||||||
|  |     char c = m_client.read(); | ||||||
|  |     if (c == '{') { | ||||||
|  |       m_foundBody = true; | ||||||
|  |     } | ||||||
|  |     if (m_foundBody) { | ||||||
|  |       m_parser.parse(c); | ||||||
|  |     } | ||||||
|  |   } else if (!m_client.connected()) { | ||||||
|  |     Log.info("Disconnected"); | ||||||
|  |     m_client.stop(); | ||||||
|  |     m_fetching = false; | ||||||
|  |     doWeatherEvent(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Weather::key(String key) | ||||||
|  | { | ||||||
|  |   m_curKey = key; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Weather::doWeatherEvent() | ||||||
|  | { | ||||||
|  |   Log.info("Dispatching weather event for %d", m_weatherId); | ||||||
|  |   if (m_weatherId >= 200 && m_weatherId < 300 ) { | ||||||
|  |     setEvent(InputEvent::SetScene, "Thunderstorm"); | ||||||
|  |     setEvent(InputEvent::SetColor, CRGB(255, 187, 0)); | ||||||
|  |   } else if (m_weatherId >= 300 && m_weatherId < 400) { | ||||||
|  |     setEvent(InputEvent::SetScene, "Drizzle"); | ||||||
|  |     setEvent(InputEvent::SetColor, CRGB(93, 183, 201)); | ||||||
|  |   } else if (m_weatherId >= 500 && m_weatherId < 600) { | ||||||
|  |     setEvent(InputEvent::SetScene, "Rain"); | ||||||
|  |     setEvent(InputEvent::SetColor, CRGB(64, 64, 255)); | ||||||
|  |   } else if (m_weatherId >= 600 && m_weatherId < 700) { | ||||||
|  |     setEvent(InputEvent::SetScene, "Snow"); | ||||||
|  |     setEvent(InputEvent::SetColor, CRGB(255, 255, 255)); | ||||||
|  |   } else if (m_weatherId == 701) { | ||||||
|  |     setEvent(InputEvent::SetScene, "Mist"); | ||||||
|  |     setEvent(InputEvent::SetColor, CRGB(77, 255, 124)); | ||||||
|  |   } else if (m_weatherId == 800) { | ||||||
|  |     setEvent(InputEvent::SetScene, "Clear"); | ||||||
|  |     setEvent(InputEvent::SetColor, CRGB(255, 249, 79)); | ||||||
|  |   } else if (m_weatherId >= 801 && m_weatherId <= 804) { | ||||||
|  |     setEvent(InputEvent::SetScene, "Cloudy"); | ||||||
|  |     setEvent(InputEvent::SetColor, CRGB(102, 110, 110)); | ||||||
|  |   } else { | ||||||
|  |     setEvent(InputEvent::SetScene, "UnknownWeather"); | ||||||
|  |     setEvent(InputEvent::SetColor, CRGB(255, 0, 0)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Weather::value(String value) | ||||||
|  | { | ||||||
|  |   if (m_curField == "weather") { | ||||||
|  |     if (m_curKey == "id") { | ||||||
|  |       m_weatherId = atoi(value.c_str()); | ||||||
|  |     } else if (m_curKey == "main") { | ||||||
|  |       Log.info("WEATHER ENCOUNTERED: %s", value.c_str()); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Weather::startObject() | ||||||
|  | { | ||||||
|  |   m_curField = m_curKey; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void | ||||||
|  | Weather::endObject() | ||||||
|  | { | ||||||
|  |   m_curField = ""; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Weather::endArray() {} | ||||||
|  | void Weather::startArray() {} | ||||||
|  | void Weather::endDocument() {} | ||||||
|  | void Weather::whitespace(char c) {} | ||||||
|  | void Weather::startDocument() {} | ||||||
|  |  | ||||||
|  | STATIC_ALLOC(Weather); | ||||||
|  | STATIC_TASK(Weather); | ||||||
							
								
								
									
										51
									
								
								src/inputs/Weather.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/inputs/Weather.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <Figments.h> | ||||||
|  | #include <JsonListener.h> | ||||||
|  | #include <JsonStreamingParser.h> | ||||||
|  | #include <WiFiClient.h> | ||||||
|  |  | ||||||
|  | class Weather : public BufferedInputSource, OnlineTaskMixin, JsonListener { | ||||||
|  |   public: | ||||||
|  |     Weather(); | ||||||
|  |  | ||||||
|  |     void handleEvent(const InputEvent& evt) override; | ||||||
|  |     void loop() override; | ||||||
|  |  | ||||||
|  |     void onOnline() override { | ||||||
|  |       update(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void onStart() override { | ||||||
|  |       update(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void loopOnline() override { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void update(); | ||||||
|  |     void parse(); | ||||||
|  |  | ||||||
|  |     virtual void whitespace(char c); | ||||||
|  |     virtual void startDocument(); | ||||||
|  |     virtual void key(String key); | ||||||
|  |     virtual void value(String value); | ||||||
|  |     virtual void endArray(); | ||||||
|  |     virtual void endObject(); | ||||||
|  |     virtual void endDocument(); | ||||||
|  |     virtual void startArray(); | ||||||
|  |     virtual void startObject(); | ||||||
|  |  | ||||||
|  |   private: | ||||||
|  |     JsonStreamingParser m_parser; | ||||||
|  |     WiFiClient m_client; | ||||||
|  |     bool m_fetching = false; | ||||||
|  |     String m_curKey; | ||||||
|  |     String m_curField; | ||||||
|  |     bool m_foundBody = false; | ||||||
|  |  | ||||||
|  |     int m_weatherId = 0; | ||||||
|  |  | ||||||
|  |     void doWeatherEvent(); | ||||||
|  | }; | ||||||
|  |  | ||||||
| @@ -44,7 +44,7 @@ ColorSequenceInput<9> idleCycle{{ | |||||||
|     CRGB(0, 255, 255), // Cyan |     CRGB(0, 255, 255), // Cyan | ||||||
| }, "IdleColors"}; | }, "IdleColors"}; | ||||||
|  |  | ||||||
| REGISTER_TASK(idleCycle); | //REGISTER_TASK(idleCycle); | ||||||
|  |  | ||||||
| ColorSequenceInput<7> rainbowCycle{{ | ColorSequenceInput<7> rainbowCycle{{ | ||||||
|     CRGB(255, 0, 0), // Red |     CRGB(255, 0, 0), // Red | ||||||
| @@ -54,7 +54,7 @@ ColorSequenceInput<7> rainbowCycle{{ | |||||||
|     CRGB(128, 0, 128), // Purple |     CRGB(128, 0, 128), // Purple | ||||||
| }, "Rainbow"}; | }, "Rainbow"}; | ||||||
|  |  | ||||||
| REGISTER_TASK(rainbowCycle); | //REGISTER_TASK(rainbowCycle); | ||||||
|  |  | ||||||
| MainLoop* runner = &SafeMode::safeModeApp; | MainLoop* runner = &SafeMode::safeModeApp; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,6 +9,8 @@ __NOINIT_ATTR static uint8_t s_rebootCount; | |||||||
| __NOINIT_ATTR static uint16_t s_forceSafeMode; | __NOINIT_ATTR static uint16_t s_forceSafeMode; | ||||||
| #define SAFE_MODE_MAGIC 6942 | #define SAFE_MODE_MAGIC 6942 | ||||||
|  |  | ||||||
|  | __NOINIT_ATTR static struct tm s_lastGoodTime; | ||||||
|  |  | ||||||
| WiFiUDP wifiUdp; | WiFiUDP wifiUdp; | ||||||
| NTPClient timeClient(wifiUdp, "pool.ntp.org", 0); | NTPClient timeClient(wifiUdp, "pool.ntp.org", 0); | ||||||
|  |  | ||||||
| @@ -44,8 +46,12 @@ template<> | |||||||
| bool | bool | ||||||
| PlatformImpl<HAL_ESP8266>::getLocalTime(struct tm* timedata, int timezone) | PlatformImpl<HAL_ESP8266>::getLocalTime(struct tm* timedata, int timezone) | ||||||
| { | { | ||||||
|   timedata->tm_hour = (timeClient.getHours() + timezone) % 23; |   if (timeClient.isTimeSet()) { | ||||||
|   timedata->tm_min = timeClient.getMinutes(); |     s_lastGoodTime.tm_min = (timeClient.getMinutes() + timezone) % 23; | ||||||
|  |     s_lastGoodTime.tm_hour = timeClient.getHours(); | ||||||
|  |     s_lastGoodTime.tm_sec = timeClient.getSeconds(); | ||||||
|  |   } | ||||||
|  |   memcpy(timedata, &s_lastGoodTime, sizeof(s_lastGoodTime)); | ||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -54,6 +60,9 @@ void | |||||||
| PlatformImpl<HAL_ESP8266>::startNTP() | PlatformImpl<HAL_ESP8266>::startNTP() | ||||||
| { | { | ||||||
|   timeClient.begin(); |   timeClient.begin(); | ||||||
|  |   if (s_lastGoodTime.tm_hour >= 24 || s_lastGoodTime.tm_min >= 60 || s_lastGoodTime.tm_sec >= 60) { | ||||||
|  |     memset(&s_lastGoodTime, 0, sizeof(s_lastGoodTime)); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| template<> | template<> | ||||||
|   | |||||||
| @@ -64,9 +64,7 @@ public: | |||||||
|           uint8_t val = lerp8by8(0, m_brightness, pixelMod); |           uint8_t val = lerp8by8(0, m_brightness, pixelMod); | ||||||
|           CHSV blobColor(m_hue, m_saturation, val); |           CHSV blobColor(m_hue, m_saturation, val); | ||||||
|  |  | ||||||
|           //PhysicalCoordinates pos{startPos.x + (i*m_fadeDir), startPos.y}; |           nblend(pixel, CRGB(blobColor), 80); | ||||||
|  |  | ||||||
|           pixel += blend(CRGB(blobColor), pixel, 80); |  | ||||||
|       }); |       }); | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user