Browse Source

garland: updates to animations, added control over mqtt and fix wdt resets

resolves #2425
mcspr-patch-1
Maxim Prokhorov 3 years ago
parent
commit
c4d817c4fb
11 changed files with 503 additions and 67 deletions
  1. +5
    -3
      README.md
  2. +289
    -50
      code/espurna/garland.cpp
  3. +36
    -8
      code/espurna/garland/animations/anim_assemble.h
  4. +1
    -1
      code/espurna/garland/animations/anim_dolphins.h
  5. +122
    -0
      code/espurna/garland/animations/anim_fountain.h
  6. +34
    -0
      code/espurna/garland/animations/anim_glow.h
  7. +1
    -1
      code/espurna/garland/animations/anim_salut.h
  8. +1
    -1
      code/espurna/garland/animations/anim_stars.h
  9. +12
    -3
      code/espurna/garland/scene.h
  10. +1
    -0
      code/espurna/libs/SecureClientHelpers.h
  11. +1
    -0
      code/espurna/thermostat.cpp

+ 5
- 3
README.md View File

@ -188,9 +188,11 @@ Since November 2018, Max Prokhorov (**@mcspr**) is also actively working as a co
* **Garland** Implementing garland using WS2812 leds * **Garland** Implementing garland using WS2812 leds
* 12 animation modes (include start animation) * 12 animation modes (include start animation)
* Web control for: * Web control for:
* ON/OFF
* brightness
* speed
* ON/OFF, brightness, speed
* MQTT control:
* ON/OFF, brightness, speed
* Animation queue
* Animation sequence
## Notices ## Notices


+ 289
- 50
code/espurna/garland.cpp View File

