Mirror of espurna firmware for wireless switches and more
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

750 lines
26 KiB

  1. /*
  2. GARLAND MODULE
  3. Copyright (C) 2020 by Dmitry Blinov <dblinov76 at gmail dot com>
  4. Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github.com/Vasil-Pahomov/Liana)
  5. Tested on 300 led strip.
  6. The most time consuming operation is actually showing leds by Adafruit Neopixel. It take about 1870 mcs.
  7. More long strip can take more time to show.
  8. Currently animation calculation, brightness calculation/transition and showing makes in one loop cycle.
  9. Debug output shows timings. Overal timing should be not more that 3000 ms.
  10. MQTT control:
  11. Topic: $root/garland/set
  12. Message: {"command":"string", "enable":"string", "brightness":int, "speed":int, "animation":"string",
  13. "palette":"string"/int, "duration":int}
  14. All parameters are optional.
  15. "command:["immediate", "queue", "sequence", "reset"] - if not set, than "immediate" by default
  16. Commands priority:
  17. - "immediate" - executes immediately, braking current animation.
  18. - "queue" - if queue is not empty, than next queue command executes after current animation end.
  19. after execution command removed from queue.
  20. - "sequence" - executes commands in sequence in cycle.
  21. - "reset" - clean queue and sequence, restore default settings, enable garland.
  22. - random if there are no commands in queue or sequence.
  23. "enable":["true", "false"] - enable or disable garland
  24. "brightness":[0-255] - set brightness
  25. "speed":[30-60] - set animation speed
  26. "animation":["PixieDust", "Sparkr", "Run", "Stars", "Spread", "R"andCyc", "Fly", "Comets", "Assemble", "Dolphins", "Salut"]
  27. - setup animation. If not set or not recognized, than setup previous anmation
  28. "palette":["RGB", "Rainbow", "Stripe", "Party", "Heat", Fire", "Blue", "Sun", "Lime", "Pastel"]
  29. - can be one of listed above or can be one color palette.
  30. - one color palette can be set by string, that represents color in the format "0xRRGGBB" (0xFF0000 for red) or
  31. integer number, corresponding to it. Examples: "palette":"0x0000FF", "palette":255 equal to Blue color.
  32. "duration":5000 - setup command duration in milliseconds. If not set, than infinite duration will setup.
  33. If command contains animation, palette or duration, than it setup next animation, that will be shown for duration (infinite if
  34. duration does not set), otherwise it just set scene parameters.
  35. Infinite commands can be interrupted by immediate command or by reset command.
  36. */
  37. #include "espurna.h"
  38. #if GARLAND_SUPPORT
  39. #include <Adafruit_NeoPixel.h>
  40. #include <array>
  41. #include <list>
  42. #include <memory>
  43. #include <queue>
  44. #include <vector>
  45. #include "garland.h"
  46. #include "mqtt.h"
  47. #include "ws.h"
  48. namespace {
  49. #include "garland/color.h"
  50. #include "garland/palette.h"
  51. #include "garland/scene.h"
  52. alignas(4) static constexpr char NAME_GARLAND_ENABLED[] = "garlandEnabled";
  53. alignas(4) static constexpr char NAME_GARLAND_BRIGHTNESS[] = "garlandBrightness";
  54. alignas(4) static constexpr char NAME_GARLAND_SPEED[] = "garlandSpeed";
  55. alignas(4) static constexpr char NAME_GARLAND_SWITCH[] = "garland_switch";
  56. alignas(4) static constexpr char NAME_GARLAND_SET_BRIGHTNESS[] = "garland_set_brightness";
  57. alignas(4) static constexpr char NAME_GARLAND_SET_SPEED[] = "garland_set_speed";
  58. alignas(4) static constexpr char NAME_GARLAND_SET_DEFAULT[] = "garland_set_default";
  59. alignas(4) static constexpr char MQTT_TOPIC_GARLAND[] = "garland";
  60. alignas(4) static constexpr char MQTT_PAYLOAD_COMMAND[] = "command";
  61. alignas(4) static constexpr char MQTT_PAYLOAD_ENABLE[] = "enable";
  62. alignas(4) static constexpr char MQTT_PAYLOAD_BRIGHTNESS[] = "brightness";
  63. alignas(4) static constexpr char MQTT_PAYLOAD_ANIM_SPEED[] = "speed";
  64. alignas(4) static constexpr char MQTT_PAYLOAD_ANIMATION[] = "animation";
  65. alignas(4) static constexpr char MQTT_PAYLOAD_PALETTE[] = "palette";
  66. alignas(4) static constexpr char MQTT_PAYLOAD_DURATION[] = "duration";
  67. alignas(4) static constexpr char MQTT_COMMAND_IMMEDIATE[] = "immediate";
  68. alignas(4) static constexpr char MQTT_COMMAND_RESET[] = "reset"; // reset queue
  69. alignas(4) static constexpr char MQTT_COMMAND_QUEUE[] = "queue"; // enqueue command payload
  70. alignas(4) static constexpr char MQTT_COMMAND_SEQUENCE[] = "sequence"; // place command to sequence
  71. #define EFFECT_UPDATE_INTERVAL_MIN 15000 // 15 sec
  72. #define EFFECT_UPDATE_INTERVAL_MAX 30000 // 30 sec
  73. #define NUMLEDS_CAN_CAUSE_WDT_RESET 100
  74. bool _garland_enabled = true;
  75. unsigned long _lastTimeUpdate = 0;
  76. unsigned long _currentDuration = ULONG_MAX;
  77. unsigned int _currentCommandInSequence = 0;
  78. String _immediate_command;
  79. std::queue<String> _command_queue;
  80. std::vector<String> _command_sequence;
  81. // Palette should
  82. std::array<Palette, 14> pals {
  83. // palettes below are taken from http://www.color-hex.com/color-palettes/ (and modified)
  84. // RGB: Red,Green,Blue sequence
  85. Palette("RGB", true, {0xFF0000, 0x00FF00, 0x0000FF}),
  86. // Rainbow: Rainbow colors
  87. Palette("Rainbow", true, {0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00, 0x00FFFF, 0x0000FF, 0x5500AB}),
  88. // Party: Blue purple ping red orange yellow (and back). Basically, everything but the greens.
  89. // This palette is good for lighting at a club or party.
  90. Palette("Party", false, {0x5500AB, 0x84007C, 0xB5004B, 0xE5001B, 0xE81700, 0xB84700, 0xAB7700, 0xABAB00,
  91. 0xAB5500, 0xDD2200, 0xF2000E, 0xC2003E, 0x8F0071, 0x5F00A1, 0x2F00D0, 0x0007F9}),
  92. // Heat: Approximate "black body radiation" palette, akin to the FastLED 'HeatColor' function.
  93. // Recommend that you use values 0-240 rather than the usual 0-255, as the last 15 colors will be
  94. // 'wrapping around' from the hot end to the cold end, which looks wrong.
  95. Palette("Heat", false, {0x700070, 0xFF0000, 0xFFFF00, 0xFFFFCC}),
  96. // Fire:
  97. Palette("Fire", false, {0x300000, 0x440000, 0x880000, 0xFF0000, 0xFF6600, 0xFFCC00}),
  98. // Blue:
  99. Palette("Blue", true, {0xffffff, 0x0000ff, 0x00ffff}),
  100. // Sun: Slice Of The Sun
  101. Palette("Sun", true, {0xfff95b, 0xffe048, 0xffc635, 0xffad22, 0xff930f}),
  102. // Lime: yellow green mix
  103. Palette("Lime", true, {0x51f000, 0x6fff00, 0x96ff00, 0xc9ff00, 0xf0ff00}),
  104. Palette("Greens", false, {0xe5f2e5, 0x91f086, 0x48bf53, 0x11823b, 0x008000, 0x004d25, 0x18392b, 0x02231c}),
  105. // Pastel: Pastel Fruity Mixture
  106. Palette("Pastel", true, {0x75aa68, 0x5960ae, 0xe4be6c, 0xca5959, 0x8366ac}),
  107. Palette("Summer", true, {0xb81616, 0xf13057, 0xf68118, 0xf2ab1e, 0xf9ca00, 0xaef133, 0x19ee9f, 0x0ea7b5, 0x0c457d}),
  108. Palette("Autumn", false, {0x8b1509, 0xce7612, 0x11805d, 0x801138, 0x32154b, 0x724c04}),
  109. Palette("Winter", true, {0xca9eb8, 0xfeeacf, 0xe0ecf2, 0x89e1c9, 0x72c3c5, 0x92c1ff, 0x3e6589, 0x052542}),
  110. Palette("Gaang", true, {0xe7a532, 0x46a8ca, 0xaf7440, 0xb4d29d, 0x9f5b72, 0x585c82})
  111. };
  112. auto one_color_palette = std::unique_ptr<Palette>(new Palette("White", true, {0xffffff}));
  113. constexpr uint16_t GarlandLeds { GARLAND_LEDS };
  114. constexpr unsigned char GarlandPin { GARLAND_DATA_PIN };
  115. constexpr neoPixelType GarlandPixelType { NEO_GRB + NEO_KHZ800 };
  116. Adafruit_NeoPixel pixels(GarlandLeds, GarlandPin, GarlandPixelType);
  117. Scene<GarlandLeds> scene(&pixels);
  118. std::array<Anim*, 18> anims {
  119. new AnimStart(),
  120. new AnimGlow(),
  121. new AnimPixieDust(),
  122. new AnimSparkr(),
  123. new AnimStars(),
  124. new AnimSpread(),
  125. new AnimRandCyc(),
  126. new AnimFly(),
  127. new AnimComets(),
  128. new AnimAssemble(),
  129. new AnimDolphins(),
  130. new AnimSalut(),
  131. new AnimFountain(),
  132. new AnimRandRun(),
  133. new AnimWaves(AnimWaves::Type::LongWaves),
  134. new AnimWaves(AnimWaves::Type::ShortWaves),
  135. new AnimWaves(AnimWaves::Type::Comets),
  136. new AnimWaves(AnimWaves::Type::CrossWaves),
  137. };
  138. #define START_ANIMATION 0
  139. //------------------------------------------------------------------------------
  140. // Setup
  141. //------------------------------------------------------------------------------
  142. void _garlandConfigure() {
  143. _garland_enabled = getSetting(NAME_GARLAND_ENABLED, true);
  144. byte brightness = getSetting(NAME_GARLAND_BRIGHTNESS, 255);
  145. scene.setBrightness(brightness);
  146. float speed = getSetting(NAME_GARLAND_SPEED, 50);
  147. scene.setSpeed(speed);
  148. DEBUG_MSG_P(PSTR("[GARLAND] enabled %s brightness %d speed %s\n"),
  149. _garland_enabled ? "YES" : "NO", brightness, String(speed).c_str());
  150. }
  151. //------------------------------------------------------------------------------
  152. void _garlandReload() {
  153. _garlandConfigure();
  154. }
  155. //------------------------------------------------------------------------------
  156. void setDefault() {
  157. scene.setDefault();
  158. byte brightness = scene.getBrightness();
  159. setSetting(NAME_GARLAND_BRIGHTNESS, brightness);
  160. byte speed = scene.getSpeed();
  161. setSetting(NAME_GARLAND_SPEED, speed);
  162. #if WEB_SUPPORT
  163. wsPost([brightness, speed](JsonObject& root) {
  164. root["garlandBrightness"] = brightness;
  165. root["garlandSpeed"] = speed;
  166. });
  167. #endif
  168. }
  169. #if WEB_SUPPORT
  170. //------------------------------------------------------------------------------
  171. void _garlandWebSocketOnVisible(JsonObject& root) {
  172. wsPayloadModule(root, PSTR("garland"));
  173. }
  174. void _garlandWebSocketOnConnected(JsonObject& root) {
  175. root[NAME_GARLAND_ENABLED] = garlandEnabled();
  176. root[NAME_GARLAND_BRIGHTNESS] = scene.getBrightness();
  177. root[NAME_GARLAND_SPEED] = scene.getSpeed();
  178. }
  179. //------------------------------------------------------------------------------
  180. bool _garlandWebSocketOnKeyCheck(espurna::StringView key, const JsonVariant&) {
  181. return espurna::settings::query::samePrefix(key, NAME_GARLAND_ENABLED)
  182. || espurna::settings::query::samePrefix(key, NAME_GARLAND_BRIGHTNESS)
  183. || espurna::settings::query::samePrefix(key, NAME_GARLAND_SPEED);
  184. }
  185. //------------------------------------------------------------------------------
  186. void _garlandWebSocketOnAction(uint32_t client_id, const char* action, JsonObject& data) {
  187. if (strcmp(action, NAME_GARLAND_SWITCH) == 0) {
  188. if (data.containsKey("status") && data.is<int>("status")) {
  189. garlandEnabled(1 == data["status"].as<int>());
  190. }
  191. }
  192. if (strcmp(action, NAME_GARLAND_SET_BRIGHTNESS) == 0) {
  193. if (data.containsKey("brightness")) {
  194. byte new_brightness = data.get<byte>("brightness");
  195. setSetting(NAME_GARLAND_BRIGHTNESS, new_brightness);
  196. scene.setBrightness(new_brightness);
  197. }
  198. }
  199. if (strcmp(action, NAME_GARLAND_SET_SPEED) == 0) {
  200. if (data.containsKey("speed")) {
  201. byte new_speed = data.get<byte>("speed");
  202. setSetting(NAME_GARLAND_SPEED, new_speed);
  203. scene.setSpeed(new_speed);
  204. }
  205. }
  206. if (strcmp(action, NAME_GARLAND_SET_DEFAULT) == 0) {
  207. setDefault();
  208. }
  209. }
  210. #endif
  211. //------------------------------------------------------------------------------
  212. void setupScene(Anim* new_anim, Palette* new_palette, unsigned long new_duration) {
  213. unsigned long currentAnimRunTime = millis() - _lastTimeUpdate;
  214. _lastTimeUpdate = millis();
  215. int numShows = scene.getNumShows();
  216. int frameRate = currentAnimRunTime > 0 ? numShows * 1000 / currentAnimRunTime : 0;
  217. static String palette_name = "Start";
  218. DEBUG_MSG_P(PSTR("[GARLAND] Anim: %-10s Pal: %-8s timings: calc: %4d pixl: %3d show: %4d frate: %d\n"),
  219. scene.getAnim()->name(), palette_name.c_str(),
  220. scene.getAvgCalcTime(), scene.getAvgPixlTime(), scene.getAvgShowTime(), frameRate);
  221. _currentDuration = new_duration;
  222. palette_name = new_palette->name();
  223. DEBUG_MSG_P(PSTR("[GARLAND] Anim: %-10s Pal: %-8s Inter: %d\n"),
  224. new_anim->name(), palette_name.c_str(), _currentDuration);
  225. scene.setAnim(new_anim);
  226. scene.setPalette(new_palette);
  227. scene.setup();
  228. }
  229. //------------------------------------------------------------------------------
  230. bool executeCommand(const String& command) {
  231. DEBUG_MSG_P(PSTR("[GARLAND] Executing command \"%s\"\n"), command.c_str());
  232. // Parse JSON input
  233. DynamicJsonBuffer jsonBuffer;
  234. JsonObject& root = jsonBuffer.parseObject(command);
  235. if (!root.success()) {
  236. DEBUG_MSG_P(PSTR("[GARLAND] Error parsing command\n"));
  237. return false;
  238. }
  239. bool scene_setup_required = false;
  240. if (root.containsKey(MQTT_PAYLOAD_ENABLE)) {
  241. auto enable = root[MQTT_PAYLOAD_ENABLE].as<String>();
  242. garlandEnabled(enable != "false");
  243. }
  244. if (root.containsKey(MQTT_PAYLOAD_BRIGHTNESS)) {
  245. auto brightness = root[MQTT_PAYLOAD_BRIGHTNESS].as<byte>();
  246. scene.setBrightness(brightness);
  247. }
  248. if (root.containsKey(MQTT_PAYLOAD_ANIM_SPEED)) {
  249. auto speed = root[MQTT_PAYLOAD_ANIM_SPEED].as<byte>();
  250. scene.setSpeed(speed);
  251. }
  252. Anim* newAnim = anims[0];
  253. if (root.containsKey(MQTT_PAYLOAD_ANIMATION)) {
  254. auto animation = root[MQTT_PAYLOAD_ANIMATION].as<const char*>();
  255. for (size_t i = 0; i < anims.size(); ++i) {
  256. auto anim_name = anims[i]->name();
  257. if (strcmp(animation, anim_name) == 0) {
  258. newAnim = anims[i];
  259. scene_setup_required = true;
  260. break;
  261. }
  262. }
  263. }
  264. Palette* newPalette = &pals[0];
  265. if (root.containsKey(MQTT_PAYLOAD_PALETTE)) {
  266. if (root.is<int>(MQTT_PAYLOAD_PALETTE)) {
  267. one_color_palette.reset(new Palette("Color", true, {root[MQTT_PAYLOAD_PALETTE].as<uint32_t>()}));
  268. newPalette = one_color_palette.get();
  269. } else {
  270. auto palette = root[MQTT_PAYLOAD_PALETTE].as<String>();
  271. bool palette_found = false;
  272. for (size_t i = 0; i < pals.size(); ++i) {
  273. auto pal_name = pals[i].name();
  274. if (palette = pal_name) {
  275. newPalette = &pals[i];
  276. palette_found = true;
  277. scene_setup_required = true;
  278. break;
  279. }
  280. }
  281. if (!palette_found) {
  282. const auto result = parseUnsigned(palette);
  283. if (result.ok) {
  284. one_color_palette.reset(new Palette("Color", true, {result.value}));
  285. newPalette = one_color_palette.get();
  286. }
  287. }
  288. }
  289. }
  290. unsigned long newAnimDuration = LONG_MAX;
  291. if (root.containsKey(MQTT_PAYLOAD_DURATION)) {
  292. newAnimDuration = root[MQTT_PAYLOAD_DURATION].as<unsigned long>();
  293. scene_setup_required = true;
  294. }
  295. if (scene_setup_required) {
  296. setupScene(newAnim, newPalette, newAnimDuration);
  297. return true;
  298. }
  299. return false;
  300. }
  301. //------------------------------------------------------------------------------
  302. // Loop
  303. //------------------------------------------------------------------------------
  304. void garlandLoop(void) {
  305. if (!_immediate_command.isEmpty()) {
  306. executeCommand(_immediate_command);
  307. _immediate_command.clear();
  308. }
  309. if (!garlandEnabled())
  310. return;
  311. scene.run();
  312. unsigned long currentAnimRunTime = millis() - _lastTimeUpdate;
  313. if (currentAnimRunTime > _currentDuration && scene.finishedAnimCycle()) {
  314. bool scene_setup_done = false;
  315. if (!_command_queue.empty()) {
  316. scene_setup_done = executeCommand(_command_queue.front());
  317. _command_queue.pop();
  318. } else if (!_command_sequence.empty()) {
  319. scene_setup_done = executeCommand(_command_sequence[_currentCommandInSequence]);
  320. ++_currentCommandInSequence;
  321. if (_currentCommandInSequence >= _command_sequence.size())
  322. _currentCommandInSequence = 0;
  323. }
  324. if (!scene_setup_done) {
  325. Anim* newAnim = scene.getAnim();
  326. while (newAnim == scene.getAnim()) {
  327. newAnim = anims[secureRandom(START_ANIMATION + 1, anims.size())];
  328. }
  329. Palette* newPalette = scene.getPalette();
  330. while (newPalette == scene.getPalette()) {
  331. newPalette = &pals[secureRandom(pals.size())];
  332. }
  333. unsigned long newAnimDuration = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX);
  334. setupScene(newAnim, newPalette, newAnimDuration);
  335. }
  336. }
  337. }
  338. //------------------------------------------------------------------------------
  339. void garlandMqttCallback(unsigned int type, espurna::StringView topic, espurna::StringView payload) {
  340. if (type == MQTT_CONNECT_EVENT) {
  341. mqttSubscribe(MQTT_TOPIC_GARLAND);
  342. }
  343. if (type == MQTT_MESSAGE_EVENT) {
  344. auto t = mqttMagnitude(topic);
  345. if (t.equals(MQTT_TOPIC_GARLAND)) {
  346. DynamicJsonBuffer jsonBuffer;
  347. JsonObject& root = jsonBuffer.parseObject(payload.begin());
  348. if (!root.success()) {
  349. DEBUG_MSG_P(PSTR("[GARLAND] Error parsing mqtt data\n"));
  350. return;
  351. }
  352. String command = MQTT_COMMAND_IMMEDIATE;
  353. if (root.containsKey(MQTT_PAYLOAD_COMMAND)) {
  354. command = root[MQTT_PAYLOAD_COMMAND].as<String>();
  355. }
  356. if (command == MQTT_COMMAND_IMMEDIATE) {
  357. _immediate_command = payload.toString();
  358. } else if (command == MQTT_COMMAND_RESET) {
  359. std::queue<String> empty_queue;
  360. std::swap(_command_queue, empty_queue);
  361. std::vector<String> empty_sequence;
  362. std::swap(_command_sequence, empty_sequence);
  363. _immediate_command.clear();
  364. _currentDuration = 0;
  365. setDefault();
  366. garlandEnabled(true);
  367. } else if (command == MQTT_COMMAND_QUEUE) {
  368. _command_queue.push(payload.toString());
  369. } else if (command == MQTT_COMMAND_SEQUENCE) {
  370. _command_sequence.push_back(payload.toString());
  371. }
  372. }
  373. }
  374. }
  375. /*#######################################################################
  376. _____
  377. / ____|
  378. | (___ ___ ___ _ __ ___
  379. \___ \ / __| / _ \ | '_ \ / _ \
  380. ____) | | (__ | __/ | | | | | __/
  381. |_____/ \___| \___| |_| |_| \___|
  382. #######################################################################*/
  383. #define GARLAND_SCENE_TRANSITION_MS 1000 // transition time between animations, ms
  384. #define GARLAND_SCENE_DEFAULT_BRIGHTNESS 255
  385. template<uint16_t Leds>
  386. void Scene<Leds>::setPalette(Palette* palette) {
  387. _palette = palette;
  388. if (setUpOnPalChange) {
  389. setupImpl();
  390. }
  391. }
  392. template<uint16_t Leds>
  393. void Scene<Leds>::setBrightness(byte value) {
  394. DEBUG_MSG_P(PSTR("[GARLAND] new brightness = %d\n"), value);
  395. brightness = value;
  396. }
  397. // Speed is reverse to cycleFactor and 10x
  398. template<uint16_t Leds>
  399. void Scene<Leds>::setSpeed(byte speed) {
  400. DEBUG_MSG_P(PSTR("[GARLAND] new speed = %d\n"), speed);
  401. this->speed = speed;
  402. cycleFactor = (float)(GARLAND_SCENE_SPEED_MAX - speed) / GARLAND_SCENE_SPEED_FACTOR;
  403. }
  404. template<uint16_t Leds>
  405. void Scene<Leds>::setDefault() {
  406. DEBUG_MSG_P(PSTR("[GARLAND] set default\n"));
  407. this->setBrightness(GARLAND_SCENE_DEFAULT_BRIGHTNESS);
  408. this->setSpeed(GARLAND_SCENE_DEFAULT_SPEED);
  409. }
  410. template<uint16_t Leds>
  411. void Scene<Leds>::run() {
  412. unsigned long iteration_start_time = micros();
  413. if (state == Calculate || cyclesRemain < 1) {
  414. // Calculate number of cycles for this animation iteration
  415. float cycleSum = cycleFactor * (_anim ? _anim->getCycleFactor() : 1.0) + cycleTail;
  416. cyclesRemain = cycleSum;
  417. if (cyclesRemain < 1) {
  418. cyclesRemain = 1;
  419. cycleSum = 0;
  420. cycleTail = 0;
  421. } else {
  422. cycleTail = cycleSum - cyclesRemain;
  423. }
  424. if (_anim) {
  425. _anim->Run();
  426. }
  427. sum_calc_time += (micros() - iteration_start_time);
  428. iteration_start_time = micros();
  429. ++calc_num;
  430. state = Transition;
  431. }
  432. if (state == Transition && cyclesRemain < 3) {
  433. // transition coef, if within 0..1 - transition is active
  434. // changes from 1 to 0 during transition, so we interpolate from current
  435. // color to previous
  436. float transc = (float)((long)transms - (long)millis()) / GARLAND_SCENE_TRANSITION_MS;
  437. Color* leds_prev = (_leds == &_leds1[0]) ? &_leds2[0] : &_leds1[0];
  438. if (transc > 0) {
  439. for (int i = 0; i < Leds; i++) {
  440. // transition is in progress
  441. Color c = _leds[i].interpolate(leds_prev[i], transc);
  442. byte r = (int)(bri_lvl[c.r]) * brightness / 256;
  443. byte g = (int)(bri_lvl[c.g]) * brightness / 256;
  444. byte b = (int)(bri_lvl[c.b]) * brightness / 256;
  445. _pixels->setPixelColor(i, _pixels->Color(r, g, b));
  446. }
  447. } else {
  448. for (int i = 0; i < Leds; i++) {
  449. // regular operation
  450. byte r = (int)(bri_lvl[_leds[i].r]) * brightness / 256;
  451. byte g = (int)(bri_lvl[_leds[i].g]) * brightness / 256;
  452. byte b = (int)(bri_lvl[_leds[i].b]) * brightness / 256;
  453. _pixels->setPixelColor(i, _pixels->Color(r, g, b));
  454. }
  455. }
  456. sum_pixl_time += (micros() - iteration_start_time);
  457. iteration_start_time = micros();
  458. ++pixl_num;
  459. state = Show;
  460. }
  461. if (state == Show && cyclesRemain < 2) {
  462. /* Showing pixels (actually transmitting their RGB data) is most time consuming operation in the
  463. garland workflow. Using 800 kHz gives 1.25 μs per bit. -> 30 μs (0.03 ms) per RGB LED.
  464. So for example 3 ms for 100 LEDs. Unfortunately it can't be postponed and resumed later as it
  465. will lead to reseting the transmition operation. From other hand, long operation can cause
  466. Soft WDT reset. To avoid wdt reset we need to switch soft wdt off for long strips.
  467. It is not best practice, but assuming that it is only garland, it can be acceptable.
  468. Tested up to 300 leds. */
  469. if (Leds > NUMLEDS_CAN_CAUSE_WDT_RESET) {
  470. ESP.wdtDisable();
  471. }
  472. _pixels->show();
  473. if (Leds > NUMLEDS_CAN_CAUSE_WDT_RESET) {
  474. ESP.wdtEnable(5000);
  475. }
  476. sum_show_time += (micros() - iteration_start_time);
  477. ++show_num;
  478. state = Calculate;
  479. ++numShows;
  480. }
  481. --cyclesRemain;
  482. }
  483. template<uint16_t Leds>
  484. void Scene<Leds>::setupImpl() {
  485. transms = millis() + GARLAND_SCENE_TRANSITION_MS;
  486. // switch operation buffers (for transition to operate)
  487. if (_leds == &_leds1[0]) {
  488. _leds = &_leds2[0];
  489. } else {
  490. _leds = &_leds1[0];
  491. }
  492. if (_anim) {
  493. _anim->Setup(_palette, _pals, _palsNum, Leds, _leds, _ledstmp.data(), _seq.data());
  494. }
  495. }
  496. template<uint16_t Leds>
  497. void Scene<Leds>::setup() {
  498. sum_calc_time = 0;
  499. sum_pixl_time = 0;
  500. sum_show_time = 0;
  501. calc_num = 0;
  502. pixl_num = 0;
  503. show_num = 0;
  504. numShows = 0;
  505. if (!setUpOnPalChange) {
  506. setupImpl();
  507. }
  508. }
  509. /*#######################################################################
  510. _ _ _
  511. /\ (_) | | (_)
  512. / \ _ __ _ _ __ ___ __ _ | |_ _ ___ _ __
  513. / /\ \ | '_ \ | | | '_ ` _ \ / _` | | __| | | / _ \ | '_ \
  514. / ____ \ | | | | | | | | | | | | | (_| | | |_ | | | (_) | | | | |
  515. /_/ \_\ |_| |_| |_| |_| |_| |_| \__,_| \__| |_| \___/ |_| |_|
  516. #######################################################################*/
  517. Anim::Anim(const char* name) : _name(name) {}
  518. void Anim::Setup(Palette* palette, Palette* pals, size_t palsNum, uint16_t numLeds, Color* leds, Color* ledstmp, byte* seq) {
  519. this->palette = palette;
  520. this->pals = pals;
  521. this->palsNum = palsNum;
  522. this->numLeds = numLeds;
  523. this->leds = leds;
  524. this->ledstmp = ledstmp;
  525. this->seq = seq;
  526. // TODO: if animation allocates 'stuff', provide some persistent memory locations instead of going to the heap?
  527. SetupImpl();
  528. }
  529. void Anim::initSeq() {
  530. for (int i = 0; i < numLeds; ++i)
  531. seq[i] = i;
  532. }
  533. void Anim::shuffleSeq() {
  534. for (int i = 0; i < numLeds; ++i) {
  535. byte ind = (unsigned int)(rngb() * numLeds / 256);
  536. if (ind != i) {
  537. std::swap(seq[ind], seq[i]);
  538. }
  539. }
  540. }
  541. void Anim::glowSetUp() {
  542. braPhaseSpd = secureRandom(4, 13);
  543. if (braPhaseSpd > 8) {
  544. braPhaseSpd = braPhaseSpd - 17;
  545. }
  546. braFreq = secureRandom(20, 60);
  547. }
  548. void Anim::glowForEachLed(uint16_t i) {
  549. int8 bra = braPhase + i * braFreq;
  550. bra = BRA_OFFSET + (abs(bra) >> BRA_AMP_SHIFT);
  551. leds[i] = leds[i].brightness(bra);
  552. }
  553. void Anim::glowRun() {
  554. braPhase += braPhaseSpd;
  555. }
  556. void Anim::flashRandomLeds(uint16_t deciPercent) {
  557. int numLedsToFlash = numLeds * deciPercent / 1000;
  558. for (int i = 0; i < numLedsToFlash; ++i) {
  559. int ind = secureRandom(numLeds);
  560. leds[ind] = sparkleColor;
  561. }
  562. }
  563. unsigned int Anim::rng() {
  564. static unsigned int y = 0;
  565. y += micros(); // seeded with changing number
  566. y ^= y << 2;
  567. y ^= y >> 7;
  568. y ^= y << 7;
  569. return (y);
  570. }
  571. // Random numbers generator in byte range (256) much faster than secureRandom.
  572. // For usage in time-critical places.
  573. byte Anim::rngb() {
  574. return static_cast<byte>(rng());
  575. }
  576. bool fiftyFifty() {
  577. return secureRandom(2) == 0;
  578. }
  579. int randDir() {
  580. return secureRandom(2) == 0 ? -1 : 1;
  581. }
  582. } // namespace
  583. //------------------------------------------------------------------------------
  584. void garlandEnabled(bool enabled) {
  585. setSetting(NAME_GARLAND_ENABLED, enabled);
  586. if (_garland_enabled != enabled) {
  587. espurnaRegisterOnceUnique([]() {
  588. pixels.clear();
  589. pixels.show();
  590. });
  591. }
  592. _garland_enabled = enabled;
  593. #if WEB_SUPPORT
  594. wsPost([enabled](JsonObject& root) {
  595. root["garlandEnabled"] = enabled;
  596. });
  597. #endif
  598. }
  599. bool garlandEnabled() {
  600. return _garland_enabled;
  601. }
  602. void garlandDisable() {
  603. pixels.clear();
  604. }
  605. void garlandSetup() {
  606. _garlandConfigure();
  607. mqttRegister(garlandMqttCallback);
  608. // Websockets
  609. #if WEB_SUPPORT
  610. wsRegister()
  611. .onVisible(_garlandWebSocketOnVisible)
  612. .onConnected(_garlandWebSocketOnConnected)
  613. .onKeyCheck(_garlandWebSocketOnKeyCheck)
  614. .onAction(_garlandWebSocketOnAction);
  615. #endif
  616. espurnaRegisterLoop(garlandLoop);
  617. espurnaRegisterReload(_garlandReload);
  618. pixels.begin();
  619. scene.setAnim(anims[START_ANIMATION]);
  620. scene.setPalette(&pals[0]);
  621. scene.setPals(pals.data(), pals.size());
  622. scene.setup();
  623. _currentDuration = 12000; // Start animation duration
  624. }
  625. #endif // GARLAND_SUPPORT