From 660ae138d4a40bd3c48058f46d086d396fb217e0 Mon Sep 17 00:00:00 2001 From: DmitryBlinov Date: Sun, 7 Feb 2021 18:15:05 +0200 Subject: [PATCH 01/14] garland: fix Stars animation with more than 255 leds --- code/espurna/garland/animations/anim_stars.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/espurna/garland/animations/anim_stars.h b/code/espurna/garland/animations/anim_stars.h index 91f8eb28..22652939 100644 --- a/code/espurna/garland/animations/anim_stars.h +++ b/code/espurna/garland/animations/anim_stars.h @@ -23,7 +23,7 @@ class AnimStars : public Anim { } void Run() override { - for (byte i = 0; i < numLeds; i++) { + for (int i = 0; i < numLeds; i++) { byte phi = seq[i]; if (phi < 254) { Color col = ledstmp[i]; From dad8878ccfcef68f616fed7296b0f07983d855c3 Mon Sep 17 00:00:00 2001 From: DmitryBlinov Date: Sun, 7 Feb 2021 18:18:33 +0200 Subject: [PATCH 02/14] garland: disable software watchdog prevent Soft WDT exception with long strips --- code/espurna/garland.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/code/espurna/garland.cpp b/code/espurna/garland.cpp index 2d46c7ef..cec52a0a 100644 --- a/code/espurna/garland.cpp +++ b/code/espurna/garland.cpp @@ -4,15 +4,12 @@ Copyright (C) 2020 by Dmitry Blinov Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github.com/Vasil-Pahomov/Liana) -Tested on 60 led strip. -!!! For more leds can cause WDT rebot. Need to be carefully tested for more than 60 leds !!! +Tested on 300 led strip. + The most time consuming operation is actually showing leds by Adafruit Neopixel. It take about 1870 mcs. More long strip can take more time to show. Currently animation calculation, brightness calculation/transition and showing makes in one loop cycle. Debug output shows timings. Overal timing should be not more that 3000 ms. - -For longer strips have sense to divide entire strip (pixels) on parts about 100 pixels and show one part -at a cycle. */ #include "garland.h" @@ -40,6 +37,8 @@ const char* NAME_GARLAND_SET_DEFAULT = "garland_set_default"; #define EFFECT_UPDATE_INTERVAL_MIN 5000 // 5 sec #define EFFECT_UPDATE_INTERVAL_MAX 10000 // 10 sec +#define NUMLEDS_CAN_CAUSE_WDT_RESET 100 + bool _garland_enabled = true; unsigned long _last_update = 0; unsigned long _interval_effect_update; @@ -368,7 +367,20 @@ void Scene::run() { } if (state == Show && cyclesRemain < 2) { + /* Showing pixels (actually transmitting their RGB data) is most time consuming operation in the + garland workflow. Using 800 kHz gives 1.25 μs per bit. -> 30 μs (0.03 ms) per RGB LED. + So for example 3 ms for 100 LEDs. Unfortunately it can't be postponed and resumed later as it + will lead to reseting the transmition operation. From other hand, long operation can cause + Soft WDT reset. To avoid wdt reset we need to switch soft wdt off for long strips. + It is not best practice, but assuming that it is only garland, it can be acceptable. + Tested up to 300 leds. */ + if (_numLeds > NUMLEDS_CAN_CAUSE_WDT_RESET) { + ESP.wdtDisable(); + } _pixels->show(); + if (_numLeds > NUMLEDS_CAN_CAUSE_WDT_RESET) { + ESP.wdtEnable(5000); + } sum_show_time += (micros() - iteration_start_time); ++show_num; state = Calculate; From 3fe68748637099f08008fa5afc3650e09551285f Mon Sep 17 00:00:00 2001 From: DmitryBlinov Date: Mon, 8 Feb 2021 10:01:12 +0200 Subject: [PATCH 03/14] garland: improve anim_assemble --- .../garland/animations/anim_assemble.h | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/code/espurna/garland/animations/anim_assemble.h b/code/espurna/garland/animations/anim_assemble.h index 9262bdc3..23d7c99c 100644 --- a/code/espurna/garland/animations/anim_assemble.h +++ b/code/espurna/garland/animations/anim_assemble.h @@ -8,6 +8,13 @@ //------------------------------------------------------------------------------ class AnimAssemble : public Anim { + enum class Phases { + Assemmble, + Glow, + Fade + }; + + Phases phase = Phases::Assemmble; public: AnimAssemble() : Anim("Assemble") { cycleFactor = 2; @@ -33,20 +40,41 @@ class AnimAssemble : public Anim { } initSeq(); shuffleSeq(); + glowSetUp(); pos = 0; } void Run() override { - if (pos < numLeds) { - byte cur_point = seq[pos]; - leds[cur_point] = ledstmp[cur_point]; - ++pos; + if (phase == Phases::Assemmble) { + if (pos < numLeds) { + byte cur_point = seq[pos]; + leds[cur_point] = ledstmp[cur_point]; + ++pos; + } else { + pos = 0; + phase = Phases::Glow; + } + } else if (phase == Phases::Glow) { + if (pos < numLeds/2) { + for (int i = 0; i < numLeds; ++i) { + leds[i] = ledstmp[i]; + glowForEachLed(i); + } + glowRun(); + ++pos; + } else { + pos = 0; + phase = Phases::Fade; + } } else { - int del_pos = pos - numLeds; - byte cur_point = seq[del_pos]; - leds[cur_point] = 0; - if (++pos >= numLeds * 2) + if (pos < numLeds) { + byte cur_point = seq[pos]; + leds[cur_point] = 0; + ++pos; + } else { pos = 0; + phase = Phases::Assemmble; + } } } }; From 0f73df7c36f940040c6a6416c15d39c7eee213be Mon Sep 17 00:00:00 2001 From: DmitryBlinov Date: Mon, 8 Feb 2021 10:01:44 +0200 Subject: [PATCH 04/14] garland: set animation interval from 7 to 12 sec --- code/espurna/garland.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/espurna/garland.cpp b/code/espurna/garland.cpp index cec52a0a..c85a1fdd 100644 --- a/code/espurna/garland.cpp +++ b/code/espurna/garland.cpp @@ -34,8 +34,8 @@ const char* NAME_GARLAND_SET_BRIGHTNESS = "garland_set_brightness"; const char* NAME_GARLAND_SET_SPEED = "garland_set_speed"; const char* NAME_GARLAND_SET_DEFAULT = "garland_set_default"; -#define EFFECT_UPDATE_INTERVAL_MIN 5000 // 5 sec -#define EFFECT_UPDATE_INTERVAL_MAX 10000 // 10 sec +#define EFFECT_UPDATE_INTERVAL_MIN 7000 // 5 sec +#define EFFECT_UPDATE_INTERVAL_MAX 12000 // 10 sec #define NUMLEDS_CAN_CAUSE_WDT_RESET 100 From 518d56b442dda92c267fdb71e6b51b8a27638c0f Mon Sep 17 00:00:00 2001 From: DmitryBlinov Date: Fri, 19 Feb 2021 01:21:54 +0200 Subject: [PATCH 05/14] garland: mqtt support --- code/espurna/garland.cpp | 103 ++++++++++++++++++++++++++++++++--- code/espurna/garland/scene.h | 13 ++++- 2 files changed, 105 insertions(+), 11 deletions(-) diff --git a/code/espurna/garland.cpp b/code/espurna/garland.cpp index c85a1fdd..1d6184f1 100644 --- a/code/espurna/garland.cpp +++ b/code/espurna/garland.cpp @@ -10,6 +10,15 @@ The most time consuming operation is actually showing leds by Adafruit Neopixel. More long strip can take more time to show. Currently animation calculation, brightness calculation/transition and showing makes in one loop cycle. Debug output shows timings. Overal timing should be not more that 3000 ms. + +MQTT control: +"command:["immediate", "queue", "sequence", "reset"] +"enable":["true", "false"] +"brightness":[0-255] +"speed":[30-60] +"animation":["PixieDust", "Sparkr", "Run", "Stars", "Spread", "R"andCyc", "Fly", "Comets", "Assemble", "Dolphins", "Salut"] +"palette":["RGB", "Rainbow", "Stripe", "Party", "Heat", Fire", "Blue", "Sun", "Lime", "Pastel"] +"duration":5000 */ #include "garland.h" @@ -23,6 +32,7 @@ Debug output shows timings. Overal timing should be not more that 3000 ms. #include "garland/color.h" #include "garland/palette.h" #include "garland/scene.h" +#include "mqtt.h" #include "ws.h" const char* NAME_GARLAND_ENABLED = "garlandEnabled"; @@ -34,6 +44,19 @@ const char* NAME_GARLAND_SET_BRIGHTNESS = "garland_set_brightness"; const char* NAME_GARLAND_SET_SPEED = "garland_set_speed"; const char* NAME_GARLAND_SET_DEFAULT = "garland_set_default"; +const char* MQTT_TOPIC_GARLAND = "garland"; +const char* MQTT_TOPIC_COMMAND = "command"; +const char* MQTT_TOPIC_ENABLE = "enable"; +const char* MQTT_TOPIC_BRIGHTNESS = "brightness"; +const char* MQTT_TOPIC_ANIM_PEED = "speed"; +const char* MQTT_TOPIC_ANIMATION = "animation"; +const char* MQTT_TOPIC_PALETTE = "palette"; +const char* MQTT_TOPIC_DURATION = "duration"; + +const char* GARLAND_COMMAND_IMMEDIATE = "immediate"; +const char* GARLAND_COMMAND_RESET = "reset"; // reset queue +const char* GARLAND_COMMAND_QUEUE = "queue"; // enqueue command payload + #define EFFECT_UPDATE_INTERVAL_MIN 7000 // 5 sec #define EFFECT_UPDATE_INTERVAL_MAX 12000 // 10 sec @@ -91,6 +114,8 @@ Anim* anims[] = {new AnimStart(), new AnimPixieDust(), new AnimSparkr(), new Ani constexpr size_t animsSize() { return sizeof(anims)/sizeof(anims[0]); } +String immediate_command; +std::queue commands; //------------------------------------------------------------------------------ void garlandDisable() { pixels.clear(); @@ -99,6 +124,13 @@ void garlandDisable() { //------------------------------------------------------------------------------ void garlandEnabled(bool enabled) { _garland_enabled = enabled; + setSetting(NAME_GARLAND_ENABLED, _garland_enabled); + if (!_garland_enabled) { + schedule_function([]() { + pixels.clear(); + pixels.show(); + }); + } } //------------------------------------------------------------------------------ @@ -147,14 +179,7 @@ bool _garlandWebSocketOnKeyCheck(const char* key, JsonVariant& value) { void _garlandWebSocketOnAction(uint32_t client_id, const char* action, JsonObject& data) { if (strcmp(action, NAME_GARLAND_SWITCH) == 0) { if (data.containsKey("status") && data.is("status")) { - _garland_enabled = (1 == data["status"].as()); - setSetting(NAME_GARLAND_ENABLED, _garland_enabled); - if (!_garland_enabled) { - schedule_function([](){ - pixels.clear(); - pixels.show(); - }); - } + garlandEnabled(1 == data["status"].as()); } } @@ -189,10 +214,33 @@ void _garlandWebSocketOnAction(uint32_t client_id, const char* action, JsonObjec } #endif +//------------------------------------------------------------------------------ +void executeCommand(const String& command) { + DEBUG_MSG_P(PSTR("[GARLAND] Executing command \"%s\"\n"), command.c_str()); + // Parse JSON input + DynamicJsonBuffer jsonBuffer; + JsonObject& root = jsonBuffer.parseObject(command); + if (!root.success()) { + DEBUG_MSG_P(PSTR("[GARLAND] Error parsing command\n")); + return; + } + + if (root.containsKey(MQTT_TOPIC_ENABLE)) { + auto enable = root[MQTT_TOPIC_ENABLE].as(); + garlandEnabled(enable != "false"); + DEBUG_MSG_P(PSTR("[GARLAND] Enabled: \"%s\"\n"), enable.c_str()); + } +} + //------------------------------------------------------------------------------ // Loop //------------------------------------------------------------------------------ void garlandLoop(void) { + if (!immediate_command.isEmpty()) { + executeCommand(immediate_command); + immediate_command.clear(); + } + if (!garlandEnabled()) return; @@ -226,10 +274,49 @@ void garlandLoop(void) { } } +//------------------------------------------------------------------------------ +void garlandMqttCallback(unsigned int type, const char * topic, const char * payload) { + if (type == MQTT_CONNECT_EVENT) { + mqttSubscribe(MQTT_TOPIC_GARLAND); + } + + if (type == MQTT_MESSAGE_EVENT) { + // Match topic + String t = mqttMagnitude((char*)topic); + + if (t.equals(MQTT_TOPIC_GARLAND)) { + // Parse JSON input + DynamicJsonBuffer jsonBuffer; + JsonObject& root = jsonBuffer.parseObject(payload); + if (!root.success()) { + DEBUG_MSG_P(PSTR("[GARLAND] Error parsing mqtt data\n")); + return; + } + + String command = GARLAND_COMMAND_IMMEDIATE; + if (root.containsKey(MQTT_TOPIC_COMMAND)) { + command = root[MQTT_TOPIC_COMMAND].as(); + DEBUG_MSG_P(PSTR("[GARLAND] Command: \"%s\"\n"), command.c_str()); + } + + if (command == GARLAND_COMMAND_IMMEDIATE) { + immediate_command = payload; + } else if (command == GARLAND_COMMAND_RESET) { + std::queue empty; + std::swap( commands, empty ); + immediate_command = ""; + } else if (command == GARLAND_COMMAND_QUEUE) { + commands.push(payload); + } + } + } +} + //------------------------------------------------------------------------------ void garlandSetup() { _garlandConfigure(); + mqttRegister(garlandMqttCallback); // Websockets #if WEB_SUPPORT wsRegister() diff --git a/code/espurna/garland/scene.h b/code/espurna/garland/scene.h index 7e2630d1..30ca5a3d 100644 --- a/code/espurna/garland/scene.h +++ b/code/espurna/garland/scene.h @@ -70,10 +70,17 @@ private: byte brightness = 0; - // Reverse to speed. If more convenient to calculate in this way. - // 1 < cycleFactor < 4 - byte speed = 50; + // cycleFactor is actually number of cycles to calculate and draw one animation step + // if cycleFactor is 2 or more, than calculation and drawing made in different cycles + // cycleFactor is float. For example cycleFactor=2.5 gives one step 2 than next 3 cycles per anim step + // Recommended values: 1 < cycleFactor < 4 float cycleFactor = 2.0; + // speed is reverse to cycleFactor. For forward direction control of animation speed. + // Recommended values: 30 < speed < 60. + // Correspondence: + // speed=60, cycleFactor=1 + // speed=30, cycleFactor=4 + byte speed = 50; float cycleTail = 0; int cyclesRemain = 0; From f640cd8ecb3d170e9082d60461b89e29454bbbd4 Mon Sep 17 00:00:00 2001 From: DmitryBlinov Date: Fri, 19 Feb 2021 18:09:31 +0200 Subject: [PATCH 06/14] garland: control parameters through mqtt --- code/espurna/garland.cpp | 156 +++++++++++++++++++++++++++------------ 1 file changed, 109 insertions(+), 47 deletions(-) diff --git a/code/espurna/garland.cpp b/code/espurna/garland.cpp index 1d6184f1..f619aecb 100644 --- a/code/espurna/garland.cpp +++ b/code/espurna/garland.cpp @@ -45,26 +45,29 @@ const char* NAME_GARLAND_SET_SPEED = "garland_set_speed"; const char* NAME_GARLAND_SET_DEFAULT = "garland_set_default"; const char* MQTT_TOPIC_GARLAND = "garland"; -const char* MQTT_TOPIC_COMMAND = "command"; -const char* MQTT_TOPIC_ENABLE = "enable"; -const char* MQTT_TOPIC_BRIGHTNESS = "brightness"; -const char* MQTT_TOPIC_ANIM_PEED = "speed"; -const char* MQTT_TOPIC_ANIMATION = "animation"; -const char* MQTT_TOPIC_PALETTE = "palette"; -const char* MQTT_TOPIC_DURATION = "duration"; - -const char* GARLAND_COMMAND_IMMEDIATE = "immediate"; -const char* GARLAND_COMMAND_RESET = "reset"; // reset queue -const char* GARLAND_COMMAND_QUEUE = "queue"; // enqueue command payload + +const char* MQTT_PAYLOAD_COMMAND = "command"; +const char* MQTT_PAYLOAD_ENABLE = "enable"; +const char* MQTT_PAYLOAD_BRIGHTNESS = "brightness"; +const char* MQTT_PAYLOAD_ANIM_SPEED = "speed"; +const char* MQTT_PAYLOAD_ANIMATION = "animation"; +const char* MQTT_PAYLOAD_PALETTE = "palette"; +const char* MQTT_PAYLOAD_DURATION = "duration"; + +const char* MQTT_COMMAND_IMMEDIATE = "immediate"; +const char* MQTT_COMMAND_RESET = "reset"; // reset queue +const char* MQTT_COMMAND_QUEUE = "queue"; // enqueue command payload #define EFFECT_UPDATE_INTERVAL_MIN 7000 // 5 sec #define EFFECT_UPDATE_INTERVAL_MAX 12000 // 10 sec #define NUMLEDS_CAN_CAUSE_WDT_RESET 100 -bool _garland_enabled = true; -unsigned long _last_update = 0; -unsigned long _interval_effect_update; +bool _garland_enabled = true; +unsigned long _lastTimeUpdate = 0; +unsigned long _currentAnimDuration = ULONG_MAX; +unsigned int _currentAnimInd = 0; +unsigned int _currentPaletteInd = 0; // Palette should Palette pals[] = { @@ -214,6 +217,32 @@ void _garlandWebSocketOnAction(uint32_t client_id, const char* action, JsonObjec } #endif +//------------------------------------------------------------------------------ +void setupScene(unsigned int anim_ind, unsigned int palette_ind, unsigned long duration) { + unsigned long currentAnimRunTime = millis() - _lastTimeUpdate; + _lastTimeUpdate = millis(); + _currentAnimDuration = duration; + + int prevAnimInd = _currentAnimInd; + _currentAnimInd = anim_ind; + + int prevPalInd = _currentPaletteInd; + _currentPaletteInd = palette_ind; + + int numShows = scene.getNumShows(); + int frameRate = currentAnimRunTime > 0 ? numShows * 1000 / currentAnimRunTime : 0; + + DEBUG_MSG_P(PSTR("[GARLAND] Anim: %-10s Pal: %-8s timings: calc: %4d pixl: %3d show: %4d frate: %d\n"), + anims[prevAnimInd]->name(), pals[prevPalInd].name(), + scene.getAvgCalcTime(), scene.getAvgPixlTime(), scene.getAvgShowTime(), frameRate); + DEBUG_MSG_P(PSTR("[GARLAND] Anim: %-10s Pal: %-8s Inter: %d\n"), + anims[_currentAnimInd]->name(), pals[_currentPaletteInd].name(), _currentAnimDuration); + + scene.setAnim(anims[_currentAnimInd]); + scene.setPalette(&pals[_currentPaletteInd]); + scene.setup(); +} + //------------------------------------------------------------------------------ void executeCommand(const String& command) { DEBUG_MSG_P(PSTR("[GARLAND] Executing command \"%s\"\n"), command.c_str()); @@ -225,10 +254,58 @@ void executeCommand(const String& command) { return; } - if (root.containsKey(MQTT_TOPIC_ENABLE)) { - auto enable = root[MQTT_TOPIC_ENABLE].as(); + bool scene_setup_required = false; + + if (root.containsKey(MQTT_PAYLOAD_ENABLE)) { + auto enable = root[MQTT_PAYLOAD_ENABLE].as(); garlandEnabled(enable != "false"); - DEBUG_MSG_P(PSTR("[GARLAND] Enabled: \"%s\"\n"), enable.c_str()); + } + + if (root.containsKey(MQTT_PAYLOAD_BRIGHTNESS)) { + auto brightness = root[MQTT_PAYLOAD_BRIGHTNESS].as(); + scene.setBrightness(brightness); + } + + if (root.containsKey(MQTT_PAYLOAD_ANIM_SPEED)) { + auto speed = root[MQTT_PAYLOAD_ANIM_SPEED].as(); + scene.setSpeed(speed); + } + + unsigned int newAnimInd = _currentAnimInd; + if (root.containsKey(MQTT_PAYLOAD_ANIMATION)) { + auto animation = root[MQTT_PAYLOAD_ANIMATION].as(); + for (size_t i = 0; i < animsSize(); ++i) { + auto anim_name = anims[i]->name(); + if (strcmp(animation, anim_name) == 0) { + newAnimInd = i; + scene_setup_required = true; + break; + } + } + } + + unsigned int newPalInd = _currentPaletteInd; + if (root.containsKey(MQTT_PAYLOAD_PALETTE)) { + auto palette = root[MQTT_PAYLOAD_PALETTE].as(); + for (size_t i = 0; i < palsSize(); ++i) { + auto pal_name = pals[i].name(); + if (strcmp(palette, pal_name) == 0) { + newPalInd = i; + scene_setup_required = true; + break; + } + } + } + + unsigned long newAnimDuration = LONG_MAX; + if (root.containsKey(MQTT_PAYLOAD_DURATION)) { + newAnimDuration = root[MQTT_PAYLOAD_DURATION].as(); + scene_setup_required = true; + } + + + if (scene_setup_required) { + setupScene(newAnimInd, newPalInd, newAnimDuration); } } @@ -246,31 +323,17 @@ void garlandLoop(void) { scene.run(); - unsigned long animation_time = millis() - _last_update; - if (animation_time > _interval_effect_update && scene.finishedAnimCycle()) { - _last_update = millis(); - _interval_effect_update = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX); - - static int animInd = 0; - int prevAnimInd = animInd; - while (prevAnimInd == animInd) animInd = secureRandom(1, animsSize()); - - static int paletteInd = 0; - int prevPalInd = paletteInd; - while (prevPalInd == paletteInd) paletteInd = secureRandom(palsSize()); + unsigned long currentAnimRunTime = millis() - _lastTimeUpdate; + if (currentAnimRunTime > _currentAnimDuration && scene.finishedAnimCycle()) { + unsigned int newAnimInd = _currentAnimInd; + while (newAnimInd == _currentAnimInd) newAnimInd = secureRandom(1, animsSize()); - int numShows = scene.getNumShows(); - int frameRate = animation_time > 0 ? numShows * 1000 / animation_time : 0; + unsigned int newPalInd = _currentPaletteInd; + while (newPalInd == _currentPaletteInd) newPalInd = secureRandom(palsSize()); - DEBUG_MSG_P(PSTR("[GARLAND] Anim: %-10s Pal: %-8s timings: calc: %4d pixl: %3d show: %4d frate: %d\n"), - anims[prevAnimInd]->name(), pals[prevPalInd].name(), - scene.getAvgCalcTime(), scene.getAvgPixlTime(), scene.getAvgShowTime(), frameRate); - DEBUG_MSG_P(PSTR("[GARLAND] Anim: %-10s Pal: %-8s Inter: %d\n"), - anims[animInd]->name(), pals[paletteInd].name(), _interval_effect_update); + unsigned long newAnimDuration = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX); - scene.setAnim(anims[animInd]); - scene.setPalette(&pals[paletteInd]); - scene.setup(); + setupScene(newAnimInd, newPalInd, newAnimDuration); } } @@ -293,19 +356,18 @@ void garlandMqttCallback(unsigned int type, const char * topic, const char * pay return; } - String command = GARLAND_COMMAND_IMMEDIATE; - if (root.containsKey(MQTT_TOPIC_COMMAND)) { - command = root[MQTT_TOPIC_COMMAND].as(); - DEBUG_MSG_P(PSTR("[GARLAND] Command: \"%s\"\n"), command.c_str()); + String command = MQTT_COMMAND_IMMEDIATE; + if (root.containsKey(MQTT_PAYLOAD_COMMAND)) { + command = root[MQTT_PAYLOAD_COMMAND].as(); } - if (command == GARLAND_COMMAND_IMMEDIATE) { + if (command == MQTT_COMMAND_IMMEDIATE) { immediate_command = payload; - } else if (command == GARLAND_COMMAND_RESET) { + } else if (command == MQTT_COMMAND_RESET) { std::queue empty; std::swap( commands, empty ); immediate_command = ""; - } else if (command == GARLAND_COMMAND_QUEUE) { + } else if (command == MQTT_COMMAND_QUEUE) { commands.push(payload); } } @@ -333,7 +395,7 @@ void garlandSetup() { scene.setPalette(&pals[0]); scene.setup(); - _interval_effect_update = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX); + _currentAnimDuration = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX); } /*####################################################################### From 4efc417a39220638079bdf060b0fc204d777f942 Mon Sep 17 00:00:00 2001 From: DmitryBlinov Date: Fri, 19 Feb 2021 19:12:36 +0200 Subject: [PATCH 07/14] garland: more mqtt improvements implement mqtt command "reset" that set default params and enabled garland --- code/espurna/garland.cpp | 49 ++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/code/espurna/garland.cpp b/code/espurna/garland.cpp index f619aecb..36b385dc 100644 --- a/code/espurna/garland.cpp +++ b/code/espurna/garland.cpp @@ -68,6 +68,8 @@ unsigned long _lastTimeUpdate = 0; unsigned long _currentAnimDuration = ULONG_MAX; unsigned int _currentAnimInd = 0; unsigned int _currentPaletteInd = 0; +String _immediate_command; +std::queue _commands; // Palette should Palette pals[] = { @@ -117,8 +119,6 @@ Anim* anims[] = {new AnimStart(), new AnimPixieDust(), new AnimSparkr(), new Ani constexpr size_t animsSize() { return sizeof(anims)/sizeof(anims[0]); } -String immediate_command; -std::queue commands; //------------------------------------------------------------------------------ void garlandDisable() { pixels.clear(); @@ -134,6 +134,11 @@ void garlandEnabled(bool enabled) { pixels.show(); }); } +#if WEB_SUPPORT + char buffer[128]; + snprintf_P(buffer, sizeof(buffer), PSTR("{\"garlandEnabled\": %s}"), enabled ? "true" : "false"); + wsSend(buffer); +#endif } //------------------------------------------------------------------------------ @@ -161,6 +166,20 @@ void _garlandReload() { _garlandConfigure(); } +//------------------------------------------------------------------------------ +void setDefault() { + scene.setDefault(); + byte brightness = scene.getBrightness(); + setSetting(NAME_GARLAND_BRIGHTNESS, brightness); + byte speed = scene.getSpeed(); + setSetting(NAME_GARLAND_SPEED, speed); +#if WEB_SUPPORT + char buffer[128]; + snprintf_P(buffer, sizeof(buffer), PSTR("{\"garlandBrightness\": %d, \"garlandSpeed\": %d}"), brightness, speed); + wsSend(buffer); +#endif +} + #if WEB_SUPPORT //------------------------------------------------------------------------------ void _garlandWebSocketOnConnected(JsonObject& root) { @@ -205,14 +224,7 @@ void _garlandWebSocketOnAction(uint32_t client_id, const char* action, JsonObjec } if (strcmp(action, NAME_GARLAND_SET_DEFAULT) == 0) { - scene.setDefault(); - byte brightness = scene.getBrightness(); - setSetting(NAME_GARLAND_BRIGHTNESS, brightness); - byte speed = scene.getSpeed(); - setSetting(NAME_GARLAND_SPEED, speed); - char buffer[128]; - snprintf_P(buffer, sizeof(buffer), PSTR("{\"garlandBrightness\": %d, \"garlandSpeed\": %d}"), brightness, speed); - wsSend(buffer); + setDefault(); } } #endif @@ -313,9 +325,9 @@ void executeCommand(const String& command) { // Loop //------------------------------------------------------------------------------ void garlandLoop(void) { - if (!immediate_command.isEmpty()) { - executeCommand(immediate_command); - immediate_command.clear(); + if (!_immediate_command.isEmpty()) { + executeCommand(_immediate_command); + _immediate_command.clear(); } if (!garlandEnabled()) @@ -362,13 +374,16 @@ void garlandMqttCallback(unsigned int type, const char * topic, const char * pay } if (command == MQTT_COMMAND_IMMEDIATE) { - immediate_command = payload; + _immediate_command = payload; } else if (command == MQTT_COMMAND_RESET) { std::queue empty; - std::swap( commands, empty ); - immediate_command = ""; + std::swap( _commands, empty ); + _immediate_command.clear(); + _currentAnimDuration = 0; + setDefault(); + garlandEnabled(true); } else if (command == MQTT_COMMAND_QUEUE) { - commands.push(payload); + _commands.push(payload); } } } From 24550a5b80e1a626a7d8090746c0cfda2bfb4b23 Mon Sep 17 00:00:00 2001 From: DmitryBlinov Date: Fri, 19 Feb 2021 22:07:11 +0200 Subject: [PATCH 08/14] garland: implement commands queue --- code/espurna/garland.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/code/espurna/garland.cpp b/code/espurna/garland.cpp index 36b385dc..7dfb2692 100644 --- a/code/espurna/garland.cpp +++ b/code/espurna/garland.cpp @@ -337,15 +337,20 @@ void garlandLoop(void) { unsigned long currentAnimRunTime = millis() - _lastTimeUpdate; if (currentAnimRunTime > _currentAnimDuration && scene.finishedAnimCycle()) { - unsigned int newAnimInd = _currentAnimInd; - while (newAnimInd == _currentAnimInd) newAnimInd = secureRandom(1, animsSize()); + if (!_commands.empty()) { + executeCommand(_commands.front()); + _commands.pop(); + } else { + unsigned int newAnimInd = _currentAnimInd; + while (newAnimInd == _currentAnimInd) newAnimInd = secureRandom(1, animsSize()); - unsigned int newPalInd = _currentPaletteInd; - while (newPalInd == _currentPaletteInd) newPalInd = secureRandom(palsSize()); + unsigned int newPalInd = _currentPaletteInd; + while (newPalInd == _currentPaletteInd) newPalInd = secureRandom(palsSize()); - unsigned long newAnimDuration = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX); + unsigned long newAnimDuration = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX); - setupScene(newAnimInd, newPalInd, newAnimDuration); + setupScene(newAnimInd, newPalInd, newAnimDuration); + } } } From 6508f6bda8da2acef555fd2b909b7e2983b97e83 Mon Sep 17 00:00:00 2001 From: DmitryBlinov Date: Fri, 19 Feb 2021 23:01:41 +0200 Subject: [PATCH 09/14] garland: implement command sequence --- code/espurna/garland.cpp | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/code/espurna/garland.cpp b/code/espurna/garland.cpp index 7dfb2692..b0bf533e 100644 --- a/code/espurna/garland.cpp +++ b/code/espurna/garland.cpp @@ -57,6 +57,7 @@ const char* MQTT_PAYLOAD_DURATION = "duration"; const char* MQTT_COMMAND_IMMEDIATE = "immediate"; const char* MQTT_COMMAND_RESET = "reset"; // reset queue const char* MQTT_COMMAND_QUEUE = "queue"; // enqueue command payload +const char* MQTT_COMMAND_SEQUENCE = "sequence"; // place command to sequence #define EFFECT_UPDATE_INTERVAL_MIN 7000 // 5 sec #define EFFECT_UPDATE_INTERVAL_MAX 12000 // 10 sec @@ -68,8 +69,10 @@ unsigned long _lastTimeUpdate = 0; unsigned long _currentAnimDuration = ULONG_MAX; unsigned int _currentAnimInd = 0; unsigned int _currentPaletteInd = 0; +unsigned int _currentCommandInSequence = 0; String _immediate_command; -std::queue _commands; +std::queue _command_queue; +std::vector _command_sequence; // Palette should Palette pals[] = { @@ -256,14 +259,14 @@ void setupScene(unsigned int anim_ind, unsigned int palette_ind, unsigned long d } //------------------------------------------------------------------------------ -void executeCommand(const String& command) { +bool executeCommand(const String& command) { DEBUG_MSG_P(PSTR("[GARLAND] Executing command \"%s\"\n"), command.c_str()); // Parse JSON input DynamicJsonBuffer jsonBuffer; JsonObject& root = jsonBuffer.parseObject(command); if (!root.success()) { DEBUG_MSG_P(PSTR("[GARLAND] Error parsing command\n")); - return; + return false; } bool scene_setup_required = false; @@ -318,7 +321,9 @@ void executeCommand(const String& command) { if (scene_setup_required) { setupScene(newAnimInd, newPalInd, newAnimDuration); + return true; } + return false; } //------------------------------------------------------------------------------ @@ -337,10 +342,18 @@ void garlandLoop(void) { unsigned long currentAnimRunTime = millis() - _lastTimeUpdate; if (currentAnimRunTime > _currentAnimDuration && scene.finishedAnimCycle()) { - if (!_commands.empty()) { - executeCommand(_commands.front()); - _commands.pop(); - } else { + bool scene_setup_done = false; + if (!_command_queue.empty()) { + scene_setup_done = executeCommand(_command_queue.front()); + _command_queue.pop(); + } else if (!_command_sequence.empty()) { + scene_setup_done = executeCommand(_command_sequence[_currentCommandInSequence]); + ++_currentCommandInSequence; + if (_currentCommandInSequence >= _command_sequence.size()) + _currentCommandInSequence = 0; + } + + if (!scene_setup_done) { unsigned int newAnimInd = _currentAnimInd; while (newAnimInd == _currentAnimInd) newAnimInd = secureRandom(1, animsSize()); @@ -381,14 +394,18 @@ void garlandMqttCallback(unsigned int type, const char * topic, const char * pay if (command == MQTT_COMMAND_IMMEDIATE) { _immediate_command = payload; } else if (command == MQTT_COMMAND_RESET) { - std::queue empty; - std::swap( _commands, empty ); + std::queue empty_queue; + std::swap(_command_queue, empty_queue); + std::vector empty_sequence; + std::swap(_command_sequence, empty_sequence); _immediate_command.clear(); _currentAnimDuration = 0; setDefault(); garlandEnabled(true); } else if (command == MQTT_COMMAND_QUEUE) { - _commands.push(payload); + _command_queue.push(payload); + } else if (command == MQTT_COMMAND_SEQUENCE) { + _command_sequence.push_back(payload); } } } From 4923377eacc5158896e8fd9ddbc993d1bb2653be Mon Sep 17 00:00:00 2001 From: DmitryBlinov Date: Sat, 20 Feb 2021 02:02:54 +0200 Subject: [PATCH 10/14] garland: animation improvements - add hidden animation Glow for one color glowing - implement one color Palette for mqtt controlled animation. --- code/espurna/garland.cpp | 91 ++++++++++++--------- code/espurna/garland/animations/anim_glow.h | 34 ++++++++ code/espurna/garland/scene.h | 1 + 3 files changed, 89 insertions(+), 37 deletions(-) create mode 100644 code/espurna/garland/animations/anim_glow.h diff --git a/code/espurna/garland.cpp b/code/espurna/garland.cpp index b0bf533e..23840906 100644 --- a/code/espurna/garland.cpp +++ b/code/espurna/garland.cpp @@ -27,6 +27,7 @@ MQTT control: #include +#include #include #include "garland/color.h" @@ -66,9 +67,7 @@ const char* MQTT_COMMAND_SEQUENCE = "sequence"; // place command to sequen bool _garland_enabled = true; unsigned long _lastTimeUpdate = 0; -unsigned long _currentAnimDuration = ULONG_MAX; -unsigned int _currentAnimInd = 0; -unsigned int _currentPaletteInd = 0; +unsigned long _currentDuration = ULONG_MAX; unsigned int _currentCommandInSequence = 0; String _immediate_command; std::queue _command_queue; @@ -117,11 +116,15 @@ constexpr size_t palsSize() { return sizeof(pals)/sizeof(pals[0]); } Adafruit_NeoPixel pixels = Adafruit_NeoPixel(GARLAND_LEDS, GARLAND_D_PIN, NEO_GRB + NEO_KHZ800); Scene scene(&pixels); -Anim* anims[] = {new AnimStart(), new AnimPixieDust(), new AnimSparkr(), new AnimRun(), new AnimStars(), new AnimSpread(), +Anim* anims[] = {new AnimGlow(), new AnimStart(), new AnimPixieDust(), new AnimSparkr(), new AnimRun(), new AnimStars(), new AnimSpread(), new AnimRandCyc(), new AnimFly(), new AnimComets(), new AnimAssemble(), new AnimDolphins(), new AnimSalut()}; constexpr size_t animsSize() { return sizeof(anims)/sizeof(anims[0]); } +#define START_ANIMATION 1 +Anim* _currentAnim = anims[1]; +Palette* _currentPalette = &pals[0]; +auto one_color_palette = std::unique_ptr(new Palette("White", {0xffffff})); //------------------------------------------------------------------------------ void garlandDisable() { pixels.clear(); @@ -233,28 +236,27 @@ void _garlandWebSocketOnAction(uint32_t client_id, const char* action, JsonObjec #endif //------------------------------------------------------------------------------ -void setupScene(unsigned int anim_ind, unsigned int palette_ind, unsigned long duration) { +void setupScene(Anim* new_anim, Palette* new_palette, unsigned long new_duration) { unsigned long currentAnimRunTime = millis() - _lastTimeUpdate; _lastTimeUpdate = millis(); - _currentAnimDuration = duration; - - int prevAnimInd = _currentAnimInd; - _currentAnimInd = anim_ind; - - int prevPalInd = _currentPaletteInd; - _currentPaletteInd = palette_ind; int numShows = scene.getNumShows(); int frameRate = currentAnimRunTime > 0 ? numShows * 1000 / currentAnimRunTime : 0; + static String palette_name = "Start"; DEBUG_MSG_P(PSTR("[GARLAND] Anim: %-10s Pal: %-8s timings: calc: %4d pixl: %3d show: %4d frate: %d\n"), - anims[prevAnimInd]->name(), pals[prevPalInd].name(), + _currentAnim->name(), palette_name.c_str(), scene.getAvgCalcTime(), scene.getAvgPixlTime(), scene.getAvgShowTime(), frameRate); + + _currentDuration = new_duration; + _currentAnim = new_anim; + _currentPalette = new_palette; + palette_name = _currentPalette->name(); DEBUG_MSG_P(PSTR("[GARLAND] Anim: %-10s Pal: %-8s Inter: %d\n"), - anims[_currentAnimInd]->name(), pals[_currentPaletteInd].name(), _currentAnimDuration); + _currentAnim->name(), palette_name.c_str(), _currentDuration); - scene.setAnim(anims[_currentAnimInd]); - scene.setPalette(&pals[_currentPaletteInd]); + scene.setAnim(_currentAnim); + scene.setPalette(_currentPalette); scene.setup(); } @@ -286,28 +288,42 @@ bool executeCommand(const String& command) { scene.setSpeed(speed); } - unsigned int newAnimInd = _currentAnimInd; + Anim* newAnim = _currentAnim; if (root.containsKey(MQTT_PAYLOAD_ANIMATION)) { auto animation = root[MQTT_PAYLOAD_ANIMATION].as(); for (size_t i = 0; i < animsSize(); ++i) { auto anim_name = anims[i]->name(); if (strcmp(animation, anim_name) == 0) { - newAnimInd = i; + newAnim = anims[i]; scene_setup_required = true; break; } } } - unsigned int newPalInd = _currentPaletteInd; + Palette* newPalette = _currentPalette; if (root.containsKey(MQTT_PAYLOAD_PALETTE)) { - auto palette = root[MQTT_PAYLOAD_PALETTE].as(); - for (size_t i = 0; i < palsSize(); ++i) { - auto pal_name = pals[i].name(); - if (strcmp(palette, pal_name) == 0) { - newPalInd = i; - scene_setup_required = true; - break; + if (root.is(MQTT_PAYLOAD_PALETTE)) { + one_color_palette.reset(new Palette("Color", {root[MQTT_PAYLOAD_PALETTE].as()})); + newPalette = one_color_palette.get(); + } else { + auto palette = root[MQTT_PAYLOAD_PALETTE].as(); + bool palette_found = false; + for (size_t i = 0; i < palsSize(); ++i) { + auto pal_name = pals[i].name(); + if (strcmp(palette, pal_name) == 0) { + newPalette = &pals[i]; + palette_found = true; + scene_setup_required = true; + break; + } + } + if (!palette_found) { + uint32_t color = (uint32_t)strtoul(palette, NULL, 0); + if (color != 0) { + one_color_palette.reset(new Palette("Color", {color})); + newPalette = one_color_palette.get(); + } } } } @@ -320,7 +336,7 @@ bool executeCommand(const String& command) { if (scene_setup_required) { - setupScene(newAnimInd, newPalInd, newAnimDuration); + setupScene(newAnim, newPalette, newAnimDuration); return true; } return false; @@ -341,7 +357,7 @@ void garlandLoop(void) { scene.run(); unsigned long currentAnimRunTime = millis() - _lastTimeUpdate; - if (currentAnimRunTime > _currentAnimDuration && scene.finishedAnimCycle()) { + if (currentAnimRunTime > _currentDuration && scene.finishedAnimCycle()) { bool scene_setup_done = false; if (!_command_queue.empty()) { scene_setup_done = executeCommand(_command_queue.front()); @@ -354,15 +370,16 @@ void garlandLoop(void) { } if (!scene_setup_done) { - unsigned int newAnimInd = _currentAnimInd; - while (newAnimInd == _currentAnimInd) newAnimInd = secureRandom(1, animsSize()); + Anim* newAnim = _currentAnim; + while (newAnim == _currentAnim) newAnim = anims[secureRandom(START_ANIMATION + 1, animsSize())]; - unsigned int newPalInd = _currentPaletteInd; - while (newPalInd == _currentPaletteInd) newPalInd = secureRandom(palsSize()); + Palette* newPalette = _currentPalette; + while (newPalette == _currentPalette) + newPalette = &pals[secureRandom(palsSize())]; unsigned long newAnimDuration = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX); - setupScene(newAnimInd, newPalInd, newAnimDuration); + setupScene(newAnim, newPalette, newAnimDuration); } } } @@ -399,7 +416,7 @@ void garlandMqttCallback(unsigned int type, const char * topic, const char * pay std::vector empty_sequence; std::swap(_command_sequence, empty_sequence); _immediate_command.clear(); - _currentAnimDuration = 0; + _currentDuration = 0; setDefault(); garlandEnabled(true); } else if (command == MQTT_COMMAND_QUEUE) { @@ -428,11 +445,11 @@ void garlandSetup() { espurnaRegisterReload(_garlandReload); pixels.begin(); - scene.setAnim(anims[0]); - scene.setPalette(&pals[0]); + scene.setAnim(_currentAnim); + scene.setPalette(_currentPalette); scene.setup(); - _currentAnimDuration = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX); + _currentDuration = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX); } /*####################################################################### diff --git a/code/espurna/garland/animations/anim_glow.h b/code/espurna/garland/animations/anim_glow.h new file mode 100644 index 00000000..96b4de99 --- /dev/null +++ b/code/espurna/garland/animations/anim_glow.h @@ -0,0 +1,34 @@ +#if GARLAND_SUPPORT + +#include "../anim.h" +#include "../palette.h" + +//------------------------------------------------------------------------------ +class AnimGlow : public Anim { + public: + AnimGlow() : Anim("Glow") { + } + + void SetupImpl() override { + curColor = palette->getRndInterpColor(); + inc = secureRandom(2) * 2 - 1; + glowSetUp(); + } + + void Run() override { + if (inc > 0) { + for (int i = 0; i < numLeds; ++i) { + leds[i] = curColor; + glowForEachLed(i); + } + } else { + for (int i = 0; i < numLeds; ++i) { + leds[i] = curColor; + glowForEachLed(i); + } + } + glowRun(); + } +}; + +#endif // GARLAND_SUPPORT diff --git a/code/espurna/garland/scene.h b/code/espurna/garland/scene.h index 30ca5a3d..0d72c537 100644 --- a/code/espurna/garland/scene.h +++ b/code/espurna/garland/scene.h @@ -17,6 +17,7 @@ Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github. #include "animations/anim_comets.h" #include "animations/anim_dolphins.h" #include "animations/anim_fly.h" +#include "animations/anim_glow.h" #include "animations/anim_pixiedust.h" #include "animations/anim_randcyc.h" #include "animations/anim_run.h" From d11f82d098a69a4a127a8db3218c5643f9831371 Mon Sep 17 00:00:00 2001 From: DmitryBlinov Date: Sat, 20 Feb 2021 10:10:55 +0200 Subject: [PATCH 11/14] garland: update documentation - instructions for mqtt commands in the source - update README --- README.md | 8 +++++--- code/espurna/garland.cpp | 32 +++++++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4b048dd4..919940b7 100644 --- a/README.md +++ b/README.md @@ -188,9 +188,11 @@ Since November 2018, Max Prokhorov (**@mcspr**) is also actively working as a co * **Garland** Implementing garland using WS2812 leds * 12 animation modes (include start animation) * Web control for: - * ON/OFF - * brightness - * speed + * ON/OFF, brightness, speed + * MQTT control: + * ON/OFF, brightness, speed + * Animation queue + * Animation sequence ## Notices diff --git a/code/espurna/garland.cpp b/code/espurna/garland.cpp index 23840906..f748ce79 100644 --- a/code/espurna/garland.cpp +++ b/code/espurna/garland.cpp @@ -12,13 +12,35 @@ Currently animation calculation, brightness calculation/transition and showing m Debug output shows timings. Overal timing should be not more that 3000 ms. MQTT control: -"command:["immediate", "queue", "sequence", "reset"] -"enable":["true", "false"] -"brightness":[0-255] -"speed":[30-60] +Topic: DEVICE_NAME/garland/set +Message: {"command":"string", "enable":"string", "brightness":int, "speed":int, "animation":"string", + "palette":"string"/int, "duration":int} +All parameters are optional. + +"command:["immediate", "queue", "sequence", "reset"] - if not set, than "immediate" by default + Commands priority: + - "immediate" - executes immediately, braking current animation. + - "queue" - if queue is not empty, than next queue command executes after current animation end. + after execution command removed from queue. + - "sequence" - executes commands in sequence in cycle. + - "reset" - clean queue and sequence, restore default settings, enable garland. + - random if there are no commands in queue or sequence. + +"enable":["true", "false"] - enable or disable garland +"brightness":[0-255] - set brightness +"speed":[30-60] - set animation speed "animation":["PixieDust", "Sparkr", "Run", "Stars", "Spread", "R"andCyc", "Fly", "Comets", "Assemble", "Dolphins", "Salut"] + - setup animation. If not set or not recognized, than setup previous anmation "palette":["RGB", "Rainbow", "Stripe", "Party", "Heat", Fire", "Blue", "Sun", "Lime", "Pastel"] -"duration":5000 + - can be one of listed above or can be one color palette. + - one color palette can be set by string, that represents color in the format "0xRRGGBB" (0xFF0000 for red) or + integer number, corresponding to it. Examples: "palette":"0x0000FF", "palette":255 equal to Blue color. +"duration":5000 - setup command duration in milliseconds. If not set, than infinite duration will setup. + +If command contains animation, palette or duration, than it setup next animation, that will be shown for duration (infinite if +duration does not set), otherwise it just set scene parameters. + +Infinite commands can be interrupted by immediate command or by reset command. */ #include "garland.h" From a79c7c4e95269d770174a77916d9f4058609cea5 Mon Sep 17 00:00:00 2001 From: DmitryBlinov Date: Sat, 20 Feb 2021 10:23:39 +0200 Subject: [PATCH 12/14] ntp: timelib header is no longer included by default --- code/espurna/libs/SecureClientHelpers.h | 1 + code/espurna/thermostat.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/code/espurna/libs/SecureClientHelpers.h b/code/espurna/libs/SecureClientHelpers.h index d656a87b..2748910a 100644 --- a/code/espurna/libs/SecureClientHelpers.h +++ b/code/espurna/libs/SecureClientHelpers.h @@ -11,6 +11,7 @@ #include "../ntp.h" #if SECURE_CLIENT == SECURE_CLIENT_BEARSSL +#include "ntp_timelib.h" #include #elif SECURE_CLIENT == SECURE_CLIENT_AXTLS #include diff --git a/code/espurna/thermostat.cpp b/code/espurna/thermostat.cpp index 24691403..0297ec82 100644 --- a/code/espurna/thermostat.cpp +++ b/code/espurna/thermostat.cpp @@ -11,6 +11,7 @@ Copyright (C) 2017 by Dmitry Blinov #if THERMOSTAT_SUPPORT #include "ntp.h" +#include "ntp_timelib.h" #include "relay.h" #include "sensor.h" #include "mqtt.h" From 46daa929f5e284877e105208c4e78f7844ae1b64 Mon Sep 17 00:00:00 2001 From: DmitryBlinov Date: Wed, 24 Feb 2021 15:46:15 +0200 Subject: [PATCH 13/14] garland: add anim_fountain --- code/espurna/garland.cpp | 2 +- .../garland/animations/anim_dolphins.h | 2 +- .../garland/animations/anim_fountain.h | 122 ++++++++++++++++++ code/espurna/garland/scene.h | 1 + 4 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 code/espurna/garland/animations/anim_fountain.h diff --git a/code/espurna/garland.cpp b/code/espurna/garland.cpp index f748ce79..7866035d 100644 --- a/code/espurna/garland.cpp +++ b/code/espurna/garland.cpp @@ -139,7 +139,7 @@ Adafruit_NeoPixel pixels = Adafruit_NeoPixel(GARLAND_LEDS, GARLAND_D_PIN, NEO_GR Scene scene(&pixels); Anim* anims[] = {new AnimGlow(), new AnimStart(), new AnimPixieDust(), new AnimSparkr(), new AnimRun(), new AnimStars(), new AnimSpread(), - new AnimRandCyc(), new AnimFly(), new AnimComets(), new AnimAssemble(), new AnimDolphins(), new AnimSalut()}; + new AnimRandCyc(), new AnimFly(), new AnimComets(), new AnimAssemble(), new AnimDolphins(), new AnimSalut(), new AnimFountain()}; constexpr size_t animsSize() { return sizeof(anims)/sizeof(anims[0]); } diff --git a/code/espurna/garland/animations/anim_dolphins.h b/code/espurna/garland/animations/anim_dolphins.h index 5c083bef..bb03acdd 100644 --- a/code/espurna/garland/animations/anim_dolphins.h +++ b/code/espurna/garland/animations/anim_dolphins.h @@ -90,7 +90,7 @@ class AnimDolphins : public Anim { return false; } else { - // dolphin accupy space for future movement + // dolphin occupy space for future movement int s = p < 0 ? 0 : p; for (int i = s; i < len; ++i) { seq[start + i * dir] = 1; diff --git a/code/espurna/garland/animations/anim_fountain.h b/code/espurna/garland/animations/anim_fountain.h new file mode 100644 index 00000000..971d50f5 --- /dev/null +++ b/code/espurna/garland/animations/anim_fountain.h @@ -0,0 +1,122 @@ +#if GARLAND_SUPPORT + +#include + +#include "../anim.h" +#include "../color.h" +#include "../palette.h" + +//------------------------------------------------------------------------------ +class AnimFountain : public Anim { + public: + AnimFountain() : Anim("Fountain") { + cycleFactor = 4; + } + + void SetupImpl() override { + fountains.clear(); + for (int i = 0; i < 3; ++i) + fountains.emplace_back(palette, numLeds); + } + + void Run() override { + for (int i = 0; i < numLeds; ++i) { + leds[i] = 0; + seq[i] = 0; + } + + // Run fountains animation. Fill seq (occupied space) + for (auto& d : fountains) + d.Run(leds, seq); + + // Try to recreate finished fountains + for (auto& d : fountains) { + if (d.done) { + for (int i = 1; i < 5; ++i) { + Fountain new_fountain(palette, numLeds); + if (new_fountain.HaveEnoughSpace(seq)) { + std::swap(d, new_fountain); + break; + } + } + } + } + } + + private: + struct Fountain { + bool done = false; + int len = secureRandom(5, 10); + int speed = secureRandom(1, 3); + int dir = 1; + int head = 0; + int start; + // Color color; + std::vector points; + Fountain(Palette* pal, uint16_t numLeds) : start(secureRandom(len, numLeds - len)), /*color(pal->getRndInterpColor()),*/ points(len) { + // DEBUG_MSG_P(PSTR("[GARLAND] Fountain created start = %d len = %d dir = %d cr = %d cg = %d cb = %d\n"), start, len, dir, color.r, color.g, color.b); + if (secureRandom(10) > 5) { + start = numLeds - start; + dir = -1; + } + + // int halflen = len / 2; + for (int i = 0; i < len; ++i) { + points[i] = pal->getRndInterpColor(); + // DEBUG_MSG_P(PSTR("[GARLAND] Fountain i=%d cr = %d cg = %d cb = %d\n"), i, points[i].r, points[i].g, points[i].b); + } + } + + bool Run(Color* leds, byte* seq) { + if (done) + return false; + + int p = 0; + for (int i = 0; i < len; ++i) { + p = head - i; + if (p >= 0 && p < len) { + if (dir == 1) { + leds[start + p] = points[i]; + leds[start - p] = points[i]; + } else { + leds[start + len - p] = points[i]; + leds[start - len + p] = points[i]; + } + } + } + + head += speed; + + // if tail moved out of len then fountain is done + if (p >= len) { + done = true; + return false; + } + else { + // fountain occupy space for future movement + int s = p < 0 ? 0 : p; + for (int i = s; i < len; ++i) { + seq[start + i] = 1; + seq[start - i] = 1; + } + } + + return true; + } + + // Decide that fountain have ehough space if seq of len before it is empty + bool HaveEnoughSpace(byte* seq) { + for (int i = 0; i < len; ++i) { + if (seq[start + i] != 0 && seq[start - i] != 0) { + // DEBUG_MSG_P(PSTR("[GARLAND] Fountain chaven't enouhg space to move.\n")); + return false; + } + } + return true; + } + }; + + std::vector fountains; +}; + +#endif // GARLAND_SUPPORT diff --git a/code/espurna/garland/scene.h b/code/espurna/garland/scene.h index 0d72c537..5a66b9e5 100644 --- a/code/espurna/garland/scene.h +++ b/code/espurna/garland/scene.h @@ -16,6 +16,7 @@ Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github. #include "animations/anim_assemble.h" #include "animations/anim_comets.h" #include "animations/anim_dolphins.h" +#include "animations/anim_fountain.h" #include "animations/anim_fly.h" #include "animations/anim_glow.h" #include "animations/anim_pixiedust.h" From 16b3f16902b5d2e6af48992488d6f8e067fc51d7 Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Fri, 26 Feb 2021 16:55:50 +0300 Subject: [PATCH 14/14] garland: clean-up whitespace --- code/espurna/garland.cpp | 18 ++++++++++-------- .../espurna/garland/animations/anim_fountain.h | 2 +- code/espurna/garland/animations/anim_salut.h | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/code/espurna/garland.cpp b/code/espurna/garland.cpp index 7866035d..68932c18 100644 --- a/code/espurna/garland.cpp +++ b/code/espurna/garland.cpp @@ -21,7 +21,7 @@ All parameters are optional. Commands priority: - "immediate" - executes immediately, braking current animation. - "queue" - if queue is not empty, than next queue command executes after current animation end. - after execution command removed from queue. + after execution command removed from queue. - "sequence" - executes commands in sequence in cycle. - "reset" - clean queue and sequence, restore default settings, enable garland. - random if there are no commands in queue or sequence. @@ -138,7 +138,7 @@ constexpr size_t palsSize() { return sizeof(pals)/sizeof(pals[0]); } Adafruit_NeoPixel pixels = Adafruit_NeoPixel(GARLAND_LEDS, GARLAND_D_PIN, NEO_GRB + NEO_KHZ800); Scene scene(&pixels); -Anim* anims[] = {new AnimGlow(), new AnimStart(), new AnimPixieDust(), new AnimSparkr(), new AnimRun(), new AnimStars(), new AnimSpread(), +Anim* anims[] = {new AnimGlow(), new AnimStart(), new AnimPixieDust(), new AnimSparkr(), new AnimRun(), new AnimStars(), new AnimSpread(), new AnimRandCyc(), new AnimFly(), new AnimComets(), new AnimAssemble(), new AnimDolphins(), new AnimSalut(), new AnimFountain()}; constexpr size_t animsSize() { return sizeof(anims)/sizeof(anims[0]); } @@ -166,7 +166,7 @@ void garlandEnabled(bool enabled) { char buffer[128]; snprintf_P(buffer, sizeof(buffer), PSTR("{\"garlandEnabled\": %s}"), enabled ? "true" : "false"); wsSend(buffer); -#endif +#endif } //------------------------------------------------------------------------------ @@ -355,7 +355,6 @@ bool executeCommand(const String& command) { newAnimDuration = root[MQTT_PAYLOAD_DURATION].as(); scene_setup_required = true; } - if (scene_setup_required) { setupScene(newAnim, newPalette, newAnimDuration); @@ -390,14 +389,17 @@ void garlandLoop(void) { if (_currentCommandInSequence >= _command_sequence.size()) _currentCommandInSequence = 0; } - + if (!scene_setup_done) { Anim* newAnim = _currentAnim; - while (newAnim == _currentAnim) newAnim = anims[secureRandom(START_ANIMATION + 1, animsSize())]; + while (newAnim == _currentAnim) { + newAnim = anims[secureRandom(START_ANIMATION + 1, animsSize())]; + } Palette* newPalette = _currentPalette; - while (newPalette == _currentPalette) + while (newPalette == _currentPalette) { newPalette = &pals[secureRandom(palsSize())]; + } unsigned long newAnimDuration = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX); @@ -596,7 +598,7 @@ void Scene::run() { garland workflow. Using 800 kHz gives 1.25 μs per bit. -> 30 μs (0.03 ms) per RGB LED. So for example 3 ms for 100 LEDs. Unfortunately it can't be postponed and resumed later as it will lead to reseting the transmition operation. From other hand, long operation can cause - Soft WDT reset. To avoid wdt reset we need to switch soft wdt off for long strips. + Soft WDT reset. To avoid wdt reset we need to switch soft wdt off for long strips. It is not best practice, but assuming that it is only garland, it can be acceptable. Tested up to 300 leds. */ if (_numLeds > NUMLEDS_CAN_CAUSE_WDT_RESET) { diff --git a/code/espurna/garland/animations/anim_fountain.h b/code/espurna/garland/animations/anim_fountain.h index 971d50f5..ed76a047 100644 --- a/code/espurna/garland/animations/anim_fountain.h +++ b/code/espurna/garland/animations/anim_fountain.h @@ -80,7 +80,7 @@ class AnimFountain : public Anim { leds[start - p] = points[i]; } else { leds[start + len - p] = points[i]; - leds[start - len + p] = points[i]; + leds[start - len + p] = points[i]; } } } diff --git a/code/espurna/garland/animations/anim_salut.h b/code/espurna/garland/animations/anim_salut.h index 0e8e3865..2b24edd1 100644 --- a/code/espurna/garland/animations/anim_salut.h +++ b/code/espurna/garland/animations/anim_salut.h @@ -26,7 +26,7 @@ class AnimSalut : public Anim { for (auto& c : shots) { if (!c.Run(leds)) { Shot new_shot(palette, numLeds); - std::swap(c, new_shot); + std::swap(c, new_shot); } } }