@ -4,15 +4,43 @@ Copyright (C) 2020 by Dmitry Blinov <dblinov76 at gmail dot com>
Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github.com/Vasil-Pahomov/Liana) 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. 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. More long strip can take more time to show.
Currently animation calculation, brightness calculation/transition and showing makes in one loop cycle. 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. 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.
MQTT control:
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"]
- 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" #include "garland.h"
@ -21,11 +49,13 @@ at a cycle.
#include <Adafruit_NeoPixel.h> #include <Adafruit_NeoPixel.h>
#include <memory>
#include <vector> #include <vector>
#include "garland/color.h" #include "garland/color.h"
#include "garland/palette.h" #include "garland/palette.h"
#include "garland/scene.h" #include "garland/scene.h"
#include "mqtt.h"
#include "ws.h" #include "ws.h"
const char* NAME_GARLAND_ENABLED = "garlandEnabled"; const char* NAME_GARLAND_ENABLED = "garlandEnabled";
@ -37,12 +67,33 @@ const char* NAME_GARLAND_SET_BRIGHTNESS = "garland_set_brightness";
const char* NAME_GARLAND_SET_SPEED = "garland_set_speed"; const char* NAME_GARLAND_SET_SPEED = "garland_set_speed";
const char* NAME_GARLAND_SET_DEFAULT = "garland_set_default"; 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
const char* MQTT_TOPIC_GARLAND = "garland";
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
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
bool _garland_enabled = true;
unsigned long _last_update = 0;
unsigned long _interval_effect_update;
#define NUMLEDS_CAN_CAUSE_WDT_RESET 100
bool _garland_enabled = true;
unsigned long _lastTimeUpdate = 0;
unsigned long _currentDuration = ULONG_MAX;
unsigned int _currentCommandInSequence = 0;
String _immediate_command;
std::queue<String> _command_queue;
std::vector<String> _command_sequence;
// Palette should // Palette should
Palette pals[] = { Palette pals[] = {
@ -87,11 +138,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); Adafruit_NeoPixel pixels = Adafruit_NeoPixel(GARLAND_LEDS, GARLAND_D_PIN, NEO_GRB + NEO_KHZ800);
Scene scene(&pixels); Scene scene(&pixels);
Anim* anims[] = {new AnimStart(), new AnimPixieDust(), new AnimSparkr(), new AnimRun(), new AnimStars(), new AnimSpread(),
new AnimRandCyc(), new AnimFly(), new AnimComets(), new AnimAssemble(), new AnimDolphins(), new AnimSalut()};
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]); } 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<Palette>(new Palette("White", {0xffffff}));
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void garlandDisable() { void garlandDisable() {
pixels.clear(); pixels.clear();
@ -100,6 +155,18 @@ void garlandDisable() {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void garlandEnabled(bool enabled) { void garlandEnabled(bool enabled) {
_garland_enabled = enabled; _garland_enabled = enabled;
setSetting(NAME_GARLAND_ENABLED, _garland_enabled);
if (!_garland_enabled) {
schedule_function([]() {
pixels.clear();
pixels.show();
});
}
#if WEB_SUPPORT
char buffer[128];
snprintf_P(buffer, sizeof(buffer), PSTR("{\"garlandEnabled\": %s}"), enabled ? "true" : "false");
wsSend(buffer);
#endif
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -127,6 +194,20 @@ void _garlandReload() {
_garlandConfigure(); _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 #if WEB_SUPPORT
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void _garlandWebSocketOnConnected(JsonObject& root) { void _garlandWebSocketOnConnected(JsonObject& root) {
@ -148,14 +229,7 @@ bool _garlandWebSocketOnKeyCheck(const char* key, JsonVariant& value) {
void _garlandWebSocketOnAction(uint32_t client_id, const char* action, JsonObject& data) { void _garlandWebSocketOnAction(uint32_t client_id, const char* action, JsonObject& data) {
if (strcmp(action, NAME_GARLAND_SWITCH) == 0) { if (strcmp(action, NAME_GARLAND_SWITCH) == 0) {
if (data.containsKey("status") && data.is<int>("status")) { if (data.containsKey("status") && data.is<int>("status")) {
_garland_enabled = (1 == data["status"].as<int>());
setSetting(NAME_GARLAND_ENABLED, _garland_enabled);
if (!_garland_enabled) {
schedule_function([](){
pixels.clear();
pixels.show();
});
}
garlandEnabled(1 == data["status"].as<int>());
} }
} }
@ -178,52 +252,203 @@ void _garlandWebSocketOnAction(uint32_t client_id, const char* action, JsonObjec
} }
if (strcmp(action, NAME_GARLAND_SET_DEFAULT) == 0) { 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 #endif
//------------------------------------------------------------------------------
void setupScene(Anim* new_anim, Palette* new_palette, unsigned long new_duration) {
unsigned long currentAnimRunTime = millis() - _lastTimeUpdate;
_lastTimeUpdate = millis();
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"),
_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"),
_currentAnim->name(), palette_name.c_str(), _currentDuration);
scene.setAnim(_currentAnim);
scene.setPalette(_currentPalette);
scene.setup();
}
//------------------------------------------------------------------------------
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 false;
}
bool scene_setup_required = false;
if (root.containsKey(MQTT_PAYLOAD_ENABLE)) {
auto enable = root[MQTT_PAYLOAD_ENABLE].as<String>();
garlandEnabled(enable != "false");
}
if (root.containsKey(MQTT_PAYLOAD_BRIGHTNESS)) {
auto brightness = root[MQTT_PAYLOAD_BRIGHTNESS].as<byte>();
scene.setBrightness(brightness);
}
if (root.containsKey(MQTT_PAYLOAD_ANIM_SPEED)) {
auto speed = root[MQTT_PAYLOAD_ANIM_SPEED].as<byte>();
scene.setSpeed(speed);
}
Anim* newAnim = _currentAnim;
if (root.containsKey(MQTT_PAYLOAD_ANIMATION)) {
auto animation = root[MQTT_PAYLOAD_ANIMATION].as<const char*>();
for (size_t i = 0; i < animsSize(); ++i) {
auto anim_name = anims[i]->name();
if (strcmp(animation, anim_name) == 0) {
newAnim = anims[i];
scene_setup_required = true;
break;
}
}
}
Palette* newPalette = _currentPalette;
if (root.containsKey(MQTT_PAYLOAD_PALETTE)) {
if (root.is<int>(MQTT_PAYLOAD_PALETTE)) {
one_color_palette.reset(new Palette("Color", {root[MQTT_PAYLOAD_PALETTE].as<uint32_t>()}));
newPalette = one_color_palette.get();
} else {
auto palette = root[MQTT_PAYLOAD_PALETTE].as<const char*>();
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();
}
}
}
}
unsigned long newAnimDuration = LONG_MAX;
if (root.containsKey(MQTT_PAYLOAD_DURATION)) {
newAnimDuration = root[MQTT_PAYLOAD_DURATION].as<unsigned long>();
scene_setup_required = true;
}
if (scene_setup_required) {
setupScene(newAnim, newPalette, newAnimDuration);
return true;
}
return false;
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Loop // Loop
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void garlandLoop(void) { void garlandLoop(void) {
if (!_immediate_command.isEmpty()) {
executeCommand(_immediate_command);
_immediate_command.clear();
}
if (!garlandEnabled()) if (!garlandEnabled())
return; return;
scene.run(); 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);
unsigned long currentAnimRunTime = millis() - _lastTimeUpdate;
if (currentAnimRunTime > _currentDuration && scene.finishedAnimCycle()) {
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;
}
static int animInd = 0;
int prevAnimInd = animInd;
while (prevAnimInd == animInd) animInd = secureRandom(1, animsSize());
if (!scene_setup_done) {
Anim* newAnim = _currentAnim;
while (newAnim == _currentAnim) {
newAnim = anims[secureRandom(START_ANIMATION + 1, animsSize())];
}
static int paletteInd = 0;
int prevPalInd = paletteInd;
while (prevPalInd == paletteInd) paletteInd = secureRandom(palsSize());
Palette* newPalette = _currentPalette;
while (newPalette == _currentPalette) {
newPalette = &pals[secureRandom(palsSize())];
}
int numShows = scene.getNumShows();
int frameRate = animation_time > 0 ? numShows * 1000 / animation_time : 0;
unsigned long newAnimDuration = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX);
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);
setupScene(newAnim, newPalette, newAnimDuration);
}
}
}
scene.setAnim(anims[animInd]);
scene.setPalette(&pals[paletteInd]);
scene.setup();
//------------------------------------------------------------------------------
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 = MQTT_COMMAND_IMMEDIATE;
if (root.containsKey(MQTT_PAYLOAD_COMMAND)) {
command = root[MQTT_PAYLOAD_COMMAND].as<String>();
}
if (command == MQTT_COMMAND_IMMEDIATE) {
_immediate_command = payload;
} else if (command == MQTT_COMMAND_RESET) {
std::queue<String> empty_queue;
std::swap(_command_queue, empty_queue);
std::vector<String> empty_sequence;
std::swap(_command_sequence, empty_sequence);
_immediate_command.clear();
_currentDuration = 0;
setDefault();
garlandEnabled(true);
} else if (command == MQTT_COMMAND_QUEUE) {
_command_queue.push(payload);
} else if (command == MQTT_COMMAND_SEQUENCE) {
_command_sequence.push_back(payload);
}
}
} }
} }
@ -231,6 +456,7 @@ void garlandLoop(void) {
void garlandSetup() { void garlandSetup() {
_garlandConfigure(); _garlandConfigure();
mqttRegister(garlandMqttCallback);
// Websockets // Websockets
#if WEB_SUPPORT #if WEB_SUPPORT
wsRegister() wsRegister()
@ -243,11 +469,11 @@ void garlandSetup() {
espurnaRegisterReload(_garlandReload); espurnaRegisterReload(_garlandReload);
pixels.begin(); pixels.begin();
scene.setAnim(anims[0]);
scene.setPalette(&pals[0]);
scene.setAnim(_currentAnim);
scene.setPalette(_currentPalette);
scene.setup(); scene.setup();
_interval_effect_update = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX);
_currentDuration = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX);
} }
/*####################################################################### /*#######################################################################
@ -368,7 +594,20 @@ void Scene::run() {
} }
if (state == Show && cyclesRemain < 2) { 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(); _pixels->show();
if (_numLeds > NUMLEDS_CAN_CAUSE_WDT_RESET) {
ESP.wdtEnable(5000);
}
sum_show_time += (micros() - iteration_start_time); sum_show_time += (micros() - iteration_start_time);
++show_num; ++show_num;
state = Calculate; state = Calculate;


+ 36
- 8
code/espurna/garland/animations/anim_assemble.h View File

@ -8,6 +8,13 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
class AnimAssemble : public Anim { class AnimAssemble : public Anim {
enum class Phases {
Assemmble,
Glow,
Fade
};
Phases phase = Phases::Assemmble;
public: public:
AnimAssemble() : Anim("Assemble") { AnimAssemble() : Anim("Assemble") {
cycleFactor = 2; cycleFactor = 2;
@ -33,20 +40,41 @@ class AnimAssemble : public Anim {
} }
initSeq(); initSeq();
shuffleSeq(); shuffleSeq();
glowSetUp();
pos = 0; pos = 0;
} }
void Run() override { 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 { } 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; pos = 0;
phase = Phases::Assemmble;
}
} }
} }
}; };


+ 1
- 1
code/espurna/garland/animations/anim_dolphins.h View File

@ -90,7 +90,7 @@ class AnimDolphins : public Anim {
return false; return false;
} }
else { else {
// dolphin accupy space for future movement
// dolphin occupy space for future movement
int s = p < 0 ? 0 : p; int s = p < 0 ? 0 : p;
for (int i = s; i < len; ++i) { for (int i = s; i < len; ++i) {
seq[start + i * dir] = 1; seq[start + i * dir] = 1;


+ 122
- 0
code/espurna/garland/animations/anim_fountain.h View File

@ -0,0 +1,122 @@
#if GARLAND_SUPPORT
#include <vector>
#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<Color> 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<Fountain> fountains;
};
#endif // GARLAND_SUPPORT

+ 34
- 0
code/espurna/garland/animations/anim_glow.h View File

@ -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

+ 1
- 1
code/espurna/garland/animations/anim_salut.h View File

@ -26,7 +26,7 @@ class AnimSalut : public Anim {
for (auto& c : shots) { for (auto& c : shots) {
if (!c.Run(leds)) { if (!c.Run(leds)) {
Shot new_shot(palette, numLeds); Shot new_shot(palette, numLeds);
std::swap(c, new_shot);
std::swap(c, new_shot);
} }
} }
} }


+ 1
- 1
code/espurna/garland/animations/anim_stars.h View File

@ -23,7 +23,7 @@ class AnimStars : public Anim {
} }
void Run() override { void Run() override {
for (byte i = 0; i < numLeds; i++) {
for (int i = 0; i < numLeds; i++) {
byte phi = seq[i]; byte phi = seq[i];
if (phi < 254) { if (phi < 254) {
Color col = ledstmp[i]; Color col = ledstmp[i];


+ 12
- 3
code/espurna/garland/scene.h View File

@ -16,7 +16,9 @@ Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github.
#include "animations/anim_assemble.h" #include "animations/anim_assemble.h"
#include "animations/anim_comets.h" #include "animations/anim_comets.h"
#include "animations/anim_dolphins.h" #include "animations/anim_dolphins.h"
#include "animations/anim_fountain.h"
#include "animations/anim_fly.h" #include "animations/anim_fly.h"
#include "animations/anim_glow.h"
#include "animations/anim_pixiedust.h" #include "animations/anim_pixiedust.h"
#include "animations/anim_randcyc.h" #include "animations/anim_randcyc.h"
#include "animations/anim_run.h" #include "animations/anim_run.h"
@ -70,10 +72,17 @@ private:
byte brightness = 0; 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; 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; float cycleTail = 0;
int cyclesRemain = 0; int cyclesRemain = 0;


+ 1
- 0
code/espurna/libs/SecureClientHelpers.h View File

@ -11,6 +11,7 @@
#include "../ntp.h" #include "../ntp.h"
#if SECURE_CLIENT == SECURE_CLIENT_BEARSSL #if SECURE_CLIENT == SECURE_CLIENT_BEARSSL
#include "ntp_timelib.h"
#include <WiFiClientSecureBearSSL.h> #include <WiFiClientSecureBearSSL.h>
#elif SECURE_CLIENT == SECURE_CLIENT_AXTLS #elif SECURE_CLIENT == SECURE_CLIENT_AXTLS
#include <WiFiClientSecureAxTLS.h> #include <WiFiClientSecureAxTLS.h>


+ 1
- 0
code/espurna/thermostat.cpp View File

@ -11,6 +11,7 @@ Copyright (C) 2017 by Dmitry Blinov <dblinov76 at gmail dot com>
#if THERMOSTAT_SUPPORT #if THERMOSTAT_SUPPORT
#include "ntp.h" #include "ntp.h"
#include "ntp_timelib.h"
#include "relay.h" #include "relay.h"
#include "sensor.h" #include "sensor.h"
#include "mqtt.h" #include "mqtt.h"


Loading…
Cancel
Save