|
|
- /*
- GARLAND MODULE
- 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)
-
- 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.
-
- 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"
-
- #if GARLAND_SUPPORT
-
- #include <Adafruit_NeoPixel.h>
-
- #include <memory>
- #include <vector>
-
- #include "garland/color.h"
- #include "garland/palette.h"
- #include "garland/scene.h"
- #include "mqtt.h"
- #include "ws.h"
-
- const char* NAME_GARLAND_ENABLED = "garlandEnabled";
- const char* NAME_GARLAND_BRIGHTNESS = "garlandBrightness";
- const char* NAME_GARLAND_SPEED = "garlandSpeed";
-
- const char* NAME_GARLAND_SWITCH = "garland_switch";
- 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_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
-
- #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 pals[] = {
- // palettes below are taken from http://www.color-hex.com/color-palettes/ (and modified)
- // RGB: Red,Green,Blue sequence
- Palette("RGB", {0xFF0000, 0x00FF00, 0x0000FF}),
-
- // Rainbow: Rainbow colors
- Palette("Rainbow", {0xFF0000, 0xAB5500, 0xABAB00, 0x00FF00, 0x00AB55, 0x0000FF, 0x5500AB, 0xAB0055}),
-
- // RainbowStripe: Rainbow colors with alternating stripes of black
- Palette("Stripe", {0xFF0000, 0x000000, 0xAB5500, 0x000000, 0xABAB00, 0x000000, 0x00FF00, 0x000000,
- 0x00AB55, 0x000000, 0x0000FF, 0x000000, 0x5500AB, 0x000000, 0xAB0055, 0x000000}),
-
- // Party: Blue purple ping red orange yellow (and back). Basically, everything but the greens.
- // This palette is good for lighting at a club or party.
- Palette("Party", {0x5500AB, 0x84007C, 0xB5004B, 0xE5001B, 0xE81700, 0xB84700, 0xAB7700, 0xABAB00,
- 0xAB5500, 0xDD2200, 0xF2000E, 0xC2003E, 0x8F0071, 0x5F00A1, 0x2F00D0, 0x0007F9}),
-
- // Heat: Approximate "black body radiation" palette, akin to the FastLED 'HeatColor' function.
- // Recommend that you use values 0-240 rather than the usual 0-255, as the last 15 colors will be
- // 'wrapping around' from the hot end to the cold end, which looks wrong.
- Palette("Heat", {0x700070, 0xFF0000, 0xFFFF00, 0xFFFFCC}),
-
- // Fire:
- Palette("Fire", {0x000000, 0x220000, 0x880000, 0xFF0000, 0xFF6600, 0xFFCC00}),
-
- // Blue:
- Palette("Blue", {0xffffff, 0x0000ff, 0x00ffff}),
-
- // Sun: Slice Of The Sun
- Palette("Sun", {0xfff95b, 0xffe048, 0xffc635, 0xffad22, 0xff930f}),
-
- // Lime: yellow green mix
- Palette("Lime", {0x51f000, 0x6fff00, 0x96ff00, 0xc9ff00, 0xf0ff00}),
-
- // Pastel: Pastel Fruity Mixture
- Palette("Pastel", {0x75aa68, 0x5960ae, 0xe4be6c, 0xca5959, 0x8366ac})};
-
- 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(),
- new AnimRandCyc(), new AnimFly(), new AnimComets(), new AnimAssemble(), new AnimDolphins(), new AnimSalut(), new AnimFountain()};
-
- 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() {
- pixels.clear();
- }
-
- //------------------------------------------------------------------------------
- void garlandEnabled(bool 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
- }
-
- //------------------------------------------------------------------------------
- bool garlandEnabled() {
- return _garland_enabled;
- }
-
- //------------------------------------------------------------------------------
- // Setup
- //------------------------------------------------------------------------------
- void _garlandConfigure() {
- _garland_enabled = getSetting(NAME_GARLAND_ENABLED, true);
- DEBUG_MSG_P(PSTR("[GARLAND] _garland_enabled = %d\n"), _garland_enabled);
-
- byte brightness = getSetting(NAME_GARLAND_BRIGHTNESS, 255);
- scene.setBrightness(brightness);
- DEBUG_MSG_P(PSTR("[GARLAND] brightness = %d\n"), brightness);
-
- float speed = getSetting(NAME_GARLAND_SPEED, 50);
- scene.setSpeed(speed);
- }
-
- //------------------------------------------------------------------------------
- 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) {
- root[NAME_GARLAND_ENABLED] = garlandEnabled();
- root[NAME_GARLAND_BRIGHTNESS] = scene.getBrightness();
- root[NAME_GARLAND_SPEED] = scene.getSpeed();
- root["garlandVisible"] = 1;
- }
-
- //------------------------------------------------------------------------------
- bool _garlandWebSocketOnKeyCheck(const char* key, JsonVariant& value) {
- if (strncmp(key, NAME_GARLAND_ENABLED, strlen(NAME_GARLAND_ENABLED)) == 0) return true;
- if (strncmp(key, NAME_GARLAND_BRIGHTNESS, strlen(NAME_GARLAND_BRIGHTNESS)) == 0) return true;
- if (strncmp(key, NAME_GARLAND_SPEED, strlen(NAME_GARLAND_SPEED)) == 0) return true;
- return false;
- }
-
- //------------------------------------------------------------------------------
- void _garlandWebSocketOnAction(uint32_t client_id, const char* action, JsonObject& data) {
- if (strcmp(action, NAME_GARLAND_SWITCH) == 0) {
- if (data.containsKey("status") && data.is<int>("status")) {
- garlandEnabled(1 == data["status"].as<int>());
- }
- }
-
- if (strcmp(action, NAME_GARLAND_SET_BRIGHTNESS) == 0) {
- if (data.containsKey("brightness")) {
- byte new_brightness = data.get<byte>("brightness");
- DEBUG_MSG_P(PSTR("[GARLAND] new brightness = %d\n"), new_brightness);
- setSetting(NAME_GARLAND_BRIGHTNESS, new_brightness);
- scene.setBrightness(new_brightness);
- }
- }
-
- if (strcmp(action, NAME_GARLAND_SET_SPEED) == 0) {
- if (data.containsKey("speed")) {
- byte new_speed = data.get<byte>("speed");
- DEBUG_MSG_P(PSTR("[GARLAND] new speed = %d\n"), new_speed);
- setSetting(NAME_GARLAND_SPEED, new_speed);
- scene.setSpeed(new_speed);
- }
- }
-
- if (strcmp(action, NAME_GARLAND_SET_DEFAULT) == 0) {
- setDefault();
- }
- }
- #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
- //------------------------------------------------------------------------------
- void garlandLoop(void) {
- if (!_immediate_command.isEmpty()) {
- executeCommand(_immediate_command);
- _immediate_command.clear();
- }
-
- if (!garlandEnabled())
- return;
-
- scene.run();
-
- 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;
- }
-
- if (!scene_setup_done) {
- Anim* newAnim = _currentAnim;
- while (newAnim == _currentAnim) {
- newAnim = anims[secureRandom(START_ANIMATION + 1, animsSize())];
- }
-
- Palette* newPalette = _currentPalette;
- while (newPalette == _currentPalette) {
- newPalette = &pals[secureRandom(palsSize())];
- }
-
- unsigned long newAnimDuration = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX);
-
- setupScene(newAnim, newPalette, newAnimDuration);
- }
- }
- }
-
- //------------------------------------------------------------------------------
- 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);
- }
- }
- }
- }
-
- //------------------------------------------------------------------------------
- void garlandSetup() {
- _garlandConfigure();
-
- mqttRegister(garlandMqttCallback);
- // Websockets
- #if WEB_SUPPORT
- wsRegister()
- .onConnected(_garlandWebSocketOnConnected)
- .onKeyCheck(_garlandWebSocketOnKeyCheck)
- .onAction(_garlandWebSocketOnAction);
- #endif
-
- espurnaRegisterLoop(garlandLoop);
- espurnaRegisterReload(_garlandReload);
-
- pixels.begin();
- scene.setAnim(_currentAnim);
- scene.setPalette(_currentPalette);
- scene.setup();
-
- _currentDuration = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX);
- }
-
- /*#######################################################################
- _____
- / ____|
- | (___ ___ ___ _ __ ___
- \___ \ / __| / _ \ | '_ \ / _ \
- ____) | | (__ | __/ | | | | | __/
- |_____/ \___| \___| |_| |_| \___|
- #######################################################################*/
-
- #define GARLAND_SCENE_TRANSITION_MS 1000 // transition time between animations, ms
- #define GARLAND_SCENE_SPEED_MAX 70
- #define GARLAND_SCENE_SPEED_FACTOR 10
- #define GARLAND_SCENE_DEFAULT_SPEED 50
- #define GARLAND_SCENE_DEFAULT_BRIGHTNESS 255
-
- Scene::Scene(Adafruit_NeoPixel* pixels)
- : _pixels(pixels),
- _numLeds(pixels->numPixels()),
- _leds1(_numLeds),
- _leds2(_numLeds),
- _ledstmp(_numLeds),
- _seq(_numLeds) {
- }
-
- void Scene::setPalette(Palette* palette) {
- _palette = palette;
- if (setUpOnPalChange) {
- setupImpl();
- }
- }
-
- void Scene::setBrightness(byte brightness) {
- DEBUG_MSG_P(PSTR("[GARLAND] Scene::setBrightness = %d\n"), brightness);
- this->brightness = brightness;
- }
-
- byte Scene::getBrightness() {
- DEBUG_MSG_P(PSTR("[GARLAND] Scene::getBrightness = %d\n"), brightness);
- return brightness;
- }
-
- // Speed is reverse to cycleFactor and 10x
- void Scene::setSpeed(byte speed) {
- this->speed = speed;
- cycleFactor = (float)(GARLAND_SCENE_SPEED_MAX - speed) / GARLAND_SCENE_SPEED_FACTOR;
- DEBUG_MSG_P(PSTR("[GARLAND] Scene::setSpeed %d cycleFactor = %d\n"), speed, (int)(cycleFactor * 1000));
- }
-
- byte Scene::getSpeed() {
- DEBUG_MSG_P(PSTR("[GARLAND] Scene::getSpeed %d cycleFactor = %d\n"), speed, (int)(cycleFactor * 1000));
- return speed;
- }
-
- void Scene::setDefault() {
- speed = GARLAND_SCENE_DEFAULT_SPEED;
- cycleFactor = (float)(GARLAND_SCENE_SPEED_MAX - speed) / GARLAND_SCENE_SPEED_FACTOR;
- brightness = GARLAND_SCENE_DEFAULT_BRIGHTNESS;
- DEBUG_MSG_P(PSTR("[GARLAND] Scene::setDefault speed = %d cycleFactor = %d brightness = %d\n"), speed, (int)(cycleFactor * 1000), brightness);
- }
-
- void Scene::run() {
- unsigned long iteration_start_time = micros();
-
- if (state == Calculate || cyclesRemain < 1) {
- // Calculate number of cycles for this animation iteration
- float cycleSum = cycleFactor * (_anim ? _anim->getCycleFactor() : 1.0) + cycleTail;
- cyclesRemain = cycleSum;
- if (cyclesRemain < 1) {
- cyclesRemain = 1;
- cycleSum = 0;
- cycleTail = 0;
- } else {
- cycleTail = cycleSum - cyclesRemain;
- }
-
- if (_anim) {
- _anim->Run();
- }
-
- sum_calc_time += (micros() - iteration_start_time);
- iteration_start_time = micros();
- ++calc_num;
- state = Transition;
- }
-
- if (state == Transition && cyclesRemain < 3) {
- // transition coef, if within 0..1 - transition is active
- // changes from 1 to 0 during transition, so we interpolate from current
- // color to previous
- float transc = (float)((long)transms - (long)millis()) / GARLAND_SCENE_TRANSITION_MS;
- Color* leds_prev = (_leds == &_leds1[0]) ? &_leds2[0] : &_leds1[0];
-
- if (transc > 0) {
- for (int i = 0; i < _numLeds; i++) {
- // transition is in progress
- Color c = _leds[i].interpolate(leds_prev[i], transc);
- byte r = (int)(bri_lvl[c.r]) * brightness / 256;
- byte g = (int)(bri_lvl[c.g]) * brightness / 256;
- byte b = (int)(bri_lvl[c.b]) * brightness / 256;
- _pixels->setPixelColor(i, _pixels->Color(r, g, b));
- }
- } else {
- for (int i = 0; i < _numLeds; i++) {
- // regular operation
- byte r = (int)(bri_lvl[_leds[i].r]) * brightness / 256;
- byte g = (int)(bri_lvl[_leds[i].g]) * brightness / 256;
- byte b = (int)(bri_lvl[_leds[i].b]) * brightness / 256;
- _pixels->setPixelColor(i, _pixels->Color(r, g, b));
- }
- }
-
- sum_pixl_time += (micros() - iteration_start_time);
- iteration_start_time = micros();
- ++pixl_num;
- state = Show;
- }
-
- 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;
- ++numShows;
- }
- --cyclesRemain;
- }
-
- void Scene::setupImpl() {
- transms = millis() + GARLAND_SCENE_TRANSITION_MS;
-
- // switch operation buffers (for transition to operate)
- if (_leds == &_leds1[0]) {
- _leds = &_leds2[0];
- } else {
- _leds = &_leds1[0];
- }
-
- if (_anim) {
- _anim->Setup(_palette, _numLeds, _leds, &_ledstmp[0], &_seq[0]);
- }
- }
-
- void Scene::setup() {
- sum_calc_time = 0;
- sum_pixl_time = 0;
- sum_show_time = 0;
- calc_num = 0;
- pixl_num = 0;
- show_num = 0;
- numShows = 0;
-
- if (!setUpOnPalChange) {
- setupImpl();
- }
- }
-
- unsigned long Scene::getAvgCalcTime() { return sum_calc_time / calc_num; }
- unsigned long Scene::getAvgPixlTime() { return sum_pixl_time / pixl_num; }
- unsigned long Scene::getAvgShowTime() { return sum_show_time / show_num; }
-
- /*#######################################################################
- _ _ _
- /\ (_) | | (_)
- / \ _ __ _ _ __ ___ __ _ | |_ _ ___ _ __
- / /\ \ | '_ \ | | | '_ ` _ \ / _` | | __| | | / _ \ | '_ \
- / ____ \ | | | | | | | | | | | | | (_| | | |_ | | | (_) | | | | |
- /_/ \_\ |_| |_| |_| |_| |_| |_| \__,_| \__| |_| \___/ |_| |_|
- #######################################################################*/
-
- Anim::Anim(const char* name) : _name(name) {}
-
- void Anim::Setup(Palette* palette, uint16_t numLeds, Color* leds, Color* ledstmp, byte* seq) {
- this->palette = palette;
- this->numLeds = numLeds;
- this->leds = leds;
- this->ledstmp = ledstmp;
- this->seq = seq;
- SetupImpl();
- }
-
- void Anim::initSeq() {
- for (int i = 0; i < numLeds; ++i)
- seq[i] = i;
- }
-
- void Anim::shuffleSeq() {
- for (int i = 0; i < numLeds; ++i) {
- byte ind = (unsigned int)(rngb() * numLeds / 256);
- if (ind != i) {
- std::swap(seq[ind], seq[i]);
- }
- }
- }
-
- void Anim::glowSetUp() {
- braPhaseSpd = secureRandom(4, 13);
- if (braPhaseSpd > 8) {
- braPhaseSpd = braPhaseSpd - 17;
- }
- braFreq = secureRandom(20, 60);
- }
-
- void Anim::glowForEachLed(int i) {
- int8 bra = braPhase + i * braFreq;
- bra = BRA_OFFSET + (abs(bra) >> BRA_AMP_SHIFT);
- leds[i] = leds[i].brightness(bra);
- }
-
- void Anim::glowRun() { braPhase += braPhaseSpd; }
-
- bool operator== (const Color &c1, const Color &c2)
- {
- return (c1.r == c2.r && c1.g == c2.g && c1.b == c2.b);
- }
-
- unsigned int rng() {
- static unsigned int y = 0;
- y += micros(); // seeded with changing number
- y ^= y << 2;
- y ^= y >> 7;
- y ^= y << 7;
- return (y);
- }
-
- // Ranom numbers generator in byte range (256) much faster than secureRandom.
- // For usage in time-critical places.
- byte rngb() { return (byte)rng(); }
-
- #endif // GARLAND_SUPPORT
|