5#include "mupplet_core.h"
6#include "helper/mup_astro.h"
7#include "Adafruit_NeoPixel.h"
11uint32_t RGB32(uint8_t r, uint8_t g, uint8_t b) {
12 return ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b;
14void RGB32Parse(uint32_t rgb, uint8_t *r =
nullptr, uint8_t *g =
nullptr,
15 uint8_t *b =
nullptr) {
17 *r = (uint8_t)((rgb >> 16) & 0xff);
19 *g = (uint8_t)((rgb >> 8) & 0xff);
21 *b = (uint8_t)(rgb & 0xff);
26 static const int effectCount = 7;
27 enum EffectType : uint16_t { Default = 0,
35 static constexpr const char *effectName[effectCount] = {
"Static",
"Butterlamp",
"Fire",
"Waves",
"Forest",
"Evening",
"Concentration"};
37 SpecialEffects(uint16_t rows, uint16_t cols)
38 : rows(rows), cols(cols) {
43 bool setFrame(EffectType type, ustd::array<uint32_t> *pf) {
47 case EffectType::ButterLamp:
48 return butterLampFrame(pf, rows, cols);
49 case EffectType::Forest:
50 return forestFrame(pf, rows, cols);
51 case EffectType::Waves:
52 return wavesFrame(pf, rows, cols);
57 bool bUseModulator =
false;
59 bool useAutoTimer =
false;
60 uint8_t start_hour = 18, start_minute = 0, end_hour = 0, end_minute = 0;
61 bool bUnitBrightness =
true;
62 double unitBrightness = 1.0;
66 void configButterLampModulator(
bool _bUseModulator,
bool _bUseAutoTimer, uint8_t _start_hour, uint8_t _start_minute, uint8_t _end_hour, uint8_t _end_minute) {
67 bUseModulator = _bUseModulator;
68 useAutoTimer = _bUseAutoTimer;
69 start_hour = _start_hour;
70 start_minute = _start_minute;
72 end_minute = _end_minute;
75 void configButterLampState(
bool _bUnitBrightness =
true,
double _unitBrightness = 1.0) {
76 bUnitBrightness = _bUnitBrightness;
77 unitBrightness = _unitBrightness;
78 manualSet = time(
nullptr);
81 double butterLampModulator() {
84 time_t now = time(
nullptr);
88 long dt = now - manualSet;
90 m2 = (3600.0 - (double)dt) / 3600.0;
94 struct tm *pTm = localtime(&now);
102 m1 = (deltaAll - deltaCur) / (
double)deltaAll;
112 if (bUnitBrightness) {
113 if (m1 > 0.0 || m2 > 0.0) {
114 m1 = m1 * unitBrightness;
115 m2 = m2 * unitBrightness;
122 m1 = (m1 + m2) / 2.0;
127 int f1 = 0, f2 = 0, max_b = 20;
129 bool butterLampFrame(ustd::array<uint32_t> *pf, uint16_t rows, uint16_t cols) {
134 int ce, cr, cg, cb, mf;
135 int flic[] = {4, 7, 8, 9, 10, 12, 16, 20, 32, 30, 32, 20, 24, 16, 8, 6};
136 uint16_t x, y, index, cx, cy;
137 if (pf->length() != rows * cols) {
138 for (uint16_t i = 0; i < pf->length(); i++)
139 (*pf)[i] = RGB32(255, 0, 0);
143 for (y = 0; y < rows; y++) {
144 for (x = 0; x < cols; x++) {
145 index = y * cols + x;
148 if ((cx == 1 || cx == 2 || cols < 4) && (cy == 1 || cy == 2 || rows < 4)) {
158 f1 += rand() % 3 - 1;
163 mf = 32 - ((32 - mf) * wind) / 100;
169 f2 += rand() % 3 - 1;
174 mf = 32 - ((32 - mf) * wind) / 100;
177 cr = cr + rand() % 2;
178 cg = cg + rand() % 2;
179 cb = cb + rand() % 1;
188 cr = (cr * amp * 4 * mf) / (max_b * 50);
189 cg = (cg * amp * 4 * mf) / (max_b * 50);
190 cb = (cb * amp * 4 * mf) / (max_b * 50);
206 double mx = butterLampModulator();
207 double dx = fabs(oldMx - mx);
211 cr = ((double)cr * mx);
212 cg = ((double)cg * mx);
213 cb = ((double)cb * mx);
215 (*pf)[index] = RGB32(cr, cg, cb);
221 uint8_t varyByte(uint8_t b, uint8_t var, uint8_t min = 0, uint8_t max = 255) {
222 int d = random(2 * var + 1) - var;
223 if ((
int)b + d < (
int)min)
return min;
224 if ((
int)b + d > (
int)max)
return max;
225 return (uint8_t)((int)b + d);
228 bool wavesFrame(ustd::array<uint32_t> *pf, uint16_t rows, uint16_t cols) {
233 for (
int i = 0; i < 20; i++) {
236 RGB32Parse(color, &r, &g, &b);
237 b = varyByte(b, 20, 20, 170);
238 g = varyByte(g, 10, 0, 50);
239 r = varyByte(r, 10, 0, 20);
240 color = RGB32(r, g, b);
245 bool forestFrame(ustd::array<uint32_t> *pf, uint16_t rows, uint16_t cols) {
250 for (
int i = 0; i < 20; i++) {
253 RGB32Parse(color, &r, &g, &b);
254 b = varyByte(b, 10, 0, 70);
255 g = varyByte(g, 20, 20, 200);
256 r = varyByte(r, 10, 0, 30);
257 color = RGB32(r, g, b);
266 String NEOPIXEL_VERSION =
"0.1.0";
270 bool bStarted =
false;
272 uint16_t numRows, numCols, numPixels;
276 double unitBrightness;
277 Adafruit_NeoPixel *pPixels;
278 ustd::array<uint32_t> *phwBuf;
279 ustd::array<uint32_t> *phwFrameBuf;
281 unsigned long ticker = 0;
282 unsigned long lastTicker = 0;
283 double zeroBrightnessUpperBound = 0.02;
284 SpecialEffects::EffectType effectType;
285 SpecialEffects *pEffects =
nullptr;
286 bool isFirstLoop =
true;
287 bool scheduled =
false;
288 int startHour, endHour, startMin, endMin;
290 NeoPixel(String name, uint8_t pin, uint16_t numRows = 1, uint16_t numCols = 1,
291 uint16_t options = NEO_RGB + NEO_KHZ800)
292 : name(name), pin(pin), numRows(numRows), numCols(numCols), options(options) {
293 if (numRows < 1) numRows = 1;
294 if (numCols < 1) numCols = 1;
295 numPixels = numRows * numCols;
302 void begin(Scheduler *_pSched) {
305 pPixels =
new Adafruit_NeoPixel(numPixels, pin, options);
306 phwBuf =
new ustd::array<uint32_t>(numPixels, numPixels);
307 phwFrameBuf =
new ustd::array<uint32_t>(numPixels, numPixels);
308 pEffects =
new SpecialEffects(numRows, numCols);
309 for (uint16_t i = 0; i < numPixels; i++) {
310 pixel(i, 0, 0, 0,
false);
313 auto ft = [=]() { this->loop(); };
314 tID = pSched->add(ft, name, 50000);
315 auto fnall = [=](String topic, String msg, String originator) {
316 this->subsMsg(topic, msg, originator);
318 pSched->subscribe(tID, name +
"/light/#", fnall);
319 pSched->subscribe(tID,
"mqtt/state", fnall);
320 setEffect(SpecialEffects::EffectType::Default,
true);
326 void pixel(uint16_t i, uint8_t r, uint8_t g, uint8_t b,
bool update =
true) {
329 (*phwFrameBuf)[i] = RGB32(r, g, b);
335 bool setFrame(ustd::array<uint32_t> *pFr) {
336 if (!pFr || pFr->length() != (*phwFrameBuf).length())
return false;
337 for (uint16_t i = 0; i < numPixels; i++) {
338 (*phwBuf)[i] = (*phwFrameBuf)[i];
343 void setEffect(SpecialEffects::EffectType _type,
bool force =
false) {
344 if (_type != effectType || force) {
351 String getEffectList() {
356 for (
int i = 0; i < SpecialEffects::effectCount; i++) {
357 if (!first) eff +=
", ";
359 eff += SpecialEffects::effectName[i];
364 bool setSchedule(String startTime, String endTime) {
366 if (pEffects ==
nullptr)
return false;
367 if (!ustd::Astro::parseHourMinuteString(startTime, &sh, &sm))
369 if (!ustd::Astro::parseHourMinuteString(endTime, &eh, &em))
375 if (effectType == SpecialEffects::EffectType::ButterLamp) {
376 pEffects->configButterLampModulator(
true,
true, sh, sm, eh, em);
380 void pixelsUpdate(
bool notify =
true) {
384 uint32_t dr = 0, dg = 0, db = 0;
385 uint8_t r, g, b, rs, gs, bs;
386 for (uint16_t i = 0; i < numPixels; i++) {
387 (*phwBuf)[i] = (*phwFrameBuf)[i];
389 RGB32Parse((*phwBuf)[i], &r, &g, &b);
390 br += ((double)r + (
double)g + (double)b) / 3.0;
394 if (unitBrightness != 1.0) {
395 rs = (uint8_t)(r * unitBrightness);
396 gs = (uint8_t)(g * unitBrightness);
397 bs = (uint8_t)(b * unitBrightness);
403 pPixels->setPixelColor(i, pPixels->Color(rs, gs, bs));
405 gbr = (br / (double)numPixels) / 255.0;
410 if (st && unitBrightness > zeroBrightnessUpperBound)
420 void brightness(
double _unitBrightness,
bool update =
true,
bool resetEffect =
true) {
421 if (_unitBrightness < 0.0) _unitBrightness = 0.0;
422 if (_unitBrightness > 1.0) _unitBrightness = 1.0;
424 if (_unitBrightness < zeroBrightnessUpperBound) _unitBrightness = 0.0;
425 unitBrightness = _unitBrightness;
427 setEffect(SpecialEffects::EffectType::Default);
428 if (update) pixelsUpdate();
431 void color(uint8_t r, uint8_t g, uint8_t b,
bool update =
true,
bool resetEffect =
true) {
432 for (uint16_t i = 0; i < numPixels; i++) {
433 pixel(i, r, g, b,
false);
436 setEffect(SpecialEffects::EffectType::Default);
442 void publishBrightness() {
444 sprintf(buf,
"%5.3f", unitBrightness);
445 pSched->publish(name +
"/light/unitbrightness", buf);
448 void publishColor(int16_t index = -1) {
451 sprintf(buf,
"%d,%d,%d", gr, gg, gb);
452 pSched->publish(name +
"/light/color", buf);
455 RGB32Parse((*phwBuf)[index], &r, &g, &b);
456 sprintf(buf,
"%d,%d,%d", r, g, b);
457 pSched->publish(name +
"/light/" + String(index) +
"/color", buf);
461 void publishEffect() {
462 String nameE = SpecialEffects::effectName[(int)effectType];
463 pSched->publish(name +
"/light/effect", nameE);
466 void publishState() {
468 pSched->publish(name +
"/light/state",
"on");
471 pSched->publish(name +
"/light/state",
"off");
481 switch (effectType) {
482 case SpecialEffects::EffectType::Default:
484 color(128, 128, 128,
false,
false);
485 brightness(0.2,
true,
false);
489 case SpecialEffects::EffectType::ButterLamp:
490 if (ticker % 3 != 0)
return;
492 brightness(1.0,
false,
false);
493 pEffects->setFrame(effectType, phwFrameBuf);
497 pEffects->setFrame(effectType, phwFrameBuf);
501 case SpecialEffects::EffectType::Fire:
503 brightness(1.0,
false,
false);
504 pEffects->setFrame(SpecialEffects::EffectType::ButterLamp, phwFrameBuf);
508 pEffects->setFrame(SpecialEffects::EffectType::ButterLamp, phwFrameBuf);
512 case SpecialEffects::EffectType::Waves:
513 if (ticker % 5 != 0)
return;
515 color(20, 50, 192,
false,
false);
516 brightness(0.1,
false,
false);
517 pEffects->setFrame(SpecialEffects::EffectType::Waves, phwFrameBuf);
521 pEffects->setFrame(SpecialEffects::EffectType::Waves, phwFrameBuf);
525 case SpecialEffects::EffectType::Forest:
526 if (ticker % 10 != 0)
return;
528 color(0, 128, 0,
false,
false);
529 brightness(0.2,
false,
false);
530 pEffects->setFrame(SpecialEffects::EffectType::Forest, phwFrameBuf);
534 pEffects->setFrame(SpecialEffects::EffectType::Forest, phwFrameBuf);
538 case SpecialEffects::EffectType::Evening:
541 color(255, 128, 0,
false,
false);
542 brightness(0.1,
true,
false);
545 case SpecialEffects::EffectType::Concentration:
548 color(128, 128, 255,
false,
false);
549 brightness(0.8,
true,
false);
558 void subsMsg(String topic, String msg, String originator) {
560 String leader = name +
"/light/";
561 if (topic == name +
"/light/state/get") {
563 }
else if (topic == name +
"/light/unitbrightness/get") {
565 }
else if (topic == name +
"/light/color/get") {
567 }
else if (topic == name +
"/light/set" || topic == name +
"/light/state/set" || topic == name +
"/light/unitbrightness/set") {
571 if (msg ==
"on" || msg ==
"true")
576 if (ab && unitBrightness > zeroBrightnessUpperBound) br = unitBrightness;
579 }
else if (topic == name +
"/light/color/set") {
583 }
else if (topic == name +
"/light/effect/set") {
584 for (
int eff = 0; eff < SpecialEffects::effectCount; eff++) {
585 String thisName = SpecialEffects::effectName[eff];
586 if (thisName == msg) {
587 setEffect((SpecialEffects::EffectType)eff);
590 }
else if (topic.startsWith(leader)) {
591 String sub = topic.substring(leader.length());
592 int pi = sub.indexOf(
'/');
594 int index = (int)strtol(sub.substring(0, pi).c_str(), 0, 10);
595 if (index >= 0 && index < numPixels) {
596 String cmd = sub.substring(pi + 1);
598 if (msg.startsWith(
"#") || msg.startsWith(
"0x") || msg.indexOf(
',') != -1) {
600 pixel(index, r, g, b);
605 pixel(index, 0xff, 0xff, 0xff);
607 pixel(index, 0xff, 0xff, 0xff);
611 if (cmd ==
"color/set") {
613 pixel(index, r, g, b);
616 if (cmd ==
"color/get") {
621 }
else if (topic ==
"mqtt/state" && msg ==
"connected") {
static bool inHourMinuteInterval(uint8_t test_hour, uint8_t test_minute, uint8_t start_hour, uint8_t start_minute, uint8_t end_hour, uint8_t end_minute)
Definition: mup_astro.h:256
static int deltaHourMinuteTime(uint8_t h1, uint8_t m1, uint8_t h2, uint8_t m2)
Definition: mup_astro.h:236
The muwerk namespace.
Definition: home_assistant.h:10
int8_t parseBoolean(String arg)
Definition: mupplet_core.h:51
double parseUnitLevel(String arg)
Definition: mupplet_core.h:154
bool parseColor(String arg, uint8_t *pr, uint8_t *pg, uint8_t *pb, uint8_t *pw=nullptr, uint8_t *pww=nullptr)
Definition: mupplet_core.h:228