Fork of the espurna firmware for `mhsw` switches
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.

481 lines
17 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 60 led strip.
  6. !!! For more leds can cause WDT rebot. Need to be carefully tested for more than 60 leds !!!
  7. The most time consuming operation is actually showing leds by Adafruit Neopixel. It take about 1870 mcs.
  8. More long strip can take more time to show.
  9. Currently animation calculation, brightness calculation/transition and showing makes in one loop cycle.
  10. Debug output shows timings. Overal timing should be not more that 3000 ms.
  11. For longer strips have sense to divide entire strip (pixels) on parts about 100 pixels and show one part
  12. at a cycle.
  13. */
  14. #include "garland.h"
  15. #if GARLAND_SUPPORT
  16. #include <Adafruit_NeoPixel.h>
  17. #include <vector>
  18. #include "garland/color.h"
  19. #include "garland/palette.h"
  20. #include "garland/scene.h"
  21. #include "ws.h"
  22. const char* NAME_GARLAND_ENABLED = "garlandEnabled";
  23. const char* NAME_GARLAND_BRIGHTNESS = "garlandBrightness";
  24. const char* NAME_GARLAND_SPEED = "garlandSpeed";
  25. const char* NAME_GARLAND_SWITCH = "garland_switch";
  26. const char* NAME_GARLAND_SET_BRIGHTNESS = "garland_set_brightness";
  27. const char* NAME_GARLAND_SET_SPEED = "garland_set_speed";
  28. const char* NAME_GARLAND_SET_DEFAULT = "garland_set_default";
  29. #define EFFECT_UPDATE_INTERVAL_MIN 5000 // 5 sec
  30. #define EFFECT_UPDATE_INTERVAL_MAX 10000 // 10 sec
  31. bool _garland_enabled = true;
  32. unsigned long _last_update = 0;
  33. unsigned long _interval_effect_update;
  34. // Palette should
  35. Palette pals[] = {
  36. // palettes below are taken from http://www.color-hex.com/color-palettes/ (and modified)
  37. // RGB: Red,Green,Blue sequence
  38. Palette("RGB", {0xFF0000, 0x00FF00, 0x0000FF}),
  39. // Rainbow: Rainbow colors
  40. Palette("Rainbow", {0xFF0000, 0xAB5500, 0xABAB00, 0x00FF00, 0x00AB55, 0x0000FF, 0x5500AB, 0xAB0055}),
  41. // RainbowStripe: Rainbow colors with alternating stripes of black
  42. Palette("Stripe", {0xFF0000, 0x000000, 0xAB5500, 0x000000, 0xABAB00, 0x000000, 0x00FF00, 0x000000,
  43. 0x00AB55, 0x000000, 0x0000FF, 0x000000, 0x5500AB, 0x000000, 0xAB0055, 0x000000}),
  44. // Party: Blue purple ping red orange yellow (and back). Basically, everything but the greens.
  45. // This palette is good for lighting at a club or party.
  46. Palette("Party", {0x5500AB, 0x84007C, 0xB5004B, 0xE5001B, 0xE81700, 0xB84700, 0xAB7700, 0xABAB00,
  47. 0xAB5500, 0xDD2200, 0xF2000E, 0xC2003E, 0x8F0071, 0x5F00A1, 0x2F00D0, 0x0007F9}),
  48. // Heat: Approximate "black body radiation" palette, akin to the FastLED 'HeatColor' function.
  49. // Recommend that you use values 0-240 rather than the usual 0-255, as the last 15 colors will be
  50. // 'wrapping around' from the hot end to the cold end, which looks wrong.
  51. Palette("Heat", {0x700070, 0xFF0000, 0xFFFF00, 0xFFFFCC}),
  52. // Fire:
  53. Palette("Fire", {0x000000, 0x220000, 0x880000, 0xFF0000, 0xFF6600, 0xFFCC00}),
  54. // Blue:
  55. Palette("Blue", {0xffffff, 0x0000ff, 0x00ffff}),
  56. // Sun: Slice Of The Sun
  57. Palette("Sun", {0xfff95b, 0xffe048, 0xffc635, 0xffad22, 0xff930f}),
  58. // Lime: yellow green mix
  59. Palette("Lime", {0x51f000, 0x6fff00, 0x96ff00, 0xc9ff00, 0xf0ff00}),
  60. // Pastel: Pastel Fruity Mixture
  61. Palette("Pastel", {0x75aa68, 0x5960ae, 0xe4be6c, 0xca5959, 0x8366ac})};
  62. constexpr size_t palsSize() { return sizeof(pals)/sizeof(pals[0]); }
  63. Adafruit_NeoPixel pixels = Adafruit_NeoPixel(GARLAND_LEDS, GARLAND_D_PIN, NEO_GRB + NEO_KHZ800);
  64. Scene scene(&pixels);
  65. Anim* anims[] = {new AnimStart(), new AnimPixieDust(), new AnimSparkr(), new AnimRun(), new AnimStars(), new AnimSpread(),
  66. new AnimRandCyc(), new AnimFly(), new AnimComets(), new AnimAssemble(), new AnimDolphins(), new AnimSalut()};
  67. constexpr size_t animsSize() { return sizeof(anims)/sizeof(anims[0]); }
  68. //------------------------------------------------------------------------------
  69. void garlandDisable() {
  70. pixels.clear();
  71. }
  72. //------------------------------------------------------------------------------
  73. void garlandEnabled(bool enabled) {
  74. _garland_enabled = enabled;
  75. }
  76. //------------------------------------------------------------------------------
  77. bool garlandEnabled() {
  78. return _garland_enabled;
  79. }
  80. //------------------------------------------------------------------------------
  81. // Setup
  82. //------------------------------------------------------------------------------
  83. void _garlandConfigure() {
  84. _garland_enabled = getSetting(NAME_GARLAND_ENABLED, true);
  85. DEBUG_MSG_P(PSTR("[GARLAND] _garland_enabled = %d\n"), _garland_enabled);
  86. byte brightness = getSetting(NAME_GARLAND_BRIGHTNESS, 255);
  87. scene.setBrightness(brightness);
  88. DEBUG_MSG_P(PSTR("[GARLAND] brightness = %d\n"), brightness);
  89. float speed = getSetting(NAME_GARLAND_SPEED, 50);
  90. scene.setSpeed(speed);
  91. }
  92. //------------------------------------------------------------------------------
  93. void _garlandReload() {
  94. _garlandConfigure();
  95. }
  96. #if WEB_SUPPORT
  97. //------------------------------------------------------------------------------
  98. void _garlandWebSocketOnConnected(JsonObject& root) {
  99. root[NAME_GARLAND_ENABLED] = garlandEnabled();
  100. root[NAME_GARLAND_BRIGHTNESS] = scene.getBrightness();
  101. root[NAME_GARLAND_SPEED] = scene.getSpeed();
  102. root["garlandVisible"] = 1;
  103. }
  104. //------------------------------------------------------------------------------
  105. bool _garlandWebSocketOnKeyCheck(const char* key, JsonVariant& value) {
  106. if (strncmp(key, NAME_GARLAND_ENABLED, strlen(NAME_GARLAND_ENABLED)) == 0) return true;
  107. if (strncmp(key, NAME_GARLAND_BRIGHTNESS, strlen(NAME_GARLAND_BRIGHTNESS)) == 0) return true;
  108. if (strncmp(key, NAME_GARLAND_SPEED, strlen(NAME_GARLAND_SPEED)) == 0) return true;
  109. return false;
  110. }
  111. //------------------------------------------------------------------------------
  112. void _garlandWebSocketOnAction(uint32_t client_id, const char* action, JsonObject& data) {
  113. if (strcmp(action, NAME_GARLAND_SWITCH) == 0) {
  114. if (data.containsKey("status") && data.is<int>("status")) {
  115. _garland_enabled = (1 == data["status"].as<int>());
  116. setSetting(NAME_GARLAND_ENABLED, _garland_enabled);
  117. if (!_garland_enabled) {
  118. schedule_function([](){
  119. pixels.clear();
  120. pixels.show();
  121. });
  122. }
  123. }
  124. }
  125. if (strcmp(action, NAME_GARLAND_SET_BRIGHTNESS) == 0) {
  126. if (data.containsKey("brightness")) {
  127. byte new_brightness = data.get<byte>("brightness");
  128. DEBUG_MSG_P(PSTR("[GARLAND] new brightness = %d\n"), new_brightness);
  129. setSetting(NAME_GARLAND_BRIGHTNESS, new_brightness);
  130. scene.setBrightness(new_brightness);
  131. }
  132. }
  133. if (strcmp(action, NAME_GARLAND_SET_SPEED) == 0) {
  134. if (data.containsKey("speed")) {
  135. byte new_speed = data.get<byte>("speed");
  136. DEBUG_MSG_P(PSTR("[GARLAND] new speed = %d\n"), new_speed);
  137. setSetting(NAME_GARLAND_SPEED, new_speed);
  138. scene.setSpeed(new_speed);
  139. }
  140. }
  141. if (strcmp(action, NAME_GARLAND_SET_DEFAULT) == 0) {
  142. scene.setDefault();
  143. byte brightness = scene.getBrightness();
  144. setSetting(NAME_GARLAND_BRIGHTNESS, brightness);
  145. byte speed = scene.getSpeed();
  146. setSetting(NAME_GARLAND_SPEED, speed);
  147. char buffer[128];
  148. snprintf_P(buffer, sizeof(buffer), PSTR("{\"garlandBrightness\": %d, \"garlandSpeed\": %d}"), brightness, speed);
  149. wsSend(buffer);
  150. }
  151. }
  152. #endif
  153. //------------------------------------------------------------------------------
  154. // Loop
  155. //------------------------------------------------------------------------------
  156. void garlandLoop(void) {
  157. if (!garlandEnabled())
  158. return;
  159. scene.run();
  160. unsigned long animation_time = millis() - _last_update;
  161. if (animation_time > _interval_effect_update && scene.finishedAnimCycle()) {
  162. _last_update = millis();
  163. _interval_effect_update = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX);
  164. static int animInd = 0;
  165. int prevAnimInd = animInd;
  166. while (prevAnimInd == animInd) animInd = secureRandom(1, animsSize());
  167. static int paletteInd = 0;
  168. int prevPalInd = paletteInd;
  169. while (prevPalInd == paletteInd) paletteInd = secureRandom(palsSize());
  170. int numShows = scene.getNumShows();
  171. int frameRate = animation_time > 0 ? numShows * 1000 / animation_time : 0;
  172. DEBUG_MSG_P(PSTR("[GARLAND] Anim: %-10s Pal: %-8s timings: calc: %4d pixl: %3d show: %4d frate: %d\n"),
  173. anims[prevAnimInd]->name(), pals[prevPalInd].name(),
  174. scene.getAvgCalcTime(), scene.getAvgPixlTime(), scene.getAvgShowTime(), frameRate);
  175. DEBUG_MSG_P(PSTR("[GARLAND] Anim: %-10s Pal: %-8s Inter: %d\n"),
  176. anims[animInd]->name(), pals[paletteInd].name(), _interval_effect_update);
  177. scene.setAnim(anims[animInd]);
  178. scene.setPalette(&pals[paletteInd]);
  179. scene.setup();
  180. }
  181. }
  182. //------------------------------------------------------------------------------
  183. void garlandSetup() {
  184. _garlandConfigure();
  185. // Websockets
  186. #if WEB_SUPPORT
  187. wsRegister()
  188. .onConnected(_garlandWebSocketOnConnected)
  189. .onKeyCheck(_garlandWebSocketOnKeyCheck)
  190. .onAction(_garlandWebSocketOnAction);
  191. #endif
  192. espurnaRegisterLoop(garlandLoop);
  193. espurnaRegisterReload(_garlandReload);
  194. pixels.begin();
  195. scene.setAnim(anims[0]);
  196. scene.setPalette(&pals[0]);
  197. scene.setup();
  198. _interval_effect_update = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX);
  199. }
  200. /*#######################################################################
  201. _____
  202. / ____|
  203. | (___ ___ ___ _ __ ___
  204. \___ \ / __| / _ \ | '_ \ / _ \
  205. ____) | | (__ | __/ | | | | | __/
  206. |_____/ \___| \___| |_| |_| \___|
  207. #######################################################################*/
  208. #define GARLAND_SCENE_TRANSITION_MS 1000 // transition time between animations, ms
  209. #define GARLAND_SCENE_SPEED_MAX 70
  210. #define GARLAND_SCENE_SPEED_FACTOR 10
  211. #define GARLAND_SCENE_DEFAULT_SPEED 50
  212. #define GARLAND_SCENE_DEFAULT_BRIGHTNESS 255
  213. Scene::Scene(Adafruit_NeoPixel* pixels)
  214. : _pixels(pixels),
  215. _numLeds(pixels->numPixels()),
  216. _leds1(_numLeds),
  217. _leds2(_numLeds),
  218. _ledstmp(_numLeds),
  219. _seq(_numLeds) {
  220. }
  221. void Scene::setPalette(Palette* palette) {
  222. _palette = palette;
  223. if (setUpOnPalChange) {
  224. setupImpl();
  225. }
  226. }
  227. void Scene::setBrightness(byte brightness) {
  228. DEBUG_MSG_P(PSTR("[GARLAND] Scene::setBrightness = %d\n"), brightness);
  229. this->brightness = brightness;
  230. }
  231. byte Scene::getBrightness() {
  232. DEBUG_MSG_P(PSTR("[GARLAND] Scene::getBrightness = %d\n"), brightness);
  233. return brightness;
  234. }
  235. // Speed is reverse to cycleFactor and 10x
  236. void Scene::setSpeed(byte speed) {
  237. this->speed = speed;
  238. cycleFactor = (float)(GARLAND_SCENE_SPEED_MAX - speed) / GARLAND_SCENE_SPEED_FACTOR;
  239. DEBUG_MSG_P(PSTR("[GARLAND] Scene::setSpeed %d cycleFactor = %d\n"), speed, (int)(cycleFactor * 1000));
  240. }
  241. byte Scene::getSpeed() {
  242. DEBUG_MSG_P(PSTR("[GARLAND] Scene::getSpeed %d cycleFactor = %d\n"), speed, (int)(cycleFactor * 1000));
  243. return speed;
  244. }
  245. void Scene::setDefault() {
  246. speed = GARLAND_SCENE_DEFAULT_SPEED;
  247. cycleFactor = (float)(GARLAND_SCENE_SPEED_MAX - speed) / GARLAND_SCENE_SPEED_FACTOR;
  248. brightness = GARLAND_SCENE_DEFAULT_BRIGHTNESS;
  249. DEBUG_MSG_P(PSTR("[GARLAND] Scene::setDefault speed = %d cycleFactor = %d brightness = %d\n"), speed, (int)(cycleFactor * 1000), brightness);
  250. }
  251. void Scene::run() {
  252. unsigned long iteration_start_time = micros();
  253. if (state == Calculate || cyclesRemain < 1) {
  254. // Calculate number of cycles for this animation iteration
  255. float cycleSum = cycleFactor * (_anim ? _anim->getCycleFactor() : 1.0) + cycleTail;
  256. cyclesRemain = cycleSum;
  257. if (cyclesRemain < 1) {
  258. cyclesRemain = 1;
  259. cycleSum = 0;
  260. cycleTail = 0;
  261. } else {
  262. cycleTail = cycleSum - cyclesRemain;
  263. }
  264. if (_anim) {
  265. _anim->Run();
  266. }
  267. sum_calc_time += (micros() - iteration_start_time);
  268. iteration_start_time = micros();
  269. ++calc_num;
  270. state = Transition;
  271. }
  272. if (state == Transition && cyclesRemain < 3) {
  273. // transition coef, if within 0..1 - transition is active
  274. // changes from 1 to 0 during transition, so we interpolate from current
  275. // color to previous
  276. float transc = (float)((long)transms - (long)millis()) / GARLAND_SCENE_TRANSITION_MS;
  277. Color* leds_prev = (_leds == &_leds1[0]) ? &_leds2[0] : &_leds1[0];
  278. if (transc > 0) {
  279. for (int i = 0; i < _numLeds; i++) {
  280. // transition is in progress
  281. Color c = _leds[i].interpolate(leds_prev[i], transc);
  282. byte r = (int)(bri_lvl[c.r]) * brightness / 256;
  283. byte g = (int)(bri_lvl[c.g]) * brightness / 256;
  284. byte b = (int)(bri_lvl[c.b]) * brightness / 256;
  285. _pixels->setPixelColor(i, _pixels->Color(r, g, b));
  286. }
  287. } else {
  288. for (int i = 0; i < _numLeds; i++) {
  289. // regular operation
  290. byte r = (int)(bri_lvl[_leds[i].r]) * brightness / 256;
  291. byte g = (int)(bri_lvl[_leds[i].g]) * brightness / 256;
  292. byte b = (int)(bri_lvl[_leds[i].b]) * brightness / 256;
  293. _pixels->setPixelColor(i, _pixels->Color(r, g, b));
  294. }
  295. }
  296. sum_pixl_time += (micros() - iteration_start_time);
  297. iteration_start_time = micros();
  298. ++pixl_num;
  299. state = Show;
  300. }
  301. if (state == Show && cyclesRemain < 2) {
  302. _pixels->show();
  303. sum_show_time += (micros() - iteration_start_time);
  304. ++show_num;
  305. state = Calculate;
  306. ++numShows;
  307. }
  308. --cyclesRemain;
  309. }
  310. void Scene::setupImpl() {
  311. transms = millis() + GARLAND_SCENE_TRANSITION_MS;
  312. // switch operation buffers (for transition to operate)
  313. if (_leds == &_leds1[0]) {
  314. _leds = &_leds2[0];
  315. } else {
  316. _leds = &_leds1[0];
  317. }
  318. if (_anim) {
  319. _anim->Setup(_palette, _numLeds, _leds, &_ledstmp[0], &_seq[0]);
  320. }
  321. }
  322. void Scene::setup() {
  323. sum_calc_time = 0;
  324. sum_pixl_time = 0;
  325. sum_show_time = 0;
  326. calc_num = 0;
  327. pixl_num = 0;
  328. show_num = 0;
  329. numShows = 0;
  330. if (!setUpOnPalChange) {
  331. setupImpl();
  332. }
  333. }
  334. unsigned long Scene::getAvgCalcTime() { return sum_calc_time / calc_num; }
  335. unsigned long Scene::getAvgPixlTime() { return sum_pixl_time / pixl_num; }
  336. unsigned long Scene::getAvgShowTime() { return sum_show_time / show_num; }
  337. /*#######################################################################
  338. _ _ _
  339. /\ (_) | | (_)
  340. / \ _ __ _ _ __ ___ __ _ | |_ _ ___ _ __
  341. / /\ \ | '_ \ | | | '_ ` _ \ / _` | | __| | | / _ \ | '_ \
  342. / ____ \ | | | | | | | | | | | | | (_| | | |_ | | | (_) | | | | |
  343. /_/ \_\ |_| |_| |_| |_| |_| |_| \__,_| \__| |_| \___/ |_| |_|
  344. #######################################################################*/
  345. Anim::Anim(const char* name) : _name(name) {}
  346. void Anim::Setup(Palette* palette, uint16_t numLeds, Color* leds, Color* ledstmp, byte* seq) {
  347. this->palette = palette;
  348. this->numLeds = numLeds;
  349. this->leds = leds;
  350. this->ledstmp = ledstmp;
  351. this->seq = seq;
  352. SetupImpl();
  353. }
  354. void Anim::initSeq() {
  355. for (int i = 0; i < numLeds; ++i)
  356. seq[i] = i;
  357. }
  358. void Anim::shuffleSeq() {
  359. for (int i = 0; i < numLeds; ++i) {
  360. byte ind = (unsigned int)(rngb() * numLeds / 256);
  361. if (ind != i) {
  362. std::swap(seq[ind], seq[i]);
  363. }
  364. }
  365. }
  366. void Anim::glowSetUp() {
  367. braPhaseSpd = secureRandom(4, 13);
  368. if (braPhaseSpd > 8) {
  369. braPhaseSpd = braPhaseSpd - 17;
  370. }
  371. braFreq = secureRandom(20, 60);
  372. }
  373. void Anim::glowForEachLed(int i) {
  374. int8 bra = braPhase + i * braFreq;
  375. bra = BRA_OFFSET + (abs(bra) >> BRA_AMP_SHIFT);
  376. leds[i] = leds[i].brightness(bra);
  377. }
  378. void Anim::glowRun() { braPhase += braPhaseSpd; }
  379. bool operator== (const Color &c1, const Color &c2)
  380. {
  381. return (c1.r == c2.r && c1.g == c2.g && c1.b == c2.b);
  382. }
  383. unsigned int rng() {
  384. static unsigned int y = 0;
  385. y += micros(); // seeded with changing number
  386. y ^= y << 2;
  387. y ^= y >> 7;
  388. y ^= y << 7;
  389. return (y);
  390. }
  391. // Ranom numbers generator in byte range (256) much faster than secureRandom.
  392. // For usage in time-critical places.
  393. byte rngb() { return (byte)rng(); }
  394. #endif // GARLAND_SUPPORT