From 0c8d9d5614a2e4f0013604cfcac8552cf1646279 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 3 Apr 2024 18:38:06 +0200 Subject: [PATCH 001/125] Mode blending styles - alternative to #3669 --- wled00/FX.h | 28 ++++++++++ wled00/FX_2Dfcn.cpp | 33 +++++++++++- wled00/FX_fcn.cpp | 123 +++++++++++++++++++++++++++++++++++++----- wled00/data/index.htm | 20 ++++++- wled00/data/index.js | 4 ++ wled00/fcn_declare.h | 1 + wled00/json.cpp | 8 +++ wled00/util.cpp | 7 +++ wled00/wled.h | 3 +- 9 files changed, 212 insertions(+), 15 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 1089a0b8b..44c5e1549 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -316,6 +316,25 @@ #define MODE_COUNT 187 + +#define BLEND_STYLE_FADE 0 +#define BLEND_STYLE_FAIRY_DUST 1 +#define BLEND_STYLE_SWIPE_RIGHT 2 +#define BLEND_STYLE_SWIPE_LEFT 3 +#define BLEND_STYLE_PINCH_OUT 4 +#define BLEND_STYLE_INSIDE_OUT 5 +#define BLEND_STYLE_SWIPE_UP 6 +#define BLEND_STYLE_SWIPE_DOWN 7 +#define BLEND_STYLE_OPEN_H 8 +#define BLEND_STYLE_OPEN_V 9 +#define BLEND_STYLE_PUSH_TL 10 +#define BLEND_STYLE_PUSH_TR 11 +#define BLEND_STYLE_PUSH_BR 12 +#define BLEND_STYLE_PUSH_BL 13 + +#define BLEND_STYLE_COUNT 14 + + typedef enum mapping1D2D { M12_Pixels = 0, M12_pBar = 1, @@ -419,6 +438,9 @@ typedef struct Segment { static uint16_t _lastPaletteBlend; // blend palette according to set Transition Delay in millis()%0xFFFF #ifndef WLED_DISABLE_MODE_BLEND static bool _modeBlend; // mode/effect blending semaphore + // clipping + static uint16_t _clipStart, _clipStop; + static uint8_t _clipStartY, _clipStopY; #endif // transition data, valid only if transitional==true, holds values during transition (72 bytes) @@ -578,6 +600,10 @@ typedef struct Segment { void setPixelColor(float i, uint32_t c, bool aa = true); inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) { setPixelColor(i, RGBW32(r,g,b,w), aa); } inline void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } + #ifndef WLED_DISABLE_MODE_BLEND + inline void setClippingRect(int startX, int stopX, int startY = 0, int stopY = 1) { _clipStart = startX; _clipStop = stopX; _clipStartY = startY; _clipStopY = stopY; }; + #endif + bool isPixelClipped(int i); uint32_t getPixelColor(int i); // 1D support functions (some implement 2D as well) void blur(uint8_t); @@ -606,6 +632,7 @@ typedef struct Segment { void setPixelColorXY(float x, float y, uint32_t c, bool aa = true); inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); } inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); } + bool isPixelXYClipped(int x, int y); uint32_t getPixelColorXY(uint16_t x, uint16_t y); // 2D support functions inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); } @@ -640,6 +667,7 @@ typedef struct Segment { inline void setPixelColorXY(float x, float y, uint32_t c, bool aa = true) { setPixelColor(x, c, aa); } inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColor(x, RGBW32(r,g,b,w), aa); } inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0), aa); } + inline bool isPixelXYClipped(int x, int y) { return isPixelClipped(x); } inline uint32_t getPixelColorXY(uint16_t x, uint16_t y) { return getPixelColor(x); } inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); } inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); } diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 7aecd2271..7e1419293 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -167,10 +167,41 @@ uint16_t IRAM_ATTR Segment::XY(uint16_t x, uint16_t y) return isActive() ? (x%width) + (y%height) * width : 0; } +// pixel is clipped if it falls outside clipping range (_modeBlend==true) or is inside clipping range (_modeBlend==false) +// if clipping start > stop the clipping range is inverted +// _modeBlend==true -> old effect during transition +// _modeBlend==false -> new effect during transition +bool IRAM_ATTR Segment::isPixelXYClipped(int x, int y) { +#ifndef WLED_DISABLE_MODE_BLEND + if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) { + const bool invertX = _clipStart > _clipStop; + const bool invertY = _clipStartY > _clipStopY; + const unsigned startX = invertX ? _clipStop : _clipStart; + const unsigned stopX = invertX ? _clipStart : _clipStop; + const unsigned startY = invertY ? _clipStopY : _clipStartY; + const unsigned stopY = invertY ? _clipStartY : _clipStopY; + if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { + const unsigned width = stopX - startX; // assumes full segment width (faster than virtualWidth()) + const unsigned len = width * (stopY - startY); // assumes full segment height (faster than virtualHeight()) + if (len < 2) return false; + const unsigned shuffled = hashInt(x + y * width) % len; + const unsigned pos = (shuffled * 0xFFFFU) / len; + return progress() <= pos; + } + bool xInside = (x >= startX && x < stopX); if (invertX) xInside = !xInside; + bool yInside = (y >= startY && y < stopY); if (invertY) yInside = !yInside; + const bool clip = (invertX && invertY) ? !_modeBlend : _modeBlend; + if (xInside && yInside) return clip; // covers window & corners (inverted) + return !clip; + } +#endif + return false; +} + void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) { if (!isActive()) return; // not active - if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit + if (x >= virtualWidth() || y >= virtualHeight() || x < 0 || y < 0 || isPixelXYClipped(x,y)) return; // if pixel would fall out of virtual segment just exit uint8_t _bri_t = currentBri(); if (_bri_t < 255) { diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index f97268f9b..989809d02 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -84,6 +84,10 @@ uint16_t Segment::_lastPaletteBlend = 0; //in millis (lowest 16 bits only) #ifndef WLED_DISABLE_MODE_BLEND bool Segment::_modeBlend = false; +uint16_t Segment::_clipStart = 0; +uint16_t Segment::_clipStop = 0; +uint8_t Segment::_clipStartY = 0; +uint8_t Segment::_clipStopY = 1; #endif // copy constructor @@ -413,12 +417,17 @@ void Segment::restoreSegenv(tmpsegd_t &tmpSeg) { uint8_t IRAM_ATTR Segment::currentBri(bool useCct) { uint32_t prog = progress(); + uint32_t curBri = useCct ? cct : (on ? opacity : 0); if (prog < 0xFFFFU) { - uint32_t curBri = (useCct ? cct : (on ? opacity : 0)) * prog; - curBri += (useCct ? _t->_cctT : _t->_briT) * (0xFFFFU - prog); + uint8_t tmpBri = useCct ? _t->_cctT : (_t->_segT._optionsT & 0x0004 ? _t->_briT : 0); +#ifndef WLED_DISABLE_MODE_BLEND + if (blendingStyle > BLEND_STYLE_FADE) return _modeBlend ? tmpBri : curBri; // not fade/blend transition, each effect uses its brightness +#endif + curBri *= prog; + curBri += tmpBri * (0xFFFFU - prog); return curBri / 0xFFFFU; } - return (useCct ? cct : (on ? opacity : 0)); + return curBri; } uint8_t IRAM_ATTR Segment::currentMode() { @@ -431,16 +440,23 @@ uint8_t IRAM_ATTR Segment::currentMode() { uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { if (slot >= NUM_COLORS) slot = 0; + uint32_t prog = progress(); + if (prog == 0xFFFFU) return colors[slot]; #ifndef WLED_DISABLE_MODE_BLEND - return isInTransition() ? color_blend(_t->_segT._colorT[slot], colors[slot], progress(), true) : colors[slot]; + if (blendingStyle > BLEND_STYLE_FADE) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color + return color_blend(_t->_segT._colorT[slot], colors[slot], prog, true); #else - return isInTransition() ? color_blend(_t->_colorT[slot], colors[slot], progress(), true) : colors[slot]; + return color_blend(_t->_colorT[slot], colors[slot], prog, true); #endif } CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, uint8_t pal) { loadPalette(targetPalette, pal); uint16_t prog = progress(); +#ifndef WLED_DISABLE_MODE_BLEND + if (prog < 0xFFFFU && blendingStyle > BLEND_STYLE_FADE && _modeBlend) targetPalette = _t->_palT; // not fade/blend transition, each effect uses its palette + else +#endif if (strip.paletteFade && prog < 0xFFFFU) { // blend palettes // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) @@ -456,9 +472,9 @@ CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, u void Segment::handleRandomPalette() { // is it time to generate a new palette? if ((millis()/1000U) - _lastPaletteChange > randomPaletteChangeTime) { - _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); - _lastPaletteChange = millis()/1000U; - _lastPaletteBlend = (uint16_t)(millis() & 0xFFFF)-512; // starts blending immediately + _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); + _lastPaletteChange = millis()/1000U; + _lastPaletteBlend = (uint16_t)(millis() & 0xFFFF)-512; // starts blending immediately } // if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls) @@ -662,6 +678,32 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { return vLength; } +// pixel is clipped if it falls outside clipping range (_modeBlend==true) or is inside clipping range (_modeBlend==false) +// if clipping start > stop the clipping range is inverted +// _modeBlend==true -> old effect during transition +// _modeBlend==false -> new effect during transition +bool IRAM_ATTR Segment::isPixelClipped(int i) { +#ifndef WLED_DISABLE_MODE_BLEND + if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) { + bool invert = _clipStart > _clipStop; + unsigned start = invert ? _clipStop : _clipStart; + unsigned stop = invert ? _clipStart : _clipStop; + if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { + unsigned len = stop - start; + if (len < 2) return false; + unsigned shuffled = hashInt(i) % len; + unsigned pos = (shuffled * 0xFFFFU) / len; + return progress() <= pos; + } + const bool iInside = (i >= start && i < stop); + if (!invert && iInside) return _modeBlend; + if ( invert && !iInside) return _modeBlend; + return !_modeBlend; + } +#endif + return false; +} + void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) { if (!isActive()) return; // not active @@ -732,6 +774,8 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) } #endif + if (isPixelClipped(i)) return; // handle clipping on 1D + uint16_t len = length(); uint8_t _bri_t = currentBri(); if (_bri_t < 255) { @@ -763,14 +807,16 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) indexMir += offset; // offset/phase if (indexMir >= stop) indexMir -= len; // wrap #ifndef WLED_DISABLE_MODE_BLEND - if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexMir), col, 0xFFFFU - progress(), true); + // _modeBlend==true -> old effect + if (_modeBlend && blendingStyle == BLEND_STYLE_FADE) tmpCol = color_blend(strip.getPixelColor(indexMir), col, 0xFFFFU - progress(), true); #endif strip.setPixelColor(indexMir, tmpCol); } indexSet += offset; // offset/phase if (indexSet >= stop) indexSet -= len; // wrap #ifndef WLED_DISABLE_MODE_BLEND - if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexSet), col, 0xFFFFU - progress(), true); + // _modeBlend==true -> old effect + if (_modeBlend && blendingStyle == BLEND_STYLE_FADE) tmpCol = color_blend(strip.getPixelColor(indexSet), col, 0xFFFFU - progress(), true); #endif strip.setPixelColor(indexSet, tmpCol); } @@ -1062,7 +1108,7 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_ // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" CRGBPalette16 curPal; - curPal = currentPalette(curPal, palette); + currentPalette(curPal, palette); CRGB fastled_col = ColorFromPalette(curPal, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global return RGBW32(fastled_col.r, fastled_col.g, fastled_col.b, W(color)); @@ -1185,8 +1231,59 @@ void WS2812FX::service() { // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer // would need to be allocated for each effect and then blended together for each pixel. [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition - delay = (*_mode[seg.mode])(); // run new/current mode #ifndef WLED_DISABLE_MODE_BLEND + seg.setClippingRect(0, 0); // disable clipping + if (modeBlending && seg.mode != tmpMode) { + // set clipping rectangle + // new mode is run inside clipping area and old mode outside clipping area + unsigned p = seg.progress(); + unsigned w = seg.is2D() ? seg.virtualWidth() : _virtualSegmentLength; + unsigned h = seg.virtualHeight(); + unsigned dw = p * w / 0xFFFFU + 1; + unsigned dh = p * h / 0xFFFFU + 1; + switch (blendingStyle) { + case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) + seg.setClippingRect(0, w, 0, h); + break; + case BLEND_STYLE_SWIPE_RIGHT: // left-to-right + seg.setClippingRect(0, dw, 0, h); + break; + case BLEND_STYLE_SWIPE_LEFT: // right-to-left + seg.setClippingRect(w - dw, w, 0, h); + break; + case BLEND_STYLE_PINCH_OUT: // corners + seg.setClippingRect((w + dw)/2, (w - dw)/2, (h + dh)/2, (h - dh)/2); // inverted!! + break; + case BLEND_STYLE_INSIDE_OUT: // outward + seg.setClippingRect((w - dw)/2, (w + dw)/2, (h - dh)/2, (h + dh)/2); + break; + case BLEND_STYLE_SWIPE_DOWN: // top-to-bottom (2D) + seg.setClippingRect(0, w, 0, dh); + break; + case BLEND_STYLE_SWIPE_UP: // bottom-to-top (2D) + seg.setClippingRect(0, w, h - dh, h); + break; + case BLEND_STYLE_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D + seg.setClippingRect((w - dw)/2, (w + dw)/2, 0, h); + break; + case BLEND_STYLE_OPEN_V: // vertical-outward (2D) + seg.setClippingRect(0, w, (h - dh)/2, (h + dh)/2); + break; + case BLEND_STYLE_PUSH_TL: // TL-to-BR (2D) + seg.setClippingRect(0, dw, 0, dh); + break; + case BLEND_STYLE_PUSH_TR: // TR-to-BL (2D) + seg.setClippingRect(w - dw, w, 0, dh); + break; + case BLEND_STYLE_PUSH_BR: // BR-to-TL (2D) + seg.setClippingRect(w - dw, w, h - dh, h); + break; + case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D) + seg.setClippingRect(0, dw, h - dh, h); + break; + } + } + delay = (*_mode[seg.mode])(); // run new/current mode if (modeBlending && seg.mode != tmpMode) { Segment::tmpsegd_t _tmpSegData; Segment::modeBlend(true); // set semaphore @@ -1197,6 +1294,8 @@ void WS2812FX::service() { delay = MIN(delay,d2); // use shortest delay Segment::modeBlend(false); // unset semaphore } +#else + delay = (*_mode[seg.mode])(); // run effect mode #endif seg.call++; if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition diff --git a/wled00/data/index.htm b/wled00/data/index.htm index 4a532abb7..dc1431767 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -265,7 +265,25 @@
-

Transition:  s

+

Transition:  s

+

Blend: + +

diff --git a/wled00/data/index.js b/wled00/data/index.js index 4ad2044ad..1a50b6a3b 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -1426,6 +1426,9 @@ function readState(s,command=false) tr = s.transition; gId('tt').value = tr/10; + gId('bs').value = s.bs || 0; + if (tr===0) gId('bsp').classList.add('hide') + else gId('bsp').classList.remove('hide') populateSegments(s); var selc=0; @@ -1682,6 +1685,7 @@ function requestJson(command=null) var tn = parseInt(t.value*10); if (tn != tr) command.transition = tn; } + //command.bs = parseInt(gId('bs').value); req = JSON.stringify(command); if (req.length > 1340) useWs = false; // do not send very long requests over websocket if (req.length > 500 && lastinfo && lastinfo.arch == "esp8266") useWs = false; // esp8266 can only handle 500 bytes diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index f1b013e99..9a843dbf4 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -383,6 +383,7 @@ uint16_t crc16(const unsigned char* data_p, size_t length); um_data_t* simulateSound(uint8_t simulationId); void enumerateLedmaps(); uint8_t get_random_wheel_index(uint8_t pos); +uint32_t hashInt(uint32_t s); // RAII guard class for the JSON Buffer lock // Modeled after std::lock_guard diff --git a/wled00/json.cpp b/wled00/json.cpp index fd1527a21..bde6c36c7 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -355,6 +355,11 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) } } +#ifndef WLED_DISABLE_MODE_BLEND + blendingStyle = root[F("bs")] | blendingStyle; + blendingStyle = constrain(blendingStyle, 0, BLEND_STYLE_COUNT-1); +#endif + // temporary transition (applies only once) tr = root[F("tt")] | -1; if (tr >= 0) { @@ -581,6 +586,9 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme root["on"] = (bri > 0); root["bri"] = briLast; root[F("transition")] = transitionDelay/100; //in 100ms +#ifndef WLED_DISABLE_MODE_BLEND + root[F("bs")] = blendingStyle; +#endif } if (!forPreset) { diff --git a/wled00/util.cpp b/wled00/util.cpp index ad7e4b670..eabd3e383 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -599,3 +599,10 @@ uint8_t get_random_wheel_index(uint8_t pos) { } return r; } + +uint32_t hashInt(uint32_t s) { + // borrowed from https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key + s = ((s >> 16) ^ s) * 0x45d9f3b; + s = ((s >> 16) ^ s) * 0x45d9f3b; + return (s >> 16) ^ s; +} diff --git a/wled00/wled.h b/wled00/wled.h index 35b99260a..a58e1ceab 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2403280 +#define VERSION 2404030 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG @@ -547,6 +547,7 @@ WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random co // transitions WLED_GLOBAL bool fadeTransition _INIT(true); // enable crossfading brightness/color WLED_GLOBAL bool modeBlending _INIT(true); // enable effect blending +WLED_GLOBAL uint8_t blendingStyle _INIT(0); // effect blending/transitionig style WLED_GLOBAL bool transitionActive _INIT(false); WLED_GLOBAL uint16_t transitionDelay _INIT(750); // global transition duration WLED_GLOBAL uint16_t transitionDelayDefault _INIT(750); // default transition time (stored in cfg.json) From f5199d2b73bb3b516b7d98b4fee8b2af2b688c07 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 3 Apr 2024 20:55:59 +0200 Subject: [PATCH 002/125] Fix compile. --- wled00/FX_fcn.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 989809d02..bd5758e31 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -419,9 +419,11 @@ uint8_t IRAM_ATTR Segment::currentBri(bool useCct) { uint32_t prog = progress(); uint32_t curBri = useCct ? cct : (on ? opacity : 0); if (prog < 0xFFFFU) { - uint8_t tmpBri = useCct ? _t->_cctT : (_t->_segT._optionsT & 0x0004 ? _t->_briT : 0); #ifndef WLED_DISABLE_MODE_BLEND + uint8_t tmpBri = useCct ? _t->_cctT : (_t->_segT._optionsT & 0x0004 ? _t->_briT : 0); if (blendingStyle > BLEND_STYLE_FADE) return _modeBlend ? tmpBri : curBri; // not fade/blend transition, each effect uses its brightness +#else + uint8_t tmpBri = useCct ? _t->_cctT : _t->_briT; #endif curBri *= prog; curBri += tmpBri * (0xFFFFU - prog); From a3a8fa1cef1f54c2d1028a0c3e5a5fcc6ef8235e Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Mon, 8 Apr 2024 16:24:27 +0200 Subject: [PATCH 003/125] Remove conditional fade/blend - transitions always enabled (use delay 0 to disable) - optimisation in on/off fade - fix for palette/color blend when blending style is not fade - various tweaks and optimisations --- CHANGELOG.md | 3 + .../stairway-wipe-usermod-v2.h | 2 +- .../stairway_wipe_basic/wled06_usermod.ino | 111 ------------------ wled00/FX.cpp | 61 +++++----- wled00/FX.h | 7 +- wled00/FX_2Dfcn.cpp | 10 +- wled00/FX_fcn.cpp | 104 ++++++++-------- wled00/bus_manager.cpp | 6 +- wled00/bus_manager.h | 3 +- wled00/cfg.cpp | 14 +-- wled00/data/settings_leds.htm | 7 +- wled00/fcn_declare.h | 1 - wled00/json.cpp | 6 +- wled00/led.cpp | 54 +++------ wled00/playlist.cpp | 2 +- wled00/set.cpp | 9 +- wled00/udp.cpp | 6 +- wled00/wled.cpp | 6 +- wled00/wled.h | 2 - wled00/wled_eeprom.cpp | 2 +- wled00/xml.cpp | 5 +- 21 files changed, 135 insertions(+), 286 deletions(-) delete mode 100644 usermods/stairway_wipe_basic/wled06_usermod.ino diff --git a/CHANGELOG.md b/CHANGELOG.md index 46f6df2de..0d86c8b2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## WLED changelog +#### Build 2404050 +- Blending styles (with help from @tkadauke) + #### Build 2403280 - Individual color channel control for JSON API (fixes #3860) - "col":[int|string|object|array, int|string|object|array, int|string|object|array] diff --git a/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h b/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h index f712316b8..707479df1 100644 --- a/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h +++ b/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h @@ -96,7 +96,7 @@ void setup() { jsonTransitionOnce = true; strip.setTransition(0); //no transition effectCurrent = FX_MODE_COLOR_WIPE; - resetTimebase(); //make sure wipe starts from beginning + strip.resetTimebase(); //make sure wipe starts from beginning //set wipe direction Segment& seg = strip.getSegment(0); diff --git a/usermods/stairway_wipe_basic/wled06_usermod.ino b/usermods/stairway_wipe_basic/wled06_usermod.ino deleted file mode 100644 index c1264ebfb..000000000 --- a/usermods/stairway_wipe_basic/wled06_usermod.ino +++ /dev/null @@ -1,111 +0,0 @@ -/* - * This file allows you to add own functionality to WLED more easily - * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality - * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in wled_eeprom.h) - * bytes 2400+ are currently ununsed, but might be used for future wled features - */ - -//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t) - -byte wipeState = 0; //0: inactive 1: wiping 2: solid -unsigned long timeStaticStart = 0; -uint16_t previousUserVar0 = 0; - -//comment this out if you want the turn off effect to be just fading out instead of reverse wipe -#define STAIRCASE_WIPE_OFF - -//gets called once at boot. Do all initialization that doesn't depend on network here -void userSetup() -{ - //setup PIR sensor here, if needed -} - -//gets called every time WiFi is (re-)connected. Initialize own network interfaces here -void userConnected() -{ - -} - -//loop. You can use "if (WLED_CONNECTED)" to check for successful connection -void userLoop() -{ - //userVar0 (U0 in HTTP API): - //has to be set to 1 if movement is detected on the PIR that is the same side of the staircase as the ESP8266 - //has to be set to 2 if movement is detected on the PIR that is the opposite side - //can be set to 0 if no movement is detected. Otherwise LEDs will turn off after a configurable timeout (userVar1 seconds) - - if (userVar0 > 0) - { - if ((previousUserVar0 == 1 && userVar0 == 2) || (previousUserVar0 == 2 && userVar0 == 1)) wipeState = 3; //turn off if other PIR triggered - previousUserVar0 = userVar0; - - if (wipeState == 0) { - startWipe(); - wipeState = 1; - } else if (wipeState == 1) { //wiping - uint32_t cycleTime = 360 + (255 - effectSpeed)*75; //this is how long one wipe takes (minus 25 ms to make sure we switch in time) - if (millis() + strip.timebase > (cycleTime - 25)) { //wipe complete - effectCurrent = FX_MODE_STATIC; - timeStaticStart = millis(); - colorUpdated(CALL_MODE_NOTIFICATION); - wipeState = 2; - } - } else if (wipeState == 2) { //static - if (userVar1 > 0) //if U1 is not set, the light will stay on until second PIR or external command is triggered - { - if (millis() - timeStaticStart > userVar1*1000) wipeState = 3; - } - } else if (wipeState == 3) { //switch to wipe off - #ifdef STAIRCASE_WIPE_OFF - effectCurrent = FX_MODE_COLOR_WIPE; - strip.timebase = 360 + (255 - effectSpeed)*75 - millis(); //make sure wipe starts fully lit - colorUpdated(CALL_MODE_NOTIFICATION); - wipeState = 4; - #else - turnOff(); - #endif - } else { //wiping off - if (millis() + strip.timebase > (725 + (255 - effectSpeed)*150)) turnOff(); //wipe complete - } - } else { - wipeState = 0; //reset for next time - if (previousUserVar0) { - #ifdef STAIRCASE_WIPE_OFF - userVar0 = previousUserVar0; - wipeState = 3; - #else - turnOff(); - #endif - } - previousUserVar0 = 0; - } -} - -void startWipe() -{ - bri = briLast; //turn on - transitionDelayTemp = 0; //no transition - effectCurrent = FX_MODE_COLOR_WIPE; - resetTimebase(); //make sure wipe starts from beginning - - //set wipe direction - Segment& seg = strip.getSegment(0); - bool doReverse = (userVar0 == 2); - seg.setOption(1, doReverse); - - colorUpdated(CALL_MODE_NOTIFICATION); -} - -void turnOff() -{ - #ifdef STAIRCASE_WIPE_OFF - transitionDelayTemp = 0; //turn off immediately after wipe completed - #else - transitionDelayTemp = 4000; //fade out slowly - #endif - bri = 0; - stateUpdated(CALL_MODE_NOTIFICATION); - wipeState = 0; - userVar0 = 0; - previousUserVar0 = 0; -} diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 14341f5b9..e75e20cd2 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1211,8 +1211,9 @@ static const char _data_FX_MODE_COMET[] PROGMEM = "Lighthouse@!,Fade rate;!,!;!" */ uint16_t mode_fireworks() { if (SEGLEN == 1) return mode_static(); - const uint16_t width = SEGMENT.is2D() ? SEGMENT.virtualWidth() : SEGMENT.virtualLength(); - const uint16_t height = SEGMENT.virtualHeight(); + const unsigned width = SEGMENT.is2D() ? SEGMENT.virtualWidth() : SEGMENT.virtualLength(); + const unsigned height = SEGMENT.virtualHeight(); + const unsigned dimension = width * height; if (SEGENV.call == 0) { SEGENV.aux0 = UINT16_MAX; @@ -1220,19 +1221,19 @@ uint16_t mode_fireworks() { } SEGMENT.fade_out(128); - bool valid1 = (SEGENV.aux0 < width*height); - bool valid2 = (SEGENV.aux1 < width*height); - uint8_t x = SEGENV.aux0%width, y = SEGENV.aux0/width; // 2D coordinates stored in upper and lower byte + bool valid1 = (SEGENV.aux0 < dimension); + bool valid2 = (SEGENV.aux1 < dimension); + unsigned x = SEGENV.aux0%width, y = SEGENV.aux0/width; // 2D coordinates stored in upper and lower byte uint32_t sv1 = 0, sv2 = 0; if (valid1) sv1 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux0); // get spark color if (valid2) sv2 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux1); - if (!SEGENV.step) SEGMENT.blur(16); + if (!SEGENV.step) SEGMENT.blur(dimension > 100 ? 16 : 8); if (valid1) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, sv1); else SEGMENT.setPixelColor(SEGENV.aux0, sv1); } // restore spark color after blur if (valid2) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, sv2); else SEGMENT.setPixelColor(SEGENV.aux1, sv2); } // restore old spark color after blur - for (int i=0; i> 1)) == 0) { - uint16_t index = random16(width*height); + unsigned index = random16(dimension); x = index % width; y = index / width; uint32_t col = SEGMENT.color_from_palette(random8(), false, false, 0); @@ -2066,41 +2067,41 @@ uint16_t mode_fire_2012() { struct virtualStrip { static void runStrip(uint16_t stripNr, byte* heat, uint32_t it) { - const uint8_t ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels + const unsigned ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels // Step 1. Cool down every cell a little - for (int i = 0; i < SEGLEN; i++) { - uint8_t cool = (it != SEGENV.step) ? random8((((20 + SEGMENT.speed/3) * 16) / SEGLEN)+2) : random8(4); - uint8_t minTemp = (i 1; k--) { + for (unsigned k = SEGLEN -1; k > 1; k--) { heat[k] = (heat[k - 1] + (heat[k - 2]<<1) ) / 3; // heat[k-2] multiplied by 2 } // Step 3. Randomly ignite new 'sparks' of heat near the bottom if (random8() <= SEGMENT.intensity) { - uint8_t y = random8(ignition); - uint8_t boost = (17+SEGMENT.custom3) * (ignition - y/2) / ignition; // integer math! + unsigned y = random8(ignition); + unsigned boost = (17+SEGMENT.custom3) * (ignition - y/2) / ignition; // integer math! heat[y] = qadd8(heat[y], random8(96+2*boost,207+boost)); } } // Step 4. Map from heat cells to LED colors for (int j = 0; j < SEGLEN; j++) { - SEGMENT.setPixelColor(indexToVStrip(j, stripNr), ColorFromPalette(SEGPALETTE, MIN(heat[j],240), 255, NOBLEND)); + SEGMENT.setPixelColor(indexToVStrip(j, stripNr), ColorFromPalette(SEGPALETTE, MIN(heat[j],240), 255, LINEARBLEND_NOWRAP)); } } }; - for (int stripNr=0; stripNr 100 ? 32 : 0); if (it != SEGENV.step) SEGENV.step = it; @@ -4856,9 +4857,9 @@ static const char _data_FX_MODE_FLOWSTRIPE[] PROGMEM = "Flow Stripe@Hue speed,Ef uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulmatelights.com/gallery/1012 , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); - uint16_t x, y; + const unsigned cols = SEGMENT.virtualWidth(); + const unsigned rows = SEGMENT.virtualHeight(); + unsigned x, y; SEGMENT.fadeToBlackBy(16 + (SEGMENT.speed>>3)); // create fading trails unsigned long t = strip.now/128; // timebase @@ -4877,7 +4878,7 @@ uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulma // central white dot SEGMENT.setPixelColorXY(cols/2, rows/2, WHITE); // blur everything a bit - SEGMENT.blur(16); + SEGMENT.blur(sqrt16(cols*rows) > 100 ? 16 : 0); return FRAMETIME; } // mode_2DBlackHole() @@ -6436,8 +6437,8 @@ static const char _data_FX_MODE_2DSWIRL[] PROGMEM = "Swirl@!,Sensitivity,Blur;,B uint16_t mode_2DWaverly(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const unsigned cols = SEGMENT.virtualWidth(); + const unsigned rows = SEGMENT.virtualHeight(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { @@ -6449,21 +6450,21 @@ uint16_t mode_2DWaverly(void) { SEGMENT.fadeToBlackBy(SEGMENT.speed); long t = strip.now / 2; - for (int i = 0; i < cols; i++) { - uint16_t thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2; + for (unsigned i = 0; i < cols; i++) { + unsigned thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2; // use audio if available if (um_data) { thisVal /= 32; // reduce intensity of inoise8() thisVal *= volumeSmth; } - uint16_t thisMax = map(thisVal, 0, 512, 0, rows); + unsigned thisMax = map(thisVal, 0, 512, 0, rows); - for (int j = 0; j < thisMax; j++) { + for (unsigned j = 0; j < thisMax; j++) { SEGMENT.addPixelColorXY(i, j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND)); SEGMENT.addPixelColorXY((cols - 1) - i, (rows - 1) - j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND)); } } - SEGMENT.blur(16); + SEGMENT.blur(sqrt16(cols*rows) > 100 ? 16 : 0); return FRAMETIME; } // mode_2DWaverly() diff --git a/wled00/FX.h b/wled00/FX.h index 44c5e1549..fd0bb297b 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -601,7 +601,7 @@ typedef struct Segment { inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) { setPixelColor(i, RGBW32(r,g,b,w), aa); } inline void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } #ifndef WLED_DISABLE_MODE_BLEND - inline void setClippingRect(int startX, int stopX, int startY = 0, int stopY = 1) { _clipStart = startX; _clipStop = stopX; _clipStartY = startY; _clipStopY = stopY; }; + static inline void setClippingRect(int startX, int stopX, int startY = 0, int stopY = 1) { _clipStart = startX; _clipStop = stopX; _clipStartY = startY; _clipStopY = stopY; }; #endif bool isPixelClipped(int i); uint32_t getPixelColor(int i); @@ -708,9 +708,7 @@ class WS2812FX { // 96 bytes public: WS2812FX() : - paletteFade(0), paletteBlend(0), - cctBlending(0), now(millis()), timebase(0), isMatrix(false), @@ -792,6 +790,7 @@ class WS2812FX { // 96 bytes addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name), // add effect to the list; defined in FX.cpp setupEffectData(void); // add default effects to the list; defined in FX.cpp + inline void resetTimebase() { timebase = 0U - millis(); } inline void restartRuntime() { for (Segment &seg : _segments) seg.markForReset(); } inline void setTransitionMode(bool t) { for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); } inline void setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setColor(slot, RGBW32(r,g,b,w)); } @@ -806,7 +805,6 @@ class WS2812FX { // 96 bytes inline void resume(void) { _suspend = false; } // will resume strip.service() execution bool - paletteFade, checkSegmentAlignment(void), hasRGBWBus(void), hasCCTBus(void), @@ -822,7 +820,6 @@ class WS2812FX { // 96 bytes uint8_t paletteBlend, - cctBlending, getActiveSegmentsNum(void), getFirstSelectedSegId(void), getLastActiveSegmentId(void), diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 7e1419293..a37e5d11f 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -176,10 +176,10 @@ bool IRAM_ATTR Segment::isPixelXYClipped(int x, int y) { if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) { const bool invertX = _clipStart > _clipStop; const bool invertY = _clipStartY > _clipStopY; - const unsigned startX = invertX ? _clipStop : _clipStart; - const unsigned stopX = invertX ? _clipStart : _clipStop; - const unsigned startY = invertY ? _clipStopY : _clipStartY; - const unsigned stopY = invertY ? _clipStartY : _clipStopY; + const int startX = invertX ? _clipStop : _clipStart; + const int stopX = invertX ? _clipStart : _clipStop; + const int startY = invertY ? _clipStopY : _clipStartY; + const int stopY = invertY ? _clipStartY : _clipStopY; if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { const unsigned width = stopX - startX; // assumes full segment width (faster than virtualWidth()) const unsigned len = width * (stopY - startY); // assumes full segment height (faster than virtualHeight()) @@ -295,7 +295,7 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) // returns RGBW values of pixel uint32_t IRAM_ATTR Segment::getPixelColorXY(uint16_t x, uint16_t y) { if (!isActive()) return 0; // not active - if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit + if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0 || isPixelXYClipped(x,y)) return 0; // if pixel would fall out of virtual segment just exit if (reverse ) x = virtualWidth() - x - 1; if (reverse_y) y = virtualHeight() - y - 1; if (transpose) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 00196e38e..4ef110e07 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -293,21 +293,17 @@ void Segment::startTransition(uint16_t dur) { _t->_briT = on ? opacity : 0; _t->_cctT = cct; #ifndef WLED_DISABLE_MODE_BLEND - if (modeBlending) { - swapSegenv(_t->_segT); - _t->_modeT = mode; - _t->_segT._dataLenT = 0; - _t->_segT._dataT = nullptr; - if (_dataLen > 0 && data) { - _t->_segT._dataT = (byte *)malloc(_dataLen); - if (_t->_segT._dataT) { - //DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT); - memcpy(_t->_segT._dataT, data, _dataLen); - _t->_segT._dataLenT = _dataLen; - } + swapSegenv(_t->_segT); + _t->_modeT = mode; + _t->_segT._dataLenT = 0; + _t->_segT._dataT = nullptr; + if (_dataLen > 0 && data) { + _t->_segT._dataT = (byte *)malloc(_dataLen); + if (_t->_segT._dataT) { + //DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT); + memcpy(_t->_segT._dataT, data, _dataLen); + _t->_segT._dataLenT = _dataLen; } - } else { - for (size_t i=0; i_segT._colorT[i] = colors[i]; } #else for (size_t i=0; i_colorT[i] = colors[i]; @@ -435,7 +431,7 @@ uint8_t IRAM_ATTR Segment::currentBri(bool useCct) { uint8_t IRAM_ATTR Segment::currentMode() { #ifndef WLED_DISABLE_MODE_BLEND uint16_t prog = progress(); - if (modeBlending && prog < 0xFFFFU) return _t->_modeT; + if (prog < 0xFFFFU) return _t->_modeT; #endif return mode; } @@ -445,7 +441,7 @@ uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { uint32_t prog = progress(); if (prog == 0xFFFFU) return colors[slot]; #ifndef WLED_DISABLE_MODE_BLEND - if (blendingStyle > BLEND_STYLE_FADE) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color + if (blendingStyle > BLEND_STYLE_FADE && mode != _t->_modeT) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color return color_blend(_t->_segT._colorT[slot], colors[slot], prog, true); #else return color_blend(_t->_colorT[slot], colors[slot], prog, true); @@ -456,10 +452,10 @@ CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, u loadPalette(targetPalette, pal); uint16_t prog = progress(); #ifndef WLED_DISABLE_MODE_BLEND - if (prog < 0xFFFFU && blendingStyle > BLEND_STYLE_FADE && _modeBlend) targetPalette = _t->_palT; // not fade/blend transition, each effect uses its palette + if (prog < 0xFFFFU && blendingStyle > BLEND_STYLE_FADE && _modeBlend && mode != _t->_modeT) targetPalette = _t->_palT; // not fade/blend transition, each effect uses its palette else #endif - if (strip.paletteFade && prog < 0xFFFFU) { + if (prog < 0xFFFFU) { // blend palettes // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) // minimum blend time is 100ms maximum is 65535ms @@ -473,19 +469,16 @@ CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, u // relies on WS2812FX::service() to call it for each frame void Segment::handleRandomPalette() { // is it time to generate a new palette? - if ((uint16_t)(millis() / 1000U) - _lastPaletteChange > randomPaletteChangeTime){ - _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); - _lastPaletteChange = (uint16_t)(millis() / 1000U); - _lastPaletteBlend = (uint16_t)millis() - 512; // starts blending immediately + if ((uint16_t)(millis()/1000U) - _lastPaletteChange > randomPaletteChangeTime) { + _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); + _lastPaletteChange = (uint16_t)(millis()/1000U); + _lastPaletteBlend = (uint16_t)(millis())-512; // starts blending immediately } - // if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls) - if (strip.paletteFade) { - // assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less) - // in reality there need to be 255 blends to fully blend two entirely different palettes - if ((uint16_t)((uint16_t)millis() - _lastPaletteBlend) < strip.getTransition() >> 7) return; // not yet time to fade, delay the update - _lastPaletteBlend = (uint16_t)millis(); - } + // assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less) + // in reality there need to be 255 blends to fully blend two entirely different palettes + if ((uint16_t)millis() - _lastPaletteBlend < strip.getTransition() >> 7) return; // not yet time to fade, delay the update + _lastPaletteBlend = (uint16_t)millis(); nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48); } @@ -549,7 +542,7 @@ bool Segment::setColor(uint8_t slot, uint32_t c) { //returns true if changed if (slot == 0 && c == BLACK) return false; // on/off segment cannot have primary color black if (slot == 1 && c != BLACK) return false; // on/off segment cannot have secondary color non black } - if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change + startTransition(strip.getTransition()); // start transition prior to change colors[slot] = c; stateChanged = true; // send UDP/WS broadcast return true; @@ -562,21 +555,21 @@ void Segment::setCCT(uint16_t k) { k = (k - 1900) >> 5; } if (cct == k) return; - if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change + startTransition(strip.getTransition()); // start transition prior to change cct = k; stateChanged = true; // send UDP/WS broadcast } void Segment::setOpacity(uint8_t o) { if (opacity == o) return; - if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change + startTransition(strip.getTransition()); // start transition prior to change opacity = o; stateChanged = true; // send UDP/WS broadcast } void Segment::setOption(uint8_t n, bool val) { bool prevOn = on; - if (fadeTransition && n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change + if (n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change if (val) options |= 0x01 << n; else options &= ~(0x01 << n); if (!(n == SEG_OPTION_SELECTED || n == SEG_OPTION_RESET)) stateChanged = true; // send UDP/WS broadcast @@ -589,7 +582,7 @@ void Segment::setMode(uint8_t fx, bool loadDefaults) { // if we have a valid mode & is not reserved if (fx != mode) { #ifndef WLED_DISABLE_MODE_BLEND - if (modeBlending) startTransition(strip.getTransition()); // set effect transitions + startTransition(strip.getTransition()); // set effect transitions #endif mode = fx; // load default values from effect string @@ -620,7 +613,7 @@ void Segment::setPalette(uint8_t pal) { if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // custom palettes if (pal != palette) { - if (strip.paletteFade) startTransition(strip.getTransition()); + startTransition(strip.getTransition()); palette = pal; stateChanged = true; // send UDP/WS broadcast } @@ -688,8 +681,8 @@ bool IRAM_ATTR Segment::isPixelClipped(int i) { #ifndef WLED_DISABLE_MODE_BLEND if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) { bool invert = _clipStart > _clipStop; - unsigned start = invert ? _clipStop : _clipStart; - unsigned stop = invert ? _clipStart : _clipStop; + int start = invert ? _clipStop : _clipStart; + int stop = invert ? _clipStart : _clipStop; if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { unsigned len = stop - start; if (len < 2) return false; @@ -888,6 +881,8 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i) } #endif + if (isPixelClipped(i)) return 0; // handle clipping on 1D + if (reverse) i = virtualLength() - i - 1; i *= groupLength(); i += start; @@ -1234,8 +1229,8 @@ void WS2812FX::service() { // would need to be allocated for each effect and then blended together for each pixel. [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition #ifndef WLED_DISABLE_MODE_BLEND - seg.setClippingRect(0, 0); // disable clipping - if (modeBlending && seg.mode != tmpMode) { + Segment::setClippingRect(0, 0); // disable clipping (just in case) + if (seg.mode != tmpMode) { // could try seg.isInTransition() to allow color and palette to follow blending styles // set clipping rectangle // new mode is run inside clipping area and old mode outside clipping area unsigned p = seg.progress(); @@ -1245,48 +1240,48 @@ void WS2812FX::service() { unsigned dh = p * h / 0xFFFFU + 1; switch (blendingStyle) { case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) - seg.setClippingRect(0, w, 0, h); + Segment::setClippingRect(0, w, 0, h); break; case BLEND_STYLE_SWIPE_RIGHT: // left-to-right - seg.setClippingRect(0, dw, 0, h); + Segment::setClippingRect(0, dw, 0, h); break; case BLEND_STYLE_SWIPE_LEFT: // right-to-left - seg.setClippingRect(w - dw, w, 0, h); + Segment::setClippingRect(w - dw, w, 0, h); break; case BLEND_STYLE_PINCH_OUT: // corners - seg.setClippingRect((w + dw)/2, (w - dw)/2, (h + dh)/2, (h - dh)/2); // inverted!! + Segment::setClippingRect((w + dw)/2, (w - dw)/2, (h + dh)/2, (h - dh)/2); // inverted!! break; case BLEND_STYLE_INSIDE_OUT: // outward - seg.setClippingRect((w - dw)/2, (w + dw)/2, (h - dh)/2, (h + dh)/2); + Segment::setClippingRect((w - dw)/2, (w + dw)/2, (h - dh)/2, (h + dh)/2); break; case BLEND_STYLE_SWIPE_DOWN: // top-to-bottom (2D) - seg.setClippingRect(0, w, 0, dh); + Segment::setClippingRect(0, w, 0, dh); break; case BLEND_STYLE_SWIPE_UP: // bottom-to-top (2D) - seg.setClippingRect(0, w, h - dh, h); + Segment::setClippingRect(0, w, h - dh, h); break; case BLEND_STYLE_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D - seg.setClippingRect((w - dw)/2, (w + dw)/2, 0, h); + Segment::setClippingRect((w - dw)/2, (w + dw)/2, 0, h); break; case BLEND_STYLE_OPEN_V: // vertical-outward (2D) - seg.setClippingRect(0, w, (h - dh)/2, (h + dh)/2); + Segment::setClippingRect(0, w, (h - dh)/2, (h + dh)/2); break; case BLEND_STYLE_PUSH_TL: // TL-to-BR (2D) - seg.setClippingRect(0, dw, 0, dh); + Segment::setClippingRect(0, dw, 0, dh); break; case BLEND_STYLE_PUSH_TR: // TR-to-BL (2D) - seg.setClippingRect(w - dw, w, 0, dh); + Segment::setClippingRect(w - dw, w, 0, dh); break; case BLEND_STYLE_PUSH_BR: // BR-to-TL (2D) - seg.setClippingRect(w - dw, w, h - dh, h); + Segment::setClippingRect(w - dw, w, h - dh, h); break; case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D) - seg.setClippingRect(0, dw, h - dh, h); + Segment::setClippingRect(0, dw, h - dh, h); break; } } delay = (*_mode[seg.mode])(); // run new/current mode - if (modeBlending && seg.mode != tmpMode) { + if (seg.mode != tmpMode) { // could try seg.isInTransition() to allow color and palette to follow blending styles Segment::tmpsegd_t _tmpSegData; Segment::modeBlend(true); // set semaphore seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) @@ -1301,13 +1296,14 @@ void WS2812FX::service() { #endif seg.call++; if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition - BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments + BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments } seg.next_time = nowUp + delay; } _segment_index++; } + Segment::setClippingRect(0, 0); // disable clipping for overlays _virtualSegmentLength = 0; _isServicing = false; _triggered = false; diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 82e81a387..02a76f69d 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -391,14 +391,14 @@ BusPwm::BusPwm(BusConfig &bc) uint8_t numPins = NUM_PWM_PINS(bc.type); _frequency = bc.frequency ? bc.frequency : WLED_PWM_FREQ; - #ifdef ESP8266 +#ifdef ESP8266 // duty cycle resolution (_depth) can be extracted from this formula: 1MHz > _frequency * 2^_depth if (_frequency > 1760) _depth = 8; else if (_frequency > 880) _depth = 9; else _depth = 10; // WLED_PWM_FREQ <= 880Hz analogWriteRange((1<<_depth)-1); analogWriteFreq(_frequency); - #else +#else _ledcStart = pinManager.allocateLedc(numPins); if (_ledcStart == 255) { //no more free LEDC channels deallocatePins(); return; @@ -408,7 +408,7 @@ BusPwm::BusPwm(BusConfig &bc) else if (_frequency > 39062) _depth = 10; else if (_frequency > 19531) _depth = 11; else _depth = 12; // WLED_PWM_FREQ <= 19531Hz - #endif +#endif for (unsigned i = 0; i < numPins; i++) { uint8_t currentPin = bc.pins[i]; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index c128f8c09..cdd1e82ff 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -173,10 +173,11 @@ class Bus { type == TYPE_FW1906 || type == TYPE_WS2805 ) return true; return false; } - static int16_t getCCT() { return _cct; } + static inline int16_t getCCT() { return _cct; } static void setCCT(int16_t cct) { _cct = cct; } + static inline uint8_t getCCTBlend() { return _cctBlend; } static void setCCTBlend(uint8_t b) { if (b > 100) b = 100; _cctBlend = (b * 127) / 100; diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 530777ab5..01c407f95 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -111,8 +111,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(correctWB, hw_led["cct"]); CJSON(cctFromRgb, hw_led[F("cr")]); CJSON(cctICused, hw_led[F("ic")]); - CJSON(strip.cctBlending, hw_led[F("cb")]); - Bus::setCCTBlend(strip.cctBlending); + uint8_t cctBlending = hw_led[F("cb")] | Bus::getCCTBlend(); + Bus::setCCTBlend(cctBlending); strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS CJSON(useGlobalLedBuffer, hw_led[F("ld")]); @@ -408,12 +408,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { } JsonObject light_tr = light["tr"]; - CJSON(fadeTransition, light_tr["mode"]); - CJSON(modeBlending, light_tr["fx"]); int tdd = light_tr["dur"] | -1; if (tdd >= 0) transitionDelay = transitionDelayDefault = tdd * 100; - strip.setTransition(fadeTransition ? transitionDelayDefault : 0); - CJSON(strip.paletteFade, light_tr["pal"]); + strip.setTransition(transitionDelayDefault); CJSON(randomPaletteChangeTime, light_tr[F("rpc")]); CJSON(useHarmonicRandomPalette, light_tr[F("hrp")]); @@ -777,7 +774,7 @@ void serializeConfig() { hw_led["cct"] = correctWB; hw_led[F("cr")] = cctFromRgb; hw_led[F("ic")] = cctICused; - hw_led[F("cb")] = strip.cctBlending; + hw_led[F("cb")] = Bus::getCCTBlend(); hw_led["fps"] = strip.getTargetFps(); hw_led[F("rgbwm")] = Bus::getGlobalAWMode(); // global auto white mode override hw_led[F("ld")] = useGlobalLedBuffer; @@ -894,10 +891,7 @@ void serializeConfig() { light_gc["val"] = gammaCorrectVal; JsonObject light_tr = light.createNestedObject("tr"); - light_tr["mode"] = fadeTransition; - light_tr["fx"] = modeBlending; light_tr["dur"] = transitionDelayDefault / 100; - light_tr["pal"] = strip.paletteFade; light_tr[F("rpc")] = randomPaletteChangeTime; light_tr[F("hrp")] = useHarmonicRandomPalette; diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index dddedd471..2a5267825 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -834,12 +834,7 @@ Swap:

Brightness factor: %

Transitions

- Enable transitions:
- - Effect blending:
- Transition Time: ms
- Palette transitions:
-
+ Transition Time: ms
Random Cycle Palette Time: s
Use harmonic Random Cycle Palette:

Timed light

diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 010ad3a53..85893dfae 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -181,7 +181,6 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0); void setValuesFromSegment(uint8_t s); void setValuesFromMainSeg(); void setValuesFromFirstSelectedSeg(); -void resetTimebase(); void toggleOnOff(); void applyBri(); void applyFinalBri(); diff --git a/wled00/json.cpp b/wled00/json.cpp index 269d8a7f6..2e00ec09c 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -351,7 +351,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) tr = root[F("transition")] | -1; if (tr >= 0) { transitionDelay = tr * 100; - if (fadeTransition) strip.setTransition(transitionDelay); + strip.setTransition(transitionDelay); } } @@ -364,7 +364,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) tr = root[F("tt")] | -1; if (tr >= 0) { jsonTransitionOnce = true; - if (fadeTransition) strip.setTransition(tr * 100); + strip.setTransition(tr * 100); } tr = root[F("tb")] | -1; @@ -779,7 +779,7 @@ void serializeInfo(JsonObject root) root[F("freeheap")] = ESP.getFreeHeap(); #if defined(ARDUINO_ARCH_ESP32) - if (psramSafe && psramFound()) root[F("psram")] = ESP.getFreePsram(); + if (psramFound()) root[F("psram")] = ESP.getFreePsram(); #endif root[F("uptime")] = millis()/1000 + rolloverMillis*4294967; diff --git a/wled00/led.cpp b/wled00/led.cpp index 23c8d03c5..acfa3ac36 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -47,12 +47,6 @@ void applyValuesToSelectedSegs() } -void resetTimebase() -{ - strip.timebase = 0 - millis(); -} - - void toggleOnOff() { if (bri == 0) @@ -76,7 +70,7 @@ byte scaledBri(byte in) } -//applies global brightness +//applies global temporary brightness (briT) to strip void applyBri() { if (!realtimeMode || !arlsForceMaxBri) { @@ -90,6 +84,7 @@ void applyFinalBri() { briOld = bri; briT = bri; applyBri(); + strip.trigger(); // force one last update } @@ -122,7 +117,7 @@ void stateUpdated(byte callMode) { nightlightStartTime = millis(); } if (briT == 0) { - if (callMode != CALL_MODE_NOTIFICATION) resetTimebase(); //effect start from beginning + if (callMode != CALL_MODE_NOTIFICATION) strip.resetTimebase(); //effect start from beginning } if (bri > 0) briLast = bri; @@ -133,31 +128,24 @@ void stateUpdated(byte callMode) { // notify usermods of state change usermods.onStateChange(callMode); - if (fadeTransition) { - if (strip.getTransition() == 0) { - jsonTransitionOnce = false; - transitionActive = false; - applyFinalBri(); - strip.trigger(); - return; - } - - if (transitionActive) { - briOld = briT; - tperLast = 0; - } else - strip.setTransitionMode(true); // force all segments to transition mode - transitionActive = true; - transitionStartTime = millis(); - } else { + if (strip.getTransition() == 0) { + jsonTransitionOnce = false; + transitionActive = false; applyFinalBri(); - strip.trigger(); + return; } + + if (transitionActive) { + briOld = briT; + tperLast = 0; + } else + strip.setTransitionMode(true); // force all segments to transition mode + transitionActive = true; + transitionStartTime = millis(); } -void updateInterfaces(uint8_t callMode) -{ +void updateInterfaces(uint8_t callMode) { if (!interfaceUpdateCallMode || millis() - lastInterfaceUpdate < INTERFACE_UPDATE_COOLDOWN) return; sendDataWs(); @@ -178,8 +166,7 @@ void updateInterfaces(uint8_t callMode) } -void handleTransitions() -{ +void handleTransitions() { //handle still pending interface update updateInterfaces(interfaceUpdateCallMode); @@ -198,7 +185,6 @@ void handleTransitions() if (tper - tperLast < 0.004f) return; tperLast = tper; briT = briOld + ((bri - briOld) * tper); - applyBri(); } } @@ -211,8 +197,7 @@ void colorUpdated(byte callMode) { } -void handleNightlight() -{ +void handleNightlight() { unsigned long now = millis(); if (now < 100 && lastNlUpdate > 0) lastNlUpdate = 0; // take care of millis() rollover if (now - lastNlUpdate < 100) return; // allow only 10 NL updates per second @@ -292,7 +277,6 @@ void handleNightlight() } //utility for FastLED to use our custom timer -uint32_t get_millisecond_timer() -{ +uint32_t get_millisecond_timer() { return strip.now; } diff --git a/wled00/playlist.cpp b/wled00/playlist.cpp index 67c4f6049..225102da6 100644 --- a/wled00/playlist.cpp +++ b/wled00/playlist.cpp @@ -146,7 +146,7 @@ void handlePlaylist() { } jsonTransitionOnce = true; - strip.setTransition(fadeTransition ? playlistEntries[playlistIndex].tr * 100 : 0); + strip.setTransition(playlistEntries[playlistIndex].tr * 100); playlistEntryDur = playlistEntries[playlistIndex].dur; applyPresetFromPlaylist(playlistEntries[playlistIndex].preset); } diff --git a/wled00/set.cpp b/wled00/set.cpp index a2e884c81..c5b9f262c 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -128,8 +128,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) correctWB = request->hasArg(F("CCT")); cctFromRgb = request->hasArg(F("CR")); cctICused = request->hasArg(F("IC")); - strip.cctBlending = request->arg(F("CB")).toInt(); - Bus::setCCTBlend(strip.cctBlending); + uint8_t cctBlending = request->arg(F("CB")).toInt(); + Bus::setCCTBlend(cctBlending); Bus::setGlobalAWMode(request->arg(F("AW")).toInt()); strip.setTargetFps(request->arg(F("FR")).toInt()); useGlobalLedBuffer = request->hasArg(F("LD")); @@ -313,11 +313,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) gammaCorrectCol = false; } - fadeTransition = request->hasArg(F("TF")); - modeBlending = request->hasArg(F("EB")); t = request->arg(F("TD")).toInt(); if (t >= 0) transitionDelayDefault = t; - strip.paletteFade = request->hasArg(F("PF")); t = request->arg(F("TP")).toInt(); randomPaletteChangeTime = MIN(255,MAX(1,t)); useHarmonicRandomPalette = request->hasArg(F("TH")); @@ -1124,7 +1121,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) pos = req.indexOf(F("TT=")); if (pos > 0) transitionDelay = getNumVal(&req, pos); - if (fadeTransition) strip.setTransition(transitionDelay); + strip.setTransition(transitionDelay); //set time (unix timestamp) pos = req.indexOf(F("ST=")); diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 100ace166..d2f49144a 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -225,10 +225,8 @@ void parseNotifyPacket(uint8_t *udpIn) { // set transition time before making any segment changes if (version > 3) { - if (fadeTransition) { - jsonTransitionOnce = true; - strip.setTransition(((udpIn[17] << 0) & 0xFF) + ((udpIn[18] << 8) & 0xFF00)); - } + jsonTransitionOnce = true; + strip.setTransition(((udpIn[17] << 0) & 0xFF) + ((udpIn[18] << 8) & 0xFF00)); } //apply colors from notification to main segment, only if not syncing full segments diff --git a/wled00/wled.cpp b/wled00/wled.cpp index eb7860851..8f64a10e9 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -538,10 +538,10 @@ void WLED::beginStrip() } else { // fix for #3196 if (bootPreset > 0) { - bool oldTransition = fadeTransition; // workaround if transitions are enabled - fadeTransition = false; // ignore transitions temporarily + uint16_t oldTransition = strip.getTransition(); // workaround if transitions are enabled + strip.setTransition(0); // ignore transitions temporarily strip.setColor(0, BLACK); // set all segments black - fadeTransition = oldTransition; // restore transitions + strip.setTransition(oldTransition); // restore transitions col[0] = col[1] = col[2] = col[3] = 0; // needed for colorUpdated() } briLast = briS; bri = 0; diff --git a/wled00/wled.h b/wled00/wled.h index 5a9b8dcf5..8e21343f3 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -545,8 +545,6 @@ WLED_GLOBAL bool wasConnected _INIT(false); WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random color so the new one is not the same // transitions -WLED_GLOBAL bool fadeTransition _INIT(true); // enable crossfading brightness/color -WLED_GLOBAL bool modeBlending _INIT(true); // enable effect blending WLED_GLOBAL uint8_t blendingStyle _INIT(0); // effect blending/transitionig style WLED_GLOBAL bool transitionActive _INIT(false); WLED_GLOBAL uint16_t transitionDelay _INIT(750); // global transition duration diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp index 4f2c14d47..a43c23e8d 100755 --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -220,7 +220,7 @@ void loadSettingsFromEEPROM() if (lastEEPROMversion > 7) { - strip.paletteFade = EEPROM.read(374); + //strip.paletteFade = EEPROM.read(374); strip.paletteBlend = EEPROM.read(382); for (int i = 0; i < 8; ++i) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 3915d9b0e..e9c1fac46 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -357,7 +357,7 @@ void getSettingsJS(byte subPage, char* dest) sappend('c',SET_F("CCT"),correctWB); sappend('c',SET_F("IC"),cctICused); sappend('c',SET_F("CR"),cctFromRgb); - sappend('v',SET_F("CB"),strip.cctBlending); + sappend('v',SET_F("CB"),Bus::getCCTBlend()); sappend('v',SET_F("FR"),strip.getTargetFps()); sappend('v',SET_F("AW"),Bus::getGlobalAWMode()); sappend('c',SET_F("LD"),useGlobalLedBuffer); @@ -445,10 +445,7 @@ void getSettingsJS(byte subPage, char* dest) sappend('c',SET_F("GB"),gammaCorrectBri); sappend('c',SET_F("GC"),gammaCorrectCol); dtostrf(gammaCorrectVal,3,1,nS); sappends('s',SET_F("GV"),nS); - sappend('c',SET_F("TF"),fadeTransition); - sappend('c',SET_F("EB"),modeBlending); sappend('v',SET_F("TD"),transitionDelayDefault); - sappend('c',SET_F("PF"),strip.paletteFade); sappend('v',SET_F("TP"),randomPaletteChangeTime); sappend('c',SET_F("TH"),useHarmonicRandomPalette); sappend('v',SET_F("BF"),briMultiplier); From ef017fd343bc0329125c52681015fc6b85284be1 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 14 Apr 2024 15:34:59 +0200 Subject: [PATCH 004/125] Revert FX.cpp --- wled00/FX.cpp | 61 +++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index e75e20cd2..14341f5b9 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1211,9 +1211,8 @@ static const char _data_FX_MODE_COMET[] PROGMEM = "Lighthouse@!,Fade rate;!,!;!" */ uint16_t mode_fireworks() { if (SEGLEN == 1) return mode_static(); - const unsigned width = SEGMENT.is2D() ? SEGMENT.virtualWidth() : SEGMENT.virtualLength(); - const unsigned height = SEGMENT.virtualHeight(); - const unsigned dimension = width * height; + const uint16_t width = SEGMENT.is2D() ? SEGMENT.virtualWidth() : SEGMENT.virtualLength(); + const uint16_t height = SEGMENT.virtualHeight(); if (SEGENV.call == 0) { SEGENV.aux0 = UINT16_MAX; @@ -1221,19 +1220,19 @@ uint16_t mode_fireworks() { } SEGMENT.fade_out(128); - bool valid1 = (SEGENV.aux0 < dimension); - bool valid2 = (SEGENV.aux1 < dimension); - unsigned x = SEGENV.aux0%width, y = SEGENV.aux0/width; // 2D coordinates stored in upper and lower byte + bool valid1 = (SEGENV.aux0 < width*height); + bool valid2 = (SEGENV.aux1 < width*height); + uint8_t x = SEGENV.aux0%width, y = SEGENV.aux0/width; // 2D coordinates stored in upper and lower byte uint32_t sv1 = 0, sv2 = 0; if (valid1) sv1 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux0); // get spark color if (valid2) sv2 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux1); - if (!SEGENV.step) SEGMENT.blur(dimension > 100 ? 16 : 8); + if (!SEGENV.step) SEGMENT.blur(16); if (valid1) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, sv1); else SEGMENT.setPixelColor(SEGENV.aux0, sv1); } // restore spark color after blur if (valid2) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, sv2); else SEGMENT.setPixelColor(SEGENV.aux1, sv2); } // restore old spark color after blur - for (unsigned i=0; i> 1)) == 0) { - unsigned index = random16(dimension); + uint16_t index = random16(width*height); x = index % width; y = index / width; uint32_t col = SEGMENT.color_from_palette(random8(), false, false, 0); @@ -2067,41 +2066,41 @@ uint16_t mode_fire_2012() { struct virtualStrip { static void runStrip(uint16_t stripNr, byte* heat, uint32_t it) { - const unsigned ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels + const uint8_t ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels // Step 1. Cool down every cell a little - for (unsigned i = 0; i < SEGLEN; i++) { - unsigned cool = (it != SEGENV.step) ? random8((((20 + SEGMENT.speed/3) * 16) / SEGLEN)+2) : random8(4); - unsigned minTemp = (i 1; k--) { + for (int k = SEGLEN -1; k > 1; k--) { heat[k] = (heat[k - 1] + (heat[k - 2]<<1) ) / 3; // heat[k-2] multiplied by 2 } // Step 3. Randomly ignite new 'sparks' of heat near the bottom if (random8() <= SEGMENT.intensity) { - unsigned y = random8(ignition); - unsigned boost = (17+SEGMENT.custom3) * (ignition - y/2) / ignition; // integer math! + uint8_t y = random8(ignition); + uint8_t boost = (17+SEGMENT.custom3) * (ignition - y/2) / ignition; // integer math! heat[y] = qadd8(heat[y], random8(96+2*boost,207+boost)); } } // Step 4. Map from heat cells to LED colors for (int j = 0; j < SEGLEN; j++) { - SEGMENT.setPixelColor(indexToVStrip(j, stripNr), ColorFromPalette(SEGPALETTE, MIN(heat[j],240), 255, LINEARBLEND_NOWRAP)); + SEGMENT.setPixelColor(indexToVStrip(j, stripNr), ColorFromPalette(SEGPALETTE, MIN(heat[j],240), 255, NOBLEND)); } } }; - for (unsigned stripNr=0; stripNr 100 ? 32 : 0); + if (SEGMENT.is2D()) SEGMENT.blur(32); if (it != SEGENV.step) SEGENV.step = it; @@ -4857,9 +4856,9 @@ static const char _data_FX_MODE_FLOWSTRIPE[] PROGMEM = "Flow Stripe@Hue speed,Ef uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulmatelights.com/gallery/1012 , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const unsigned cols = SEGMENT.virtualWidth(); - const unsigned rows = SEGMENT.virtualHeight(); - unsigned x, y; + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + uint16_t x, y; SEGMENT.fadeToBlackBy(16 + (SEGMENT.speed>>3)); // create fading trails unsigned long t = strip.now/128; // timebase @@ -4878,7 +4877,7 @@ uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulma // central white dot SEGMENT.setPixelColorXY(cols/2, rows/2, WHITE); // blur everything a bit - SEGMENT.blur(sqrt16(cols*rows) > 100 ? 16 : 0); + SEGMENT.blur(16); return FRAMETIME; } // mode_2DBlackHole() @@ -6437,8 +6436,8 @@ static const char _data_FX_MODE_2DSWIRL[] PROGMEM = "Swirl@!,Sensitivity,Blur;,B uint16_t mode_2DWaverly(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const unsigned cols = SEGMENT.virtualWidth(); - const unsigned rows = SEGMENT.virtualHeight(); + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { @@ -6450,21 +6449,21 @@ uint16_t mode_2DWaverly(void) { SEGMENT.fadeToBlackBy(SEGMENT.speed); long t = strip.now / 2; - for (unsigned i = 0; i < cols; i++) { - unsigned thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2; + for (int i = 0; i < cols; i++) { + uint16_t thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2; // use audio if available if (um_data) { thisVal /= 32; // reduce intensity of inoise8() thisVal *= volumeSmth; } - unsigned thisMax = map(thisVal, 0, 512, 0, rows); + uint16_t thisMax = map(thisVal, 0, 512, 0, rows); - for (unsigned j = 0; j < thisMax; j++) { + for (int j = 0; j < thisMax; j++) { SEGMENT.addPixelColorXY(i, j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND)); SEGMENT.addPixelColorXY((cols - 1) - i, (rows - 1) - j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND)); } } - SEGMENT.blur(sqrt16(cols*rows) > 100 ? 16 : 0); + SEGMENT.blur(16); return FRAMETIME; } // mode_2DWaverly() From da484b07f5bda6d0a955e389658971e4b18f18f1 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 2 Jun 2024 21:30:44 +0200 Subject: [PATCH 005/125] Use transition style for palette and color change - as requested by @willmmiles & @tkadauke --- wled00/FX.h | 2 ++ wled00/FX_fcn.cpp | 63 +++++++++++++++++++++++++++++++---------------- 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index cb9cafb23..acca0b20d 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -457,6 +457,7 @@ typedef struct Segment { #else uint32_t _colorT[NUM_COLORS]; #endif + uint8_t _palTid; // previous palette uint8_t _briT; // temporary brightness uint8_t _cctT; // temporary CCT CRGBPalette16 _palT; // temporary palette @@ -594,6 +595,7 @@ typedef struct Segment { uint16_t progress(void); // transition progression between 0-65535 uint8_t currentBri(bool useCct = false); // current segment brightness/CCT (blended while in transition) uint8_t currentMode(void); // currently active effect/mode (while in transition) + uint8_t currentPalette(void); // currently active palette (while in transition) uint32_t currentColor(uint8_t slot); // currently active segment color (blended while in transition) CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); void setCurrentPalette(void); diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 29302bfb2..df06e56ad 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -291,6 +291,7 @@ void Segment::startTransition(uint16_t dur) { //DEBUG_PRINTF_P(PSTR("-- Started transition: %p (%p)\n"), this, _t); loadPalette(_t->_palT, palette); + _t->_palTid = palette; _t->_briT = on ? opacity : 0; _t->_cctT = cct; #ifndef WLED_DISABLE_MODE_BLEND @@ -442,27 +443,43 @@ uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { uint32_t prog = progress(); if (prog == 0xFFFFU) return colors[slot]; #ifndef WLED_DISABLE_MODE_BLEND - if (blendingStyle > BLEND_STYLE_FADE && mode != _t->_modeT) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color + if (blendingStyle > BLEND_STYLE_FADE) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color return color_blend(_t->_segT._colorT[slot], colors[slot], prog, true); #else return color_blend(_t->_colorT[slot], colors[slot], prog, true); #endif } +uint8_t IRAM_ATTR Segment::currentPalette() { + unsigned prog = progress(); + if (prog < 0xFFFFU) { +#ifndef WLED_DISABLE_MODE_BLEND + if (blendingStyle > BLEND_STYLE_FADE && _modeBlend) return _t->_palTid; +#else + return _t->_palTid; +#endif + } + return palette; +} + void Segment::setCurrentPalette() { loadPalette(_currentPalette, palette); unsigned prog = progress(); -#ifndef WLED_DISABLE_MODE_BLEND - if (prog < 0xFFFFU && blendingStyle > BLEND_STYLE_FADE && _modeBlend && mode != _t->_modeT) _currentPalette = _t->_palT; // not fade/blend transition, each effect uses its palette - else -#endif if (prog < 0xFFFFU) { - // blend palettes - // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) - // minimum blend time is 100ms maximum is 65535ms - unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends; - for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, _currentPalette, 48); - _currentPalette = _t->_palT; // copy transitioning/temporary palette +#ifndef WLED_DISABLE_MODE_BLEND + if (blendingStyle > BLEND_STYLE_FADE) { + //if (_modeBlend) loadPalette(_currentPalette, _t->_palTid); // not fade/blend transition, each effect uses its palette + if (_modeBlend) _currentPalette = _t->_palT; // not fade/blend transition, each effect uses its palette + } else +#endif + { + // blend palettes + // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) + // minimum blend time is 100ms maximum is 65535ms + unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends; + for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, _currentPalette, 48); + _currentPalette = _t->_palT; // copy transitioning/temporary palette + } } } @@ -719,7 +736,7 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { bool IRAM_ATTR Segment::isPixelClipped(int i) { #ifndef WLED_DISABLE_MODE_BLEND if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) { - bool invert = _clipStart > _clipStop; + bool invert = _clipStart > _clipStop; // ineverted start & stop int start = invert ? _clipStop : _clipStart; int stop = invert ? _clipStart : _clipStop; if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { @@ -727,12 +744,13 @@ bool IRAM_ATTR Segment::isPixelClipped(int i) { if (len < 2) return false; unsigned shuffled = hashInt(i) % len; unsigned pos = (shuffled * 0xFFFFU) / len; - return progress() <= pos; + return (progress() <= pos) ^ _modeBlend; } const bool iInside = (i >= start && i < stop); - if (!invert && iInside) return _modeBlend; - if ( invert && !iInside) return _modeBlend; - return !_modeBlend; + //if (!invert && iInside) return _modeBlend; + //if ( invert && !iInside) return _modeBlend; + //return !_modeBlend; + return !iInside ^ invert ^ _modeBlend; // thanks @willmmiles (https://github.com/Aircoookie/WLED/pull/3877#discussion_r1554633876) } #endif return false; @@ -1220,7 +1238,7 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_ uint32_t color = gamma32(currentColor(mcol)); // default palette or no RGB support on segment - if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) return (pbri == 255) ? color : color_fade(color, pbri, true); + if ((currentPalette() == 0 && mcol < NUM_COLORS) || !_isRGB) return (pbri == 255) ? color : color_fade(color, pbri, true); unsigned paletteIndex = i; if (mapping && virtualLength() > 1) paletteIndex = (i*255)/(virtualLength() -1); @@ -1313,6 +1331,7 @@ void WS2812FX::service() { now = nowUp + timebase; if (nowUp - _lastShow < MIN_SHOW_DELAY || _suspend) return; bool doShow = false; + int pal = -1; // optimise palette loading _isServicing = true; _segment_index = 0; @@ -1339,7 +1358,8 @@ void WS2812FX::service() { _colors_t[0] = gamma32(seg.currentColor(0)); _colors_t[1] = gamma32(seg.currentColor(1)); _colors_t[2] = gamma32(seg.currentColor(2)); - seg.setCurrentPalette(); // load actual palette + if (seg.currentPalette() != pal) seg.setCurrentPalette(); // load actual palette + pal = seg.currentPalette(); // when correctWB is true we need to correct/adjust RGB value according to desired CCT value, but it will also affect actual WW/CW ratio // when cctFromRgb is true we implicitly calculate WW and CW from RGB values if (cctFromRgb) BusManager::setSegmentCCT(-1); @@ -1350,10 +1370,10 @@ void WS2812FX::service() { // The blending will largely depend on the effect behaviour since actual output (LEDs) may be // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer // would need to be allocated for each effect and then blended together for each pixel. - [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition #ifndef WLED_DISABLE_MODE_BLEND + uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition Segment::setClippingRect(0, 0); // disable clipping (just in case) - if (seg.mode != tmpMode) { // could try seg.isInTransition() to allow color and palette to follow blending styles + if (seg.isInTransition()) { // set clipping rectangle // new mode is run inside clipping area and old mode outside clipping area unsigned p = seg.progress(); @@ -1404,11 +1424,12 @@ void WS2812FX::service() { } } delay = (*_mode[seg.mode])(); // run new/current mode - if (seg.mode != tmpMode) { // could try seg.isInTransition() to allow color and palette to follow blending styles + if (seg.isInTransition()) { Segment::tmpsegd_t _tmpSegData; Segment::modeBlend(true); // set semaphore seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) _virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed) + seg.setCurrentPalette(); // load actual palette unsigned d2 = (*_mode[tmpMode])(); // run old mode seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) delay = MIN(delay,d2); // use shortest delay From b9849da66e9d7c052e314a1d1eda0531a4939ffd Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Sat, 8 Jun 2024 13:16:56 -0400 Subject: [PATCH 006/125] Added Cube Mapping tool --- tools/AutoCubeMap.xlsx | Bin 0 -> 80009 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tools/AutoCubeMap.xlsx diff --git a/tools/AutoCubeMap.xlsx b/tools/AutoCubeMap.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b3f5cee2ad32ae887d6cc62d6667a05c61a8ac35 GIT binary patch literal 80009 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 literal 0 HcmV?d00001 From bee75a450827ad571bc59241386b425a153981d1 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 23 Jun 2024 14:14:26 +0200 Subject: [PATCH 007/125] Hide 2D blending styles on non-2D set-up --- wled00/data/index.htm | 16 ++++++++-------- wled00/data/index.js | 2 ++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/wled00/data/index.htm b/wled00/data/index.htm index e91bc10ba..d7ef3707a 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -275,14 +275,14 @@ - - - - - - - - + + + + + + + +

diff --git a/wled00/data/index.js b/wled00/data/index.js index 4cb707965..1f8a91616 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -677,8 +677,10 @@ function parseInfo(i) { isM = mw>0 && mh>0; if (!isM) { gId("filter2D").classList.add('hide'); + gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='none';}); } else { gId("filter2D").classList.remove('hide'); + gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='';}); } // if (i.noaudio) { // gId("filterVol").classList.add("hide"); From 0275bd1d45660ae1630792c835e65fd5a34c40ca Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 5 Jul 2024 21:23:59 +0200 Subject: [PATCH 008/125] On/Off blending respected --- wled00/FX_fcn.cpp | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 3ebf0c448..af9b3218c 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -433,9 +433,17 @@ uint8_t IRAM_ATTR Segment::currentBri(bool useCct) { uint8_t IRAM_ATTR Segment::currentMode() { #ifndef WLED_DISABLE_MODE_BLEND unsigned prog = progress(); - if (prog < 0xFFFFU) return _t->_modeT; -#endif + if (prog == 0xFFFFU) return mode; + if (blendingStyle > BLEND_STYLE_FADE) { + // workaround for on/off transition to respect blending style + uint8_t modeT = (bri != briT) && bri ? FX_MODE_STATIC : _t->_modeT; // On/Off transition active (bri!=briT) and final bri>0 : old mode is STATIC + uint8_t modeS = (bri != briT) && !bri ? FX_MODE_STATIC : mode; // On/Off transition active (bri!=briT) and final bri==0 : new mode is STATIC + return _modeBlend ? modeT : modeS; + } + return _modeBlend ? _t->_modeT : mode; +#else return mode; +#endif } uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { @@ -443,7 +451,12 @@ uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { uint32_t prog = progress(); if (prog == 0xFFFFU) return colors[slot]; #ifndef WLED_DISABLE_MODE_BLEND - if (blendingStyle > BLEND_STYLE_FADE) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color + if (blendingStyle > BLEND_STYLE_FADE) { + // workaround for on/off transition to respect blending style + uint32_t colT = (bri != briT) && bri ? BLACK : _t->_segT._colorT[slot]; // On/Off transition active (bri!=briT) and final bri>0 : old color is BLACK + uint32_t colS = (bri != briT) && !bri ? BLACK : colors[slot]; // On/Off transition active (bri!=briT) and final bri==0 : new color is BLACK + return _modeBlend ? colT : colS; + } return color_blend(_t->_segT._colorT[slot], colors[slot], prog, true); #else return color_blend(_t->_colorT[slot], colors[slot], prog, true); @@ -559,6 +572,7 @@ bool Segment::setColor(uint8_t slot, uint32_t c) { //returns true if changed if (slot == 0 && c == BLACK) return false; // on/off segment cannot have primary color black if (slot == 1 && c != BLACK) return false; // on/off segment cannot have secondary color non black } + //DEBUG_PRINTF_P(PSTR("- Starting color transition: %d [0x%X]\n"), slot, c); startTransition(strip.getTransition()); // start transition prior to change colors[slot] = c; stateChanged = true; // send UDP/WS broadcast @@ -572,6 +586,7 @@ void Segment::setCCT(uint16_t k) { k = (k - 1900) >> 5; } if (cct == k) return; + //DEBUG_PRINTF_P(PSTR("- Starting CCT transition: %d\n"), k); startTransition(strip.getTransition()); // start transition prior to change cct = k; stateChanged = true; // send UDP/WS broadcast @@ -579,6 +594,7 @@ void Segment::setCCT(uint16_t k) { void Segment::setOpacity(uint8_t o) { if (opacity == o) return; + //DEBUG_PRINTF_P(PSTR("- Starting opacity transition: %d\n"), o); startTransition(strip.getTransition()); // start transition prior to change opacity = o; stateChanged = true; // send UDP/WS broadcast @@ -599,6 +615,7 @@ void Segment::setMode(uint8_t fx, bool loadDefaults) { // if we have a valid mode & is not reserved if (fx != mode) { #ifndef WLED_DISABLE_MODE_BLEND + //DEBUG_PRINTF_P(PSTR("- Starting effect transition: %d\n"), fx); startTransition(strip.getTransition()); // set effect transitions #endif mode = fx; @@ -630,6 +647,7 @@ void Segment::setPalette(uint8_t pal) { if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // custom palettes if (pal != palette) { + //DEBUG_PRINTF_P(PSTR("- Starting palette transition: %d\n"), pal); startTransition(strip.getTransition()); palette = pal; stateChanged = true; // send UDP/WS broadcast @@ -1373,7 +1391,6 @@ void WS2812FX::service() { // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer // would need to be allocated for each effect and then blended together for each pixel. #ifndef WLED_DISABLE_MODE_BLEND - uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition Segment::setClippingRect(0, 0); // disable clipping (just in case) if (seg.isInTransition()) { // set clipping rectangle @@ -1425,14 +1442,20 @@ void WS2812FX::service() { break; } } - delay = (*_mode[seg.mode])(); // run new/current mode + delay = (*_mode[seg.currentMode()])(); // run new/current mode if (seg.isInTransition()) { Segment::tmpsegd_t _tmpSegData; Segment::modeBlend(true); // set semaphore seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) _virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed) - seg.setCurrentPalette(); // load actual palette - unsigned d2 = (*_mode[tmpMode])(); // run old mode + _colors_t[0] = gamma32(seg.currentColor(0)); + _colors_t[1] = gamma32(seg.currentColor(1)); + _colors_t[2] = gamma32(seg.currentColor(2)); + if (seg.currentPalette() != pal) { + seg.setCurrentPalette(); // load actual palette + pal = seg.currentPalette(); + } + unsigned d2 = (*_mode[seg.currentMode()])(); // run old mode seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) delay = MIN(delay,d2); // use shortest delay Segment::modeBlend(false); // unset semaphore From c03422ee3703dd604c909a58997442c0f2c532e4 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 30 Jul 2024 17:26:50 +0200 Subject: [PATCH 009/125] Push variants --- wled00/FX.h | 26 ++++++++++-------- wled00/FX_2Dfcn.cpp | 61 ++++++++++++++++++++++++++++++++++++++----- wled00/FX_fcn.cpp | 30 ++++++++++++++++++--- wled00/data/index.htm | 28 +++++++++++--------- 4 files changed, 112 insertions(+), 33 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 136f8e18b..3dc69e987 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -325,18 +325,22 @@ #define BLEND_STYLE_FAIRY_DUST 1 #define BLEND_STYLE_SWIPE_RIGHT 2 #define BLEND_STYLE_SWIPE_LEFT 3 -#define BLEND_STYLE_PINCH_OUT 4 -#define BLEND_STYLE_INSIDE_OUT 5 -#define BLEND_STYLE_SWIPE_UP 6 -#define BLEND_STYLE_SWIPE_DOWN 7 -#define BLEND_STYLE_OPEN_H 8 -#define BLEND_STYLE_OPEN_V 9 -#define BLEND_STYLE_PUSH_TL 10 -#define BLEND_STYLE_PUSH_TR 11 -#define BLEND_STYLE_PUSH_BR 12 -#define BLEND_STYLE_PUSH_BL 13 +#define BLEND_STYLE_PUSH_RIGHT 4 +#define BLEND_STYLE_PUSH_LEFT 5 +#define BLEND_STYLE_PINCH_OUT 6 +#define BLEND_STYLE_INSIDE_OUT 7 +#define BLEND_STYLE_SWIPE_UP 8 +#define BLEND_STYLE_SWIPE_DOWN 9 +#define BLEND_STYLE_OPEN_H 10 +#define BLEND_STYLE_OPEN_V 11 +#define BLEND_STYLE_PUSH_UP 12 +#define BLEND_STYLE_PUSH_DOWN 13 +#define BLEND_STYLE_PUSH_TL 14 +#define BLEND_STYLE_PUSH_TR 15 +#define BLEND_STYLE_PUSH_BR 16 +#define BLEND_STYLE_PUSH_BL 17 -#define BLEND_STYLE_COUNT 14 +#define BLEND_STYLE_COUNT 18 typedef enum mapping1D2D { diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index a62aa330e..846c78675 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -202,15 +202,39 @@ bool IRAM_ATTR Segment::isPixelXYClipped(int x, int y) { void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) { if (!isActive()) return; // not active - if (x >= virtualWidth() || y >= virtualHeight() || x < 0 || y < 0 || isPixelXYClipped(x,y)) return; // if pixel would fall out of virtual segment just exit + + int vW = virtualWidth(); + int vH = virtualHeight(); + +#ifndef WLED_DISABLE_MODE_BLEND + if (!_modeBlend && + (blendingStyle == BLEND_STYLE_PUSH_RIGHT || + blendingStyle == BLEND_STYLE_PUSH_LEFT || + blendingStyle == BLEND_STYLE_PUSH_UP || + blendingStyle == BLEND_STYLE_PUSH_DOWN || + blendingStyle == BLEND_STYLE_PUSH_TL || + blendingStyle == BLEND_STYLE_PUSH_TR || + blendingStyle == BLEND_STYLE_PUSH_BR || + blendingStyle == BLEND_STYLE_PUSH_BL)) { + unsigned prog = 0xFFFF - progress(); + unsigned dX = (blendingStyle == BLEND_STYLE_PUSH_UP || blendingStyle == BLEND_STYLE_PUSH_DOWN) ? 0 : prog * vW / 0xFFFF; + unsigned dY = (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_RIGHT) ? 0 : prog * vH / 0xFFFF; + if (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_BL) x -= dX; + else x += dX; + if (blendingStyle == BLEND_STYLE_PUSH_DOWN || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_TR) y -= dY; + else y += dY; + } +#endif + + if (x >= vW || y >= vH || x < 0 || y < 0 || isPixelXYClipped(x,y)) return; // if pixel would fall out of virtual segment just exit uint8_t _bri_t = currentBri(); if (_bri_t < 255) { col = color_fade(col, _bri_t); } - if (reverse ) x = virtualWidth() - x - 1; - if (reverse_y) y = virtualHeight() - y - 1; + if (reverse ) x = vW - x - 1; + if (reverse_y) y = vH - y - 1; if (transpose) { unsigned t = x; x = y; y = t; } // swap X & Y if segment transposed x *= groupLength(); // expand to physical pixels @@ -294,9 +318,34 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) // returns RGBW values of pixel uint32_t IRAM_ATTR Segment::getPixelColorXY(int x, int y) { if (!isActive()) return 0; // not active - if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0 || isPixelXYClipped(x,y)) return 0; // if pixel would fall out of virtual segment just exit - if (reverse ) x = virtualWidth() - x - 1; - if (reverse_y) y = virtualHeight() - y - 1; + + int vW = virtualWidth(); + int vH = virtualHeight(); + +#ifndef WLED_DISABLE_MODE_BLEND + if (!_modeBlend && + (blendingStyle == BLEND_STYLE_PUSH_RIGHT || + blendingStyle == BLEND_STYLE_PUSH_LEFT || + blendingStyle == BLEND_STYLE_PUSH_UP || + blendingStyle == BLEND_STYLE_PUSH_DOWN || + blendingStyle == BLEND_STYLE_PUSH_TL || + blendingStyle == BLEND_STYLE_PUSH_TR || + blendingStyle == BLEND_STYLE_PUSH_BR || + blendingStyle == BLEND_STYLE_PUSH_BL)) { + unsigned prog = 0xFFFF - progress(); + unsigned dX = (blendingStyle == BLEND_STYLE_PUSH_UP || blendingStyle == BLEND_STYLE_PUSH_DOWN) ? 0 : prog * vW / 0xFFFF; + unsigned dY = (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_RIGHT) ? 0 : prog * vH / 0xFFFF; + if (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_BL) x -= dX; + else x += dX; + if (blendingStyle == BLEND_STYLE_PUSH_DOWN || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_TR) y -= dY; + else y += dY; + } +#endif + + if (x >= vW || y >= vH || x<0 || y<0 || isPixelXYClipped(x,y)) return 0; // if pixel would fall out of virtual segment just exit + + if (reverse ) x = vW - x - 1; + if (reverse_y) y = vH - y - 1; if (transpose) { unsigned t = x; x = y; y = t; } // swap X & Y if segment transposed x *= groupLength(); // expand to physical pixels y *= groupLength(); // expand to physical pixels diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 4af961764..583496de0 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -782,7 +782,8 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) #endif i &= 0xFFFF; - if (i >= virtualLength() || i<0) return; // if pixel would fall out of segment just exit + int vL = virtualLength(); + if (i >= vL || i < 0) return; // if pixel would fall out of segment just exit #ifndef WLED_DISABLE_2D if (is2D()) { @@ -890,7 +891,16 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) } #endif - if (isPixelClipped(i)) return; // handle clipping on 1D +#ifndef WLED_DISABLE_MODE_BLEND + if (!_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) { + unsigned prog = 0xFFFF - progress(); + unsigned dI = prog * vL / 0xFFFF; + if (blendingStyle == BLEND_STYLE_PUSH_RIGHT) i -= dI; + else i += dI; + } +#endif + + if (i >= vL || i < 0 || isPixelClipped(i)) return; // handle clipping on 1D unsigned len = length(); uint8_t _bri_t = currentBri(); @@ -978,6 +988,9 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i) #endif i &= 0xFFFF; + int vL = virtualLength(); + if (i >= vL || i < 0) return 0; + #ifndef WLED_DISABLE_2D if (is2D()) { unsigned vH = virtualHeight(); // segment height in logical pixels @@ -1029,9 +1042,18 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i) } #endif - if (isPixelClipped(i)) return 0; // handle clipping on 1D +#ifndef WLED_DISABLE_MODE_BLEND + if (!_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) { + unsigned prog = 0xFFFF - progress(); + unsigned dI = prog * vL / 0xFFFF; + if (blendingStyle == BLEND_STYLE_PUSH_RIGHT) i -= dI; + else i += dI; + } +#endif - if (reverse) i = virtualLength() - i - 1; + if (i >= vL || i < 0 || isPixelClipped(i)) return 0; // handle clipping on 1D + + if (reverse) i = vL - i - 1; i *= groupLength(); i += start; /* offset/phase */ diff --git a/wled00/data/index.htm b/wled00/data/index.htm index d7ef3707a..df867d3a5 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -271,18 +271,22 @@

From 365c1987ed8f5f8070081c2619d1b784ff354a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Thu, 1 Aug 2024 10:24:40 +0200 Subject: [PATCH 010/125] Missing clipping fix - small speed optimisations --- wled00/FX.h | 6 +++--- wled00/FX_2Dfcn.cpp | 22 ++++++++++++---------- wled00/FX_fcn.cpp | 35 ++++++++++++++++++++--------------- 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 3dc69e987..3abc81166 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -351,7 +351,7 @@ typedef enum mapping1D2D { M12_sPinwheel = 4 } mapping1D2D_t; -// segment, 80 bytes +// segment, 68 bytes typedef struct Segment { public: uint16_t start; // start index / start X coordinate 2D (left) @@ -639,7 +639,7 @@ typedef struct Segment { uint16_t virtualHeight(void) const; // segment height in virtual pixels (accounts for groupping and spacing) uint16_t nrOfVStrips(void) const; // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) #ifndef WLED_DISABLE_2D - uint16_t XY(uint16_t x, uint16_t y); // support function to get relative index within segment + uint16_t XY(int x, int y); // support function to get relative index within segment void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColorXY(int(x), int(y), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } @@ -678,7 +678,7 @@ typedef struct Segment { inline void blur2d(fract8 blur_amount) { blur(blur_amount); } inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } #else - inline uint16_t XY(uint16_t x, uint16_t y) { return x; } + inline uint16_t XY(int x, int y) { return x; } inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); } inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColor(int(x), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColor(x, RGBW32(r,g,b,w)); } diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 846c78675..493cb8963 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -161,8 +161,7 @@ void WS2812FX::setUpMatrix() { #ifndef WLED_DISABLE_2D // XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element) -uint16_t IRAM_ATTR Segment::XY(uint16_t x, uint16_t y) -{ +uint16_t IRAM_ATTR Segment::XY(int x, int y) { unsigned width = virtualWidth(); // segment width in logical pixels (can be 0 if segment is inactive) unsigned height = virtualHeight(); // segment height in logical pixels (is always >= 1) return isActive() ? (x%width) + (y%height) * width : 0; @@ -207,7 +206,7 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) int vH = virtualHeight(); #ifndef WLED_DISABLE_MODE_BLEND - if (!_modeBlend && + if (isInTransition() && !_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_UP || @@ -239,13 +238,16 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) x *= groupLength(); // expand to physical pixels y *= groupLength(); // expand to physical pixels - if (x >= width() || y >= height()) return; // if pixel would fall out of segment just exit + + int W = width(); + int H = height(); + if (x >= W || y >= H) return; // if pixel would fall out of segment just exit uint32_t tmpCol = col; for (int j = 0; j < grouping; j++) { // groupping vertically for (int g = 0; g < grouping; g++) { // groupping horizontally unsigned xX = (x+g), yY = (y+j); - if (xX >= width() || yY >= height()) continue; // we have reached one dimension's end + if (xX >= W || yY >= H) continue; // we have reached one dimension's end #ifndef WLED_DISABLE_MODE_BLEND // if blending modes, blend with underlying pixel @@ -255,15 +257,15 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) strip.setPixelColorXY(start + xX, startY + yY, tmpCol); if (mirror) { //set the corresponding horizontally mirrored pixel - if (transpose) strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); - else strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol); + if (transpose) strip.setPixelColorXY(start + xX, startY + H - yY - 1, tmpCol); + else strip.setPixelColorXY(start + W - xX - 1, startY + yY, tmpCol); } if (mirror_y) { //set the corresponding vertically mirrored pixel - if (transpose) strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol); - else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); + if (transpose) strip.setPixelColorXY(start + W - xX - 1, startY + yY, tmpCol); + else strip.setPixelColorXY(start + xX, startY + H - yY - 1, tmpCol); } if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel - strip.setPixelColorXY(width() - xX - 1, height() - yY - 1, tmpCol); + strip.setPixelColorXY(W - xX - 1, H - yY - 1, tmpCol); } } } diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 583496de0..dbbd24b7d 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -754,7 +754,7 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { bool IRAM_ATTR Segment::isPixelClipped(int i) { #ifndef WLED_DISABLE_MODE_BLEND if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) { - bool invert = _clipStart > _clipStop; // ineverted start & stop + bool invert = _clipStart > _clipStop; // ineverted start & stop int start = invert ? _clipStop : _clipStart; int stop = invert ? _clipStart : _clipStop; if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { @@ -892,7 +892,8 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) #endif #ifndef WLED_DISABLE_MODE_BLEND - if (!_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) { + // if we blend using "push" style we need to "shift" new mode to left or right + if (isInTransition() && !_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) { unsigned prog = 0xFFFF - progress(); unsigned dI = prog * vL / 0xFFFF; if (blendingStyle == BLEND_STYLE_PUSH_RIGHT) i -= dI; @@ -1043,7 +1044,7 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i) #endif #ifndef WLED_DISABLE_MODE_BLEND - if (!_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) { + if (isInTransition() && !_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) { unsigned prog = 0xFFFF - progress(); unsigned dI = prog * vL / 0xFFFF; if (blendingStyle == BLEND_STYLE_PUSH_RIGHT) i -= dI; @@ -1425,43 +1426,47 @@ void WS2812FX::service() { unsigned dw = p * w / 0xFFFFU + 1; unsigned dh = p * h / 0xFFFFU + 1; switch (blendingStyle) { - case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) + case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) Segment::setClippingRect(0, w, 0, h); break; case BLEND_STYLE_SWIPE_RIGHT: // left-to-right + case BLEND_STYLE_PUSH_RIGHT: // left-to-right Segment::setClippingRect(0, dw, 0, h); break; - case BLEND_STYLE_SWIPE_LEFT: // right-to-left + case BLEND_STYLE_SWIPE_LEFT: // right-to-left + case BLEND_STYLE_PUSH_LEFT: // right-to-left Segment::setClippingRect(w - dw, w, 0, h); break; - case BLEND_STYLE_PINCH_OUT: // corners + case BLEND_STYLE_PINCH_OUT: // corners Segment::setClippingRect((w + dw)/2, (w - dw)/2, (h + dh)/2, (h - dh)/2); // inverted!! break; - case BLEND_STYLE_INSIDE_OUT: // outward + case BLEND_STYLE_INSIDE_OUT: // outward Segment::setClippingRect((w - dw)/2, (w + dw)/2, (h - dh)/2, (h + dh)/2); break; - case BLEND_STYLE_SWIPE_DOWN: // top-to-bottom (2D) + case BLEND_STYLE_SWIPE_DOWN: // top-to-bottom (2D) + case BLEND_STYLE_PUSH_DOWN: // top-to-bottom (2D) Segment::setClippingRect(0, w, 0, dh); break; - case BLEND_STYLE_SWIPE_UP: // bottom-to-top (2D) + case BLEND_STYLE_SWIPE_UP: // bottom-to-top (2D) + case BLEND_STYLE_PUSH_UP: // bottom-to-top (2D) Segment::setClippingRect(0, w, h - dh, h); break; - case BLEND_STYLE_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D + case BLEND_STYLE_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D Segment::setClippingRect((w - dw)/2, (w + dw)/2, 0, h); break; - case BLEND_STYLE_OPEN_V: // vertical-outward (2D) + case BLEND_STYLE_OPEN_V: // vertical-outward (2D) Segment::setClippingRect(0, w, (h - dh)/2, (h + dh)/2); break; - case BLEND_STYLE_PUSH_TL: // TL-to-BR (2D) + case BLEND_STYLE_PUSH_TL: // TL-to-BR (2D) Segment::setClippingRect(0, dw, 0, dh); break; - case BLEND_STYLE_PUSH_TR: // TR-to-BL (2D) + case BLEND_STYLE_PUSH_TR: // TR-to-BL (2D) Segment::setClippingRect(w - dw, w, 0, dh); break; - case BLEND_STYLE_PUSH_BR: // BR-to-TL (2D) + case BLEND_STYLE_PUSH_BR: // BR-to-TL (2D) Segment::setClippingRect(w - dw, w, h - dh, h); break; - case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D) + case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D) Segment::setClippingRect(0, dw, h - dh, h); break; } From 77723b615f5c482a63053c7a40705b073f072681 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 8 Aug 2024 21:10:27 +0200 Subject: [PATCH 011/125] Fix compiler warning --- wled00/FX_2Dfcn.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 36d2038b9..7aec73cad 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -246,7 +246,7 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) uint32_t tmpCol = col; for (int j = 0; j < grouping; j++) { // groupping vertically for (int g = 0; g < grouping; g++) { // groupping horizontally - unsigned xX = (x+g), yY = (y+j); + int xX = (x+g), yY = (y+j); if (xX >= W || yY >= H) continue; // we have reached one dimension's end #ifndef WLED_DISABLE_MODE_BLEND From ebd8a10cefdfa83f289961de8d3d6ef5fa6e6da6 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 3 Sep 2024 17:20:16 +0200 Subject: [PATCH 012/125] Prevent styles on 1px segments --- wled00/FX_fcn.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index a5e007e89..f825cecd3 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1428,6 +1428,8 @@ void WS2812FX::service() { unsigned h = seg.virtualHeight(); unsigned dw = p * w / 0xFFFFU + 1; unsigned dh = p * h / 0xFFFFU + 1; + unsigned orgBS = blendingStyle; + if (w*h == 1) blendingStyle = BLEND_STYLE_FADE; // disable belending for single pixel segments (use fade instead) switch (blendingStyle) { case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) Segment::setClippingRect(0, w, 0, h); @@ -1473,9 +1475,8 @@ void WS2812FX::service() { Segment::setClippingRect(0, dw, h - dh, h); break; } - } - delay = (*_mode[seg.currentMode()])(); // run new/current mode - if (seg.isInTransition()) { + delay = (*_mode[seg.currentMode()])(); // run new/current mode + // now run old/previous mode Segment::tmpsegd_t _tmpSegData; Segment::modeBlend(true); // set semaphore seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) @@ -1491,10 +1492,10 @@ void WS2812FX::service() { seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) delay = MIN(delay,d2); // use shortest delay Segment::modeBlend(false); // unset semaphore - } -#else - delay = (*_mode[seg.mode])(); // run effect mode + blendingStyle = orgBS; // restore blending style if it was modified for single pixel segment + } else #endif + delay = (*_mode[seg.mode])(); // run effect mode (not in transition) seg.call++; if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments From 95b4bde918c00c2d3191a0f71a391965d69b3d34 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 20 Oct 2024 10:42:02 -0400 Subject: [PATCH 013/125] UsermodManager: Make into namespace Namespaces are the C++ language construct for grouping global functions. --- wled00/fcn_declare.h | 47 ++++++++++++++++++++----------------------- wled00/um_manager.cpp | 5 +++-- wled00/wled.h | 3 --- 3 files changed, 25 insertions(+), 30 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 71b00599c..78655b271 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -326,36 +326,33 @@ class Usermod { template static inline void oappend(const T& t) { oappend_shim->print(t); }; }; -class UsermodManager { - private: - static Usermod* ums[WLED_MAX_USERMODS]; - static byte numMods; +namespace UsermodManager { + extern byte numMods; - public: - static void loop(); - static void handleOverlayDraw(); - static bool handleButton(uint8_t b); - static bool getUMData(um_data_t **um_data, uint8_t mod_id = USERMOD_ID_RESERVED); // USERMOD_ID_RESERVED will poll all usermods - static void setup(); - static void connected(); - static void appendConfigData(Print&); - static void addToJsonState(JsonObject& obj); - static void addToJsonInfo(JsonObject& obj); - static void readFromJsonState(JsonObject& obj); - static void addToConfig(JsonObject& obj); - static bool readFromConfig(JsonObject& obj); + void loop(); + void handleOverlayDraw(); + bool handleButton(uint8_t b); + bool getUMData(um_data_t **um_data, uint8_t mod_id = USERMOD_ID_RESERVED); // USERMOD_ID_RESERVED will poll all usermods + void setup(); + void connected(); + void appendConfigData(Print&); + void addToJsonState(JsonObject& obj); + void addToJsonInfo(JsonObject& obj); + void readFromJsonState(JsonObject& obj); + void addToConfig(JsonObject& obj); + bool readFromConfig(JsonObject& obj); #ifndef WLED_DISABLE_MQTT - static void onMqttConnect(bool sessionPresent); - static bool onMqttMessage(char* topic, char* payload); + void onMqttConnect(bool sessionPresent); + bool onMqttMessage(char* topic, char* payload); #endif #ifndef WLED_DISABLE_ESPNOW - static bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len); + bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len); #endif - static void onUpdateBegin(bool); - static void onStateChange(uint8_t); - static bool add(Usermod* um); - static Usermod* lookup(uint16_t mod_id); - static inline byte getModCount() {return numMods;}; + void onUpdateBegin(bool); + void onStateChange(uint8_t); + bool add(Usermod* um); + Usermod* lookup(uint16_t mod_id); + inline byte getModCount() {return numMods;}; }; //usermods_list.cpp diff --git a/wled00/um_manager.cpp b/wled00/um_manager.cpp index 46fdf5b3b..1fdb6d688 100644 --- a/wled00/um_manager.cpp +++ b/wled00/um_manager.cpp @@ -3,6 +3,9 @@ * Registration and management utility for v2 usermods */ +static Usermod* ums[WLED_MAX_USERMODS] = {nullptr}; +byte UsermodManager::numMods = 0; + //Usermod Manager internals void UsermodManager::setup() { for (unsigned i = 0; i < numMods; i++) ums[i]->setup(); } void UsermodManager::connected() { for (unsigned i = 0; i < numMods; i++) ums[i]->connected(); } @@ -69,8 +72,6 @@ bool UsermodManager::add(Usermod* um) return true; } -Usermod* UsermodManager::ums[WLED_MAX_USERMODS] = {nullptr}; -byte UsermodManager::numMods = 0; /* Usermod v2 interface shim for oappend */ Print* Usermod::oappend_shim = nullptr; diff --git a/wled00/wled.h b/wled00/wled.h index bc525cd6f..5f1952bec 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -896,9 +896,6 @@ WLED_GLOBAL uint32_t ledMaps _INIT(0); // bitfield representation of available l WLED_GLOBAL uint16_t ledMaps _INIT(0); // bitfield representation of available ledmaps #endif -// Usermod manager -WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager()); - // global I2C SDA pin (used for usermods) #ifndef I2CSDAPIN WLED_GLOBAL int8_t i2c_sda _INIT(-1); From 32eee3365ac06858b5d6821931c39c020af233bc Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 20 Oct 2024 10:48:31 -0400 Subject: [PATCH 014/125] PinManager: Make in to namespace Namespaces are the C++ language construct for grouping global functions. --- wled00/pin_manager.cpp | 20 +++++------ wled00/pin_manager.h | 78 +++++++++++++++++++----------------------- 2 files changed, 45 insertions(+), 53 deletions(-) diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 793b5440c..14209977a 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -13,6 +13,16 @@ #endif #endif +// Pin management state variables +#ifdef ESP8266 +static uint32_t pinAlloc = 0UL; // 1 bit per pin, we use first 17bits +#else +static uint64_t pinAlloc = 0ULL; // 1 bit per pin, we use 50 bits on ESP32-S3 +static uint16_t ledcAlloc = 0; // up to 16 LEDC channels (WLED_MAX_ANALOG_CHANNELS) +#endif +static uint8_t i2cAllocCount = 0; // allow multiple allocation of I2C bus pins but keep track of allocations +static uint8_t spiAllocCount = 0; // allow multiple allocation of SPI bus pins but keep track of allocations +static PinOwner ownerTag[WLED_NUM_PINS] = { PinOwner::None }; /// Actual allocation/deallocation routines bool PinManager::deallocatePin(byte gpio, PinOwner tag) @@ -273,13 +283,3 @@ void PinManager::deallocateLedc(byte pos, byte channels) } } #endif - -#ifdef ESP8266 -uint32_t PinManager::pinAlloc = 0UL; -#else -uint64_t PinManager::pinAlloc = 0ULL; -uint16_t PinManager::ledcAlloc = 0; -#endif -uint8_t PinManager::i2cAllocCount = 0; -uint8_t PinManager::spiAllocCount = 0; -PinOwner PinManager::ownerTag[WLED_NUM_PINS] = { PinOwner::None }; diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index 73a4a3656..c8fb165ce 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -9,6 +9,12 @@ #endif #include "const.h" // for USERMOD_* values +#ifdef ESP8266 +#define WLED_NUM_PINS (GPIO_PIN_COUNT+1) // somehow they forgot GPIO 16 (0-16==17) +#else +#define WLED_NUM_PINS (GPIO_PIN_COUNT) +#endif + typedef struct PinManagerPinType { int8_t pin; bool isOutput; @@ -70,53 +76,39 @@ enum struct PinOwner : uint8_t { }; static_assert(0u == static_cast(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected"); -class PinManager { - private: - #ifdef ESP8266 - #define WLED_NUM_PINS (GPIO_PIN_COUNT+1) // somehow they forgot GPIO 16 (0-16==17) - static uint32_t pinAlloc; // 1 bit per pin, we use first 17bits - #else - #define WLED_NUM_PINS (GPIO_PIN_COUNT) - static uint64_t pinAlloc; // 1 bit per pin, we use 50 bits on ESP32-S3 - static uint16_t ledcAlloc; // up to 16 LEDC channels (WLED_MAX_ANALOG_CHANNELS) - #endif - static uint8_t i2cAllocCount; // allow multiple allocation of I2C bus pins but keep track of allocations - static uint8_t spiAllocCount; // allow multiple allocation of SPI bus pins but keep track of allocations - static PinOwner ownerTag[WLED_NUM_PINS]; +namespace PinManager { + // De-allocates a single pin + bool deallocatePin(byte gpio, PinOwner tag); + // De-allocates multiple pins but only if all can be deallocated (PinOwner has to be specified) + bool deallocateMultiplePins(const uint8_t *pinArray, byte arrayElementCount, PinOwner tag); + bool deallocateMultiplePins(const managed_pin_type *pinArray, byte arrayElementCount, PinOwner tag); + // Allocates a single pin, with an owner tag. + // De-allocation requires the same owner tag (or override) + bool allocatePin(byte gpio, bool output, PinOwner tag); + // Allocates all the pins, or allocates none of the pins, with owner tag. + // Provided to simplify error condition handling in clients + // using more than one pin, such as I2C, SPI, rotary encoders, + // ethernet, etc.. + bool allocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag ); - public: - // De-allocates a single pin - static bool deallocatePin(byte gpio, PinOwner tag); - // De-allocates multiple pins but only if all can be deallocated (PinOwner has to be specified) - static bool deallocateMultiplePins(const uint8_t *pinArray, byte arrayElementCount, PinOwner tag); - static bool deallocateMultiplePins(const managed_pin_type *pinArray, byte arrayElementCount, PinOwner tag); - // Allocates a single pin, with an owner tag. - // De-allocation requires the same owner tag (or override) - static bool allocatePin(byte gpio, bool output, PinOwner tag); - // Allocates all the pins, or allocates none of the pins, with owner tag. - // Provided to simplify error condition handling in clients - // using more than one pin, such as I2C, SPI, rotary encoders, - // ethernet, etc.. - static bool allocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag ); + [[deprecated("Replaced by three-parameter allocatePin(gpio, output, ownerTag), for improved debugging")]] + inline bool allocatePin(byte gpio, bool output = true) { return allocatePin(gpio, output, PinOwner::None); } + [[deprecated("Replaced by two-parameter deallocatePin(gpio, ownerTag), for improved debugging")]] + inline void deallocatePin(byte gpio) { deallocatePin(gpio, PinOwner::None); } - [[deprecated("Replaced by three-parameter allocatePin(gpio, output, ownerTag), for improved debugging")]] - static inline bool allocatePin(byte gpio, bool output = true) { return allocatePin(gpio, output, PinOwner::None); } - [[deprecated("Replaced by two-parameter deallocatePin(gpio, ownerTag), for improved debugging")]] - static inline void deallocatePin(byte gpio) { deallocatePin(gpio, PinOwner::None); } + // will return true for reserved pins + bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None); + // will return false for reserved pins + bool isPinOk(byte gpio, bool output = true); + + bool isReadOnlyPin(byte gpio); - // will return true for reserved pins - static bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None); - // will return false for reserved pins - static bool isPinOk(byte gpio, bool output = true); - - static bool isReadOnlyPin(byte gpio); + PinOwner getPinOwner(byte gpio); - static PinOwner getPinOwner(byte gpio); - - #ifdef ARDUINO_ARCH_ESP32 - static byte allocateLedc(byte channels); - static void deallocateLedc(byte pos, byte channels); - #endif + #ifdef ARDUINO_ARCH_ESP32 + byte allocateLedc(byte channels); + void deallocateLedc(byte pos, byte channels); + #endif }; //extern PinManager pinManager; From 0160e3fa87d08cee401bebfe82c0c42e2bd6935d Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Wed, 20 Nov 2024 12:39:39 +0100 Subject: [PATCH 015/125] Use MQTT_MAX_TOPIC_LEN in places where it was not used before to avoid buffer overflows when value is increased --- wled00/button.cpp | 8 ++++---- wled00/mqtt.cpp | 22 +++++++++++----------- wled00/wled.h | 8 ++++---- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/wled00/button.cpp b/wled00/button.cpp index 4d6f954f6..6f9c84560 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -29,7 +29,7 @@ void shortPressAction(uint8_t b) #ifndef WLED_DISABLE_MQTT // publish MQTT message if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { - char subuf[64]; + char subuf[MQTT_MAX_TOPIC_LEN + 32]; sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); mqtt->publish(subuf, 0, false, "short"); } @@ -62,7 +62,7 @@ void longPressAction(uint8_t b) #ifndef WLED_DISABLE_MQTT // publish MQTT message if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { - char subuf[64]; + char subuf[MQTT_MAX_TOPIC_LEN + 32]; sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); mqtt->publish(subuf, 0, false, "long"); } @@ -83,7 +83,7 @@ void doublePressAction(uint8_t b) #ifndef WLED_DISABLE_MQTT // publish MQTT message if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { - char subuf[64]; + char subuf[MQTT_MAX_TOPIC_LEN + 32]; sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); mqtt->publish(subuf, 0, false, "double"); } @@ -151,7 +151,7 @@ void handleSwitch(uint8_t b) #ifndef WLED_DISABLE_MQTT // publish MQTT message if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { - char subuf[64]; + char subuf[MQTT_MAX_TOPIC_LEN + 32]; if (buttonType[b] == BTN_TYPE_PIR_SENSOR) sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)b); else sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); mqtt->publish(subuf, 0, false, !buttonPressedBefore[b] ? "off" : "on"); diff --git a/wled00/mqtt.cpp b/wled00/mqtt.cpp index a476db87a..d909494ee 100644 --- a/wled00/mqtt.cpp +++ b/wled00/mqtt.cpp @@ -23,24 +23,24 @@ static void parseMQTTBriPayload(char* payload) static void onMqttConnect(bool sessionPresent) { //(re)subscribe to required topics - char subuf[38]; + char subuf[MQTT_MAX_TOPIC_LEN + 6]; if (mqttDeviceTopic[0] != 0) { - strlcpy(subuf, mqttDeviceTopic, 33); + strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); mqtt->subscribe(subuf, 0); strcat_P(subuf, PSTR("/col")); mqtt->subscribe(subuf, 0); - strlcpy(subuf, mqttDeviceTopic, 33); + strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(subuf, PSTR("/api")); mqtt->subscribe(subuf, 0); } if (mqttGroupTopic[0] != 0) { - strlcpy(subuf, mqttGroupTopic, 33); + strlcpy(subuf, mqttGroupTopic, MQTT_MAX_TOPIC_LEN + 1); mqtt->subscribe(subuf, 0); strcat_P(subuf, PSTR("/col")); mqtt->subscribe(subuf, 0); - strlcpy(subuf, mqttGroupTopic, 33); + strlcpy(subuf, mqttGroupTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(subuf, PSTR("/api")); mqtt->subscribe(subuf, 0); } @@ -158,19 +158,19 @@ void publishMqtt() #ifndef USERMOD_SMARTNEST char s[10]; - char subuf[48]; + char subuf[MQTT_MAX_TOPIC_LEN + 16]; sprintf_P(s, PSTR("%u"), bri); - strlcpy(subuf, mqttDeviceTopic, 33); + strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(subuf, PSTR("/g")); mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) sprintf_P(s, PSTR("#%06X"), (col[3] << 24) | (col[0] << 16) | (col[1] << 8) | (col[2])); - strlcpy(subuf, mqttDeviceTopic, 33); + strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(subuf, PSTR("/c")); mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) - strlcpy(subuf, mqttDeviceTopic, 33); + strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(subuf, PSTR("/status")); mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT @@ -178,7 +178,7 @@ void publishMqtt() DynamicBuffer buf(1024); bufferPrint pbuf(buf.data(), buf.size()); XML_response(pbuf); - strlcpy(subuf, mqttDeviceTopic, 33); + strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(subuf, PSTR("/v")); mqtt->publish(subuf, 0, retainMqttMsg, buf.data(), pbuf.size()); // optionally retain message (#2263) #endif @@ -211,7 +211,7 @@ bool initMqtt() if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass); #ifndef USERMOD_SMARTNEST - strlcpy(mqttStatusTopic, mqttDeviceTopic, 33); + strlcpy(mqttStatusTopic, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(mqttStatusTopic, PSTR("/status")); mqtt->setWill(mqttStatusTopic, 0, true, "offline"); // LWT message #endif diff --git a/wled00/wled.h b/wled00/wled.h index 2b3a77d24..29b43753a 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -483,10 +483,10 @@ WLED_GLOBAL unsigned long lastMqttReconnectAttempt _INIT(0); // used for other #endif WLED_GLOBAL AsyncMqttClient *mqtt _INIT(NULL); WLED_GLOBAL bool mqttEnabled _INIT(false); -WLED_GLOBAL char mqttStatusTopic[40] _INIT(""); // this must be global because of async handlers -WLED_GLOBAL char mqttDeviceTopic[MQTT_MAX_TOPIC_LEN+1] _INIT(""); // main MQTT topic (individual per device, default is wled/mac) -WLED_GLOBAL char mqttGroupTopic[MQTT_MAX_TOPIC_LEN+1] _INIT("wled/all"); // second MQTT topic (for example to group devices) -WLED_GLOBAL char mqttServer[MQTT_MAX_SERVER_LEN+1] _INIT(""); // both domains and IPs should work (no SSL) +WLED_GLOBAL char mqttStatusTopic[MQTT_MAX_TOPIC_LEN + 8] _INIT(""); // this must be global because of async handlers +WLED_GLOBAL char mqttDeviceTopic[MQTT_MAX_TOPIC_LEN + 1] _INIT(""); // main MQTT topic (individual per device, default is wled/mac) +WLED_GLOBAL char mqttGroupTopic[MQTT_MAX_TOPIC_LEN + 1] _INIT("wled/all"); // second MQTT topic (for example to group devices) +WLED_GLOBAL char mqttServer[MQTT_MAX_SERVER_LEN + 1] _INIT(""); // both domains and IPs should work (no SSL) WLED_GLOBAL char mqttUser[41] _INIT(""); // optional: username for MQTT auth WLED_GLOBAL char mqttPass[65] _INIT(""); // optional: password for MQTT auth WLED_GLOBAL char mqttClientID[41] _INIT(""); // override the client ID From cec89788865bf29aae0799f03ba0c8c43638d297 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Wed, 20 Nov 2024 12:45:39 +0100 Subject: [PATCH 016/125] Fix comment alignment --- wled00/wled.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/wled.h b/wled00/wled.h index 29b43753a..3630170f9 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -483,7 +483,7 @@ WLED_GLOBAL unsigned long lastMqttReconnectAttempt _INIT(0); // used for other #endif WLED_GLOBAL AsyncMqttClient *mqtt _INIT(NULL); WLED_GLOBAL bool mqttEnabled _INIT(false); -WLED_GLOBAL char mqttStatusTopic[MQTT_MAX_TOPIC_LEN + 8] _INIT(""); // this must be global because of async handlers +WLED_GLOBAL char mqttStatusTopic[MQTT_MAX_TOPIC_LEN + 8] _INIT(""); // this must be global because of async handlers WLED_GLOBAL char mqttDeviceTopic[MQTT_MAX_TOPIC_LEN + 1] _INIT(""); // main MQTT topic (individual per device, default is wled/mac) WLED_GLOBAL char mqttGroupTopic[MQTT_MAX_TOPIC_LEN + 1] _INIT("wled/all"); // second MQTT topic (for example to group devices) WLED_GLOBAL char mqttServer[MQTT_MAX_SERVER_LEN + 1] _INIT(""); // both domains and IPs should work (no SSL) From 0db47a8586526541b0cdd810b466923c64b80cd6 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Thu, 21 Nov 2024 09:51:13 +0100 Subject: [PATCH 017/125] Add comment warning about modification of MQTT_MAX_TOPIC_LEN --- wled00/wled.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/wled.h b/wled00/wled.h index 3630170f9..5dbc013d9 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -476,7 +476,7 @@ WLED_GLOBAL uint16_t pollReplyCount _INIT(0); // count numbe WLED_GLOBAL unsigned long lastMqttReconnectAttempt _INIT(0); // used for other periodic tasks too #ifndef WLED_DISABLE_MQTT #ifndef MQTT_MAX_TOPIC_LEN - #define MQTT_MAX_TOPIC_LEN 32 + #define MQTT_MAX_TOPIC_LEN 32 // should not be less than 32. might cause trouble when increased with usermods active that do not handle this correctly. #endif #ifndef MQTT_MAX_SERVER_LEN #define MQTT_MAX_SERVER_LEN 32 From 8f8afd98a5dc5982271babbcce84ffff28c32fab Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Thu, 21 Nov 2024 11:20:42 +0100 Subject: [PATCH 018/125] Replace comment with compile-time error and warning --- wled00/mqtt.cpp | 4 ++++ wled00/wled.h | 2 +- wled00/wled_eeprom.cpp | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) mode change 100755 => 100644 wled00/wled_eeprom.cpp diff --git a/wled00/mqtt.cpp b/wled00/mqtt.cpp index d909494ee..38afeffe3 100644 --- a/wled00/mqtt.cpp +++ b/wled00/mqtt.cpp @@ -7,6 +7,10 @@ #ifndef WLED_DISABLE_MQTT #define MQTT_KEEP_ALIVE_TIME 60 // contact the MQTT broker every 60 seconds +#if MQTT_MAX_TOPIC_LEN > 32 +#warning "MQTT topics length > 32 is not recommended for compatibility with usermods!" +#endif + static void parseMQTTBriPayload(char* payload) { if (strstr(payload, "ON") || strstr(payload, "on") || strstr(payload, "true")) {bri = briLast; stateUpdated(CALL_MODE_DIRECT_CHANGE);} diff --git a/wled00/wled.h b/wled00/wled.h index 5dbc013d9..3630170f9 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -476,7 +476,7 @@ WLED_GLOBAL uint16_t pollReplyCount _INIT(0); // count numbe WLED_GLOBAL unsigned long lastMqttReconnectAttempt _INIT(0); // used for other periodic tasks too #ifndef WLED_DISABLE_MQTT #ifndef MQTT_MAX_TOPIC_LEN - #define MQTT_MAX_TOPIC_LEN 32 // should not be less than 32. might cause trouble when increased with usermods active that do not handle this correctly. + #define MQTT_MAX_TOPIC_LEN 32 #endif #ifndef MQTT_MAX_SERVER_LEN #define MQTT_MAX_SERVER_LEN 32 diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp old mode 100755 new mode 100644 index 4f2c14d47..8582b49df --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -2,6 +2,10 @@ #include #include "wled.h" +#if defined(WLED_ENABLE_MQTT) && MQTT_MAX_TOPIC_LEN < 32 +#error "MQTT topics length < 32 is not supported by the EEPROM module!" +#endif + /* * DEPRECATED, do not use for new settings * Only used to restore config from pre-0.11 installations using the deEEP() methods From e16d3bf040a20d104c29fe870639d04e018a7b8d Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 13 Dec 2024 07:40:04 +0100 Subject: [PATCH 019/125] Fix: output-glitching on ESPNow remote command reception Processing of received button command is no longer processed in the callback, instead the value is saved to a variable and processed in the main loop. The actual fix is to not access the file system while data is being sent out: even just trying to open a non-existing file causes glitches on the C3. Waiting for the bus to finish fixes this BUT it causes a frame-delay which is the lesser evil than random color flashes. --- wled00/fcn_declare.h | 1 + wled00/remote.cpp | 19 ++++++++++++++++--- wled00/wled.cpp | 3 +++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 3d8c27aca..e883c5fbd 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -231,6 +231,7 @@ bool getPresetName(byte index, String& name); //remote.cpp void handleRemote(uint8_t *data, size_t len); +void processESPNowButton(); //set.cpp bool isAsterisksOnly(const char* str, byte maxLen); diff --git a/wled00/remote.cpp b/wled00/remote.cpp index 9bc5430c0..de386932a 100644 --- a/wled00/remote.cpp +++ b/wled00/remote.cpp @@ -1,6 +1,8 @@ #include "wled.h" #ifndef WLED_DISABLE_ESPNOW +#define ESPNOW_BUSWAIT_TIMEOUT 30 // timeout in ms to wait for bus to finish updating + #define NIGHT_MODE_DEACTIVATED -1 #define NIGHT_MODE_BRIGHTNESS 5 @@ -38,6 +40,7 @@ typedef struct WizMoteMessageStructure { static uint32_t last_seq = UINT32_MAX; static int brightnessBeforeNightMode = NIGHT_MODE_DEACTIVATED; +static uint8_t ESPNowButton = 0; // set in callback if new button value is received // Pulled from the IR Remote logic but reduced to 10 steps with a constant of 3 static const byte brightnessSteps[] = { @@ -121,6 +124,9 @@ static bool remoteJson(int button) sprintf_P(objKey, PSTR("\"%d\":"), button); + unsigned long start = millis(); + while (strip.isUpdating() && millis()-start < ESPNOW_BUSWAIT_TIMEOUT) yield(); // wait for strip to finish updating, accessing FS during sendout causes glitches + // attempt to read command from remote.json readObjectFromFile(PSTR("/remote.json"), objKey, pDoc); JsonObject fdo = pDoc->as(); @@ -202,8 +208,14 @@ void handleRemote(uint8_t *incomingData, size_t len) { DEBUG_PRINT(F("] button: ")); DEBUG_PRINTLN(incoming->button); - if (!remoteJson(incoming->button)) - switch (incoming->button) { + ESPNowButton = incoming->button; // save state, do not process in callback (can cause glitches) + last_seq = cur_seq; +} + +void processESPNowButton() { + if(ESPNowButton > 0) { + if (!remoteJson(ESPNowButton)) + switch (ESPNowButton) { case WIZMOTE_BUTTON_ON : setOn(); break; case WIZMOTE_BUTTON_OFF : setOff(); break; case WIZMOTE_BUTTON_ONE : presetWithFallback(1, FX_MODE_STATIC, 0); break; @@ -219,7 +231,8 @@ void handleRemote(uint8_t *incomingData, size_t len) { case WIZ_SMART_BUTTON_BRIGHT_DOWN : brightnessDown(); break; default: break; } - last_seq = cur_seq; + } + ESPNowButton = 0; } #else diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 394da6783..229d363b3 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -84,6 +84,9 @@ void WLED::loop() #ifndef WLED_DISABLE_INFRARED handleIR(); #endif + #ifndef WLED_DISABLE_ESPNOW + processESPNowButton(); + #endif #ifndef WLED_DISABLE_ALEXA handleAlexa(); #endif From fd3b47908b8eab700391a759a99d3987f536586f Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 19 Dec 2024 17:41:44 +0100 Subject: [PATCH 020/125] renamed functions, changed timeout to 24ms --- wled00/fcn_declare.h | 4 ++-- wled00/remote.cpp | 9 +++++---- wled00/udp.cpp | 2 +- wled00/wled.cpp | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index e883c5fbd..2e965a9af 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -230,8 +230,8 @@ void deletePreset(byte index); bool getPresetName(byte index, String& name); //remote.cpp -void handleRemote(uint8_t *data, size_t len); -void processESPNowButton(); +void handleWiZdata(uint8_t *incomingData, size_t len); +void handleRemote(); //set.cpp bool isAsterisksOnly(const char* str, byte maxLen); diff --git a/wled00/remote.cpp b/wled00/remote.cpp index de386932a..8787369c0 100644 --- a/wled00/remote.cpp +++ b/wled00/remote.cpp @@ -1,7 +1,7 @@ #include "wled.h" #ifndef WLED_DISABLE_ESPNOW -#define ESPNOW_BUSWAIT_TIMEOUT 30 // timeout in ms to wait for bus to finish updating +#define ESPNOW_BUSWAIT_TIMEOUT 24 // one frame timeout to wait for bus to finish updating #define NIGHT_MODE_DEACTIVATED -1 #define NIGHT_MODE_BRIGHTNESS 5 @@ -182,7 +182,7 @@ static bool remoteJson(int button) } // Callback function that will be executed when data is received -void handleRemote(uint8_t *incomingData, size_t len) { +void handleWiZdata(uint8_t *incomingData, size_t len) { message_structure_t *incoming = reinterpret_cast(incomingData); if (strcmp(last_signal_src, linked_remote) != 0) { @@ -212,7 +212,8 @@ void handleRemote(uint8_t *incomingData, size_t len) { last_seq = cur_seq; } -void processESPNowButton() { +// process ESPNow button data (acesses FS, should not be called while update to avoid glitches) +void handleRemote() { if(ESPNowButton > 0) { if (!remoteJson(ESPNowButton)) switch (ESPNowButton) { @@ -236,5 +237,5 @@ void processESPNowButton() { } #else -void handleRemote(uint8_t *incomingData, size_t len) {} +void handleRemote() {} #endif diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 60774d701..5173842a6 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -979,7 +979,7 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs // handle WiZ Mote data if (data[0] == 0x91 || data[0] == 0x81 || data[0] == 0x80) { - handleRemote(data, len); + handleWiZdata(data, len); return; } diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 229d363b3..b7f4ad7d6 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -85,7 +85,7 @@ void WLED::loop() handleIR(); #endif #ifndef WLED_DISABLE_ESPNOW - processESPNowButton(); + handleRemote(); #endif #ifndef WLED_DISABLE_ALEXA handleAlexa(); From dcfebcb9732e3ffae63d8e2b8566f70397b6c745 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 19 Dec 2024 17:46:39 +0100 Subject: [PATCH 021/125] allow for 0 value button code --- wled00/remote.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wled00/remote.cpp b/wled00/remote.cpp index 8787369c0..c3325ab98 100644 --- a/wled00/remote.cpp +++ b/wled00/remote.cpp @@ -40,7 +40,7 @@ typedef struct WizMoteMessageStructure { static uint32_t last_seq = UINT32_MAX; static int brightnessBeforeNightMode = NIGHT_MODE_DEACTIVATED; -static uint8_t ESPNowButton = 0; // set in callback if new button value is received +static int16_t ESPNowButton = -1; // set in callback if new button value is received // Pulled from the IR Remote logic but reduced to 10 steps with a constant of 3 static const byte brightnessSteps[] = { @@ -214,7 +214,7 @@ void handleWiZdata(uint8_t *incomingData, size_t len) { // process ESPNow button data (acesses FS, should not be called while update to avoid glitches) void handleRemote() { - if(ESPNowButton > 0) { + if(ESPNowButton >= 0) { if (!remoteJson(ESPNowButton)) switch (ESPNowButton) { case WIZMOTE_BUTTON_ON : setOn(); break; @@ -233,7 +233,7 @@ void handleRemote() { default: break; } } - ESPNowButton = 0; + ESPNowButton = -1; } #else From 97e8382a4179cbdde56f25b95de791a59e8cc4dd Mon Sep 17 00:00:00 2001 From: Malachi Soord Date: Tue, 24 Dec 2024 11:01:14 +0100 Subject: [PATCH 022/125] Use consistent node version --- .github/workflows/build.yml | 3 ++- .nvmrc | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 .nvmrc diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e5fdfc5a3..2bac314f3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,6 +38,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: + node-version-file: '.nvmrc' cache: 'npm' - run: npm ci - name: Cache PlatformIO @@ -74,7 +75,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v4 with: - node-version: '20.x' + node-version-file: '.nvmrc' cache: 'npm' - run: npm ci - run: npm test diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..d4b7699d3 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20.18.1 From a2d84886c0569f52768f11cc09ca6cfeb32ef3cc Mon Sep 17 00:00:00 2001 From: Malachi Soord Date: Tue, 24 Dec 2024 13:55:10 +0100 Subject: [PATCH 023/125] Update .nvmrc --- .nvmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nvmrc b/.nvmrc index d4b7699d3..10fef252a 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20.18.1 +20.18 From 1a82a3bf7b5ed515c86838fa60a17dfcea033be5 Mon Sep 17 00:00:00 2001 From: Malachi Soord Date: Tue, 24 Dec 2024 20:30:54 +0000 Subject: [PATCH 024/125] Fix devcontainer --- .devcontainer/Dockerfile | 7 +------ .devcontainer/devcontainer.json | 7 ++----- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index d09c8a605..1340da91c 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -2,12 +2,7 @@ # [Choice] Python version: 3, 3.9, 3.8, 3.7, 3.6 ARG VARIANT="3" -FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} - -# [Option] Install Node.js -ARG INSTALL_NODE="true" -ARG NODE_VERSION="lts/*" -RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi +FROM mcr.microsoft.com/devcontainers/python:0-${VARIANT} # [Optional] If your pip requirements rarely change, uncomment this section to add them to the image. # COPY requirements.txt /tmp/pip-tmp/ diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2a8e4712d..241acd79d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,10 +5,7 @@ "context": "..", "args": { // Update 'VARIANT' to pick a Python version: 3, 3.6, 3.7, 3.8, 3.9 - "VARIANT": "3", - // Options - "INSTALL_NODE": "true", - "NODE_VERSION": "lts/*" + "VARIANT": "3" } }, @@ -54,7 +51,7 @@ // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "npm install", + "postCreateCommand": "bash -i -c 'nvm install && npm ci'", // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode" From 0ac627dfbb21a153a218009f3ed1374ea2bd9330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Wed, 25 Dec 2024 10:40:16 +0100 Subject: [PATCH 025/125] FX: Waterfall and Matripix fix - for Arc expansion - or gaps - or ledmaps with missing pixels --- wled00/FX.cpp | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index fd2118fd0..295f2e9a8 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -6639,14 +6639,16 @@ static const char _data_FX_MODE_JUGGLES[] PROGMEM = "Juggles@!,# of balls;!,!;!; // * MATRIPIX // ////////////////////// uint16_t mode_matripix(void) { // Matripix. By Andrew Tuline. - if (SEGLEN == 1) return mode_static(); - // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment + // effect can work on single pixels, we just lose the shifting effect + unsigned dataSize = sizeof(uint32_t) * SEGLEN; + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + uint32_t* pixels = reinterpret_cast(SEGENV.data); um_data_t *um_data = getAudioData(); int volumeRaw = *(int16_t*)um_data->u_data[1]; if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); + for (int i = 0; i < SEGLEN; i++) pixels[i] = BLACK; // may not be needed as resetIfRequired() clears buffer } uint8_t secondHand = micros()/(256-SEGMENT.speed)/500 % 16; @@ -6654,8 +6656,14 @@ uint16_t mode_matripix(void) { // Matripix. By Andrew Tuline. SEGENV.aux0 = secondHand; int pixBri = volumeRaw * SEGMENT.intensity / 64; - for (int i = 0; i < SEGLEN-1; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // shift left - SEGMENT.setPixelColor(SEGLEN-1, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(strip.now, false, PALETTE_SOLID_WRAP, 0), pixBri)); + unsigned k = SEGLEN-1; + // loop will not execute if SEGLEN equals 1 + for (unsigned i = 0; i < k; i++) { + pixels[i] = pixels[i+1]; // shift left + SEGMENT.setPixelColor(i, pixels[i]); + } + pixels[k] = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(strip.now, false, PALETTE_SOLID_WRAP, 0), pixBri); + SEGMENT.setPixelColor(k, pixels[k]); } return FRAMETIME; @@ -7283,8 +7291,11 @@ static const char _data_FX_MODE_ROCKTAVES[] PROGMEM = "Rocktaves@;!,!;!;01f;m12= // Combines peak detection with FFT_MajorPeak and FFT_Magnitude. uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tuline // effect can work on single pixels, we just lose the shifting effect - - um_data_t *um_data = getAudioData(); + unsigned dataSize = sizeof(uint32_t) * SEGLEN; + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + uint32_t* pixels = reinterpret_cast(SEGENV.data); + + um_data_t *um_data = getAudioData(); uint8_t samplePeak = *(uint8_t*)um_data->u_data[3]; float FFT_MajorPeak = *(float*) um_data->u_data[4]; uint8_t *maxVol = (uint8_t*)um_data->u_data[6]; @@ -7294,7 +7305,7 @@ uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tulin if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); + for (int i = 0; i < SEGLEN; i++) pixels[i] = BLACK; // may not be needed as resetIfRequired() clears buffer SEGENV.aux0 = 255; SEGMENT.custom1 = *binNum; SEGMENT.custom2 = *maxVol * 2; @@ -7311,13 +7322,18 @@ uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tulin uint8_t pixCol = (log10f(FFT_MajorPeak) - 2.26f) * 150; // 22Khz sampling - log10 frequency range is from 2.26 (182hz) to 3.967 (9260hz). Let's scale accordingly. if (FFT_MajorPeak < 182.0f) pixCol = 0; // handle underflow + int k = SEGLEN-1; if (samplePeak) { - SEGMENT.setPixelColor(SEGLEN-1, CHSV(92,92,92)); + pixels[k] = (uint32_t)CRGB(CHSV(92,92,92)); } else { - SEGMENT.setPixelColor(SEGLEN-1, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(pixCol+SEGMENT.intensity, false, PALETTE_SOLID_WRAP, 0), (int)my_magnitude)); + pixels[k] = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(pixCol+SEGMENT.intensity, false, PALETTE_SOLID_WRAP, 0), (int)my_magnitude); } + SEGMENT.setPixelColor(k, pixels[k]); // loop will not execute if SEGLEN equals 1 - for (int i = 0; i < SEGLEN-1; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // shift left + for (unsigned i = 0; i < k; i++) { + pixels[i] = pixels[i+1]; // shift left + SEGMENT.setPixelColor(i, pixels[i]); + } } return FRAMETIME; From 0441ede2299bc286dadf23383c3d1613c5ce8823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Wed, 25 Dec 2024 15:18:34 +0100 Subject: [PATCH 026/125] Fix for #4401 --- wled00/FX.cpp | 32 +++++++++++++------------------- wled00/led.cpp | 5 ++--- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 394b5df0d..da7b0c4ba 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -642,11 +642,12 @@ static const char _data_FX_MODE_TWINKLE[] PROGMEM = "Twinkle@!,!;!,!;!;;m12=0"; * Dissolve function */ uint16_t dissolve(uint32_t color) { - unsigned dataSize = (SEGLEN+7) >> 3; //1 bit per LED + unsigned dataSize = sizeof(uint32_t) * SEGLEN; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + uint32_t* pixels = reinterpret_cast(SEGENV.data); if (SEGENV.call == 0) { - memset(SEGMENT.data, 0xFF, dataSize); // start by fading pixels up + for (unsigned i = 0; i < SEGLEN; i++) pixels[i] = SEGCOLOR(1); SEGENV.aux0 = 1; } @@ -654,33 +655,26 @@ uint16_t dissolve(uint32_t color) { if (hw_random8() <= SEGMENT.intensity) { for (size_t times = 0; times < 10; times++) { //attempt to spawn a new pixel 10 times unsigned i = hw_random16(SEGLEN); - unsigned index = i >> 3; - unsigned bitNum = i & 0x07; - bool fadeUp = bitRead(SEGENV.data[index], bitNum); if (SEGENV.aux0) { //dissolve to primary/palette - if (fadeUp) { - if (color == SEGCOLOR(0)) { - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); - } else { - SEGMENT.setPixelColor(i, color); - } - bitWrite(SEGENV.data[index], bitNum, false); + if (pixels[i] == SEGCOLOR(1)) { + pixels[i] = color == SEGCOLOR(0) ? SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0) : color; break; //only spawn 1 new pixel per frame per 50 LEDs } } else { //dissolve to secondary - if (!fadeUp) { - SEGMENT.setPixelColor(i, SEGCOLOR(1)); break; - bitWrite(SEGENV.data[index], bitNum, true); + if (pixels[i] != SEGCOLOR(1)) { + pixels[i] = SEGCOLOR(1); + break; } } } } } + // fix for #4401 + for (unsigned i = 0; i < SEGLEN; i++) SEGMENT.setPixelColor(i, pixels[i]); if (SEGENV.step > (255 - SEGMENT.speed) + 15U) { SEGENV.aux0 = !SEGENV.aux0; SEGENV.step = 0; - memset(SEGMENT.data, (SEGENV.aux0 ? 0xFF : 0), dataSize); // switch fading } else { SEGENV.step++; } @@ -6577,7 +6571,7 @@ uint16_t mode_matripix(void) { // Matripix. By Andrew Tuline. int volumeRaw = *(int16_t*)um_data->u_data[1]; if (SEGENV.call == 0) { - for (int i = 0; i < SEGLEN; i++) pixels[i] = BLACK; // may not be needed as resetIfRequired() clears buffer + for (unsigned i = 0; i < SEGLEN; i++) pixels[i] = BLACK; // may not be needed as resetIfRequired() clears buffer } uint8_t secondHand = micros()/(256-SEGMENT.speed)/500 % 16; @@ -7161,7 +7155,7 @@ uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tulin if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) if (SEGENV.call == 0) { - for (int i = 0; i < SEGLEN; i++) pixels[i] = BLACK; // may not be needed as resetIfRequired() clears buffer + for (unsigned i = 0; i < SEGLEN; i++) pixels[i] = BLACK; // may not be needed as resetIfRequired() clears buffer SEGENV.aux0 = 255; SEGMENT.custom1 = *binNum; SEGMENT.custom2 = *maxVol * 2; @@ -7178,7 +7172,7 @@ uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tulin uint8_t pixCol = (log10f(FFT_MajorPeak) - 2.26f) * 150; // 22Khz sampling - log10 frequency range is from 2.26 (182hz) to 3.967 (9260hz). Let's scale accordingly. if (FFT_MajorPeak < 182.0f) pixCol = 0; // handle underflow - int k = SEGLEN-1; + unsigned k = SEGLEN-1; if (samplePeak) { pixels[k] = (uint32_t)CRGB(CHSV(92,92,92)); } else { diff --git a/wled00/led.cpp b/wled00/led.cpp index 68169509d..8b34bbf0c 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -73,8 +73,7 @@ byte scaledBri(byte in) //applies global brightness void applyBri() { - if (!realtimeMode || !arlsForceMaxBri) - { + if (!(realtimeMode && arlsForceMaxBri)) { //DEBUG_PRINTF_P(PSTR("Applying strip brightness: %d (%d,%d)\n"), (int)briT, (int)bri, (int)briOld); strip.setBrightness(scaledBri(briT)); } @@ -86,6 +85,7 @@ void applyFinalBri() { briOld = bri; briT = bri; applyBri(); + strip.trigger(); } @@ -146,7 +146,6 @@ void stateUpdated(byte callMode) { transitionStartTime = millis(); } else { applyFinalBri(); - strip.trigger(); } } From 56e1d577fdd1abc5f73a6fa1ef9158cf52f18510 Mon Sep 17 00:00:00 2001 From: TripleWhy Date: Fri, 27 Dec 2024 11:19:24 +0100 Subject: [PATCH 027/125] palette effect overflow fix --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 5e43430ae..7bf054581 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2032,7 +2032,7 @@ uint16_t mode_palette() { const mathType sourceX = xtSinTheta + ytCosTheta + centerX; // The computation was scaled just right so that the result should always be in range [0, maxXOut], but enforce this anyway // to account for imprecision. Then scale it so that the range is [0, 255], which we can use with the palette. - int colorIndex = (std::min(std::max(sourceX, mathType(0)), maxXOut * sInt16Scale) * 255) / (sInt16Scale * maxXOut); + int colorIndex = (std::min(std::max(sourceX, mathType(0)), maxXOut * sInt16Scale) * wideMathType(255)) / (sInt16Scale * maxXOut); // inputSize determines by how much we want to scale the palette: // values < 128 display a fraction of a palette, // values > 128 display multiple palettes. From 3fc8c7d560b949eec8e2c55ffa7f0a014fd72a3f Mon Sep 17 00:00:00 2001 From: AlDIY <87589371+dosipod@users.noreply.github.com> Date: Sun, 29 Dec 2024 15:46:49 +0300 Subject: [PATCH 028/125] Update readme.md Updated the readme to use lennarthennigs/ESP Rotary@^2.1.1 as the old lib fail to compile --- usermods/rgb-rotary-encoder/readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usermods/rgb-rotary-encoder/readme.md b/usermods/rgb-rotary-encoder/readme.md index ba5aad4df..653179179 100644 --- a/usermods/rgb-rotary-encoder/readme.md +++ b/usermods/rgb-rotary-encoder/readme.md @@ -9,7 +9,7 @@ The actual / original code that controls the LED modes is from Adam Zeloof. I ta It was quite a bit more work than I hoped, but I got there eventually :) ## Requirements -* "ESP Rotary" by Lennart Hennigs, v1.5.0 or higher: https://github.com/LennartHennigs/ESPRotary +* "ESP Rotary" by Lennart Hennigs, v2.1.1 or higher: https://github.com/LennartHennigs/ESPRotary ## Usermod installation Simply copy the below block (build task) to your `platformio_override.ini` and compile WLED using this new build task. Or use an existing one and add the buildflag `-D RGB_ROTARY_ENCODER`. @@ -20,7 +20,7 @@ ESP32: extends = env:esp32dev build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 -D RGB_ROTARY_ENCODER lib_deps = ${esp32.lib_deps} - lennarthennigs/ESP Rotary@^1.5.0 + lennarthennigs/ESP Rotary@^2.1.1 ``` ESP8266 / D1 Mini: @@ -29,7 +29,7 @@ ESP8266 / D1 Mini: extends = env:d1_mini build_flags = ${common.build_flags_esp8266} -D RGB_ROTARY_ENCODER lib_deps = ${esp8266.lib_deps} - lennarthennigs/ESP Rotary@^1.5.0 + lennarthennigs/ESP Rotary@^2.1.1 ``` ## How to connect the board to your ESP From 6a1d3de75b0705549469608afad353605d479da8 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Mon, 30 Dec 2024 12:58:38 +0100 Subject: [PATCH 029/125] Fix output glitches when playlist changes preset (#4442) same issue as with https://github.com/Aircoookie/WLED/pull/4386 waiting on bus to finish updating before file access fixes the glitches. this issue is only present on S2 and C3, not on ESP8266 or dual-core ESPs, the fix is only applied for these two. --- wled00/presets.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wled00/presets.cpp b/wled00/presets.cpp index 04474113d..fb09d48a4 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -164,6 +164,11 @@ void handlePresets() DEBUG_PRINTF_P(PSTR("Applying preset: %u\n"), (unsigned)tmpPreset); + #if defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32C3) + unsigned long start = millis(); + while (strip.isUpdating() && millis() - start < FRAMETIME_FIXED) yield(); // wait for strip to finish updating, accessing FS during sendout causes glitches + #endif + #ifdef ARDUINO_ARCH_ESP32 if (tmpPreset==255 && tmpRAMbuffer!=nullptr) { deserializeJson(*pDoc,tmpRAMbuffer); From 0937064e18c771c1930c61af6c27c0ac1ee5473b Mon Sep 17 00:00:00 2001 From: ladyada Date: Tue, 31 Dec 2024 16:40:11 -0500 Subject: [PATCH 030/125] fix pin availability calculations for ESP32-mini modules --- wled00/pin_manager.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 0d4c2ad5c..1dc14fbcf 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -214,7 +214,18 @@ bool PinManager::isPinOk(byte gpio, bool output) // JTAG: GPIO39-42 are usually used for inline debugging // GPIO46 is input only and pulled down #else - if (gpio > 5 && gpio < 12) return false; //SPI flash pins + + if (strncmp_P(PSTR("ESP32-U4WDH"), ESP.getChipModel(), 11) == 0) { + // this chip has 4 MB of internal Flash and different packaging, so available pins are different! + if ((gpio == 1) || (gpio == 3) || ((gpio >= 6) && (gpio =< 8)) || + (gpio == 11) || (gpio == 16) || (gpio == 17) || (gpio == 20) || + (gpio == 24) || ((gpio >= 28) && (gpio <= 31))) + return false; + } else { + // for classic ESP32 (non-mini) modules, these are the SPI flash pins + if (gpio > 5 && gpio < 12) return false; //SPI flash pins + } + if (strncmp_P(PSTR("ESP32-PICO"), ESP.getChipModel(), 10) == 0 && (gpio == 16 || gpio == 17)) return false; // PICO-D4: gpio16+17 are in use for onboard SPI FLASH if (gpio == 16 || gpio == 17) return !psramFound(); //PSRAM pins on ESP32 (these are IO) #endif From d637260dc3d729d50d256b21564c8c8ea486a087 Mon Sep 17 00:00:00 2001 From: ladyada Date: Tue, 31 Dec 2024 16:42:49 -0500 Subject: [PATCH 031/125] typo fix --- wled00/pin_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 1dc14fbcf..1110ae0e4 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -217,7 +217,7 @@ bool PinManager::isPinOk(byte gpio, bool output) if (strncmp_P(PSTR("ESP32-U4WDH"), ESP.getChipModel(), 11) == 0) { // this chip has 4 MB of internal Flash and different packaging, so available pins are different! - if ((gpio == 1) || (gpio == 3) || ((gpio >= 6) && (gpio =< 8)) || + if ((gpio == 1) || (gpio == 3) || ((gpio >= 6) && (gpio <= 8)) || (gpio == 11) || (gpio == 16) || (gpio == 17) || (gpio == 20) || (gpio == 24) || ((gpio >= 28) && (gpio <= 31))) return false; From 12db60885f7e82d6b4b194a70f7e398b7cf39dfe Mon Sep 17 00:00:00 2001 From: ladyada Date: Tue, 31 Dec 2024 17:02:52 -0500 Subject: [PATCH 032/125] try debug --- wled00/pin_manager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 1110ae0e4..7ee1076bd 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -215,7 +215,9 @@ bool PinManager::isPinOk(byte gpio, bool output) // GPIO46 is input only and pulled down #else + Serial.printf("GPIO TEST %d\n\r", gpio); if (strncmp_P(PSTR("ESP32-U4WDH"), ESP.getChipModel(), 11) == 0) { + Serial.println("U4WDH"); // this chip has 4 MB of internal Flash and different packaging, so available pins are different! if ((gpio == 1) || (gpio == 3) || ((gpio >= 6) && (gpio <= 8)) || (gpio == 11) || (gpio == 16) || (gpio == 17) || (gpio == 20) || From 35d92f43c050c0c5afd8fd4cc41270da99b283f7 Mon Sep 17 00:00:00 2001 From: ladyada Date: Thu, 2 Jan 2025 20:54:19 -0500 Subject: [PATCH 033/125] >sigh< https://github.com/espressif/arduino-esp32/issues/10683 --- wled00/pin_manager.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 7ee1076bd..aa2dcf069 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -215,9 +215,8 @@ bool PinManager::isPinOk(byte gpio, bool output) // GPIO46 is input only and pulled down #else - Serial.printf("GPIO TEST %d\n\r", gpio); - if (strncmp_P(PSTR("ESP32-U4WDH"), ESP.getChipModel(), 11) == 0) { - Serial.println("U4WDH"); + if ((strncmp_P(PSTR("ESP32-U4WDH"), ESP.getChipModel(), 11) == 0) || // this is the correct identifier, but.... + (strncmp_P(PSTR("ESP32-PICO-D2"), ESP.getChipModel(), 13) == 0)) { // https://github.com/espressif/arduino-esp32/issues/10683 // this chip has 4 MB of internal Flash and different packaging, so available pins are different! if ((gpio == 1) || (gpio == 3) || ((gpio >= 6) && (gpio <= 8)) || (gpio == 11) || (gpio == 16) || (gpio == 17) || (gpio == 20) || From dcf89e0dbd428c6cb658e94854edd2969590f4ee Mon Sep 17 00:00:00 2001 From: ladyada Date: Fri, 3 Jan 2025 16:37:26 -0500 Subject: [PATCH 034/125] simplify logic --- wled00/pin_manager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index aa2dcf069..c37b19367 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -218,16 +218,16 @@ bool PinManager::isPinOk(byte gpio, bool output) if ((strncmp_P(PSTR("ESP32-U4WDH"), ESP.getChipModel(), 11) == 0) || // this is the correct identifier, but.... (strncmp_P(PSTR("ESP32-PICO-D2"), ESP.getChipModel(), 13) == 0)) { // https://github.com/espressif/arduino-esp32/issues/10683 // this chip has 4 MB of internal Flash and different packaging, so available pins are different! - if ((gpio == 1) || (gpio == 3) || ((gpio >= 6) && (gpio <= 8)) || - (gpio == 11) || (gpio == 16) || (gpio == 17) || (gpio == 20) || - (gpio == 24) || ((gpio >= 28) && (gpio <= 31))) + if (((gpio > 5) && (gpio < 9)) || (gpio == 11)) return false; } else { // for classic ESP32 (non-mini) modules, these are the SPI flash pins if (gpio > 5 && gpio < 12) return false; //SPI flash pins } - if (strncmp_P(PSTR("ESP32-PICO"), ESP.getChipModel(), 10) == 0 && (gpio == 16 || gpio == 17)) return false; // PICO-D4: gpio16+17 are in use for onboard SPI FLASH + if (((strncmp_P(PSTR("ESP32-PICO"), ESP.getChipModel(), 10) == 0) || + (strncmp_P(PSTR("ESP32-U4WDH"), ESP.getChipModel(), 11) == 0)) + && (gpio == 16 || gpio == 17)) return false; // PICO-D4/U4WDH: gpio16+17 are in use for onboard SPI FLASH if (gpio == 16 || gpio == 17) return !psramFound(); //PSRAM pins on ESP32 (these are IO) #endif if (output) return digitalPinCanOutput(gpio); From ae4de2782a10a1db6cb007558af9ec81f339366c Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 4 Jan 2025 08:07:11 +0100 Subject: [PATCH 035/125] DeepSleep usermod (#4190) * DeepSleep Usermod - sleep delay is now 1 by default, disabling sleep at powerup - renamed bootup variable to powerup - using delay counter for proper bootup - changed power-up and bootup logic - added fallback to always power-on at boot except at powerup - fixed bug in settings page --- usermods/deep_sleep/readme.md | 84 +++++++++ usermods/deep_sleep/usermod_deep_sleep.h | 227 +++++++++++++++++++++++ wled00/const.h | 1 + wled00/usermods_list.cpp | 9 + 4 files changed, 321 insertions(+) create mode 100644 usermods/deep_sleep/readme.md create mode 100644 usermods/deep_sleep/usermod_deep_sleep.h diff --git a/usermods/deep_sleep/readme.md b/usermods/deep_sleep/readme.md new file mode 100644 index 000000000..006aa31fd --- /dev/null +++ b/usermods/deep_sleep/readme.md @@ -0,0 +1,84 @@ +# Deep Sleep usermod + +This usermod unleashes the low power capabilities of th ESP: when you power off your LEDs (using the UI power button or a macro) the ESP will be put into deep sleep mode, reducing power consumption to a minimum. +During deep sleep the ESP is shut down completely: no WiFi, no CPU, no outputs. The only way to wake it up is to use an external signal or a button. Once it wakes from deep sleep it reboots so ***make sure to use a boot-up preset.*** + +# A word of warning + +When you disable the WLED option 'Turn LEDs on after power up/reset' and 'DelaySleep' is set to zero the ESP will go into deep sleep directly after power-up and only start WLED after it has been woken up. +If the ESP can not be awoken from deep sleep due to a wrong configuration it has to be factory reset, disabling sleep at power-up. There is no other way to wake it up. + +# Power Consumption in deep sleep + +The current drawn by the ESP in deep sleep mode depends on the type and is in the range of 5uA-20uA (as in micro Amperes): +- ESP32: 10uA +- ESP32 S3: 8uA +- ESP32 S2: 20uA +- ESP32 C3: 5uA +- ESP8266: 20uA (not supported in this usermod) + +However, there is usually additional components on a controller that increase the value: +- Power LED: the power LED on a ESP board draws 500uA - 1mA +- LDO: the voltage regulator also draws idle current. Depending on the type used this can be around 50uA up to 10mA (LM1117). Special low power LDOs with very low idle currents do exist +- Digital LEDs: WS2812 for example draw a current of about 1mA per LED. To make good use of this usermod it is required to power them off using MOSFETs or a Relay + +For lowest power consumption, remove the Power LED and make sure your board does not use an LM1117. On a ESP32 C3 Supermini with the power LED removed (no other modifications) powered through the 5V pin I measured a current draw of 50uA in deep sleep. + +# Useable GPIOs + +The GPIOs that can be used to wake the ESP from deep sleep are limited. Only pins connected to the internal RTC unit can be used: + +- ESP32: GPIO 0, 2, 4, 12-15, 25-39 +- ESP32 S3: GPIO 0-21 +- ESP32 S2: GPIO 0-21 +- ESP32 C3: GPIO 0-5 +- ESP8266 is not supported in this usermod + +You can however use the selected wake-up pin normally in WLED, it only gets activated as a wake-up pin when your LEDs are powered down. + +# Limitations + +To keep this usermod simple and easy to use, it is a very basic implementation of the low-power capabilities provided by the ESP. If you need more advanced control you are welcome to implement your own version based on this usermod. + +## Usermod installation + +Use `#define USERMOD_DEEP_SLEEP` in wled.h or `-D USERMOD_DEEP_SLEEP` in your platformio.ini. Settings can be changed in the usermod config UI. + +### Define Settings + +There are five parameters you can set: + +- GPIO: the pin to use for wake-up +- WakeWhen High/Low: the pin state that triggers the wake-up +- Pull-up/down disable: enable or disable the internal pullup resistors during sleep (does not affect normal use while running) +- Wake after: if set larger than 0, ESP will automatically wake-up after this many seconds (Turn LEDs on after power up/reset is overriden, it will always turn on) +- Delay sleep: if set larger than 0, ESP will not go to sleep for this many seconds after you power it off. Timer is reset when switched back on during this time. + +To override the default settings, place the `#define` in wled.h or add `-D DEEPSLEEP_xxx` to your platformio_override.ini build flags + +* `DEEPSLEEP_WAKEUPPIN x` - define the pin to be used for wake-up, see list of useable pins above. The pin can be used normally as a button pin in WLED. +* `DEEPSLEEP_WAKEWHENHIGH` - if defined, wakes up when pin goes high (default is low) +* `DEEPSLEEP_DISABLEPULL` - if defined, internal pullup/pulldown is disabled in deep sleep (default is ebnabled) +* `DEEPSLEEP_WAKEUPINTERVAL` - number of seconds after which a wake-up happens automatically, sooner if button is pressed. 0 = never. accuracy is about 2% +* `DEEPSLEEP_DELAY` - delay between power-off and sleep + +example for env build flags: + `-D USERMOD_DEEP_SLEEP` + `-D DEEPSLEEP_WAKEUPPIN=4` + `-D DEEPSLEEP_DISABLEPULL=0` ;enable pull-up/down resistors by default + `-D DEEPSLEEP_WAKEUPINTERVAL=43200` ;wake up after 12 hours (or when button is pressed) + +### Hardware Setup + +To wake from deep-sleep an external trigger signal on the configured GPIO is required. When using timed-only wake-up, use a GPIO that has an on-board pull-up resistor (GPIO0 on most boards). When using push-buttons it is highly recommended to use an external pull-up resistor: not all IO's on all devices have properly working internal resistors. + +Using sensors like PIR, IR, touch sensors or any other sensor with a digital output can be used instead of a button. + +now go on and save some power +@dedehai + +## Change log +2024-09 +* Initial version +2024-10 +* Changed from #define configuration to UI configuration \ No newline at end of file diff --git a/usermods/deep_sleep/usermod_deep_sleep.h b/usermods/deep_sleep/usermod_deep_sleep.h new file mode 100644 index 000000000..7f4efd5ca --- /dev/null +++ b/usermods/deep_sleep/usermod_deep_sleep.h @@ -0,0 +1,227 @@ +#pragma once + +#include "wled.h" +#include "driver/rtc_io.h" + +#ifdef ESP8266 +#error The "Deep Sleep" usermod does not support ESP8266 +#endif + +#ifndef DEEPSLEEP_WAKEUPPIN +#define DEEPSLEEP_WAKEUPPIN 0 +#endif +#ifndef DEEPSLEEP_WAKEWHENHIGH +#define DEEPSLEEP_WAKEWHENHIGH 0 +#endif +#ifndef DEEPSLEEP_DISABLEPULL +#define DEEPSLEEP_DISABLEPULL 1 +#endif +#ifndef DEEPSLEEP_WAKEUPINTERVAL +#define DEEPSLEEP_WAKEUPINTERVAL 0 +#endif +#ifndef DEEPSLEEP_DELAY +#define DEEPSLEEP_DELAY 1 +#endif + +RTC_DATA_ATTR bool powerup = true; // variable in RTC data persists on a reboot + +class DeepSleepUsermod : public Usermod { + + private: + + bool enabled = true; + bool initDone = false; + uint8_t wakeupPin = DEEPSLEEP_WAKEUPPIN; + uint8_t wakeWhenHigh = DEEPSLEEP_WAKEWHENHIGH; // wake up when pin goes high if 1, triggers on low if 0 + bool noPull = true; // use pullup/pulldown resistor + int wakeupAfter = DEEPSLEEP_WAKEUPINTERVAL; // in seconds, <=0: button only + int sleepDelay = DEEPSLEEP_DELAY; // in seconds, 0 = immediate + int delaycounter = 5; // delay deep sleep at bootup until preset settings are applied + uint32_t lastLoopTime = 0; + // string that are used multiple time (this will save some flash memory) + static const char _name[]; + static const char _enabled[]; + + bool pin_is_valid(uint8_t wakePin) { + #ifdef CONFIG_IDF_TARGET_ESP32 //ESP32: GPIOs 0,2,4, 12-15, 25-39 can be used for wake-up + if (wakePin == 0 || wakePin == 2 || wakePin == 4 || (wakePin >= 12 && wakePin <= 15) || (wakePin >= 25 && wakePin <= 27) || (wakePin >= 32 && wakePin <= 39)) { + return true; + } + #endif + #if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32S2) //ESP32 S3 & S3: GPIOs 0-21 can be used for wake-up + if (wakePin <= 21) { + return true; + } + #endif + #ifdef CONFIG_IDF_TARGET_ESP32C3 // ESP32 C3: GPIOs 0-5 can be used for wake-up + if (wakePin <= 5) { + return true; + } + #endif + DEBUG_PRINTLN(F("Error: unsupported deep sleep wake-up pin")); + return false; + } + + public: + + inline void enable(bool enable) { enabled = enable; } // Enable/Disable the usermod + inline bool isEnabled() { return enabled; } //Get usermod enabled/disabled state + + // setup is called at boot (or in this case after every exit of sleep mode) + void setup() { + //TODO: if the de-init of RTC pins is required to do it could be done here + //rtc_gpio_deinit(wakeupPin); + initDone = true; + } + + void loop() { + if (!enabled || !offMode) { // disabled or LEDs are on + lastLoopTime = 0; // reset timer + return; + } + + if (sleepDelay > 0) { + if(lastLoopTime == 0) lastLoopTime = millis(); // initialize + if (millis() - lastLoopTime < sleepDelay * 1000) { + return; // wait until delay is over + } + } + + if(powerup == false && delaycounter) { // delay sleep in case a preset is being loaded and turnOnAtBoot is disabled (handleIO() does enable offMode temporarily in this case) + delaycounter--; + if(delaycounter == 2 && offMode) { // force turn on, no matter the settings (device is bricked if user set sleepDelay=0, no bootup preset and turnOnAtBoot=false) + if (briS == 0) bri = 10; // turn on at low brightness + else bri = briS; + strip.setBrightness(bri); // needed to make handleIO() not turn off LEDs (really? does not help in bootup preset) + offMode = false; + applyPresetWithFallback(0, CALL_MODE_INIT, FX_MODE_STATIC, 0); // try to apply preset 0, fallback to static + if (rlyPin >= 0) { + digitalWrite(rlyPin, (rlyMde ? HIGH : LOW)); // turn relay on TODO: this should be done by wled, what function to call? + } + } + return; + } + + DEBUG_PRINTLN(F("DeepSleep UM: entering deep sleep...")); + powerup = false; // turn leds on in all subsequent bootups (overrides Turn LEDs on after power up/reset' at reboot) + if(!pin_is_valid(wakeupPin)) return; + esp_err_t halerror = ESP_OK; + pinMode(wakeupPin, INPUT); // make sure GPIO is input with pullup/pulldown disabled + esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL); //disable all wake-up sources (just in case) + + if(wakeupAfter) + esp_sleep_enable_timer_wakeup((uint64_t)wakeupAfter * (uint64_t)1e6); //sleep for x seconds + + #if defined(CONFIG_IDF_TARGET_ESP32C3) // ESP32 C3 + if(noPull) + gpio_sleep_set_pull_mode((gpio_num_t)wakeupPin, GPIO_FLOATING); + else { // enable pullup/pulldown resistor + if(wakeWhenHigh) + gpio_sleep_set_pull_mode((gpio_num_t)wakeupPin, GPIO_PULLDOWN_ONLY); + else + gpio_sleep_set_pull_mode((gpio_num_t)wakeupPin, GPIO_PULLUP_ONLY); + } + if(wakeWhenHigh) + halerror = esp_deep_sleep_enable_gpio_wakeup(1<(0 = never)');")); + oappend(SET_F("addInfo('DeepSleep:delaySleep',1,'seconds (0 = sleep at powerup)');")); // first string is suffix, second string is prefix + } + + /* + * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). + * This could be used in the future for the system to determine whether your usermod is installed. + */ + uint16_t getId() { + return USERMOD_ID_DEEP_SLEEP; + } + +}; + +// add more strings here to reduce flash memory usage +const char DeepSleepUsermod::_name[] PROGMEM = "DeepSleep"; +const char DeepSleepUsermod::_enabled[] PROGMEM = "enabled"; \ No newline at end of file diff --git a/wled00/const.h b/wled00/const.h index 928b150da..bdd20beba 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -203,6 +203,7 @@ #define USERMOD_ID_LD2410 52 //Usermod "usermod_ld2410.h" #define USERMOD_ID_POV_DISPLAY 53 //Usermod "usermod_pov_display.h" #define USERMOD_ID_PIXELS_DICE_TRAY 54 //Usermod "pixels_dice_tray.h" +#define USERMOD_ID_DEEP_SLEEP 55 //Usermod "usermod_deep_sleep.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index 3283e013b..627fa6308 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -242,6 +242,11 @@ #include "../usermods/LD2410_v2/usermod_ld2410.h" #endif + +#ifdef USERMOD_DEEP_SLEEP + #include "../usermods/deep_sleep/usermod_deep_sleep.h" +#endif + void registerUsermods() { /* @@ -470,4 +475,8 @@ void registerUsermods() #ifdef USERMOD_POV_DISPLAY UsermodManager::add(new PovDisplayUsermod()); #endif + + #ifdef USERMOD_DEEP_SLEEP + usermods.add(new DeepSleepUsermod()); + #endif } From 27e98147ef481d6cef7d9cdbc153b40402c43104 Mon Sep 17 00:00:00 2001 From: netmindz Date: Sun, 5 Jan 2025 18:49:51 +0000 Subject: [PATCH 036/125] Swap from dev to alpha for our current work in progress builds whilst working towards the next version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 91e2a615d..68260982e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.16.0-dev", + "version": "0.16.0-alpha", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { From 3adcbb7904ffca360e00caf2b0ebf42dac90089b Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Mon, 6 Jan 2025 22:24:28 +0100 Subject: [PATCH 037/125] Playlist output glitchfix update: found it also happens on S3 (#4462) * Fix output glitches when playlist changes preset update: glitches also happen on S3 --- wled00/presets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/presets.cpp b/wled00/presets.cpp index fb09d48a4..1abcb52b9 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -164,7 +164,7 @@ void handlePresets() DEBUG_PRINTF_P(PSTR("Applying preset: %u\n"), (unsigned)tmpPreset); - #if defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32C3) + #if defined(ARDUINO_ARCH_ESP32S3) || defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32C3) unsigned long start = millis(); while (strip.isUpdating() && millis() - start < FRAMETIME_FIXED) yield(); // wait for strip to finish updating, accessing FS during sendout causes glitches #endif From 50d505b8963b053e612f8e82d99a4d0fbf065659 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 8 Jan 2025 08:50:33 +0000 Subject: [PATCH 038/125] Nightly release --- .github/workflows/nightly.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/nightly.yml diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 000000000..58e31472a --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,31 @@ + +name: Deploy Nightly +on: + # This can be used to automatically publish nightlies at UTC nighttime + schedule: + - cron: '0 2 * * *' # run at 2 AM UTC + # This can be used to allow manually triggering nightlies from the web interface + workflow_dispatch: + +jobs: + wled_build: + uses: ./.github/workflows/build.yml + nightly: + name: Deploy nightly + runs-on: ubuntu-latest + needs: wled_build + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + merge-multiple: true + + - name: Deploy release + uses: WebFreak001/deploy-nightly@v3.2.0 + with: + upload_url: https://uploads.github.com/repos/Aircoookie/WLED/releases/190052071/assets{?name,label} + release_id: 190052071 + asset_path: ./WLED_*_ESP32.bin + asset_name: WLED_$$_ESP32.bin + asset_content_type: application/octet-stream + max_releases: 7 From 7d29edf6f46fe6b9bdffb482b55f18e6765ace2a Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 8 Jan 2025 09:01:15 +0000 Subject: [PATCH 039/125] Nightly release - fix filename --- .github/workflows/nightly.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 58e31472a..d3c20d65f 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -19,13 +19,14 @@ jobs: uses: actions/download-artifact@v4 with: merge-multiple: true - + - name: Show Files + run: ls -la - name: Deploy release uses: WebFreak001/deploy-nightly@v3.2.0 with: upload_url: https://uploads.github.com/repos/Aircoookie/WLED/releases/190052071/assets{?name,label} release_id: 190052071 - asset_path: ./WLED_*_ESP32.bin - asset_name: WLED_$$_ESP32.bin + asset_path: firmware-esp32dev.zip + asset_name: WLED_$$_ESP32.zip asset_content_type: application/octet-stream max_releases: 7 From 438c5d9909a7f1a676d13d3840aedb08a5e680d4 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 8 Jan 2025 09:05:28 +0000 Subject: [PATCH 040/125] Nightly release - fix filename --- .github/workflows/nightly.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index d3c20d65f..f350085b5 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -26,7 +26,7 @@ jobs: with: upload_url: https://uploads.github.com/repos/Aircoookie/WLED/releases/190052071/assets{?name,label} release_id: 190052071 - asset_path: firmware-esp32dev.zip - asset_name: WLED_$$_ESP32.zip + asset_path: WLED_0.16.0-alpha_ESP32.bin + asset_name: WLED_$$_ESP32.bin asset_content_type: application/octet-stream max_releases: 7 From 0c431d9746e92aea36caf354880060fe5f172f26 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 8 Jan 2025 09:22:00 +0000 Subject: [PATCH 041/125] Nightly release - fix release --- .github/workflows/nightly.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f350085b5..8d15a7f22 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -24,8 +24,8 @@ jobs: - name: Deploy release uses: WebFreak001/deploy-nightly@v3.2.0 with: - upload_url: https://uploads.github.com/repos/Aircoookie/WLED/releases/190052071/assets{?name,label} - release_id: 190052071 + upload_url: https://uploads.github.com/repos/Aircoookie/WLED/releases/193651519/assets{?name,label} + release_id: 193651519 asset_path: WLED_0.16.0-alpha_ESP32.bin asset_name: WLED_$$_ESP32.bin asset_content_type: application/octet-stream From 5e3b4c3a110bddadfbf2fedf7ae9f30b9e8ea4f5 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 8 Jan 2025 09:32:12 +0000 Subject: [PATCH 042/125] Nightly release - swap action --- .github/workflows/nightly.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 8d15a7f22..918e2ab0d 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -21,12 +21,14 @@ jobs: merge-multiple: true - name: Show Files run: ls -la - - name: Deploy release - uses: WebFreak001/deploy-nightly@v3.2.0 + - name: Update Nightly Release + uses: andelf/nightly-release@main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - upload_url: https://uploads.github.com/repos/Aircoookie/WLED/releases/193651519/assets{?name,label} - release_id: 193651519 - asset_path: WLED_0.16.0-alpha_ESP32.bin - asset_name: WLED_$$_ESP32.bin - asset_content_type: application/octet-stream - max_releases: 7 + tag_name: nightly + name: 'Nightly Release $$' + prerelease: true + body: 'Nightly build' + files: | + ./*.bin \ No newline at end of file From 1750512477fb009b552807a01390be7add7c535e Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 8 Jan 2025 09:50:54 +0000 Subject: [PATCH 043/125] Nightly release - add changelog --- .github/workflows/nightly.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 918e2ab0d..d67706dd7 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -21,6 +21,12 @@ jobs: merge-multiple: true - name: Show Files run: ls -la + - name: "✏️ Generate release changelog" + id: changelog + uses: janheinrichmerker/action-github-changelog-generator@v2.3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + sinceTag: nightly - name: Update Nightly Release uses: andelf/nightly-release@main env: @@ -29,6 +35,6 @@ jobs: tag_name: nightly name: 'Nightly Release $$' prerelease: true - body: 'Nightly build' + body: ${{ steps.changelog.outputs.changelog }} files: | ./*.bin \ No newline at end of file From d0e99923fdc9af8f5c32888514e9b8bf559ffbca Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 8 Jan 2025 09:55:41 +0000 Subject: [PATCH 044/125] Nightly release - add changelog --- .github/workflows/build.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e5fdfc5a3..af523074e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,3 @@ -name: WLED Build - # Only included into other workflows on: workflow_call: From 08b263bf4e8b9443aeaab0d00810ce522a1391d4 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 8 Jan 2025 09:55:52 +0000 Subject: [PATCH 045/125] Nightly release - add changelog --- .github/workflows/nightly.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index d67706dd7..4503d63bb 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -27,6 +27,7 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} sinceTag: nightly + onlyLastTag: true - name: Update Nightly Release uses: andelf/nightly-release@main env: From f626dfb7b7d636127b29698f24a98afe79dd7f85 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 8 Jan 2025 10:00:39 +0000 Subject: [PATCH 046/125] Nightly release - add changelog since last release --- .github/workflows/build.yml | 2 ++ .github/workflows/nightly.yml | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index af523074e..e5fdfc5a3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,3 +1,5 @@ +name: WLED Build + # Only included into other workflows on: workflow_call: diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 4503d63bb..138730058 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -26,8 +26,7 @@ jobs: uses: janheinrichmerker/action-github-changelog-generator@v2.3 with: token: ${{ secrets.GITHUB_TOKEN }} - sinceTag: nightly - onlyLastTag: true + sinceTag: v0.15.0 - name: Update Nightly Release uses: andelf/nightly-release@main env: From bb0c0af1893e774f8401515083097caf0429c381 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 9 Jan 2025 22:41:45 +0100 Subject: [PATCH 047/125] added a delay after switching relay (#4474) - helps to stabilize power on the LEDs before sending data --- wled00/button.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/wled00/button.cpp b/wled00/button.cpp index 6f9c84560..4e063c120 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -375,6 +375,7 @@ void handleIO() if (rlyPin>=0) { pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT); digitalWrite(rlyPin, rlyMde); + delay(50); // wait for relay to switch and power to stabilize } offMode = false; } From ec7a7f4c2522f9958636ceca802b9f6ed24ff64a Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Fri, 10 Jan 2025 16:49:15 +0000 Subject: [PATCH 048/125] Set build version during nightly build --- .github/workflows/nightly.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 138730058..acf974ff1 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -8,7 +8,15 @@ on: workflow_dispatch: jobs: + prep: + runs-on: ubuntu-latest + steps: + - name: Set Version + run: | + VERSION=`date +%y%m%d0` + sed -i -r -e "s/#define VERSION .+/#define VERSION $VERSION/" wled00/wled.h wled_build: + needs: prep uses: ./.github/workflows/build.yml nightly: name: Deploy nightly From 471bd83eb2dd9fa3636d89b884378cb6a65e62ad Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Fri, 10 Jan 2025 16:53:20 +0000 Subject: [PATCH 049/125] Revert "Set build version during nightly build" - doesn't work as source files not yet checked out This reverts commit ec7a7f4c2522f9958636ceca802b9f6ed24ff64a. --- .github/workflows/nightly.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index acf974ff1..138730058 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -8,15 +8,7 @@ on: workflow_dispatch: jobs: - prep: - runs-on: ubuntu-latest - steps: - - name: Set Version - run: | - VERSION=`date +%y%m%d0` - sed -i -r -e "s/#define VERSION .+/#define VERSION $VERSION/" wled00/wled.h wled_build: - needs: prep uses: ./.github/workflows/build.yml nightly: name: Deploy nightly From 2aab617c2e2d3f49ba786532b0e44508659f1fae Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Fri, 10 Jan 2025 16:56:07 +0000 Subject: [PATCH 050/125] Reapply "Set build version during nightly build" - doesn't work as source This reverts commit 471bd83eb2dd9fa3636d89b884378cb6a65e62ad. --- .github/workflows/nightly.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 138730058..acf974ff1 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -8,7 +8,15 @@ on: workflow_dispatch: jobs: + prep: + runs-on: ubuntu-latest + steps: + - name: Set Version + run: | + VERSION=`date +%y%m%d0` + sed -i -r -e "s/#define VERSION .+/#define VERSION $VERSION/" wled00/wled.h wled_build: + needs: prep uses: ./.github/workflows/build.yml nightly: name: Deploy nightly From 4a56c92e7b87703166e0ef6dfa3b54f68ffeb237 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Fri, 10 Jan 2025 16:59:08 +0000 Subject: [PATCH 051/125] Set build version during nightly build --- .github/workflows/build.yml | 2 ++ .github/workflows/nightly.yml | 1 + 2 files changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2bac314f3..5a25bb4dd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + clean: false - uses: actions/setup-python@v5 with: python-version: '3.12' diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index acf974ff1..6bc577500 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -11,6 +11,7 @@ jobs: prep: runs-on: ubuntu-latest steps: + - uses: actions/checkout@v4 - name: Set Version run: | VERSION=`date +%y%m%d0` From 3502a391811df0e309cb83063dddd77de2e056ef Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Fri, 10 Jan 2025 17:09:11 +0000 Subject: [PATCH 052/125] Revert "Set build version during nightly build" This reverts commit 4a56c92e7b87703166e0ef6dfa3b54f68ffeb237. --- .github/workflows/build.yml | 2 -- .github/workflows/nightly.yml | 1 - 2 files changed, 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5a25bb4dd..2bac314f3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,8 +11,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - with: - clean: false - uses: actions/setup-python@v5 with: python-version: '3.12' diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 6bc577500..acf974ff1 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -11,7 +11,6 @@ jobs: prep: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - name: Set Version run: | VERSION=`date +%y%m%d0` From c4e697d797e1d7eef72f39de72be3268c9a14535 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Fri, 10 Jan 2025 17:09:25 +0000 Subject: [PATCH 053/125] Revert "Reapply "Set build version during nightly build" - doesn't work as source" This reverts commit 2aab617c2e2d3f49ba786532b0e44508659f1fae. --- .github/workflows/nightly.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index acf974ff1..138730058 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -8,15 +8,7 @@ on: workflow_dispatch: jobs: - prep: - runs-on: ubuntu-latest - steps: - - name: Set Version - run: | - VERSION=`date +%y%m%d0` - sed -i -r -e "s/#define VERSION .+/#define VERSION $VERSION/" wled00/wled.h wled_build: - needs: prep uses: ./.github/workflows/build.yml nightly: name: Deploy nightly From 3c19692312df2eda362db394faeae5f92bb5f747 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Mon, 13 Jan 2025 10:58:32 +0000 Subject: [PATCH 054/125] Cleanup copy-paste of platform and platform_packages - use the proper common esp32_idf_V4 --- platformio.ini | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/platformio.ini b/platformio.ini index 0870cde9d..9a72b75d6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -288,8 +288,8 @@ board_build.partitions = ${esp32.default_partitions} ;; default partioning for [esp32s2] ;; generic definitions for all ESP32-S2 boards -platform = espressif32@ ~6.3.2 -platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) +platform = ${esp32_idf_V4.platform} +platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} build_flags = -g -DARDUINO_ARCH_ESP32 @@ -308,8 +308,8 @@ board_build.partitions = ${esp32.default_partitions} ;; default partioning for [esp32c3] ;; generic definitions for all ESP32-C3 boards -platform = espressif32@ ~6.3.2 -platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) +platform = ${esp32_idf_V4.platform} +platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} build_flags = -g -DARDUINO_ARCH_ESP32 @@ -327,8 +327,8 @@ board_build.partitions = ${esp32.default_partitions} ;; default partioning for [esp32s3] ;; generic definitions for all ESP32-S3 boards -platform = espressif32@ ~6.3.2 -platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) +platform = ${esp32_idf_V4.platform} +platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} build_flags = -g -DESP32 From 5b5e4157e3dbf170416f9e7b18fe7398798d15b3 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Mon, 13 Jan 2025 11:12:14 +0000 Subject: [PATCH 055/125] Add esp32dev_v4 env --- platformio.ini | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 9a72b75d6..b7395dded 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,7 +10,7 @@ # ------------------------------------------------------------------------------ # CI/release binaries -default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover +default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover src_dir = ./wled00 data_dir = ./wled00/data @@ -432,6 +432,18 @@ lib_deps = ${esp32.lib_deps} monitor_filters = esp32_exception_decoder board_build.partitions = ${esp32.default_partitions} +[env:esp32dev_V4] +board = esp32dev +platform = ${esp32_idf_V4.platform} +platform_packages = ${esp32_idf_V4.platform_packages} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_V4\" #-D WLED_DISABLE_BROWNOUT_DET + ${esp32.AR_build_flags} +lib_deps = ${esp32_idf_V4.lib_deps} + ${esp32.AR_lib_deps} +monitor_filters = esp32_exception_decoder +board_build.partitions = ${esp32.default_partitions} + [env:esp32dev_8M] board = esp32dev platform = ${esp32_idf_V4.platform} From 29ee551b068e12f353079bb4fb8b5bdd4c93709f Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Mon, 13 Jan 2025 15:38:25 +0000 Subject: [PATCH 056/125] Swap to tasmota/platform-espressif32 --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index b7395dded..f3e11ce3c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -273,7 +273,7 @@ board_build.partitions = ${esp32.default_partitions} ;; default partioning for ;; ;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly. ;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio. -platform = espressif32@ ~6.3.2 +platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.06.02/platform-espressif32.zip ;; Tasmota Arduino Core 2.0.9 with IPv6 support, based on IDF 4.4.4 platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) build_unflags = ${common.build_unflags} build_flags = -g @@ -435,7 +435,7 @@ board_build.partitions = ${esp32.default_partitions} [env:esp32dev_V4] board = esp32dev platform = ${esp32_idf_V4.platform} -platform_packages = ${esp32_idf_V4.platform_packages} +; platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_V4\" #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} From 5bd0a261260f0130550cb431da24fe7935c53b09 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Mon, 13 Jan 2025 16:39:58 +0000 Subject: [PATCH 057/125] Remove platform_package override --- platformio.ini | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/platformio.ini b/platformio.ini index f3e11ce3c..6fdaef321 100644 --- a/platformio.ini +++ b/platformio.ini @@ -273,8 +273,9 @@ board_build.partitions = ${esp32.default_partitions} ;; default partioning for ;; ;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly. ;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio. + +;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.06.02/platform-espressif32.zip ;; Tasmota Arduino Core 2.0.9 with IPv6 support, based on IDF 4.4.4 -platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) build_unflags = ${common.build_unflags} build_flags = -g -Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one @@ -289,7 +290,6 @@ board_build.partitions = ${esp32.default_partitions} ;; default partioning for [esp32s2] ;; generic definitions for all ESP32-S2 boards platform = ${esp32_idf_V4.platform} -platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} build_flags = -g -DARDUINO_ARCH_ESP32 @@ -309,7 +309,6 @@ board_build.partitions = ${esp32.default_partitions} ;; default partioning for [esp32c3] ;; generic definitions for all ESP32-C3 boards platform = ${esp32_idf_V4.platform} -platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} build_flags = -g -DARDUINO_ARCH_ESP32 @@ -328,7 +327,6 @@ board_build.partitions = ${esp32.default_partitions} ;; default partioning for [esp32s3] ;; generic definitions for all ESP32-S3 boards platform = ${esp32_idf_V4.platform} -platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} build_flags = -g -DESP32 @@ -435,7 +433,6 @@ board_build.partitions = ${esp32.default_partitions} [env:esp32dev_V4] board = esp32dev platform = ${esp32_idf_V4.platform} -; platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_V4\" #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} @@ -447,7 +444,6 @@ board_build.partitions = ${esp32.default_partitions} [env:esp32dev_8M] board = esp32dev platform = ${esp32_idf_V4.platform} -platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_8M\" #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} @@ -463,7 +459,6 @@ board_upload.maximum_size = 8388608 [env:esp32dev_16M] board = esp32dev platform = ${esp32_idf_V4.platform} -platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_16M\" #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} @@ -506,7 +501,6 @@ board_build.partitions = ${esp32.default_partitions} [env:esp32_wrover] extends = esp32_idf_V4 platform = ${esp32_idf_V4.platform} -platform_packages = ${esp32_idf_V4.platform_packages} board = ttgo-t7-v14-mini32 board_build.f_flash = 80000000L board_build.flash_mode = qio From f240a33935ce168608dfa007ecdc331ff97efc98 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Mon, 13 Jan 2025 18:33:30 +0000 Subject: [PATCH 058/125] Remove platform_package override --- platformio.ini | 6 ------ 1 file changed, 6 deletions(-) diff --git a/platformio.ini b/platformio.ini index 6fdaef321..73a76f05e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -516,7 +516,6 @@ lib_deps = ${esp32_idf_V4.lib_deps} [env:esp32c3dev] extends = esp32c3 platform = ${esp32c3.platform} -platform_packages = ${esp32c3.platform_packages} framework = arduino board = esp32-c3-devkitm-1 board_build.partitions = ${esp32.default_partitions} @@ -534,7 +533,6 @@ lib_deps = ${esp32c3.lib_deps} board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB platform = ${esp32s3.platform} -platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_16MB_opi\" @@ -557,7 +555,6 @@ monitor_filters = esp32_exception_decoder board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB platform = ${esp32s3.platform} -platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_8MB_opi\" @@ -577,7 +574,6 @@ monitor_filters = esp32_exception_decoder ;; For ESP32-S3 WROOM-2, a.k.a. ESP32-S3 DevKitC-1 v1.1 ;; with >= 16MB FLASH and >= 8MB PSRAM (memory_type: opi_opi) platform = ${esp32s3.platform} -platform_packages = ${esp32s3.platform_packages} board = esp32s3camlcd ;; this is the only standard board with "opi_opi" board_build.arduino.memory_type = opi_opi upload_speed = 921600 @@ -604,7 +600,6 @@ monitor_filters = esp32_exception_decoder ;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi) board = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM platform = ${esp32s3.platform} -platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_4M_qspi\" @@ -622,7 +617,6 @@ monitor_filters = esp32_exception_decoder [env:lolin_s2_mini] platform = ${esp32s2.platform} -platform_packages = ${esp32s2.platform_packages} board = lolin_s2_mini board_build.partitions = ${esp32.default_partitions} board_build.flash_mode = qio From 7e9f7d410139b6b625610713a4be2e0945f3c50c Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Mon, 13 Jan 2025 18:39:21 +0000 Subject: [PATCH 059/125] set board_build.flash_mode to fix missing sdkconfig.h --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index 73a76f05e..d6788a0f5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -440,6 +440,7 @@ lib_deps = ${esp32_idf_V4.lib_deps} ${esp32.AR_lib_deps} monitor_filters = esp32_exception_decoder board_build.partitions = ${esp32.default_partitions} +board_build.flash_mode = qio ;; TODO: is this the correct flash mode? [env:esp32dev_8M] board = esp32dev From 1ed82426a1c23745a81541e0f3e7a7d83e6a5636 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Mon, 13 Jan 2025 19:30:35 +0000 Subject: [PATCH 060/125] Add esp32dev back to default_envs --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index b7395dded..63433ca7b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,7 +10,7 @@ # ------------------------------------------------------------------------------ # CI/release binaries -default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover +default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover src_dir = ./wled00 data_dir = ./wled00/data From ca80d0489b552a35be309d47ee3eb7ed5b4f5ad3 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Mon, 13 Jan 2025 20:30:10 +0000 Subject: [PATCH 061/125] Add env:esp8266_2m_tasmota --- platformio.ini | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index d6788a0f5..b27525ab1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,7 +10,7 @@ # ------------------------------------------------------------------------------ # CI/release binaries -default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover +default_envs = nodemcuv2, esp8266_2m, esp8266_2m_tasmota, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover src_dir = ./wled00 data_dir = ./wled00/data @@ -381,6 +381,14 @@ build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02\" lib_deps = ${esp8266.lib_deps} +[env:esp8266_2m_tasmota] +board = esp_wroom_02 +platform = https://github.com/tasmota/platform-espressif8266#2024.09.00 ;; TODO: not sure this is the correct version +board_build.ldscript = ${common.ldscript_2m512k} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02_Tasmota\" +lib_deps = ${esp8266.lib_deps} + [env:esp8266_2m_compat] extends = env:esp8266_2m ;; using platform version and build options from WLED 0.14.0 From b421f7ae87aa0c76da34b12ba49dd6fc493e26b1 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Mon, 13 Jan 2025 20:56:48 +0000 Subject: [PATCH 062/125] Update to Tasmota Arduino Core 2.0.18 --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index b27525ab1..6db26b2e0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -275,7 +275,7 @@ board_build.partitions = ${esp32.default_partitions} ;; default partioning for ;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio. ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) -platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.06.02/platform-espressif32.zip ;; Tasmota Arduino Core 2.0.9 with IPv6 support, based on IDF 4.4.4 +platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.06.00/platform-espressif32.zip ;; Tasmota Arduino Core 2.0.18 with IPv6 support, based on IDF 4.4.8 build_unflags = ${common.build_unflags} build_flags = -g -Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one From 4e4f823141224435a220412f0c52d54fb64103e2 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Mon, 13 Jan 2025 20:57:49 +0000 Subject: [PATCH 063/125] Update comment --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 6db26b2e0..9e221724c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -274,7 +274,7 @@ board_build.partitions = ${esp32.default_partitions} ;; default partioning for ;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly. ;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio. -;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) +;; arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.06.00/platform-espressif32.zip ;; Tasmota Arduino Core 2.0.18 with IPv6 support, based on IDF 4.4.8 build_unflags = ${common.build_unflags} build_flags = -g From f920fdecfe7c67cbc700bacc9f81a4a8a315da5c Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Mon, 13 Jan 2025 21:02:28 +0000 Subject: [PATCH 064/125] Add esp32dev back to default_envs --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 9e221724c..701c90d7b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,7 +10,7 @@ # ------------------------------------------------------------------------------ # CI/release binaries -default_envs = nodemcuv2, esp8266_2m, esp8266_2m_tasmota, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover +default_envs = nodemcuv2, esp8266_2m, esp8266_2m_tasmota, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover src_dir = ./wled00 data_dir = ./wled00/data From 650853c17741323cc46be3652740f6b4877e54d9 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Mon, 13 Jan 2025 21:09:05 +0000 Subject: [PATCH 065/125] Use flash_mode = dio --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 701c90d7b..59709eb1b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -448,7 +448,7 @@ lib_deps = ${esp32_idf_V4.lib_deps} ${esp32.AR_lib_deps} monitor_filters = esp32_exception_decoder board_build.partitions = ${esp32.default_partitions} -board_build.flash_mode = qio ;; TODO: is this the correct flash mode? +board_build.flash_mode = dio [env:esp32dev_8M] board = esp32dev From 022e4986ee26c80f5fe60f71dc90ef88171c5950 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Tue, 14 Jan 2025 00:24:37 +0000 Subject: [PATCH 066/125] Revert "Update to Tasmota Arduino Core 2.0.18" - Frank says to stay on 2.0.9 This reverts commit b421f7ae87aa0c76da34b12ba49dd6fc493e26b1. --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 59709eb1b..76783dba8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -274,8 +274,8 @@ board_build.partitions = ${esp32.default_partitions} ;; default partioning for ;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly. ;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio. -;; arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them -platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.06.00/platform-espressif32.zip ;; Tasmota Arduino Core 2.0.18 with IPv6 support, based on IDF 4.4.8 +;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) +platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.06.02/platform-espressif32.zip ;; Tasmota Arduino Core 2.0.9 with IPv6 support, based on IDF 4.4.4 build_unflags = ${common.build_unflags} build_flags = -g -Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one From 4f4476b79f46791a4c146eaad0df80a23871447e Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Tue, 14 Jan 2025 11:53:10 +0000 Subject: [PATCH 067/125] Set new codename --- wled00/wled.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/wled.h b/wled00/wled.h index ae93d9548..533b12063 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -269,7 +269,7 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument; // Global Variable definitions WLED_GLOBAL char versionString[] _INIT(TOSTRING(WLED_VERSION)); WLED_GLOBAL char releaseString[] _INIT(WLED_RELEASE_NAME); // must include the quotes when defining, e.g -D WLED_RELEASE_NAME=\"ESP32_MULTI_USREMODS\" -#define WLED_CODENAME "Kōsen" +#define WLED_CODENAME "Niji" // AP and OTA default passwords (for maximum security change them!) WLED_GLOBAL char apPass[65] _INIT(WLED_AP_PASS); From bd00d012e22796a5a3580328f4cb3ace93de9230 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Tue, 14 Jan 2025 12:29:03 +0000 Subject: [PATCH 068/125] Remove esp8266_2m_tasmota as not a V4 change and no suitable tasmota build using the currently used Arduino Core version of 3.1.2 --- platformio.ini | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/platformio.ini b/platformio.ini index 59709eb1b..069d9c536 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,7 +10,7 @@ # ------------------------------------------------------------------------------ # CI/release binaries -default_envs = nodemcuv2, esp8266_2m, esp8266_2m_tasmota, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover +default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover src_dir = ./wled00 data_dir = ./wled00/data @@ -381,14 +381,6 @@ build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02\" lib_deps = ${esp8266.lib_deps} -[env:esp8266_2m_tasmota] -board = esp_wroom_02 -platform = https://github.com/tasmota/platform-espressif8266#2024.09.00 ;; TODO: not sure this is the correct version -board_build.ldscript = ${common.ldscript_2m512k} -build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02_Tasmota\" -lib_deps = ${esp8266.lib_deps} - [env:esp8266_2m_compat] extends = env:esp8266_2m ;; using platform version and build options from WLED 0.14.0 From bba5188594364f8e141288f708e7dfaf2b6b5c08 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Tue, 14 Jan 2025 12:43:06 +0000 Subject: [PATCH 069/125] Add the safe option of flash_mode for esp32c3dev, qio also possible --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index 48b8c152d..33e14799c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -528,6 +528,7 @@ build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME= upload_speed = 460800 build_unflags = ${common.build_unflags} lib_deps = ${esp32c3.lib_deps} +board_build.flash_mode = dio [env:esp32s3dev_16MB_opi] ;; ESP32-S3 development board, with 16MB FLASH and >= 8MB PSRAM (memory_type: qio_opi) From a37b953e7293a3af9a327fdc539829cab3b878ff Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Tue, 14 Jan 2025 12:44:40 +0000 Subject: [PATCH 070/125] Set flash_mode to qio for esp32c3dev to maintain current behaviour --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 33e14799c..9b9b693f5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -528,7 +528,7 @@ build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME= upload_speed = 460800 build_unflags = ${common.build_unflags} lib_deps = ${esp32c3.lib_deps} -board_build.flash_mode = dio +board_build.flash_mode = qio [env:esp32s3dev_16MB_opi] ;; ESP32-S3 development board, with 16MB FLASH and >= 8MB PSRAM (memory_type: qio_opi) From 881da25e8c6642c9df347b03f8d0253a1c1aee9d Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Tue, 14 Jan 2025 18:06:51 +0000 Subject: [PATCH 071/125] Add local defintion of lolin_s3_mini as missing from Tasmota platform --- boards/lolin_s3_mini.json | 47 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 boards/lolin_s3_mini.json diff --git a/boards/lolin_s3_mini.json b/boards/lolin_s3_mini.json new file mode 100644 index 000000000..7f55f0bde --- /dev/null +++ b/boards/lolin_s3_mini.json @@ -0,0 +1,47 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_qspi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_LOLIN_S3_MINI", + "-DARDUINO_USB_MODE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [ + [ + "0x303A", + "0x8167" + ] + ], + "mcu": "esp32s3", + "variant": "lolin_s3_mini" + }, + "connectivity": [ + "bluetooth", + "wifi" + ], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "WEMOS LOLIN S3 Mini", + "upload": { + "flash_size": "4MB", + "maximum_ram_size": 327680, + "maximum_size": 4194304, + "require_upload_port": true, + "speed": 460800 + }, + "url": "https://www.wemos.cc/en/latest/s3/index.html", + "vendor": "WEMOS" +} + \ No newline at end of file From 39b3e7e5079791e2da00eaa638a65079e1a15a4e Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 15 Jan 2025 15:17:56 +0100 Subject: [PATCH 072/125] BUGFIX in oscillate FX (#4494) effect was changed from int to uint but it relied on negative numbers. fixed by checking overflow and a cast. --- wled00/FX.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 7bf054581..dba59a5ce 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1827,7 +1827,7 @@ uint16_t mode_oscillate(void) { // if the counter has increased, move the oscillator by the random step if (it != SEGENV.step) oscillators[i].pos += oscillators[i].dir * oscillators[i].speed; oscillators[i].size = SEGLEN/(3+SEGMENT.intensity/8); - if((oscillators[i].dir == -1) && (oscillators[i].pos <= 0)) { + if((oscillators[i].dir == -1) && (oscillators[i].pos > SEGLEN << 1)) { // use integer overflow oscillators[i].pos = 0; oscillators[i].dir = 1; // make bigger steps for faster speeds @@ -1843,7 +1843,7 @@ uint16_t mode_oscillate(void) { for (unsigned i = 0; i < SEGLEN; i++) { uint32_t color = BLACK; for (unsigned j = 0; j < numOscillators; j++) { - if(i >= (unsigned)oscillators[j].pos - oscillators[j].size && i <= oscillators[j].pos + oscillators[j].size) { + if((int)i >= (int)oscillators[j].pos - oscillators[j].size && i <= oscillators[j].pos + oscillators[j].size) { color = (color == BLACK) ? SEGCOLOR(j) : color_blend(color, SEGCOLOR(j), uint8_t(128)); } } From 278d204d1c04766e1313a5b5d8c91bff74079431 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 15 Jan 2025 20:36:53 +0100 Subject: [PATCH 073/125] merge fix for Deep-Sleep UM --- wled00/usermods_list.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index 627fa6308..3430e337d 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -477,6 +477,6 @@ void registerUsermods() #endif #ifdef USERMOD_DEEP_SLEEP - usermods.add(new DeepSleepUsermod()); + UsermodManager::add(new DeepSleepUsermod()); #endif } From 7fcc4a5283b6314d5750a39514479856ae6e0534 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 16 Jan 2025 12:07:52 +0100 Subject: [PATCH 074/125] fix for existing C3 overrides From 356a0d72c33413f65dab759f04d72911e708666d Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 16 Jan 2025 12:11:38 +0100 Subject: [PATCH 075/125] proper fix for existing C3 override envs --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 9b9b693f5..6d959fe12 100644 --- a/platformio.ini +++ b/platformio.ini @@ -323,6 +323,7 @@ lib_deps = https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${env.lib_deps} board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs +board_build.flash_mode = qio [esp32s3] ;; generic definitions for all ESP32-S3 boards @@ -528,7 +529,6 @@ build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME= upload_speed = 460800 build_unflags = ${common.build_unflags} lib_deps = ${esp32c3.lib_deps} -board_build.flash_mode = qio [env:esp32s3dev_16MB_opi] ;; ESP32-S3 development board, with 16MB FLASH and >= 8MB PSRAM (memory_type: qio_opi) From 702d0851176e73762fce651ad7ff32b357c9f4ee Mon Sep 17 00:00:00 2001 From: Arne Date: Mon, 14 Aug 2023 13:28:34 +0200 Subject: [PATCH 076/125] rename global dmx... variables to dmxInput... This is the first step in supporting both dmx input and dmx output on different pins. --- wled00/cfg.cpp | 12 +++++++++ wled00/dmx.cpp | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ wled00/set.cpp | 6 +++++ wled00/wled.cpp | 5 ++++ wled00/wled.h | 8 +++++- wled00/xml.cpp | 11 ++++++++ 6 files changed, 108 insertions(+), 1 deletion(-) diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 38e804ed9..1105077ca 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -522,6 +522,13 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { tdd = if_live[F("timeout")] | -1; if (tdd >= 0) realtimeTimeoutMs = tdd * 100; + + #ifdef WLED_ENABLE_DMX_INPUT + CJSON(dmxInputTransmitPin, if_live_dmx[F("inputRxPin")]); + CJSON(dmxInputReceivePin, if_live_dmx[F("inputTxPin")]); + CJSON(dmxInputEnablePin, if_live_dmx[F("enablePin")]); + #endif + CJSON(arlsForceMaxBri, if_live[F("maxbri")]); CJSON(arlsDisableGammaCorrection, if_live[F("no-gc")]); // false CJSON(arlsOffset, if_live[F("offset")]); // 0 @@ -1001,6 +1008,11 @@ void serializeConfig() { if_live_dmx[F("addr")] = DMXAddress; if_live_dmx[F("dss")] = DMXSegmentSpacing; if_live_dmx["mode"] = DMXMode; + #ifdef WLED_ENABLE_DMX_INPUT + if_live_dmx[F("rxPin")] = dmxInputTransmitPin; + if_live_dmx[F("txPin")] = dmxInputReceivePin; + if_live_dmx[F("enablePin")] = dmxInputEnablePin; + #endif if_live[F("timeout")] = realtimeTimeoutMs / 100; if_live[F("maxbri")] = arlsForceMaxBri; diff --git a/wled00/dmx.cpp b/wled00/dmx.cpp index dbe70f2aa..305d5130d 100644 --- a/wled00/dmx.cpp +++ b/wled00/dmx.cpp @@ -76,3 +76,70 @@ void initDMX() { #endif } #endif + + +#ifdef WLED_ENABLE_DMX_INPUT + +#include + + +dmx_port_t dmxPort = 2; +void initDMX() { +/* Set the DMX hardware pins to the pins that we want to use. */ + if(dmxInputReceivePin > 0) { + USER_PRINTF("Listening for DMX on pin %u\n", dmxInputReceivePin); + dmx_set_pin(dmxPort, dmxInputTransmitPin, dmxInputReceivePin, dmxInputEnablePin); + } + else { + USER_PRINTLN("DMX input disabled due to dmxReceivePin not being set"); + return; + } + + /* Now we can install the DMX driver! We'll tell it which DMX port to use and + which interrupt priority it should have. If you aren't sure which interrupt + priority to use, you can use the macro `DMX_DEFAULT_INTR_FLAG` to set the + interrupt to its default settings.*/ + dmx_driver_install(dmxPort, ESP_INTR_FLAG_LEVEL3); +} + +bool dmxIsConnected = false; +unsigned long dmxLastUpdate = 0; + +void handleDMXInput() { + if(dmxInputReceivePin < 1) { + return; + } + byte dmxdata[DMX_PACKET_SIZE]; + dmx_packet_t packet; + unsigned long now = millis(); + if (dmx_receive(dmxPort, &packet, 0)) { + + /* We should check to make sure that there weren't any DMX errors. */ + if (!packet.err) { + /* If this is the first DMX data we've received, lets log it! */ + if (!dmxIsConnected) { + USER_PRINTLN("DMX is connected!"); + dmxIsConnected = true; + } + + dmx_read(dmxPort, dmxdata, packet.size); + handleDMXData(1, 512, dmxdata, REALTIME_MODE_DMX, 0); + dmxLastUpdate = now; + + } else { + /* Oops! A DMX error occurred! Don't worry, this can happen when you first + connect or disconnect your DMX devices. If you are consistently getting + DMX errors, then something may have gone wrong with your code or + something is seriously wrong with your DMX transmitter. */ + DEBUG_PRINT("A DMX error occurred - "); + DEBUG_PRINTLN(packet.err); + } + } + else if (dmxIsConnected && (now - dmxLastUpdate > 5000)) { + dmxIsConnected = false; + USER_PRINTLN("DMX was disconnected."); + } +} +#else +void initDMX(); +#endif diff --git a/wled00/set.cpp b/wled00/set.cpp index 160eb48f0..9a12cdc21 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -420,6 +420,12 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) t = request->arg(F("WO")).toInt(); if (t >= -255 && t <= 255) arlsOffset = t; +#ifdef WLED_ENABLE_DMX_INPUT + dmxInputTransmitPin = request->arg(F("IDMT")).toInt(); + dmxInputReceivePin = request->arg(F("IDMR")).toInt(); + dmxInputEnablePin= request->arg(F("IDME")).toInt(); +#endif + #ifndef WLED_DISABLE_ALEXA alexaEnabled = request->hasArg(F("AL")); strlcpy(alexaInvocationName, request->arg(F("AI")).c_str(), 33); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 1f978a39b..8144b0cd4 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -423,6 +423,11 @@ void WLED::setup() #ifdef WLED_ENABLE_DMX //reserve GPIO2 as hardcoded DMX pin PinManager::allocatePin(2, true, PinOwner::DMX); #endif +#ifdef WLED_ENABLE_DMX_INPUT + if(dmxInputTransmitPin > 0) PinManager::allocatePin(dmxInputTransmitPin, true, PinOwner::DMX); + if(dmxInputReceivePin > 0) PinManager::allocatePin(dmxInputReceivePin, true, PinOwner::DMX); + if(dmxInputEnablePin > 0) PinManager::allocatePin(dmxInputEnablePin, true, PinOwner::DMX); +#endif DEBUG_PRINTLN(F("Registering usermods ...")); registerUsermods(); diff --git a/wled00/wled.h b/wled00/wled.h index 533b12063..8adbd1ba5 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -459,7 +459,13 @@ WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to f WLED_GLOBAL uint16_t DMXStart _INIT(10); // start address of the first fixture WLED_GLOBAL uint16_t DMXStartLED _INIT(0); // LED from which DMX fixtures start #endif -WLED_GLOBAL uint16_t e131Universe _INIT(1); // settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consecutive universes) +#ifdef WLED_ENABLE_DMX_INPUT + WLED_GLOBAL int dmxInputTransmitPin _INIT(0); + WLED_GLOBAL int dmxInputReceivePin _INIT(0); + WLED_GLOBAL int dmxInputEnablePin _INIT(0); +#endif + +WLED_GLOBAL uint16_t e131Universe _INIT(1); // settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) WLED_GLOBAL uint16_t e131Port _INIT(5568); // DMX in port. E1.31 default is 5568, Art-Net is 6454 WLED_GLOBAL byte e131Priority _INIT(0); // E1.31 port priority (if != 0 priority handling is active) WLED_GLOBAL E131Priority highPriority _INIT(3); // E1.31 highest priority tracking, init = timeout in seconds diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 2a19cdfab..62de53425 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -436,6 +436,17 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormCheckbox(settingsScript,PSTR("ES"),e131SkipOutOfSequence); printSetFormCheckbox(settingsScript,PSTR("EM"),e131Multicast); printSetFormValue(settingsScript,PSTR("EU"),e131Universe); +#ifdef WLED_ENABLE_DMX + oappend(SET_F("hideNoDMX();")); // WLEDMM hide "not compiled in" message +#endif +#ifndef WLED_ENABLE_DMX_INPUT + oappend(SET_F("hideDMXInput();")); // WLEDMM hide "dmx input" settings +#else + oappend(SET_F("hideNoDMXInput();")); // WLEDMM hide "not compiled in" message + sappend('v',SET_F("IDMT"),dmxInputTransmitPin); + sappend('v',SET_F("IDMR"),dmxInputReceivePin); + sappend('v',SET_F("IDME"),dmxInputEnablePin); +#endif printSetFormValue(settingsScript,PSTR("DA"),DMXAddress); printSetFormValue(settingsScript,PSTR("XX"),DMXSegmentSpacing); printSetFormValue(settingsScript,PSTR("PY"),e131Priority); From 9e2268bd74bb35c2695d22668ca191f2242b041d Mon Sep 17 00:00:00 2001 From: Arne Date: Mon, 14 Aug 2023 13:38:04 +0200 Subject: [PATCH 077/125] Adapt to new api of esp_dmx v3.1 --- wled00/dmx.cpp | 55 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/wled00/dmx.cpp b/wled00/dmx.cpp index 305d5130d..1bb1b250d 100644 --- a/wled00/dmx.cpp +++ b/wled00/dmx.cpp @@ -1,7 +1,7 @@ #include "wled.h" /* - * Support for DMX Output via MAX485. + * Support for DMX input and output via MAX485. * Change the output pin in src/dependencies/ESPDMX.cpp, if needed (ESP8266) * Change the output pin in src/dependencies/SparkFunDMX.cpp, if needed (ESP32) * ESP8266 Library from: @@ -83,36 +83,61 @@ void initDMX() { #include -dmx_port_t dmxPort = 2; +static dmx_port_t dmxInputPort = 2; //TODO make this configurable +bool dmxInputInitialized = false; //true once initDmx finished successfully + void initDMX() { -/* Set the DMX hardware pins to the pins that we want to use. */ - if(dmxInputReceivePin > 0) { + + + if(dmxInputReceivePin > 0 && dmxInputEnablePin > 0 && dmxInputTransmitPin > 0) + { + dmx_config_t config{ + 255, /*alloc_size*/ + 0, /*model_id*/ + RDM_PRODUCT_CATEGORY_FIXTURE, /*product_category*/ + VERSION, /*software_version_id*/ + "undefined", /*software_version_label*/ + 1, /*current_personality*/ + {{15, "WLED Effect Mode"}}, /*personalities*/ + 1, /*personality_count*/ + 1, /*dmx_start_address*/ + }; + const std::string versionString = "WLED_V" + std::to_string(VERSION); + strncpy(config.software_version_label, versionString.c_str(), 32); + config.software_version_label[32] = '\0';//zero termination in case our string was longer than 32 chars + + if(!dmx_driver_install(dmxInputPort, &config, DMX_INTR_FLAGS_DEFAULT)) + { + USER_PRINTF("Error: Failed to install dmx driver\n"); + return; + } + USER_PRINTF("Listening for DMX on pin %u\n", dmxInputReceivePin); - dmx_set_pin(dmxPort, dmxInputTransmitPin, dmxInputReceivePin, dmxInputEnablePin); + USER_PRINTF("Sending DMX on pin %u\n", dmxInputTransmitPin); + USER_PRINTF("DMX enable pin is: %u\n", dmxInputEnablePin); + dmx_set_pin(dmxInputPort, dmxInputTransmitPin, dmxInputReceivePin, dmxInputEnablePin); + + dmxInputInitialized = true; } - else { - USER_PRINTLN("DMX input disabled due to dmxReceivePin not being set"); + else + { + USER_PRINTLN("DMX input disabled due to dmxInputReceivePin, dmxInputEnablePin or dmxInputTransmitPin not set"); return; } - /* Now we can install the DMX driver! We'll tell it which DMX port to use and - which interrupt priority it should have. If you aren't sure which interrupt - priority to use, you can use the macro `DMX_DEFAULT_INTR_FLAG` to set the - interrupt to its default settings.*/ - dmx_driver_install(dmxPort, ESP_INTR_FLAG_LEVEL3); } bool dmxIsConnected = false; unsigned long dmxLastUpdate = 0; void handleDMXInput() { - if(dmxInputReceivePin < 1) { + if(!dmxInputInitialized) { return; } byte dmxdata[DMX_PACKET_SIZE]; dmx_packet_t packet; unsigned long now = millis(); - if (dmx_receive(dmxPort, &packet, 0)) { + if (dmx_receive(dmxInputPort, &packet, 0)) { /* We should check to make sure that there weren't any DMX errors. */ if (!packet.err) { @@ -122,7 +147,7 @@ void handleDMXInput() { dmxIsConnected = true; } - dmx_read(dmxPort, dmxdata, packet.size); + dmx_read(dmxInputPort, dmxdata, packet.size); handleDMXData(1, 512, dmxdata, REALTIME_MODE_DMX, 0); dmxLastUpdate = now; From a0ca243955d6ccefb68be3d6b99c83f7f3400593 Mon Sep 17 00:00:00 2001 From: Arne Date: Mon, 14 Aug 2023 13:58:22 +0200 Subject: [PATCH 078/125] Move dmx_input pin allocations from wled.cpp to dmx.cpp --- wled00/dmx.cpp | 18 +++++++++++++++++- wled00/pin_manager.cpp | 4 +++- wled00/pin_manager.h | 19 ++++++++++--------- wled00/wled.cpp | 3 +++ wled00/xml.cpp | 6 +++--- 5 files changed, 36 insertions(+), 14 deletions(-) diff --git a/wled00/dmx.cpp b/wled00/dmx.cpp index 1bb1b250d..f2f7d5033 100644 --- a/wled00/dmx.cpp +++ b/wled00/dmx.cpp @@ -88,9 +88,25 @@ bool dmxInputInitialized = false; //true once initDmx finished successfully void initDMX() { - if(dmxInputReceivePin > 0 && dmxInputEnablePin > 0 && dmxInputTransmitPin > 0) { + + const managed_pin_type pins[] = { + {dmxInputTransmitPin, false}, //these are not used as gpio pins, this isOutput is always false. + {dmxInputReceivePin, false}, + {dmxInputEnablePin, false} + }; + const bool pinsAllocated = pinManager.allocateMultiplePins(pins, 3, PinOwner::DMX_INPUT); + if(!pinsAllocated) + { + USER_PRINTF("Error: Failed to allocate pins for DMX_INPUT. Pins already in use:\n"); + USER_PRINTF("rx in use by: %s\n", pinManager.getPinOwnerText(dmxInputReceivePin).c_str()); + USER_PRINTF("tx in use by: %s\n", pinManager.getPinOwnerText(dmxInputTransmitPin).c_str()); + USER_PRINTF("en in use by: %s\n", pinManager.getPinOwnerText(dmxInputEnablePin).c_str()); + return; + } + + dmx_config_t config{ 255, /*alloc_size*/ 0, /*model_id*/ diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 37ebd41ec..6f1652301 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -141,7 +141,9 @@ bool PinManager::allocateMultiplePins(const managed_pin_type * mptArray, byte ar bool PinManager::allocatePin(byte gpio, bool output, PinOwner tag) { // HW I2C & SPI pins have to be allocated using allocateMultiplePins variant since there is always SCL/SDA pair - if (!isPinOk(gpio, output) || (gpio >= WLED_NUM_PINS) || tag==PinOwner::HW_I2C || tag==PinOwner::HW_SPI) { + // DMX_INPUT pins have to be allocated using allocateMultiplePins variant since there is always RX/TX/EN triple + if (!isPinOk(gpio, output) || (gpio >= WLED_NUM_PINS) || tag==PinOwner::HW_I2C || tag==PinOwner::HW_SPI + || tag==PinOwner::DMX_INPUT) { #ifdef WLED_DEBUG if (gpio < 255) { // 255 (-1) is the "not defined GPIO" if (!isPinOk(gpio, output)) { diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index c8fb165ce..b285b6ee5 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -35,15 +35,16 @@ enum struct PinOwner : uint8_t { Ethernet = 0x81, BusDigital = 0x82, BusOnOff = 0x83, - BusPwm = 0x84, // 'BusP' == PWM output using BusPwm - Button = 0x85, // 'Butn' == button from configuration - IR = 0x86, // 'IR' == IR receiver pin from configuration - Relay = 0x87, // 'Rly' == Relay pin from configuration - SPI_RAM = 0x88, // 'SpiR' == SPI RAM - DebugOut = 0x89, // 'Dbg' == debug output always IO1 - DMX = 0x8A, // 'DMX' == hard-coded to IO2 - HW_I2C = 0x8B, // 'I2C' == hardware I2C pins (4&5 on ESP8266, 21&22 on ESP32) - HW_SPI = 0x8C, // 'SPI' == hardware (V)SPI pins (13,14&15 on ESP8266, 5,18&23 on ESP32) + BusPwm = 0x84, // 'BusP' == PWM output using BusPwm + Button = 0x85, // 'Butn' == button from configuration + IR = 0x86, // 'IR' == IR receiver pin from configuration + Relay = 0x87, // 'Rly' == Relay pin from configuration + SPI_RAM = 0x88, // 'SpiR' == SPI RAM + DebugOut = 0x89, // 'Dbg' == debug output always IO1 + DMX = 0x8A, // 'DMX' == hard-coded to IO2 + HW_I2C = 0x8B, // 'I2C' == hardware I2C pins (4&5 on ESP8266, 21&22 on ESP32) + HW_SPI = 0x8C, // 'SPI' == hardware (V)SPI pins (13,14&15 on ESP8266, 5,18&23 on ESP32) + DMX_INPUT = 0x8D, // 'DMX_INPUT' == DMX input via serial // Use UserMod IDs from const.h here UM_Unspecified = USERMOD_ID_UNSPECIFIED, // 0x01 UM_Example = USERMOD_ID_EXAMPLE, // 0x02 // Usermod "usermod_v2_example.h" diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 8144b0cd4..a8748162e 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -423,11 +423,14 @@ void WLED::setup() #ifdef WLED_ENABLE_DMX //reserve GPIO2 as hardcoded DMX pin PinManager::allocatePin(2, true, PinOwner::DMX); #endif +<<<<<<< HEAD #ifdef WLED_ENABLE_DMX_INPUT if(dmxInputTransmitPin > 0) PinManager::allocatePin(dmxInputTransmitPin, true, PinOwner::DMX); if(dmxInputReceivePin > 0) PinManager::allocatePin(dmxInputReceivePin, true, PinOwner::DMX); if(dmxInputEnablePin > 0) PinManager::allocatePin(dmxInputEnablePin, true, PinOwner::DMX); #endif +======= +>>>>>>> a516a7b8 (Move dmx_input pin allocations from wled.cpp to dmx.cpp) DEBUG_PRINTLN(F("Registering usermods ...")); registerUsermods(); diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 62de53425..5caa7159c 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -437,12 +437,12 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormCheckbox(settingsScript,PSTR("EM"),e131Multicast); printSetFormValue(settingsScript,PSTR("EU"),e131Universe); #ifdef WLED_ENABLE_DMX - oappend(SET_F("hideNoDMX();")); // WLEDMM hide "not compiled in" message + settingsScript.print(SET_F("hideNoDMX();")); // hide "not compiled in" message #endif #ifndef WLED_ENABLE_DMX_INPUT - oappend(SET_F("hideDMXInput();")); // WLEDMM hide "dmx input" settings + settingsScript.print(SET_F("hideDMXInput();")); // hide "dmx input" settings #else - oappend(SET_F("hideNoDMXInput();")); // WLEDMM hide "not compiled in" message + settingsScript.print(SET_F("hideNoDMXInput();")); //hide "not comp iled in" message sappend('v',SET_F("IDMT"),dmxInputTransmitPin); sappend('v',SET_F("IDMR"),dmxInputReceivePin); sappend('v',SET_F("IDME"),dmxInputEnablePin); From f06a1e8b49697bdca84e9228c51fafda78b7cbae Mon Sep 17 00:00:00 2001 From: Arne Date: Mon, 14 Aug 2023 14:12:08 +0200 Subject: [PATCH 079/125] Extract dmx_input from dmx.cpp into dmx_input.cpp. This greatly improves readability because it gets rid of most of the ifdefs. --- wled00/dmx.cpp | 111 ++------------------------------------ wled00/dmx_input.cpp | 124 +++++++++++++++++++++++++++++++++++++++++++ wled00/fcn_declare.h | 7 +++ wled00/wled.cpp | 3 ++ 4 files changed, 137 insertions(+), 108 deletions(-) create mode 100644 wled00/dmx_input.cpp diff --git a/wled00/dmx.cpp b/wled00/dmx.cpp index f2f7d5033..aa05c0113 100644 --- a/wled00/dmx.cpp +++ b/wled00/dmx.cpp @@ -1,7 +1,7 @@ #include "wled.h" /* - * Support for DMX input and output via MAX485. + * Support for DMX input and output via serial (e.g. MAX485). * Change the output pin in src/dependencies/ESPDMX.cpp, if needed (ESP8266) * Change the output pin in src/dependencies/SparkFunDMX.cpp, if needed (ESP32) * ESP8266 Library from: @@ -75,112 +75,7 @@ void initDMX() { dmx.initWrite(512); // initialize with bus length #endif } -#endif - - -#ifdef WLED_ENABLE_DMX_INPUT - -#include - - -static dmx_port_t dmxInputPort = 2; //TODO make this configurable -bool dmxInputInitialized = false; //true once initDmx finished successfully - -void initDMX() { - - if(dmxInputReceivePin > 0 && dmxInputEnablePin > 0 && dmxInputTransmitPin > 0) - { - - const managed_pin_type pins[] = { - {dmxInputTransmitPin, false}, //these are not used as gpio pins, this isOutput is always false. - {dmxInputReceivePin, false}, - {dmxInputEnablePin, false} - }; - const bool pinsAllocated = pinManager.allocateMultiplePins(pins, 3, PinOwner::DMX_INPUT); - if(!pinsAllocated) - { - USER_PRINTF("Error: Failed to allocate pins for DMX_INPUT. Pins already in use:\n"); - USER_PRINTF("rx in use by: %s\n", pinManager.getPinOwnerText(dmxInputReceivePin).c_str()); - USER_PRINTF("tx in use by: %s\n", pinManager.getPinOwnerText(dmxInputTransmitPin).c_str()); - USER_PRINTF("en in use by: %s\n", pinManager.getPinOwnerText(dmxInputEnablePin).c_str()); - return; - } - - - dmx_config_t config{ - 255, /*alloc_size*/ - 0, /*model_id*/ - RDM_PRODUCT_CATEGORY_FIXTURE, /*product_category*/ - VERSION, /*software_version_id*/ - "undefined", /*software_version_label*/ - 1, /*current_personality*/ - {{15, "WLED Effect Mode"}}, /*personalities*/ - 1, /*personality_count*/ - 1, /*dmx_start_address*/ - }; - const std::string versionString = "WLED_V" + std::to_string(VERSION); - strncpy(config.software_version_label, versionString.c_str(), 32); - config.software_version_label[32] = '\0';//zero termination in case our string was longer than 32 chars - - if(!dmx_driver_install(dmxInputPort, &config, DMX_INTR_FLAGS_DEFAULT)) - { - USER_PRINTF("Error: Failed to install dmx driver\n"); - return; - } - - USER_PRINTF("Listening for DMX on pin %u\n", dmxInputReceivePin); - USER_PRINTF("Sending DMX on pin %u\n", dmxInputTransmitPin); - USER_PRINTF("DMX enable pin is: %u\n", dmxInputEnablePin); - dmx_set_pin(dmxInputPort, dmxInputTransmitPin, dmxInputReceivePin, dmxInputEnablePin); - - dmxInputInitialized = true; - } - else - { - USER_PRINTLN("DMX input disabled due to dmxInputReceivePin, dmxInputEnablePin or dmxInputTransmitPin not set"); - return; - } - -} - -bool dmxIsConnected = false; -unsigned long dmxLastUpdate = 0; - -void handleDMXInput() { - if(!dmxInputInitialized) { - return; - } - byte dmxdata[DMX_PACKET_SIZE]; - dmx_packet_t packet; - unsigned long now = millis(); - if (dmx_receive(dmxInputPort, &packet, 0)) { - - /* We should check to make sure that there weren't any DMX errors. */ - if (!packet.err) { - /* If this is the first DMX data we've received, lets log it! */ - if (!dmxIsConnected) { - USER_PRINTLN("DMX is connected!"); - dmxIsConnected = true; - } - - dmx_read(dmxInputPort, dmxdata, packet.size); - handleDMXData(1, 512, dmxdata, REALTIME_MODE_DMX, 0); - dmxLastUpdate = now; - - } else { - /* Oops! A DMX error occurred! Don't worry, this can happen when you first - connect or disconnect your DMX devices. If you are consistently getting - DMX errors, then something may have gone wrong with your code or - something is seriously wrong with your DMX transmitter. */ - DEBUG_PRINT("A DMX error occurred - "); - DEBUG_PRINTLN(packet.err); - } - } - else if (dmxIsConnected && (now - dmxLastUpdate > 5000)) { - dmxIsConnected = false; - USER_PRINTLN("DMX was disconnected."); - } -} #else -void initDMX(); +void initDMX(){} +void handleDMX() {} #endif diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp new file mode 100644 index 000000000..8ed1d7ac3 --- /dev/null +++ b/wled00/dmx_input.cpp @@ -0,0 +1,124 @@ +#include "wled.h" + +#ifdef WLED_ENABLE_DMX_INPUT +#include +/* + * Support for DMX/RDM input via serial (e.g. max485) on ESP32 + * ESP32 Library from: + * https://github.com/sparkfun/SparkFunDMX + */ + +static dmx_port_t dmxInputPort = 2; //TODO make this configurable +bool dmxInputInitialized = false; //true once initDmx finished successfully + +void initDMXInput() { + + /** + * TODOS: + * - add personalities for all supported dmx input modes + * - select the personality that is stored in flash on startup + * - attach callback for personality change and store in flash if changed + * - attach callback for address change and store in flash + * - load dmx address from flash and set in config on startup + * - attach callback to rdm identify and flash leds when on + */ + if(dmxInputReceivePin > 0 && dmxInputEnablePin > 0 && dmxInputTransmitPin > 0) + { + + const managed_pin_type pins[] = { + {(int8_t)dmxInputTransmitPin, false}, //these are not used as gpio pins, this isOutput is always false. + {(int8_t)dmxInputReceivePin, false}, + {(int8_t)dmxInputEnablePin, false} + }; + const bool pinsAllocated = pinManager.allocateMultiplePins(pins, 3, PinOwner::DMX_INPUT); + if(!pinsAllocated) + { + USER_PRINTF("Error: Failed to allocate pins for DMX_INPUT. Pins already in use:\n"); + USER_PRINTF("rx in use by: %s\n", pinManager.getPinOwnerText(dmxInputReceivePin).c_str()); + USER_PRINTF("tx in use by: %s\n", pinManager.getPinOwnerText(dmxInputTransmitPin).c_str()); + USER_PRINTF("en in use by: %s\n", pinManager.getPinOwnerText(dmxInputEnablePin).c_str()); + return; + } + + + dmx_config_t config{ + 255, /*alloc_size*/ + 0, /*model_id*/ + RDM_PRODUCT_CATEGORY_FIXTURE, /*product_category*/ + VERSION, /*software_version_id*/ + "undefined", /*software_version_label*/ + 1, /*current_personality*/ + {{15, "WLED Effect Mode"}}, /*personalities*/ + 1, /*personality_count*/ + 1, /*dmx_start_address*/ + }; + const std::string versionString = "WLED_V" + std::to_string(VERSION); + strncpy(config.software_version_label, versionString.c_str(), 32); + config.software_version_label[32] = '\0';//zero termination in case our string was longer than 32 chars + + if(!dmx_driver_install(dmxInputPort, &config, DMX_INTR_FLAGS_DEFAULT)) + { + USER_PRINTF("Error: Failed to install dmx driver\n"); + return; + } + + USER_PRINTF("Listening for DMX on pin %u\n", dmxInputReceivePin); + USER_PRINTF("Sending DMX on pin %u\n", dmxInputTransmitPin); + USER_PRINTF("DMX enable pin is: %u\n", dmxInputEnablePin); + dmx_set_pin(dmxInputPort, dmxInputTransmitPin, dmxInputReceivePin, dmxInputEnablePin); + + dmxInputInitialized = true; + } + else + { + USER_PRINTLN("DMX input disabled due to dmxInputReceivePin, dmxInputEnablePin or dmxInputTransmitPin not set"); + return; + } + +} + +static bool dmxIsConnected = false; +static unsigned long dmxLastUpdate = 0; + +void handleDMXInput() { + if(!dmxInputInitialized) { + return; + } + byte dmxdata[DMX_PACKET_SIZE]; + dmx_packet_t packet; + unsigned long now = millis(); + if (dmx_receive(dmxInputPort, &packet, 0)) { + + /* We should check to make sure that there weren't any DMX errors. */ + if (!packet.err) { + /* If this is the first DMX data we've received, lets log it! */ + if (!dmxIsConnected) { + USER_PRINTLN("DMX is connected!"); + dmxIsConnected = true; + } + + dmx_read(dmxInputPort, dmxdata, packet.size); + handleDMXData(1, 512, dmxdata, REALTIME_MODE_DMX, 0); + dmxLastUpdate = now; + + } else { + /* Oops! A DMX error occurred! Don't worry, this can happen when you first + connect or disconnect your DMX devices. If you are consistently getting + DMX errors, then something may have gone wrong with your code or + something is seriously wrong with your DMX transmitter. */ + DEBUG_PRINT("A DMX error occurred - "); + DEBUG_PRINTLN(packet.err); + } + } + else if (dmxIsConnected && (now - dmxLastUpdate > 5000)) { + dmxIsConnected = false; + USER_PRINTLN("DMX was disconnected."); + } +} + + +#else +void initDMXInput(){} +void handleDMXInput(){} + +#endif \ No newline at end of file diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index cb21e8c2e..c3ada1a57 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -186,6 +186,13 @@ void setRandomColor(byte* rgb); //dmx.cpp void initDMX(); void handleDMX(); +<<<<<<< HEAD +======= + +//dmx_input.cpp +void initDMXInput(); +void handleDMXInput(); +>>>>>>> b4bbf0a5 (Extract dmx_input from dmx.cpp into dmx_input.cpp.) //e131.cpp void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index a8748162e..5e1539479 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -534,6 +534,9 @@ void WLED::setup() #ifdef WLED_ENABLE_DMX initDMX(); #endif +#ifdef WLED_ENABLE_DMX_INPUT + initDMXInput(); +#endif #ifdef WLED_ENABLE_ADALIGHT if (serialCanRX && Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket(); From 789d68e80d12a4be2d3e98cd75e6e6b0f9e4da2d Mon Sep 17 00:00:00 2001 From: Arne Date: Mon, 14 Aug 2023 14:14:33 +0200 Subject: [PATCH 080/125] Move globals to top of file and change scope to compile unit only. Some minor cleanup changes --- wled00/dmx_input.cpp | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index 8ed1d7ac3..009112b73 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -9,7 +9,9 @@ */ static dmx_port_t dmxInputPort = 2; //TODO make this configurable -bool dmxInputInitialized = false; //true once initDmx finished successfully +static bool dmxInputInitialized = false; //true once initDmx finished successfully +static bool dmxIsConnected = false; +static unsigned long dmxLastUpdate = 0; void initDMXInput() { @@ -21,6 +23,8 @@ void initDMXInput() { * - attach callback for address change and store in flash * - load dmx address from flash and set in config on startup * - attach callback to rdm identify and flash leds when on + * - Turn this into a class + * - Make all important config variables available via rdm */ if(dmxInputReceivePin > 0 && dmxInputEnablePin > 0 && dmxInputTransmitPin > 0) { @@ -40,7 +44,6 @@ void initDMXInput() { return; } - dmx_config_t config{ 255, /*alloc_size*/ 0, /*model_id*/ @@ -54,7 +57,7 @@ void initDMXInput() { }; const std::string versionString = "WLED_V" + std::to_string(VERSION); strncpy(config.software_version_label, versionString.c_str(), 32); - config.software_version_label[32] = '\0';//zero termination in case our string was longer than 32 chars + config.software_version_label[32] = '\0';//zero termination in case versionString string was longer than 32 chars if(!dmx_driver_install(dmxInputPort, &config, DMX_INTR_FLAGS_DEFAULT)) { @@ -74,12 +77,8 @@ void initDMXInput() { USER_PRINTLN("DMX input disabled due to dmxInputReceivePin, dmxInputEnablePin or dmxInputTransmitPin not set"); return; } - } -static bool dmxIsConnected = false; -static unsigned long dmxLastUpdate = 0; - void handleDMXInput() { if(!dmxInputInitialized) { return; @@ -88,10 +87,7 @@ void handleDMXInput() { dmx_packet_t packet; unsigned long now = millis(); if (dmx_receive(dmxInputPort, &packet, 0)) { - - /* We should check to make sure that there weren't any DMX errors. */ if (!packet.err) { - /* If this is the first DMX data we've received, lets log it! */ if (!dmxIsConnected) { USER_PRINTLN("DMX is connected!"); dmxIsConnected = true; @@ -102,12 +98,10 @@ void handleDMXInput() { dmxLastUpdate = now; } else { - /* Oops! A DMX error occurred! Don't worry, this can happen when you first - connect or disconnect your DMX devices. If you are consistently getting - DMX errors, then something may have gone wrong with your code or - something is seriously wrong with your DMX transmitter. */ + /*This can happen when you first connect or disconnect your DMX devices. + If you are consistently getting DMX errors, then something may have gone wrong. */ DEBUG_PRINT("A DMX error occurred - "); - DEBUG_PRINTLN(packet.err); + DEBUG_PRINTLN(packet.err); //TODO translate err code to string for output } } else if (dmxIsConnected && (now - dmxLastUpdate > 5000)) { From 0ad31c90f62961de6a03533d94ab7aeb6fa81067 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Thu, 16 Jan 2025 11:26:42 +0000 Subject: [PATCH 081/125] fix merge error --- wled00/wled.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 5e1539479..7b49b8b5d 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -423,14 +423,6 @@ void WLED::setup() #ifdef WLED_ENABLE_DMX //reserve GPIO2 as hardcoded DMX pin PinManager::allocatePin(2, true, PinOwner::DMX); #endif -<<<<<<< HEAD -#ifdef WLED_ENABLE_DMX_INPUT - if(dmxInputTransmitPin > 0) PinManager::allocatePin(dmxInputTransmitPin, true, PinOwner::DMX); - if(dmxInputReceivePin > 0) PinManager::allocatePin(dmxInputReceivePin, true, PinOwner::DMX); - if(dmxInputEnablePin > 0) PinManager::allocatePin(dmxInputEnablePin, true, PinOwner::DMX); -#endif -======= ->>>>>>> a516a7b8 (Move dmx_input pin allocations from wled.cpp to dmx.cpp) DEBUG_PRINTLN(F("Registering usermods ...")); registerUsermods(); From a3bcf92ea5496e3ce56a9bed1eb74d06e0d03ec6 Mon Sep 17 00:00:00 2001 From: Arne Date: Mon, 14 Aug 2023 15:18:54 +0200 Subject: [PATCH 082/125] Turn dmx_into into class with state. This is much nicer to read and in the future more state will be added to support all the rdm stuff. --- wled00/dmx_input.cpp | 147 +++++++++++++++++++++++-------------------- wled00/dmx_input.h | 23 +++++++ wled00/wled.cpp | 6 +- wled00/wled.h | 5 ++ 4 files changed, 111 insertions(+), 70 deletions(-) create mode 100644 wled00/dmx_input.h diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index 009112b73..dcb04781c 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -1,118 +1,127 @@ #include "wled.h" #ifdef WLED_ENABLE_DMX_INPUT +#include "dmx_input.h" #include -/* - * Support for DMX/RDM input via serial (e.g. max485) on ESP32 - * ESP32 Library from: - * https://github.com/sparkfun/SparkFunDMX - */ -static dmx_port_t dmxInputPort = 2; //TODO make this configurable -static bool dmxInputInitialized = false; //true once initDmx finished successfully -static bool dmxIsConnected = false; -static unsigned long dmxLastUpdate = 0; +#ifdef ESP8266 +#error DMX input is only supported on ESP32 +#endif -void initDMXInput() { +void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPortNum) +{ - /** - * TODOS: - * - add personalities for all supported dmx input modes - * - select the personality that is stored in flash on startup - * - attach callback for personality change and store in flash if changed - * - attach callback for address change and store in flash - * - load dmx address from flash and set in config on startup - * - attach callback to rdm identify and flash leds when on - * - Turn this into a class - * - Make all important config variables available via rdm - */ - if(dmxInputReceivePin > 0 && dmxInputEnablePin > 0 && dmxInputTransmitPin > 0) + if (inputPortNum < 3 && inputPortNum > 0) + { + this->inputPortNum = inputPortNum; + } + else + { + USER_PRINTF("DMXInput: Error: invalid inputPortNum: %d\n", inputPortNum); + return; + } + + /** + * TODOS: + * - add personalities for all supported dmx input modes + * - select the personality that is stored in flash on startup + * - attach callback for personality change and store in flash if changed + * - attach callback for address change and store in flash + * - load dmx address from flash and set in config on startup + * - attach callback to rdm identify and flash leds when on + * - Make all important config variables available via rdm + */ + if (rxPin > 0 && enPin > 0 && txPin > 0) { const managed_pin_type pins[] = { - {(int8_t)dmxInputTransmitPin, false}, //these are not used as gpio pins, this isOutput is always false. - {(int8_t)dmxInputReceivePin, false}, - {(int8_t)dmxInputEnablePin, false} - }; + {(int8_t)txPin, false}, // these are not used as gpio pins, this isOutput is always false. + {(int8_t)rxPin, false}, + {(int8_t)enPin, false}}; const bool pinsAllocated = pinManager.allocateMultiplePins(pins, 3, PinOwner::DMX_INPUT); - if(!pinsAllocated) + if (!pinsAllocated) { - USER_PRINTF("Error: Failed to allocate pins for DMX_INPUT. Pins already in use:\n"); - USER_PRINTF("rx in use by: %s\n", pinManager.getPinOwnerText(dmxInputReceivePin).c_str()); - USER_PRINTF("tx in use by: %s\n", pinManager.getPinOwnerText(dmxInputTransmitPin).c_str()); - USER_PRINTF("en in use by: %s\n", pinManager.getPinOwnerText(dmxInputEnablePin).c_str()); + USER_PRINTF("DMXInput: Error: Failed to allocate pins for DMX_INPUT. Pins already in use:\n"); + USER_PRINTF("rx in use by: %s\n", pinManager.getPinOwnerText(rxPin).c_str()); + USER_PRINTF("tx in use by: %s\n", pinManager.getPinOwnerText(txPin).c_str()); + USER_PRINTF("en in use by: %s\n", pinManager.getPinOwnerText(enPin).c_str()); return; } - dmx_config_t config{ - 255, /*alloc_size*/ - 0, /*model_id*/ - RDM_PRODUCT_CATEGORY_FIXTURE, /*product_category*/ - VERSION, /*software_version_id*/ - "undefined", /*software_version_label*/ - 1, /*current_personality*/ - {{15, "WLED Effect Mode"}}, /*personalities*/ - 1, /*personality_count*/ - 1, /*dmx_start_address*/ + dmx_config_t config{ + 255, /*alloc_size*/ + 0, /*model_id*/ + RDM_PRODUCT_CATEGORY_FIXTURE, /*product_category*/ + VERSION, /*software_version_id*/ + "undefined", /*software_version_label*/ + 1, /*current_personality*/ + {{15, "WLED Effect Mode"}}, /*personalities*/ + 1, /*personality_count*/ + 1, /*dmx_start_address*/ }; const std::string versionString = "WLED_V" + std::to_string(VERSION); strncpy(config.software_version_label, versionString.c_str(), 32); - config.software_version_label[32] = '\0';//zero termination in case versionString string was longer than 32 chars + config.software_version_label[32] = '\0'; // zero termination in case versionString string was longer than 32 chars - if(!dmx_driver_install(dmxInputPort, &config, DMX_INTR_FLAGS_DEFAULT)) + if (!dmx_driver_install(inputPortNum, &config, DMX_INTR_FLAGS_DEFAULT)) { USER_PRINTF("Error: Failed to install dmx driver\n"); return; } - - USER_PRINTF("Listening for DMX on pin %u\n", dmxInputReceivePin); - USER_PRINTF("Sending DMX on pin %u\n", dmxInputTransmitPin); - USER_PRINTF("DMX enable pin is: %u\n", dmxInputEnablePin); - dmx_set_pin(dmxInputPort, dmxInputTransmitPin, dmxInputReceivePin, dmxInputEnablePin); - dmxInputInitialized = true; + USER_PRINTF("Listening for DMX on pin %u\n", rxPin); + USER_PRINTF("Sending DMX on pin %u\n", txPin); + USER_PRINTF("DMX enable pin is: %u\n", enPin); + dmx_set_pin(inputPortNum, txPin, rxPin, enPin); + + initialized = true; } - else + else { - USER_PRINTLN("DMX input disabled due to dmxInputReceivePin, dmxInputEnablePin or dmxInputTransmitPin not set"); + USER_PRINTLN("DMX input disabled due to rxPin, enPin or txPin not set"); return; } } -void handleDMXInput() { - if(!dmxInputInitialized) { +void DMXInput::update() +{ + if (!initialized) + { return; } byte dmxdata[DMX_PACKET_SIZE]; dmx_packet_t packet; unsigned long now = millis(); - if (dmx_receive(dmxInputPort, &packet, 0)) { - if (!packet.err) { - if (!dmxIsConnected) { + if (dmx_receive(inputPortNum, &packet, 0)) + { + if (!packet.err) + { + if (!connected) + { USER_PRINTLN("DMX is connected!"); - dmxIsConnected = true; + connected = true; } - dmx_read(dmxInputPort, dmxdata, packet.size); + dmx_read(inputPortNum, dmxdata, packet.size); handleDMXData(1, 512, dmxdata, REALTIME_MODE_DMX, 0); - dmxLastUpdate = now; - - } else { + lastUpdate = now; + } + else + { /*This can happen when you first connect or disconnect your DMX devices. If you are consistently getting DMX errors, then something may have gone wrong. */ - DEBUG_PRINT("A DMX error occurred - "); - DEBUG_PRINTLN(packet.err); //TODO translate err code to string for output + DEBUG_PRINT("A DMX error occurred - "); + DEBUG_PRINTLN(packet.err); // TODO translate err code to string for output } } - else if (dmxIsConnected && (now - dmxLastUpdate > 5000)) { - dmxIsConnected = false; + else if (connected && (now - lastUpdate > 5000)) + { + connected = false; USER_PRINTLN("DMX was disconnected."); } } - #else -void initDMXInput(){} -void handleDMXInput(){} - +void DMXInput::init(uint8_t, uint8_t, uint8_t, uint8_t) {} +void DMXInput::update() {} #endif \ No newline at end of file diff --git a/wled00/dmx_input.h b/wled00/dmx_input.h new file mode 100644 index 000000000..ac67bc206 --- /dev/null +++ b/wled00/dmx_input.h @@ -0,0 +1,23 @@ +#pragma once +#include + +/* + * Support for DMX/RDM input via serial (e.g. max485) on ESP32 + * ESP32 Library from: + * https://github.com/sparkfun/SparkFunDMX + */ +class DMXInput +{ +public: + void init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPortNum); + void update(); + +private: + uint8_t inputPortNum = 255; // TODO make this configurable + /// True once the dmx input has been initialized successfully + bool initialized = false; // true once init finished successfully + /// True if dmx is currently connected + bool connected = false; + /// Timestamp of the last time a dmx frame was received + unsigned long lastUpdate = 0; +}; diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 7b49b8b5d..f9dfa55f3 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -67,6 +67,9 @@ void WLED::loop() #ifdef WLED_ENABLE_DMX handleDMX(); #endif + #ifdef WLED_ENABLE_DMX_INPUT + dmxInput.update(); + #endif #ifdef WLED_DEBUG unsigned long usermodMillis = millis(); @@ -527,7 +530,8 @@ void WLED::setup() initDMX(); #endif #ifdef WLED_ENABLE_DMX_INPUT - initDMXInput(); + const uint8_t dmxInputPortNumber = 2; //TODO turn into config variable?! + dmxInput.init(dmxInputReceivePin, dmxInputTransmitPin, dmxInputEnablePin, dmxInputPortNumber); #endif #ifdef WLED_ENABLE_ADALIGHT diff --git a/wled00/wled.h b/wled00/wled.h index 8adbd1ba5..b366e82a1 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -144,6 +144,10 @@ #endif #endif +#ifdef WLED_ENABLE_DMX_INPUT + #include "dmx_input.h" +#endif + #include "src/dependencies/e131/ESPAsyncE131.h" #ifndef WLED_DISABLE_MQTT #include "src/dependencies/async-mqtt-client/AsyncMqttClient.h" @@ -463,6 +467,7 @@ WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to f WLED_GLOBAL int dmxInputTransmitPin _INIT(0); WLED_GLOBAL int dmxInputReceivePin _INIT(0); WLED_GLOBAL int dmxInputEnablePin _INIT(0); + WLED_GLOBAL DMXInput dmxInput; #endif WLED_GLOBAL uint16_t e131Universe _INIT(1); // settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) From 5a5661f136417eea1e19ce82541e034b8ca79228 Mon Sep 17 00:00:00 2001 From: Arne Date: Mon, 14 Aug 2023 15:54:19 +0200 Subject: [PATCH 083/125] handle dmx rdm identify --- wled00/dmx_input.cpp | 37 ++++++++++++++++++++++++++++++------- wled00/dmx_input.h | 8 ++++++-- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index dcb04781c..28c337258 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -1,13 +1,14 @@ #include "wled.h" #ifdef WLED_ENABLE_DMX_INPUT -#include "dmx_input.h" -#include #ifdef ESP8266 #error DMX input is only supported on ESP32 #endif +#include "dmx_input.h" +#include + void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPortNum) { @@ -102,8 +103,21 @@ void DMXInput::update() connected = true; } - dmx_read(inputPortNum, dmxdata, packet.size); - handleDMXData(1, 512, dmxdata, REALTIME_MODE_DMX, 0); + uint8_t identify = 0; + const bool gotIdentify = rdm_get_identify_device(inputPortNum, &identify); + // gotIdentify should never be false because it is a default parameter in rdm but just in case we check for it anyway + if (identify && gotIdentify) + { + turnOnAllLeds(); + } + else + { + if (!packet.is_rdm) + { + dmx_read(inputPortNum, dmxdata, packet.size); + handleDMXData(1, 512, dmxdata, REALTIME_MODE_DMX, 0); + } + } lastUpdate = now; } else @@ -121,7 +135,16 @@ void DMXInput::update() } } -#else -void DMXInput::init(uint8_t, uint8_t, uint8_t, uint8_t) {} -void DMXInput::update() {} +void DMXInput::turnOnAllLeds() +{ + // TODO not sure if this is the correct way? + const uint16_t numPixels = strip.getLengthTotal(); + for (uint16_t i = 0; i < numPixels; ++i) + { + strip.setPixelColor(i, 255, 255, 255, 255); + } + strip.setBrightness(255, true); + strip.show(); +} + #endif \ No newline at end of file diff --git a/wled00/dmx_input.h b/wled00/dmx_input.h index ac67bc206..b33c2a16f 100644 --- a/wled00/dmx_input.h +++ b/wled00/dmx_input.h @@ -1,6 +1,6 @@ #pragma once #include - +#include /* * Support for DMX/RDM input via serial (e.g. max485) on ESP32 * ESP32 Library from: @@ -10,9 +10,13 @@ class DMXInput { public: void init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPortNum); - void update(); + void update(); private: + + /// overrides everything and turns on all leds + void turnOnAllLeds(); + uint8_t inputPortNum = 255; // TODO make this configurable /// True once the dmx input has been initialized successfully bool initialized = false; // true once init finished successfully From aed03cd03baca7100f7eef713905bc3bae6b55ce Mon Sep 17 00:00:00 2001 From: Arne Date: Thu, 17 Aug 2023 16:52:25 +0200 Subject: [PATCH 084/125] hack: disable dmx receiver while wifi is being activated This fixes a crash in the dmx receiver. The dmx receiver cannot work while cache is disabled. For some reason activating wifi disables the cache. In theory, the driver is placed in iram and should work, but it doesn't. This might be a bug in the driver. --- wled00/wled.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index f9dfa55f3..376cb6b28 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -783,6 +783,9 @@ void WLED::initConnection() { DEBUG_PRINTF_P(PSTR("initConnection() called @ %lus.\n"), millis()/1000); + #ifdef WLED_ENABLE_DMX_INPUT + dmxInput.disable(); + #endif #ifdef WLED_ENABLE_WEBSOCKETS ws.onEvent(wsEvent); #endif @@ -811,6 +814,11 @@ void WLED::initConnection() if (!WLED_WIFI_CONFIGURED) { DEBUG_PRINTLN(F("No connection configured.")); if (!apActive) initAP(); // instantly go to ap mode + + #ifdef WLED_ENABLE_DMX_INPUT + dmxInput.enable(); + #endif + return; } else if (!apActive) { if (apBehavior == AP_BEHAVIOR_ALWAYS) { DEBUG_PRINTLN(F("Access point ALWAYS enabled.")); @@ -860,6 +868,10 @@ void WLED::initConnection() statusESPNow = espNowOK ? ESP_NOW_STATE_ON : ESP_NOW_STATE_ERROR; } #endif + + #ifdef WLED_ENABLE_DMX_INPUT + dmxInput.enable(); + #endif } void WLED::initInterfaces() From 5525a216969ab2150ffecef1659135887422fd24 Mon Sep 17 00:00:00 2001 From: Arne Date: Thu, 17 Aug 2023 16:53:02 +0200 Subject: [PATCH 085/125] rename settings --- wled00/cfg.cpp | 8 ++++---- wled00/data/settings_sync.htm | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 1105077ca..d3a8a0c6d 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -526,7 +526,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { #ifdef WLED_ENABLE_DMX_INPUT CJSON(dmxInputTransmitPin, if_live_dmx[F("inputRxPin")]); CJSON(dmxInputReceivePin, if_live_dmx[F("inputTxPin")]); - CJSON(dmxInputEnablePin, if_live_dmx[F("enablePin")]); + CJSON(dmxInputEnablePin, if_live_dmx[F("inputEnablePin")]); #endif CJSON(arlsForceMaxBri, if_live[F("maxbri")]); @@ -1009,9 +1009,9 @@ void serializeConfig() { if_live_dmx[F("dss")] = DMXSegmentSpacing; if_live_dmx["mode"] = DMXMode; #ifdef WLED_ENABLE_DMX_INPUT - if_live_dmx[F("rxPin")] = dmxInputTransmitPin; - if_live_dmx[F("txPin")] = dmxInputReceivePin; - if_live_dmx[F("enablePin")] = dmxInputEnablePin; + if_live_dmx[F("inputRxPin")] = dmxInputTransmitPin; + if_live_dmx[F("inputTxPin")] = dmxInputReceivePin; + if_live_dmx[F("inputEnablePin")] = dmxInputEnablePin; #endif if_live[F("timeout")] = realtimeTimeoutMs / 100; diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index 34b9fc6cd..16bd3f7f6 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -151,6 +151,18 @@ Timeout: ms
Force max brightness:
Disable realtime gamma correction:
Realtime LED offset: +
+ DMX Input Pins
+ DMX RX:
+ DMX TX:
+ DMX Enable:
+
+
+
This firmware build does not include DMX Input support.
+
+
+
This firmware build does not include DMX output support.
+

Alexa Voice Assistant

From 033c7abe62198052a53e8c29f04ec9b56a66ecf3 Mon Sep 17 00:00:00 2001 From: Arne Date: Thu, 17 Aug 2023 16:53:48 +0200 Subject: [PATCH 086/125] add enable/disable methods for dmxInput --- wled00/dmx_input.cpp | 15 +++++++++++++++ wled00/dmx_input.h | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index 28c337258..a150fa780 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -147,4 +147,19 @@ void DMXInput::turnOnAllLeds() strip.show(); } +void DMXInput::disable() +{ + if (initialized) + { + dmx_driver_disable(inputPortNum); + } +} +void DMXInput::enable() +{ + if(initialized) + { + dmx_driver_enable(inputPortNum); + } +} + #endif \ No newline at end of file diff --git a/wled00/dmx_input.h b/wled00/dmx_input.h index b33c2a16f..b96077c1b 100644 --- a/wled00/dmx_input.h +++ b/wled00/dmx_input.h @@ -12,6 +12,10 @@ public: void init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPortNum); void update(); + /**disable dmx receiver (do this before disabling the cache)*/ + void disable(); + void enable(); + private: /// overrides everything and turns on all leds From 9d8fdd0b20e407948b975d847cd1eecbd062f0cf Mon Sep 17 00:00:00 2001 From: Arne Date: Fri, 18 Aug 2023 15:43:14 +0200 Subject: [PATCH 087/125] extract test for rdm identify into own method --- wled00/dmx_input.cpp | 15 +++++++++++---- wled00/dmx_input.h | 2 ++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index a150fa780..d46e3bebb 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -103,11 +103,9 @@ void DMXInput::update() connected = true; } - uint8_t identify = 0; - const bool gotIdentify = rdm_get_identify_device(inputPortNum, &identify); - // gotIdentify should never be false because it is a default parameter in rdm but just in case we check for it anyway - if (identify && gotIdentify) + if (isIdentifyOn()) { + DEBUG_PRINTLN("RDM Identify active"); turnOnAllLeds(); } else @@ -162,4 +160,13 @@ void DMXInput::enable() } } +bool DMXInput::isIdentifyOn() const +{ + + uint8_t identify = 0; + const bool gotIdentify = rdm_get_identify_device(inputPortNum, &identify); + // gotIdentify should never be false because it is a default parameter in rdm + // but just in case we check for it anyway + return bool(identify) && gotIdentify; +} #endif \ No newline at end of file diff --git a/wled00/dmx_input.h b/wled00/dmx_input.h index b96077c1b..27425d0d4 100644 --- a/wled00/dmx_input.h +++ b/wled00/dmx_input.h @@ -17,6 +17,8 @@ public: void enable(); private: + /// @return true if rdm identify is active + bool isIdentifyOn() const; /// overrides everything and turns on all leds void turnOnAllLeds(); From be3e331afba624182625d3d8dceddb4b550bc79e Mon Sep 17 00:00:00 2001 From: Arne Date: Fri, 18 Aug 2023 15:44:27 +0200 Subject: [PATCH 088/125] Monitor dmx personality and dmx start address for change and update rdm --- wled00/dmx_input.cpp | 28 ++++++++++++++++++++++++++++ wled00/dmx_input.h | 5 +++++ 2 files changed, 33 insertions(+) diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index d46e3bebb..e60be4200 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -90,6 +90,9 @@ void DMXInput::update() { return; } + + checkAndUpdateConfig(); + byte dmxdata[DMX_PACKET_SIZE]; dmx_packet_t packet; unsigned long now = millis(); @@ -169,4 +172,29 @@ bool DMXInput::isIdentifyOn() const // but just in case we check for it anyway return bool(identify) && gotIdentify; } + +void DMXInput::checkAndUpdateConfig() +{ + + /** + * The global configuration variables are modified by the web interface. + * If they differ from the driver configuration, we have to update the driver + * configuration. + */ + + const uint8_t currentPersonality = dmx_get_current_personality(inputPortNum); + if (currentPersonality != DMXMode) + { + DEBUG_PRINTF("DMX personality has changed from %d to %d\n", currentPersonality, DMXMode); + dmx_set_current_personality(inputPortNum, DMXMode); + } + + const uint16_t currentAddr = dmx_get_start_address(inputPortNum); + if (currentAddr != DMXAddress) + { + DEBUG_PRINTF("DMX address has changed from %d to %d\n", currentAddr, DMXAddress); + dmx_set_start_address(inputPortNum, DMXAddress); + } +} + #endif \ No newline at end of file diff --git a/wled00/dmx_input.h b/wled00/dmx_input.h index 27425d0d4..1871176e9 100644 --- a/wled00/dmx_input.h +++ b/wled00/dmx_input.h @@ -20,6 +20,11 @@ private: /// @return true if rdm identify is active bool isIdentifyOn() const; + /** + * Checks if the global dmx config has changed and updates the changes in rdm + */ + void checkAndUpdateConfig(); + /// overrides everything and turns on all leds void turnOnAllLeds(); From 50b56c64f5af3be65395682949d05439f92a5b8a Mon Sep 17 00:00:00 2001 From: Arne Date: Fri, 18 Aug 2023 15:46:18 +0200 Subject: [PATCH 089/125] extract creation of dmx config into own method --- wled00/dmx_input.cpp | 59 +++++++++++++++++++++++++++++++++----------- wled00/dmx_input.h | 1 + 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index e60be4200..b60f56cdd 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -9,6 +9,49 @@ #include "dmx_input.h" #include + +dmx_config_t DMXInput::createConfig() const +{ + + dmx_config_t config; + config.pd_size = 255; + config.dmx_start_address = DMXAddress; // TODO split between input and output address + config.model_id = 0; + config.product_category = RDM_PRODUCT_CATEGORY_FIXTURE; + config.software_version_id = VERSION; + + const std::string versionString = "WLED_V" + std::to_string(VERSION); + strncpy(config.software_version_label, versionString.c_str(), 32); + config.software_version_label[32] = '\0'; // zero termination in case versionString string was longer than 32 chars + + config.personalities[0].description = "SINGLE_RGB"; + config.personalities[0].footprint = 3; + config.personalities[1].description = "SINGLE_DRGB"; + config.personalities[1].footprint = 4; + config.personalities[2].description = "EFFECT"; + config.personalities[2].footprint = 15; + config.personalities[3].description = "MULTIPLE_RGB"; + config.personalities[3].footprint = std::min(512, int(strip.getLengthTotal()) * 3); + config.personalities[4].description = "MULTIPLE_DRGB"; + config.personalities[4].footprint = std::min(512, int(strip.getLengthTotal()) * 3 + 1); + config.personalities[5].description = "MULTIPLE_RGBW"; + config.personalities[5].footprint = std::min(512, int(strip.getLengthTotal()) * 4); + config.personalities[6].description = "EFFECT_W"; + config.personalities[6].footprint = 18; + config.personalities[7].description = "EFFECT_SEGMENT"; + config.personalities[7].footprint = std::min(512, strip.getSegmentsNum() * 15); + config.personalities[8].description = "EFFECT_SEGMENT_W"; + config.personalities[8].footprint = std::min(512, strip.getSegmentsNum() * 18); + config.personalities[9].description = "PRESET"; + config.personalities[9].footprint = 1; + + config.personality_count = 10; + // rdm personalities are numbered from 1, thus we can just set the DMXMode directly. + config.current_personality = DMXMode; + + return config; +} + void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPortNum) { @@ -49,21 +92,7 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo return; } - dmx_config_t config{ - 255, /*alloc_size*/ - 0, /*model_id*/ - RDM_PRODUCT_CATEGORY_FIXTURE, /*product_category*/ - VERSION, /*software_version_id*/ - "undefined", /*software_version_label*/ - 1, /*current_personality*/ - {{15, "WLED Effect Mode"}}, /*personalities*/ - 1, /*personality_count*/ - 1, /*dmx_start_address*/ - }; - const std::string versionString = "WLED_V" + std::to_string(VERSION); - strncpy(config.software_version_label, versionString.c_str(), 32); - config.software_version_label[32] = '\0'; // zero termination in case versionString string was longer than 32 chars - + const auto config = createConfig(); if (!dmx_driver_install(inputPortNum, &config, DMX_INTR_FLAGS_DEFAULT)) { USER_PRINTF("Error: Failed to install dmx driver\n"); diff --git a/wled00/dmx_input.h b/wled00/dmx_input.h index 1871176e9..88b1755c1 100644 --- a/wled00/dmx_input.h +++ b/wled00/dmx_input.h @@ -28,6 +28,7 @@ private: /// overrides everything and turns on all leds void turnOnAllLeds(); + dmx_config_t createConfig() const; uint8_t inputPortNum = 255; // TODO make this configurable /// True once the dmx input has been initialized successfully bool initialized = false; // true once init finished successfully From 2989155f0567df45f9eca6cf644b7b2523f9fcbd Mon Sep 17 00:00:00 2001 From: Arne Date: Fri, 18 Aug 2023 15:47:01 +0200 Subject: [PATCH 090/125] handle rdm dmx address changes --- wled00/dmx_input.cpp | 19 +++++++++++++++++++ wled00/dmx_input.h | 5 +++++ 2 files changed, 24 insertions(+) diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index b60f56cdd..3cb9090b2 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -9,6 +9,24 @@ #include "dmx_input.h" #include +void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, + void *context) +{ + DMXInput *dmx = static_cast(context); + + if (!dmx) + { + USER_PRINTLN("DMX: Error: no context in rdmAddressChangedCb"); + return; + } + + if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) + { + const uint16_t addr = dmx_get_start_address(dmx->inputPortNum); + DMXAddress = std::min(512, int(addr)); + USER_PRINTF("DMX start addr changed to: %d\n", DMXAddress); + } +} dmx_config_t DMXInput::createConfig() const { @@ -104,6 +122,7 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo USER_PRINTF("DMX enable pin is: %u\n", enPin); dmx_set_pin(inputPortNum, txPin, rxPin, enPin); + rdm_register_dmx_start_address(inputPortNum, rdmAddressChangedCb, this); initialized = true; } else diff --git a/wled00/dmx_input.h b/wled00/dmx_input.h index 88b1755c1..df6c31d6f 100644 --- a/wled00/dmx_input.h +++ b/wled00/dmx_input.h @@ -29,6 +29,11 @@ private: void turnOnAllLeds(); dmx_config_t createConfig() const; + + //is invoked whenver the dmx start address is changed via rdm + friend void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, + void *context); + uint8_t inputPortNum = 255; // TODO make this configurable /// True once the dmx input has been initialized successfully bool initialized = false; // true once init finished successfully From 9a3b208ac5ed1a0bf12c6e1cefc73172d9bf3757 Mon Sep 17 00:00:00 2001 From: Arne Date: Fri, 18 Aug 2023 15:47:43 +0200 Subject: [PATCH 091/125] comments and cleanup --- wled00/dmx_input.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index 3cb9090b2..4294a3483 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -85,19 +85,22 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo /** * TODOS: - * - add personalities for all supported dmx input modes - * - select the personality that is stored in flash on startup * - attach callback for personality change and store in flash if changed * - attach callback for address change and store in flash * - load dmx address from flash and set in config on startup * - attach callback to rdm identify and flash leds when on * - Make all important config variables available via rdm + * - RDM_PID_DEVICE_LABEL does not seem to be supported, yet? Implement in esp_dmx and create PR + * - implement changing personality in rdm. (not yet implemented in esp_dmx?) + * - This is more complicated because get personality requests two bytes but + * set personality only contains one byte. Thus the default parameter callback will + * not work. Need to think about this :D */ if (rxPin > 0 && enPin > 0 && txPin > 0) { const managed_pin_type pins[] = { - {(int8_t)txPin, false}, // these are not used as gpio pins, this isOutput is always false. + {(int8_t)txPin, false}, // these are not used as gpio pins, thus isOutput is always false. {(int8_t)rxPin, false}, {(int8_t)enPin, false}}; const bool pinsAllocated = pinManager.allocateMultiplePins(pins, 3, PinOwner::DMX_INPUT); @@ -159,14 +162,12 @@ void DMXInput::update() DEBUG_PRINTLN("RDM Identify active"); turnOnAllLeds(); } - else + else if (!packet.is_rdm) { - if (!packet.is_rdm) - { - dmx_read(inputPortNum, dmxdata, packet.size); - handleDMXData(1, 512, dmxdata, REALTIME_MODE_DMX, 0); - } + dmx_read(inputPortNum, dmxdata, packet.size); + handleDMXData(1, 512, dmxdata, REALTIME_MODE_DMX, 0); } + lastUpdate = now; } else @@ -205,7 +206,7 @@ void DMXInput::disable() } void DMXInput::enable() { - if(initialized) + if (initialized) { dmx_driver_enable(inputPortNum); } From b178c082714ce8c34b2eda92aab38a000e7e5811 Mon Sep 17 00:00:00 2001 From: Arne Date: Tue, 22 Aug 2023 22:09:21 +0200 Subject: [PATCH 092/125] Support dmx rdm personality change --- wled00/dmx_input.cpp | 35 ++++++++++++++++++++++------------- wled00/dmx_input.h | 6 +++++- wled00/wled.cpp | 11 +++++++++++ 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index 4294a3483..0de1337e2 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -9,6 +9,25 @@ #include "dmx_input.h" #include +void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, + void *context) +{ + DMXInput *dmx = static_cast(context); + + if (!dmx) + { + USER_PRINTLN("DMX: Error: no context in rdmPersonalityChangedCb"); + return; + } + + if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) + { + const uint8_t personality = dmx_get_current_personality(dmx->inputPortNum); + DMXMode = std::min(DMX_MODE_PRESET, std::max(DMX_MODE_SINGLE_RGB, int(personality))); + doSerializeConfig = true; + USER_PRINTF("DMX personality changed to to: %d\n", DMXMode); + } +} void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, void *context) { @@ -24,6 +43,7 @@ void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, { const uint16_t addr = dmx_get_start_address(dmx->inputPortNum); DMXAddress = std::min(512, int(addr)); + doSerializeConfig = true; USER_PRINTF("DMX start addr changed to: %d\n", DMXAddress); } } @@ -37,6 +57,7 @@ dmx_config_t DMXInput::createConfig() const config.model_id = 0; config.product_category = RDM_PRODUCT_CATEGORY_FIXTURE; config.software_version_id = VERSION; + strcpy(config.device_label, "WLED_MM"); const std::string versionString = "WLED_V" + std::to_string(VERSION); strncpy(config.software_version_label, versionString.c_str(), 32); @@ -83,19 +104,6 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo return; } - /** - * TODOS: - * - attach callback for personality change and store in flash if changed - * - attach callback for address change and store in flash - * - load dmx address from flash and set in config on startup - * - attach callback to rdm identify and flash leds when on - * - Make all important config variables available via rdm - * - RDM_PID_DEVICE_LABEL does not seem to be supported, yet? Implement in esp_dmx and create PR - * - implement changing personality in rdm. (not yet implemented in esp_dmx?) - * - This is more complicated because get personality requests two bytes but - * set personality only contains one byte. Thus the default parameter callback will - * not work. Need to think about this :D - */ if (rxPin > 0 && enPin > 0 && txPin > 0) { @@ -126,6 +134,7 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo dmx_set_pin(inputPortNum, txPin, rxPin, enPin); rdm_register_dmx_start_address(inputPortNum, rdmAddressChangedCb, this); + rdm_register_dmx_personality(inputPortNum, rdmPersonalityChangedCb, this); initialized = true; } else diff --git a/wled00/dmx_input.h b/wled00/dmx_input.h index df6c31d6f..b762e59be 100644 --- a/wled00/dmx_input.h +++ b/wled00/dmx_input.h @@ -30,10 +30,14 @@ private: dmx_config_t createConfig() const; - //is invoked whenver the dmx start address is changed via rdm + // is invoked whenver the dmx start address is changed via rdm friend void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, void *context); + // is invoked whenever the personality is changed via rdm + friend void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, + void *context); + uint8_t inputPortNum = 255; // TODO make this configurable /// True once the dmx input has been initialized successfully bool initialized = false; // true once init finished successfully diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 376cb6b28..8b2b33762 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -256,6 +256,17 @@ void WLED::loop() } #endif +if (doSerializeConfig) + { + #ifdef WLED_ENABLE_DMX_INPUT + dmxInput.disable(); + #endif + + #ifdef WLED_ENABLE_DMX_INPUT + dmxInput.enable(); + #endif + } + if (doReboot && (!doInitBusses || !doSerializeConfig)) // if busses have to be inited & saved, wait until next iteration reset(); From 2cc5a29b8633dcb04e0bd2b68b54756e57cb63d6 Mon Sep 17 00:00:00 2001 From: Arne Date: Tue, 22 Aug 2023 22:24:31 +0200 Subject: [PATCH 093/125] keep dmx rdm identify on if dmx disconnects. Some rdm testers disconnect after setting it. --- wled00/dmx_input.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index 0de1337e2..f90515fd0 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -165,12 +165,6 @@ void DMXInput::update() USER_PRINTLN("DMX is connected!"); connected = true; } - - if (isIdentifyOn()) - { - DEBUG_PRINTLN("RDM Identify active"); - turnOnAllLeds(); - } else if (!packet.is_rdm) { dmx_read(inputPortNum, dmxdata, packet.size); @@ -192,6 +186,12 @@ void DMXInput::update() connected = false; USER_PRINTLN("DMX was disconnected."); } + + if (isIdentifyOn()) + { + DEBUG_PRINTLN("RDM Identify active"); + turnOnAllLeds(); + } } void DMXInput::turnOnAllLeds() From 84eb6fd460cbbb87712e67d789a079c9fb60e48a Mon Sep 17 00:00:00 2001 From: Arne Date: Wed, 23 Aug 2023 14:33:12 +0200 Subject: [PATCH 094/125] Add dmx input port to configuration --- wled00/data/settings_sync.htm | 1 + wled00/set.cpp | 4 +++- wled00/wled.cpp | 3 +-- wled00/wled.h | 1 + wled00/xml.cpp | 1 + 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index 16bd3f7f6..8e142dc9b 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -156,6 +156,7 @@ Realtime LED offset:
DMX TX:
DMX Enable:
+ DMX Port:

This firmware build does not include DMX Input support.
diff --git a/wled00/set.cpp b/wled00/set.cpp index 9a12cdc21..08a0180ad 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -423,7 +423,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) #ifdef WLED_ENABLE_DMX_INPUT dmxInputTransmitPin = request->arg(F("IDMT")).toInt(); dmxInputReceivePin = request->arg(F("IDMR")).toInt(); - dmxInputEnablePin= request->arg(F("IDME")).toInt(); + dmxInputEnablePin = request->arg(F("IDME")).toInt(); + dmxInputPort = request->arg(F("IDMP")).toInt(); + if(dmxInputPort <= 0 || dmxInputPort > 2) dmxInputPort = 2; #endif #ifndef WLED_DISABLE_ALEXA diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 8b2b33762..341036abf 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -541,8 +541,7 @@ void WLED::setup() initDMX(); #endif #ifdef WLED_ENABLE_DMX_INPUT - const uint8_t dmxInputPortNumber = 2; //TODO turn into config variable?! - dmxInput.init(dmxInputReceivePin, dmxInputTransmitPin, dmxInputEnablePin, dmxInputPortNumber); + dmxInput.init(dmxInputReceivePin, dmxInputTransmitPin, dmxInputEnablePin, dmxInputPort); #endif #ifdef WLED_ENABLE_ADALIGHT diff --git a/wled00/wled.h b/wled00/wled.h index b366e82a1..b7f1ae710 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -467,6 +467,7 @@ WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to f WLED_GLOBAL int dmxInputTransmitPin _INIT(0); WLED_GLOBAL int dmxInputReceivePin _INIT(0); WLED_GLOBAL int dmxInputEnablePin _INIT(0); + WLED_GLOBAL int dmxInputPort _INIT(2); WLED_GLOBAL DMXInput dmxInput; #endif diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 5caa7159c..aa49de022 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -446,6 +446,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) sappend('v',SET_F("IDMT"),dmxInputTransmitPin); sappend('v',SET_F("IDMR"),dmxInputReceivePin); sappend('v',SET_F("IDME"),dmxInputEnablePin); + sappend('v',SET_F("IDMP"),dmxInputPort); #endif printSetFormValue(settingsScript,PSTR("DA"),DMXAddress); printSetFormValue(settingsScript,PSTR("XX"),DMXSegmentSpacing); From 7f9cc6751875dd4882f91bf58adcb820d76cab8c Mon Sep 17 00:00:00 2001 From: Arne Date: Wed, 23 Aug 2023 14:50:22 +0200 Subject: [PATCH 095/125] Rename WLED_ENABLE_DMX to WLED_ENABLE_DMX_OUTPUT --- tools/cdata.js | 2 +- wled00/cfg.cpp | 6 +++--- wled00/const.h | 7 +++++++ wled00/dmx.cpp | 2 +- wled00/e131.cpp | 2 +- wled00/set.cpp | 2 +- wled00/wled.cpp | 6 +++--- wled00/wled.h | 7 ++++--- wled00/wled_eeprom.cpp | 4 ++-- wled00/wled_server.cpp | 10 +++++----- wled00/xml.cpp | 8 ++++---- 11 files changed, 32 insertions(+), 24 deletions(-) diff --git a/tools/cdata.js b/tools/cdata.js index c5d3c6aa5..b9f29694c 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -358,7 +358,7 @@ writeChunks( method: "plaintext", filter: "html-minify", mangle: (str) => ` -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT ${str.replace(/function FM\(\)[ ]?\{/gms, "function FM() {%DMXVARS%\n")} #else const char PAGE_dmxmap[] PROGMEM = R"=====()====="; diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index d3a8a0c6d..4d8918db4 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -651,7 +651,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { getStringFromJson(otaPass, pwd, 33); //normally not present due to security } - #ifdef WLED_ENABLE_DMX + #ifdef WLED_ENABLE_DMX_OUTPUT JsonObject dmx = doc["dmx"]; CJSON(DMXChannels, dmx[F("chan")]); CJSON(DMXGap,dmx[F("gap")]); @@ -1116,8 +1116,8 @@ void serializeConfig() { ota[F("pskl")] = strlen(otaPass); ota[F("aota")] = aOtaEnabled; - #ifdef WLED_ENABLE_DMX - JsonObject dmx = root.createNestedObject("dmx"); + #ifdef WLED_ENABLE_DMX_OUTPUT + JsonObject dmx = doc.createNestedObject("dmx"); dmx[F("chan")] = DMXChannels; dmx[F("gap")] = DMXGap; dmx["start"] = DMXStart; diff --git a/wled00/const.h b/wled00/const.h index bdd20beba..e0e4916d3 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -583,6 +583,13 @@ #define DEFAULT_LED_COUNT 30 #define INTERFACE_UPDATE_COOLDOWN 1000 // time in ms to wait between websockets, alexa, and MQTT updates +#ifdef WLED_ENABLE_DMX_OUTPUT +#if (LEDPIN == 2) + #undef LEDPIN + #define LEDPIN 1 + #warning "Pin conflict compiling with DMX and LEDs on pin 2. The default LED pin has been changed to pin 1." +#endif +#endif #define PIN_RETRY_COOLDOWN 3000 // time in ms after an incorrect attempt PIN and OTA pass will be rejected even if correct #define PIN_TIMEOUT 900000 // time in ms after which the PIN will be required again, 15 minutes diff --git a/wled00/dmx.cpp b/wled00/dmx.cpp index aa05c0113..efdd902ec 100644 --- a/wled00/dmx.cpp +++ b/wled00/dmx.cpp @@ -10,7 +10,7 @@ * https://github.com/sparkfun/SparkFunDMX */ -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT void handleDMX() { diff --git a/wled00/e131.cpp b/wled00/e131.cpp index bc26a0639..98ba5dfd9 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -93,7 +93,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ return; } - #ifdef WLED_ENABLE_DMX + #ifdef WLED_ENABLE_DMX_OUTPUT // does not act on out-of-order packets yet if (e131ProxyUniverse > 0 && uni == e131ProxyUniverse) { for (uint16_t i = 1; i <= dmxChannels; i++) diff --git a/wled00/set.cpp b/wled00/set.cpp index 08a0180ad..7ffd80653 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -598,7 +598,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) } } - #ifdef WLED_ENABLE_DMX // include only if DMX is enabled + #ifdef WLED_ENABLE_DMX_OUTPUT // include only if DMX is enabled if (subPage == SUBPAGE_DMX) { int t = request->arg(F("PU")).toInt(); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 341036abf..e19361a18 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -64,7 +64,7 @@ void WLED::loop() handleImprovWifiScan(); handleNotifications(); handleTransitions(); - #ifdef WLED_ENABLE_DMX + #ifdef WLED_ENABLE_DMX_OUTPUT handleDMX(); #endif #ifdef WLED_ENABLE_DMX_INPUT @@ -434,7 +434,7 @@ void WLED::setup() #if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST) PinManager::allocatePin(hardwareTX, true, PinOwner::DebugOut); // TX (GPIO1 on ESP32) reserved for debug output #endif -#ifdef WLED_ENABLE_DMX //reserve GPIO2 as hardcoded DMX pin +#ifdef WLED_ENABLE_DMX_OUTPUT //reserve GPIO2 as hardcoded DMX pin PinManager::allocatePin(2, true, PinOwner::DMX); #endif @@ -537,7 +537,7 @@ void WLED::setup() ArduinoOTA.setHostname(cmDNS); } #endif -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT initDMX(); #endif #ifdef WLED_ENABLE_DMX_INPUT diff --git a/wled00/wled.h b/wled00/wled.h index b7f1ae710..af4a0a84d 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -34,7 +34,8 @@ #else #undef WLED_ENABLE_ADALIGHT // disable has priority over enable #endif -//#define WLED_ENABLE_DMX // uses 3.5kb +//#define WLED_ENABLE_DMX_OUTPUT // uses 3.5kb (use LEDPIN other than 2) +//#define WLED_ENABLE_DMX_INPUT // Listen for DMX over Serial #ifndef WLED_DISABLE_LOXONE #define WLED_ENABLE_LOXONE // uses 1.2kb #endif @@ -136,7 +137,7 @@ #include "src/dependencies/espalexa/EspalexaDevice.h" #endif -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT #if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) #include "src/dependencies/dmx/ESPDMX.h" #else //ESP32 @@ -448,7 +449,7 @@ WLED_GLOBAL int arlsOffset _INIT(0); // realtime LE WLED_GLOBAL bool arlsDisableGammaCorrection _INIT(true); // activate if gamma correction is handled by the source WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to force max brightness if source has very dark colors that would be black -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT #if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) WLED_GLOBAL DMXESPSerial dmx; #else //ESP32 diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp index 8582b49df..99995debd 100644 --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -313,7 +313,7 @@ void loadSettingsFromEEPROM() e131Port = EEPROM.read(2187) + ((EEPROM.read(2188) << 8) & 0xFF00); } - #ifdef WLED_ENABLE_DMX + #ifdef WLED_ENABLE_DMX_OUTPUT if (lastEEPROMversion > 19) { e131ProxyUniverse = EEPROM.read(2185) + ((EEPROM.read(2186) << 8) & 0xFF00); @@ -342,7 +342,7 @@ void loadSettingsFromEEPROM() //custom macro memory (16 slots/ each 64byte) //1024-2047 reserved - #ifdef WLED_ENABLE_DMX + #ifdef WLED_ENABLE_DMX_OUTPUT // DMX (2530 - 2549)2535 DMXChannels = EEPROM.read(2530); DMXGap = EEPROM.read(2531) + ((EEPROM.read(2532) << 8) & 0xFF00); diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index e8cbb41ae..327cff2c3 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -96,7 +96,7 @@ static void handleStaticContent(AsyncWebServerRequest *request, const String &pa request->send(response); } -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT static String dmxProcessor(const String& var) { String mapJS; @@ -426,7 +426,7 @@ void initServer() #endif -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT server.on(F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, FPSTR(CONTENT_TYPE_HTML), PAGE_dmxmap , dmxProcessor); }); @@ -554,7 +554,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { else if (url.indexOf( "sync") > 0) subPage = SUBPAGE_SYNC; else if (url.indexOf( "time") > 0) subPage = SUBPAGE_TIME; else if (url.indexOf(F("sec")) > 0) subPage = SUBPAGE_SEC; -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT else if (url.indexOf( "dmx") > 0) subPage = SUBPAGE_DMX; #endif else if (url.indexOf( "um") > 0) subPage = SUBPAGE_UM; @@ -591,7 +591,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { case SUBPAGE_SYNC : strcpy_P(s, PSTR("Sync")); break; case SUBPAGE_TIME : strcpy_P(s, PSTR("Time")); break; case SUBPAGE_SEC : strcpy_P(s, PSTR("Security")); if (doReboot) strcpy_P(s2, PSTR("Rebooting, please wait ~10 seconds...")); break; -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT case SUBPAGE_DMX : strcpy_P(s, PSTR("DMX")); break; #endif case SUBPAGE_UM : strcpy_P(s, PSTR("Usermods")); break; @@ -626,7 +626,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { case SUBPAGE_SYNC : content = PAGE_settings_sync; len = PAGE_settings_sync_length; break; case SUBPAGE_TIME : content = PAGE_settings_time; len = PAGE_settings_time_length; break; case SUBPAGE_SEC : content = PAGE_settings_sec; len = PAGE_settings_sec_length; break; -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT case SUBPAGE_DMX : content = PAGE_settings_dmx; len = PAGE_settings_dmx_length; break; #endif case SUBPAGE_UM : content = PAGE_settings_um; len = PAGE_settings_um_length; break; diff --git a/wled00/xml.cpp b/wled00/xml.cpp index aa49de022..b3ba4a0e8 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -100,7 +100,7 @@ void appendGPIOinfo(Print& settingsScript) { firstPin = false; } } - #ifdef WLED_ENABLE_DMX + #ifdef WLED_ENABLE_DMX_OUTPUT if (!firstPin) settingsScript.print(','); settingsScript.print(2); // DMX hardcoded pin firstPin = false; @@ -164,7 +164,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) #ifdef WLED_DISABLE_2D // include only if 2D is not compiled in settingsScript.print(F("gId('2dbtn').style.display='none';")); #endif - #ifdef WLED_ENABLE_DMX // include only if DMX is enabled + #ifdef WLED_ENABLE_DMX_OUTPUT // include only if DMX is enabled settingsScript.print(F("gId('dmxbtn').style.display='';")); #endif } @@ -436,7 +436,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormCheckbox(settingsScript,PSTR("ES"),e131SkipOutOfSequence); printSetFormCheckbox(settingsScript,PSTR("EM"),e131Multicast); printSetFormValue(settingsScript,PSTR("EU"),e131Universe); -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT settingsScript.print(SET_F("hideNoDMX();")); // hide "not compiled in" message #endif #ifndef WLED_ENABLE_DMX_INPUT @@ -596,7 +596,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) settingsScript.printf_P(PSTR("sd=\"%s\";"), serverDescription); } - #ifdef WLED_ENABLE_DMX // include only if DMX is enabled + #ifdef WLED_ENABLE_DMX_OUTPUT // include only if DMX is enabled if (subPage == SUBPAGE_DMX) { printSetFormValue(settingsScript,PSTR("PU"),e131ProxyUniverse); From fa80c62b2862352ac073e8a3067900e0541d4a4d Mon Sep 17 00:00:00 2001 From: Arne Date: Wed, 23 Aug 2023 14:50:53 +0200 Subject: [PATCH 096/125] rename dmx.cpp -> dmx_output.cpp --- wled00/{dmx.cpp => dmx_output.cpp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename wled00/{dmx.cpp => dmx_output.cpp} (100%) diff --git a/wled00/dmx.cpp b/wled00/dmx_output.cpp similarity index 100% rename from wled00/dmx.cpp rename to wled00/dmx_output.cpp From 11b48bc3745a7dd1b20857486c9d2c43e8ce294e Mon Sep 17 00:00:00 2001 From: Arne Date: Wed, 23 Aug 2023 14:52:57 +0200 Subject: [PATCH 097/125] rename handleDMX() handleDMXOutput() --- wled00/dmx_output.cpp | 4 ++-- wled00/fcn_declare.h | 5 +---- wled00/wled.cpp | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/wled00/dmx_output.cpp b/wled00/dmx_output.cpp index efdd902ec..1c883c15a 100644 --- a/wled00/dmx_output.cpp +++ b/wled00/dmx_output.cpp @@ -12,7 +12,7 @@ #ifdef WLED_ENABLE_DMX_OUTPUT -void handleDMX() +void handleDMXOutput() { // don't act, when in DMX Proxy mode if (e131ProxyUniverse != 0) return; @@ -77,5 +77,5 @@ void initDMX() { } #else void initDMX(){} -void handleDMX() {} +void handleDMXOutput() {} #endif diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index c3ada1a57..831cc68f8 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -185,14 +185,11 @@ void setRandomColor(byte* rgb); //dmx.cpp void initDMX(); -void handleDMX(); -<<<<<<< HEAD -======= +void handleDMXOutput(); //dmx_input.cpp void initDMXInput(); void handleDMXInput(); ->>>>>>> b4bbf0a5 (Extract dmx_input from dmx.cpp into dmx_input.cpp.) //e131.cpp void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index e19361a18..27f74af02 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -65,7 +65,7 @@ void WLED::loop() handleNotifications(); handleTransitions(); #ifdef WLED_ENABLE_DMX_OUTPUT - handleDMX(); + handleDMXOutput(); #endif #ifdef WLED_ENABLE_DMX_INPUT dmxInput.update(); From 67e8a00b6dfd7673076d3617c339a8d41e59e51f Mon Sep 17 00:00:00 2001 From: Arne Date: Wed, 23 Aug 2023 15:04:28 +0200 Subject: [PATCH 098/125] rename initDmx() -> initDmxOutput() --- wled00/dmx_output.cpp | 4 ++-- wled00/fcn_declare.h | 4 ++-- wled00/wled.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/wled00/dmx_output.cpp b/wled00/dmx_output.cpp index 1c883c15a..12095965e 100644 --- a/wled00/dmx_output.cpp +++ b/wled00/dmx_output.cpp @@ -68,7 +68,7 @@ void handleDMXOutput() dmx.update(); // update the DMX bus } -void initDMX() { +void initDMXOutput() { #if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) dmx.init(512); // initialize with bus length #else @@ -76,6 +76,6 @@ void initDMX() { #endif } #else -void initDMX(){} +void initDMXOutput(){} void handleDMXOutput() {} #endif diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 831cc68f8..db6c6b872 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -183,8 +183,8 @@ uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); uint16_t approximateKelvinFromRGB(uint32_t rgb); void setRandomColor(byte* rgb); -//dmx.cpp -void initDMX(); +//dmx_output.cpp +void initDMXOutput(); void handleDMXOutput(); //dmx_input.cpp diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 27f74af02..44e2faf5a 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -538,7 +538,7 @@ void WLED::setup() } #endif #ifdef WLED_ENABLE_DMX_OUTPUT - initDMX(); + initDMXOutput(); #endif #ifdef WLED_ENABLE_DMX_INPUT dmxInput.init(dmxInputReceivePin, dmxInputTransmitPin, dmxInputEnablePin, dmxInputPort); From 68e9d701dea5620a4daa503ee8672e8707e7cbe7 Mon Sep 17 00:00:00 2001 From: Arne Date: Fri, 25 Aug 2023 20:59:03 +0200 Subject: [PATCH 099/125] Do no longer disable dmx_input when cache is disabled. No longer needed because missing ISR_ATTR have been added to esp_dmx. --- wled00/wled.cpp | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 44e2faf5a..46d156e79 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -256,17 +256,6 @@ void WLED::loop() } #endif -if (doSerializeConfig) - { - #ifdef WLED_ENABLE_DMX_INPUT - dmxInput.disable(); - #endif - - #ifdef WLED_ENABLE_DMX_INPUT - dmxInput.enable(); - #endif - } - if (doReboot && (!doInitBusses || !doSerializeConfig)) // if busses have to be inited & saved, wait until next iteration reset(); @@ -792,10 +781,6 @@ int8_t WLED::findWiFi(bool doScan) { void WLED::initConnection() { DEBUG_PRINTF_P(PSTR("initConnection() called @ %lus.\n"), millis()/1000); - - #ifdef WLED_ENABLE_DMX_INPUT - dmxInput.disable(); - #endif #ifdef WLED_ENABLE_WEBSOCKETS ws.onEvent(wsEvent); #endif @@ -824,10 +809,6 @@ void WLED::initConnection() if (!WLED_WIFI_CONFIGURED) { DEBUG_PRINTLN(F("No connection configured.")); if (!apActive) initAP(); // instantly go to ap mode - - #ifdef WLED_ENABLE_DMX_INPUT - dmxInput.enable(); - #endif return; } else if (!apActive) { if (apBehavior == AP_BEHAVIOR_ALWAYS) { @@ -878,10 +859,6 @@ void WLED::initConnection() statusESPNow = espNowOK ? ESP_NOW_STATE_ON : ESP_NOW_STATE_ERROR; } #endif - - #ifdef WLED_ENABLE_DMX_INPUT - dmxInput.enable(); - #endif } void WLED::initInterfaces() From 8f398dfd08e44497b17ed16f0432995858a0c363 Mon Sep 17 00:00:00 2001 From: Arne Date: Fri, 25 Aug 2023 20:59:46 +0200 Subject: [PATCH 100/125] Move dmx_input into its own task on core 0. This was necessary because otherwise it is not able to respond to rdm in time. --- wled00/dmx_input.cpp | 110 +++++++++++++++++++++++++++++-------------- wled00/dmx_input.h | 31 ++++++++++-- 2 files changed, 103 insertions(+), 38 deletions(-) diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index f90515fd0..4ccfbab76 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -28,6 +28,7 @@ void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, USER_PRINTF("DMX personality changed to to: %d\n", DMXMode); } } + void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, void *context) { @@ -48,9 +49,8 @@ void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, } } -dmx_config_t DMXInput::createConfig() const +static dmx_config_t createConfig() { - dmx_config_t config; config.pd_size = 255; config.dmx_start_address = DMXAddress; // TODO split between input and output address @@ -91,9 +91,55 @@ dmx_config_t DMXInput::createConfig() const return config; } +void dmxReceiverTask(void *context) +{ + DMXInput *instance = static_cast(context); + if (instance == nullptr) + { + return; + } + + if (instance->installDriver()) + { + while (true) + { + instance->updateInternal(); + } + } +} + +bool DMXInput::installDriver() +{ + + const auto config = createConfig(); + if (!dmx_driver_install(inputPortNum, &config, DMX_INTR_FLAGS_DEFAULT)) + { + USER_PRINTF("Error: Failed to install dmx driver\n"); + return false; + } + + USER_PRINTF("Listening for DMX on pin %u\n", rxPin); + USER_PRINTF("Sending DMX on pin %u\n", txPin); + USER_PRINTF("DMX enable pin is: %u\n", enPin); + dmx_set_pin(inputPortNum, txPin, rxPin, enPin); + + rdm_register_dmx_start_address(inputPortNum, rdmAddressChangedCb, this); + rdm_register_dmx_personality(inputPortNum, rdmPersonalityChangedCb, this); + initialized = true; + return true; +} + void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPortNum) { +#ifdef WLED_ENABLE_DMX_OUTPUT + if(inputPortNum == dmxOutputPort) + { + USER_PRINTF("DMXInput: Error: Input port == output port"); + return; + } +#endif + if (inputPortNum < 3 && inputPortNum > 0) { this->inputPortNum = inputPortNum; @@ -121,21 +167,17 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo return; } - const auto config = createConfig(); - if (!dmx_driver_install(inputPortNum, &config, DMX_INTR_FLAGS_DEFAULT)) + this->rxPin = rxPin; + this->txPin = txPin; + this->enPin = enPin; + + // put dmx receiver into seperate task because it should not be blocked + // pin to core 0 because wled is running on core 1 + xTaskCreatePinnedToCore(dmxReceiverTask, "DMX_RCV_TASK", 10240, this, 2, &task, 0); + if (!task) { - USER_PRINTF("Error: Failed to install dmx driver\n"); - return; + USER_PRINTF("Error: Failed to create dmx rcv task"); } - - USER_PRINTF("Listening for DMX on pin %u\n", rxPin); - USER_PRINTF("Sending DMX on pin %u\n", txPin); - USER_PRINTF("DMX enable pin is: %u\n", enPin); - dmx_set_pin(inputPortNum, txPin, rxPin, enPin); - - rdm_register_dmx_start_address(inputPortNum, rdmAddressChangedCb, this); - rdm_register_dmx_personality(inputPortNum, rdmPersonalityChangedCb, this); - initialized = true; } else { @@ -144,7 +186,7 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo } } -void DMXInput::update() +void DMXInput::updateInternal() { if (!initialized) { @@ -153,45 +195,43 @@ void DMXInput::update() checkAndUpdateConfig(); - byte dmxdata[DMX_PACKET_SIZE]; dmx_packet_t packet; unsigned long now = millis(); - if (dmx_receive(inputPortNum, &packet, 0)) + if (dmx_receive(inputPortNum, &packet, DMX_TIMEOUT_TICK)) { if (!packet.err) { - if (!connected) - { - USER_PRINTLN("DMX is connected!"); - connected = true; - } - else if (!packet.is_rdm) + connected = true; + identify = isIdentifyOn(); + if (!packet.is_rdm) { + const std::lock_guard lock(dmxDataLock); dmx_read(inputPortNum, dmxdata, packet.size); - handleDMXData(1, 512, dmxdata, REALTIME_MODE_DMX, 0); } - - lastUpdate = now; } else { - /*This can happen when you first connect or disconnect your DMX devices. - If you are consistently getting DMX errors, then something may have gone wrong. */ - DEBUG_PRINT("A DMX error occurred - "); - DEBUG_PRINTLN(packet.err); // TODO translate err code to string for output + connected = false; } } - else if (connected && (now - lastUpdate > 5000)) + else { connected = false; - USER_PRINTLN("DMX was disconnected."); } +} - if (isIdentifyOn()) + +void DMXInput::update() +{ + if (identify) { - DEBUG_PRINTLN("RDM Identify active"); turnOnAllLeds(); } + else if (connected) + { + const std::lock_guard lock(dmxDataLock); + handleDMXData(1, 512, dmxdata, REALTIME_MODE_DMX, 0); + } } void DMXInput::turnOnAllLeds() diff --git a/wled00/dmx_input.h b/wled00/dmx_input.h index b762e59be..7a6266c50 100644 --- a/wled00/dmx_input.h +++ b/wled00/dmx_input.h @@ -1,6 +1,9 @@ #pragma once #include #include +#include +#include + /* * Support for DMX/RDM input via serial (e.g. max485) on ESP32 * ESP32 Library from: @@ -28,7 +31,12 @@ private: /// overrides everything and turns on all leds void turnOnAllLeds(); - dmx_config_t createConfig() const; + /// installs the dmx driver + /// @return false on fail + bool installDriver(); + + /// is called by the dmx receive task regularly to receive new dmx data + void updateInternal(); // is invoked whenver the dmx start address is changed via rdm friend void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, @@ -38,11 +46,28 @@ private: friend void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, void *context); - uint8_t inputPortNum = 255; // TODO make this configurable + /// The internal dmx task. + /// This is the main loop of the dmx receiver. It never returns. + friend void dmxReceiverTask(void * context); + + uint8_t inputPortNum = 255; + uint8_t rxPin = 255; + uint8_t txPin = 255; + uint8_t enPin = 255; + + /// is written to by the dmx receive task. + byte dmxdata[DMX_PACKET_SIZE]; //TODO add locking somehow? maybe double buffer? /// True once the dmx input has been initialized successfully bool initialized = false; // true once init finished successfully /// True if dmx is currently connected - bool connected = false; + std::atomic connected{false}; + std::atomic identify{false}; /// Timestamp of the last time a dmx frame was received unsigned long lastUpdate = 0; + + /// Taskhandle of the dmx task that is running in the background + TaskHandle_t task; + /// Guards access to dmxData + std::mutex dmxDataLock; + }; From 6598265f9bf6d2c0de6cea38c4091eb8bafa5167 Mon Sep 17 00:00:00 2001 From: Arne Date: Sun, 3 Sep 2023 16:37:19 +0200 Subject: [PATCH 101/125] make compile after rebase --- wled00/dmx_input.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index 4ccfbab76..902303bb7 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -133,11 +133,12 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo { #ifdef WLED_ENABLE_DMX_OUTPUT - if(inputPortNum == dmxOutputPort) - { - USER_PRINTF("DMXInput: Error: Input port == output port"); - return; - } + //TODO add again once dmx output has been merged + // if(inputPortNum == dmxOutputPort) + // { + // USER_PRINTF("DMXInput: Error: Input port == output port"); + // return; + // } #endif if (inputPortNum < 3 && inputPortNum > 0) From 8570922dccc3c3cf2ffa27130c3a500b9ebacd0a Mon Sep 17 00:00:00 2001 From: Arne Date: Sun, 22 Oct 2023 20:32:46 +0200 Subject: [PATCH 102/125] chore: adapt code style --- wled00/dmx_input.cpp | 80 +++++++++++++++----------------------------- 1 file changed, 27 insertions(+), 53 deletions(-) diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index 902303bb7..af0bb679d 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -14,14 +14,12 @@ void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, { DMXInput *dmx = static_cast(context); - if (!dmx) - { + if (!dmx) { USER_PRINTLN("DMX: Error: no context in rdmPersonalityChangedCb"); return; } - if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) - { + if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) { const uint8_t personality = dmx_get_current_personality(dmx->inputPortNum); DMXMode = std::min(DMX_MODE_PRESET, std::max(DMX_MODE_SINGLE_RGB, int(personality))); doSerializeConfig = true; @@ -34,14 +32,12 @@ void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, { DMXInput *dmx = static_cast(context); - if (!dmx) - { + if (!dmx) { USER_PRINTLN("DMX: Error: no context in rdmAddressChangedCb"); return; } - if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) - { + if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) { const uint16_t addr = dmx_get_start_address(dmx->inputPortNum); DMXAddress = std::min(512, int(addr)); doSerializeConfig = true; @@ -53,7 +49,7 @@ static dmx_config_t createConfig() { dmx_config_t config; config.pd_size = 255; - config.dmx_start_address = DMXAddress; // TODO split between input and output address + config.dmx_start_address = DMXAddress; config.model_id = 0; config.product_category = RDM_PRODUCT_CATEGORY_FIXTURE; config.software_version_id = VERSION; @@ -94,15 +90,12 @@ static dmx_config_t createConfig() void dmxReceiverTask(void *context) { DMXInput *instance = static_cast(context); - if (instance == nullptr) - { + if (instance == nullptr) { return; } - if (instance->installDriver()) - { - while (true) - { + if (instance->installDriver()) { + while (true) { instance->updateInternal(); } } @@ -112,8 +105,7 @@ bool DMXInput::installDriver() { const auto config = createConfig(); - if (!dmx_driver_install(inputPortNum, &config, DMX_INTR_FLAGS_DEFAULT)) - { + if (!dmx_driver_install(inputPortNum, &config, DMX_INTR_FLAGS_DEFAULT)) { USER_PRINTF("Error: Failed to install dmx driver\n"); return false; } @@ -141,26 +133,22 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo // } #endif - if (inputPortNum < 3 && inputPortNum > 0) - { + if (inputPortNum < 3 && inputPortNum > 0) { this->inputPortNum = inputPortNum; } - else - { + else { USER_PRINTF("DMXInput: Error: invalid inputPortNum: %d\n", inputPortNum); return; } - if (rxPin > 0 && enPin > 0 && txPin > 0) - { + if (rxPin > 0 && enPin > 0 && txPin > 0) { const managed_pin_type pins[] = { {(int8_t)txPin, false}, // these are not used as gpio pins, thus isOutput is always false. {(int8_t)rxPin, false}, {(int8_t)enPin, false}}; const bool pinsAllocated = pinManager.allocateMultiplePins(pins, 3, PinOwner::DMX_INPUT); - if (!pinsAllocated) - { + if (!pinsAllocated) { USER_PRINTF("DMXInput: Error: Failed to allocate pins for DMX_INPUT. Pins already in use:\n"); USER_PRINTF("rx in use by: %s\n", pinManager.getPinOwnerText(rxPin).c_str()); USER_PRINTF("tx in use by: %s\n", pinManager.getPinOwnerText(txPin).c_str()); @@ -175,13 +163,11 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo // put dmx receiver into seperate task because it should not be blocked // pin to core 0 because wled is running on core 1 xTaskCreatePinnedToCore(dmxReceiverTask, "DMX_RCV_TASK", 10240, this, 2, &task, 0); - if (!task) - { + if (!task) { USER_PRINTF("Error: Failed to create dmx rcv task"); } } - else - { + else { USER_PRINTLN("DMX input disabled due to rxPin, enPin or txPin not set"); return; } @@ -189,8 +175,7 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo void DMXInput::updateInternal() { - if (!initialized) - { + if (!initialized) { return; } @@ -198,25 +183,20 @@ void DMXInput::updateInternal() dmx_packet_t packet; unsigned long now = millis(); - if (dmx_receive(inputPortNum, &packet, DMX_TIMEOUT_TICK)) - { - if (!packet.err) - { + if (dmx_receive(inputPortNum, &packet, DMX_TIMEOUT_TICK)) { + if (!packet.err) { connected = true; identify = isIdentifyOn(); - if (!packet.is_rdm) - { + if (!packet.is_rdm) { const std::lock_guard lock(dmxDataLock); dmx_read(inputPortNum, dmxdata, packet.size); } } - else - { + else { connected = false; } } - else - { + else { connected = false; } } @@ -224,12 +204,10 @@ void DMXInput::updateInternal() void DMXInput::update() { - if (identify) - { + if (identify) { turnOnAllLeds(); } - else if (connected) - { + else if (connected) { const std::lock_guard lock(dmxDataLock); handleDMXData(1, 512, dmxdata, REALTIME_MODE_DMX, 0); } @@ -249,15 +227,13 @@ void DMXInput::turnOnAllLeds() void DMXInput::disable() { - if (initialized) - { + if (initialized) { dmx_driver_disable(inputPortNum); } } void DMXInput::enable() { - if (initialized) - { + if (initialized) { dmx_driver_enable(inputPortNum); } } @@ -282,15 +258,13 @@ void DMXInput::checkAndUpdateConfig() */ const uint8_t currentPersonality = dmx_get_current_personality(inputPortNum); - if (currentPersonality != DMXMode) - { + if (currentPersonality != DMXMode) { DEBUG_PRINTF("DMX personality has changed from %d to %d\n", currentPersonality, DMXMode); dmx_set_current_personality(inputPortNum, DMXMode); } const uint16_t currentAddr = dmx_get_start_address(inputPortNum); - if (currentAddr != DMXAddress) - { + if (currentAddr != DMXAddress) { DEBUG_PRINTF("DMX address has changed from %d to %d\n", currentAddr, DMXAddress); dmx_set_start_address(inputPortNum, DMXAddress); } From d637524bfccdc1ed7b7d6b340225aab64667543d Mon Sep 17 00:00:00 2001 From: Arne Date: Sun, 22 Oct 2023 20:45:58 +0200 Subject: [PATCH 103/125] chore: remove outdated comments --- wled00/dmx_input.h | 2 +- wled00/dmx_output.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/dmx_input.h b/wled00/dmx_input.h index 7a6266c50..0a02f2d11 100644 --- a/wled00/dmx_input.h +++ b/wled00/dmx_input.h @@ -56,7 +56,7 @@ private: uint8_t enPin = 255; /// is written to by the dmx receive task. - byte dmxdata[DMX_PACKET_SIZE]; //TODO add locking somehow? maybe double buffer? + byte dmxdata[DMX_PACKET_SIZE]; /// True once the dmx input has been initialized successfully bool initialized = false; // true once init finished successfully /// True if dmx is currently connected diff --git a/wled00/dmx_output.cpp b/wled00/dmx_output.cpp index 12095965e..a868c7898 100644 --- a/wled00/dmx_output.cpp +++ b/wled00/dmx_output.cpp @@ -1,7 +1,7 @@ #include "wled.h" /* - * Support for DMX input and output via serial (e.g. MAX485). + * Support for DMX output via serial (e.g. MAX485). * Change the output pin in src/dependencies/ESPDMX.cpp, if needed (ESP8266) * Change the output pin in src/dependencies/SparkFunDMX.cpp, if needed (ESP32) * ESP8266 Library from: From 3996f02dea6d41b70251a814470ad1e0b428c5ed Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Thu, 16 Jan 2025 12:19:25 +0000 Subject: [PATCH 104/125] Revert "Rename WLED_ENABLE_DMX to WLED_ENABLE_DMX_OUTPUT" This reverts commit 7f9cc6751875dd4882f91bf58adcb820d76cab8c. --- tools/cdata.js | 2 +- wled00/cfg.cpp | 6 +++--- wled00/const.h | 7 ------- wled00/dmx_output.cpp | 2 +- wled00/e131.cpp | 2 +- wled00/set.cpp | 2 +- wled00/wled.cpp | 6 +++--- wled00/wled.h | 7 +++---- wled00/wled_eeprom.cpp | 4 ++-- wled00/wled_server.cpp | 10 +++++----- wled00/xml.cpp | 8 ++++---- 11 files changed, 24 insertions(+), 32 deletions(-) diff --git a/tools/cdata.js b/tools/cdata.js index b9f29694c..c5d3c6aa5 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -358,7 +358,7 @@ writeChunks( method: "plaintext", filter: "html-minify", mangle: (str) => ` -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX ${str.replace(/function FM\(\)[ ]?\{/gms, "function FM() {%DMXVARS%\n")} #else const char PAGE_dmxmap[] PROGMEM = R"=====()====="; diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 4d8918db4..d3a8a0c6d 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -651,7 +651,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { getStringFromJson(otaPass, pwd, 33); //normally not present due to security } - #ifdef WLED_ENABLE_DMX_OUTPUT + #ifdef WLED_ENABLE_DMX JsonObject dmx = doc["dmx"]; CJSON(DMXChannels, dmx[F("chan")]); CJSON(DMXGap,dmx[F("gap")]); @@ -1116,8 +1116,8 @@ void serializeConfig() { ota[F("pskl")] = strlen(otaPass); ota[F("aota")] = aOtaEnabled; - #ifdef WLED_ENABLE_DMX_OUTPUT - JsonObject dmx = doc.createNestedObject("dmx"); + #ifdef WLED_ENABLE_DMX + JsonObject dmx = root.createNestedObject("dmx"); dmx[F("chan")] = DMXChannels; dmx[F("gap")] = DMXGap; dmx["start"] = DMXStart; diff --git a/wled00/const.h b/wled00/const.h index e0e4916d3..bdd20beba 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -583,13 +583,6 @@ #define DEFAULT_LED_COUNT 30 #define INTERFACE_UPDATE_COOLDOWN 1000 // time in ms to wait between websockets, alexa, and MQTT updates -#ifdef WLED_ENABLE_DMX_OUTPUT -#if (LEDPIN == 2) - #undef LEDPIN - #define LEDPIN 1 - #warning "Pin conflict compiling with DMX and LEDs on pin 2. The default LED pin has been changed to pin 1." -#endif -#endif #define PIN_RETRY_COOLDOWN 3000 // time in ms after an incorrect attempt PIN and OTA pass will be rejected even if correct #define PIN_TIMEOUT 900000 // time in ms after which the PIN will be required again, 15 minutes diff --git a/wled00/dmx_output.cpp b/wled00/dmx_output.cpp index a868c7898..eace2145e 100644 --- a/wled00/dmx_output.cpp +++ b/wled00/dmx_output.cpp @@ -10,7 +10,7 @@ * https://github.com/sparkfun/SparkFunDMX */ -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX void handleDMXOutput() { diff --git a/wled00/e131.cpp b/wled00/e131.cpp index 98ba5dfd9..bc26a0639 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -93,7 +93,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ return; } - #ifdef WLED_ENABLE_DMX_OUTPUT + #ifdef WLED_ENABLE_DMX // does not act on out-of-order packets yet if (e131ProxyUniverse > 0 && uni == e131ProxyUniverse) { for (uint16_t i = 1; i <= dmxChannels; i++) diff --git a/wled00/set.cpp b/wled00/set.cpp index 7ffd80653..08a0180ad 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -598,7 +598,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) } } - #ifdef WLED_ENABLE_DMX_OUTPUT // include only if DMX is enabled + #ifdef WLED_ENABLE_DMX // include only if DMX is enabled if (subPage == SUBPAGE_DMX) { int t = request->arg(F("PU")).toInt(); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 46d156e79..564f3e8c2 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -64,7 +64,7 @@ void WLED::loop() handleImprovWifiScan(); handleNotifications(); handleTransitions(); - #ifdef WLED_ENABLE_DMX_OUTPUT + #ifdef WLED_ENABLE_DMX handleDMXOutput(); #endif #ifdef WLED_ENABLE_DMX_INPUT @@ -423,7 +423,7 @@ void WLED::setup() #if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST) PinManager::allocatePin(hardwareTX, true, PinOwner::DebugOut); // TX (GPIO1 on ESP32) reserved for debug output #endif -#ifdef WLED_ENABLE_DMX_OUTPUT //reserve GPIO2 as hardcoded DMX pin +#ifdef WLED_ENABLE_DMX //reserve GPIO2 as hardcoded DMX pin PinManager::allocatePin(2, true, PinOwner::DMX); #endif @@ -526,7 +526,7 @@ void WLED::setup() ArduinoOTA.setHostname(cmDNS); } #endif -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX initDMXOutput(); #endif #ifdef WLED_ENABLE_DMX_INPUT diff --git a/wled00/wled.h b/wled00/wled.h index af4a0a84d..b7f1ae710 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -34,8 +34,7 @@ #else #undef WLED_ENABLE_ADALIGHT // disable has priority over enable #endif -//#define WLED_ENABLE_DMX_OUTPUT // uses 3.5kb (use LEDPIN other than 2) -//#define WLED_ENABLE_DMX_INPUT // Listen for DMX over Serial +//#define WLED_ENABLE_DMX // uses 3.5kb #ifndef WLED_DISABLE_LOXONE #define WLED_ENABLE_LOXONE // uses 1.2kb #endif @@ -137,7 +136,7 @@ #include "src/dependencies/espalexa/EspalexaDevice.h" #endif -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX #if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) #include "src/dependencies/dmx/ESPDMX.h" #else //ESP32 @@ -449,7 +448,7 @@ WLED_GLOBAL int arlsOffset _INIT(0); // realtime LE WLED_GLOBAL bool arlsDisableGammaCorrection _INIT(true); // activate if gamma correction is handled by the source WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to force max brightness if source has very dark colors that would be black -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX #if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) WLED_GLOBAL DMXESPSerial dmx; #else //ESP32 diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp index 99995debd..8582b49df 100644 --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -313,7 +313,7 @@ void loadSettingsFromEEPROM() e131Port = EEPROM.read(2187) + ((EEPROM.read(2188) << 8) & 0xFF00); } - #ifdef WLED_ENABLE_DMX_OUTPUT + #ifdef WLED_ENABLE_DMX if (lastEEPROMversion > 19) { e131ProxyUniverse = EEPROM.read(2185) + ((EEPROM.read(2186) << 8) & 0xFF00); @@ -342,7 +342,7 @@ void loadSettingsFromEEPROM() //custom macro memory (16 slots/ each 64byte) //1024-2047 reserved - #ifdef WLED_ENABLE_DMX_OUTPUT + #ifdef WLED_ENABLE_DMX // DMX (2530 - 2549)2535 DMXChannels = EEPROM.read(2530); DMXGap = EEPROM.read(2531) + ((EEPROM.read(2532) << 8) & 0xFF00); diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 327cff2c3..e8cbb41ae 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -96,7 +96,7 @@ static void handleStaticContent(AsyncWebServerRequest *request, const String &pa request->send(response); } -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX static String dmxProcessor(const String& var) { String mapJS; @@ -426,7 +426,7 @@ void initServer() #endif -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX server.on(F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, FPSTR(CONTENT_TYPE_HTML), PAGE_dmxmap , dmxProcessor); }); @@ -554,7 +554,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { else if (url.indexOf( "sync") > 0) subPage = SUBPAGE_SYNC; else if (url.indexOf( "time") > 0) subPage = SUBPAGE_TIME; else if (url.indexOf(F("sec")) > 0) subPage = SUBPAGE_SEC; -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX else if (url.indexOf( "dmx") > 0) subPage = SUBPAGE_DMX; #endif else if (url.indexOf( "um") > 0) subPage = SUBPAGE_UM; @@ -591,7 +591,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { case SUBPAGE_SYNC : strcpy_P(s, PSTR("Sync")); break; case SUBPAGE_TIME : strcpy_P(s, PSTR("Time")); break; case SUBPAGE_SEC : strcpy_P(s, PSTR("Security")); if (doReboot) strcpy_P(s2, PSTR("Rebooting, please wait ~10 seconds...")); break; -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX case SUBPAGE_DMX : strcpy_P(s, PSTR("DMX")); break; #endif case SUBPAGE_UM : strcpy_P(s, PSTR("Usermods")); break; @@ -626,7 +626,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { case SUBPAGE_SYNC : content = PAGE_settings_sync; len = PAGE_settings_sync_length; break; case SUBPAGE_TIME : content = PAGE_settings_time; len = PAGE_settings_time_length; break; case SUBPAGE_SEC : content = PAGE_settings_sec; len = PAGE_settings_sec_length; break; -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX case SUBPAGE_DMX : content = PAGE_settings_dmx; len = PAGE_settings_dmx_length; break; #endif case SUBPAGE_UM : content = PAGE_settings_um; len = PAGE_settings_um_length; break; diff --git a/wled00/xml.cpp b/wled00/xml.cpp index b3ba4a0e8..aa49de022 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -100,7 +100,7 @@ void appendGPIOinfo(Print& settingsScript) { firstPin = false; } } - #ifdef WLED_ENABLE_DMX_OUTPUT + #ifdef WLED_ENABLE_DMX if (!firstPin) settingsScript.print(','); settingsScript.print(2); // DMX hardcoded pin firstPin = false; @@ -164,7 +164,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) #ifdef WLED_DISABLE_2D // include only if 2D is not compiled in settingsScript.print(F("gId('2dbtn').style.display='none';")); #endif - #ifdef WLED_ENABLE_DMX_OUTPUT // include only if DMX is enabled + #ifdef WLED_ENABLE_DMX // include only if DMX is enabled settingsScript.print(F("gId('dmxbtn').style.display='';")); #endif } @@ -436,7 +436,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormCheckbox(settingsScript,PSTR("ES"),e131SkipOutOfSequence); printSetFormCheckbox(settingsScript,PSTR("EM"),e131Multicast); printSetFormValue(settingsScript,PSTR("EU"),e131Universe); -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX settingsScript.print(SET_F("hideNoDMX();")); // hide "not compiled in" message #endif #ifndef WLED_ENABLE_DMX_INPUT @@ -596,7 +596,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) settingsScript.printf_P(PSTR("sd=\"%s\";"), serverDescription); } - #ifdef WLED_ENABLE_DMX_OUTPUT // include only if DMX is enabled + #ifdef WLED_ENABLE_DMX // include only if DMX is enabled if (subPage == SUBPAGE_DMX) { printSetFormValue(settingsScript,PSTR("PU"),e131ProxyUniverse); From ebfc438bd4f7a18918de2a8b07fd467ab1a1d82d Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Tue, 16 Jan 2024 20:13:52 +0000 Subject: [PATCH 105/125] Tweak DMX settings UI --- wled00/data/settings_sync.htm | 8 ++++---- wled00/dmx_input.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index 8e142dc9b..550288488 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -152,10 +152,10 @@ Force max brightness:
Disable realtime gamma correction:
Realtime LED offset:
- DMX Input Pins
- DMX RX:
- DMX TX:
- DMX Enable:
+

Wired DMX Input Pins

+ DMX RX: RO
+ DMX TX: DI
+ DMX Enable: RE+DE
DMX Port:
diff --git a/wled00/dmx_input.h b/wled00/dmx_input.h index 0a02f2d11..7845778d7 100644 --- a/wled00/dmx_input.h +++ b/wled00/dmx_input.h @@ -7,7 +7,7 @@ /* * Support for DMX/RDM input via serial (e.g. max485) on ESP32 * ESP32 Library from: - * https://github.com/sparkfun/SparkFunDMX + * https://github.com/someweisguy/esp_dmx */ class DMXInput { From a56014bb668d4871f997971d571be330b6f39952 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 17 Jan 2024 18:03:16 +0000 Subject: [PATCH 106/125] Hide DMX port as just confusing to users --- wled00/data/settings_sync.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index 550288488..fabb2d5dd 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -156,7 +156,7 @@ Realtime LED offset: RO
DMX TX: DI
DMX Enable: RE+DE
- DMX Port:
+
DMX Port:

This firmware build does not include DMX Input support.
From fc4e7a2deeb5e4db10ee3e05f113131bf19d7d73 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Thu, 7 Mar 2024 10:44:07 +0000 Subject: [PATCH 107/125] Swap DMX port to 1, persist user choice of port, validate port vs UART count --- wled00/cfg.cpp | 2 ++ wled00/data/settings_sync.htm | 2 +- wled00/dmx_input.cpp | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index d3a8a0c6d..443f16c73 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -527,6 +527,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(dmxInputTransmitPin, if_live_dmx[F("inputRxPin")]); CJSON(dmxInputReceivePin, if_live_dmx[F("inputTxPin")]); CJSON(dmxInputEnablePin, if_live_dmx[F("inputEnablePin")]); + CJSON(dmxInputPort, if_live_dmx[F("dmxInputPort")]); #endif CJSON(arlsForceMaxBri, if_live[F("maxbri")]); @@ -1012,6 +1013,7 @@ void serializeConfig() { if_live_dmx[F("inputRxPin")] = dmxInputTransmitPin; if_live_dmx[F("inputTxPin")] = dmxInputReceivePin; if_live_dmx[F("inputEnablePin")] = dmxInputEnablePin; + if_live_dmx[F("dmxInputPort")] = dmxInputPort; #endif if_live[F("timeout")] = realtimeTimeoutMs / 100; diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index fabb2d5dd..550288488 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -156,7 +156,7 @@ Realtime LED offset: RO
DMX TX: DI
DMX Enable: RE+DE
-
DMX Port:
+ DMX Port:

This firmware build does not include DMX Input support.
diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index af0bb679d..facafe17c 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -105,6 +105,7 @@ bool DMXInput::installDriver() { const auto config = createConfig(); + USER_PRINTF("DMX port: %u\n", inputPortNum); if (!dmx_driver_install(inputPortNum, &config, DMX_INTR_FLAGS_DEFAULT)) { USER_PRINTF("Error: Failed to install dmx driver\n"); return false; @@ -133,7 +134,7 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo // } #endif - if (inputPortNum < 3 && inputPortNum > 0) { + if (inputPortNum <= (SOC_UART_NUM - 1) && inputPortNum > 0) { this->inputPortNum = inputPortNum; } else { From 9a6e91d3e5039f252b347c314a2a69526c8cc34a Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Tue, 30 Jul 2024 19:15:08 +0100 Subject: [PATCH 108/125] DMX Input - reinstate loggers for connection state change --- wled00/dmx_input.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index facafe17c..ffdea6321 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -186,6 +186,9 @@ void DMXInput::updateInternal() unsigned long now = millis(); if (dmx_receive(inputPortNum, &packet, DMX_TIMEOUT_TICK)) { if (!packet.err) { + if(!connected) { + USER_PRINTLN("DMX Input - connected"); + } connected = true; identify = isIdentifyOn(); if (!packet.is_rdm) { @@ -198,6 +201,9 @@ void DMXInput::updateInternal() } } else { + if(connected) { + USER_PRINTLN("DMX Input - disconnected"); + } connected = false; } } From 953e994c88abf5e6b62f5026730635c96a0d8278 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Thu, 16 Jan 2025 12:24:42 +0000 Subject: [PATCH 109/125] Add DMX Input support to builds --- platformio.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/platformio.ini b/platformio.ini index 9b9b693f5..18a2db8aa 100644 --- a/platformio.ini +++ b/platformio.ini @@ -282,8 +282,10 @@ build_flags = -g -DARDUINO_ARCH_ESP32 -DESP32 -D CONFIG_ASYNC_TCP_USE_WDT=0 -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 + -D WLED_ENABLE_DMX_INPUT lib_deps = https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + https://github.com/someweisguy/esp_dmx.git#47db25d ${env.lib_deps} board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs From a58278665568eff509df0b721bc8ce23cc04e412 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Thu, 16 Jan 2025 12:48:36 +0000 Subject: [PATCH 110/125] Port over remaining WLEDMM part of DMX Input and adapt for AC --- wled00/const.h | 1 + wled00/data/settings_sync.htm | 6 +++--- wled00/dmx_input.cpp | 40 +++++++++++++++++------------------ wled00/e131.cpp | 11 +++++++--- wled00/fcn_declare.h | 1 + wled00/xml.cpp | 10 ++++----- 6 files changed, 38 insertions(+), 31 deletions(-) diff --git a/wled00/const.h b/wled00/const.h index bdd20beba..b5eb20f81 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -249,6 +249,7 @@ #define REALTIME_MODE_ARTNET 6 #define REALTIME_MODE_TPM2NET 7 #define REALTIME_MODE_DDP 8 +#define REALTIME_MODE_DMX 9 //realtime override modes #define REALTIME_OVERRIDE_NONE 0 diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index 550288488..775f87a96 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -151,17 +151,17 @@ Timeout: ms
Force max brightness:
Disable realtime gamma correction:
Realtime LED offset: -
+

Wired DMX Input Pins

DMX RX: RO
DMX TX: DI
DMX Enable: RE+DE
DMX Port:
-
+

This firmware build does not include DMX Input support.
-
+

This firmware build does not include DMX output support.

diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index ffdea6321..afbc9f0d0 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -15,7 +15,7 @@ void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, DMXInput *dmx = static_cast(context); if (!dmx) { - USER_PRINTLN("DMX: Error: no context in rdmPersonalityChangedCb"); + DEBUG_PRINTLN("DMX: Error: no context in rdmPersonalityChangedCb"); return; } @@ -23,7 +23,7 @@ void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, const uint8_t personality = dmx_get_current_personality(dmx->inputPortNum); DMXMode = std::min(DMX_MODE_PRESET, std::max(DMX_MODE_SINGLE_RGB, int(personality))); doSerializeConfig = true; - USER_PRINTF("DMX personality changed to to: %d\n", DMXMode); + DEBUG_PRINTF("DMX personality changed to to: %d\n", DMXMode); } } @@ -33,7 +33,7 @@ void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, DMXInput *dmx = static_cast(context); if (!dmx) { - USER_PRINTLN("DMX: Error: no context in rdmAddressChangedCb"); + DEBUG_PRINTLN("DMX: Error: no context in rdmAddressChangedCb"); return; } @@ -41,7 +41,7 @@ void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, const uint16_t addr = dmx_get_start_address(dmx->inputPortNum); DMXAddress = std::min(512, int(addr)); doSerializeConfig = true; - USER_PRINTF("DMX start addr changed to: %d\n", DMXAddress); + DEBUG_PRINTF("DMX start addr changed to: %d\n", DMXAddress); } } @@ -105,15 +105,15 @@ bool DMXInput::installDriver() { const auto config = createConfig(); - USER_PRINTF("DMX port: %u\n", inputPortNum); + DEBUG_PRINTF("DMX port: %u\n", inputPortNum); if (!dmx_driver_install(inputPortNum, &config, DMX_INTR_FLAGS_DEFAULT)) { - USER_PRINTF("Error: Failed to install dmx driver\n"); + DEBUG_PRINTF("Error: Failed to install dmx driver\n"); return false; } - USER_PRINTF("Listening for DMX on pin %u\n", rxPin); - USER_PRINTF("Sending DMX on pin %u\n", txPin); - USER_PRINTF("DMX enable pin is: %u\n", enPin); + DEBUG_PRINTF("Listening for DMX on pin %u\n", rxPin); + DEBUG_PRINTF("Sending DMX on pin %u\n", txPin); + DEBUG_PRINTF("DMX enable pin is: %u\n", enPin); dmx_set_pin(inputPortNum, txPin, rxPin, enPin); rdm_register_dmx_start_address(inputPortNum, rdmAddressChangedCb, this); @@ -129,7 +129,7 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo //TODO add again once dmx output has been merged // if(inputPortNum == dmxOutputPort) // { - // USER_PRINTF("DMXInput: Error: Input port == output port"); + // DEBUG_PRINTF("DMXInput: Error: Input port == output port"); // return; // } #endif @@ -138,7 +138,7 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo this->inputPortNum = inputPortNum; } else { - USER_PRINTF("DMXInput: Error: invalid inputPortNum: %d\n", inputPortNum); + DEBUG_PRINTF("DMXInput: Error: invalid inputPortNum: %d\n", inputPortNum); return; } @@ -148,12 +148,12 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo {(int8_t)txPin, false}, // these are not used as gpio pins, thus isOutput is always false. {(int8_t)rxPin, false}, {(int8_t)enPin, false}}; - const bool pinsAllocated = pinManager.allocateMultiplePins(pins, 3, PinOwner::DMX_INPUT); + const bool pinsAllocated = PinManager::allocateMultiplePins(pins, 3, PinOwner::DMX_INPUT); if (!pinsAllocated) { - USER_PRINTF("DMXInput: Error: Failed to allocate pins for DMX_INPUT. Pins already in use:\n"); - USER_PRINTF("rx in use by: %s\n", pinManager.getPinOwnerText(rxPin).c_str()); - USER_PRINTF("tx in use by: %s\n", pinManager.getPinOwnerText(txPin).c_str()); - USER_PRINTF("en in use by: %s\n", pinManager.getPinOwnerText(enPin).c_str()); + DEBUG_PRINTF("DMXInput: Error: Failed to allocate pins for DMX_INPUT. Pins already in use:\n"); + DEBUG_PRINTF("rx in use by: %s\n", pinManager.getPinOwnerText(rxPin).c_str()); + DEBUG_PRINTF("tx in use by: %s\n", pinManager.getPinOwnerText(txPin).c_str()); + DEBUG_PRINTF("en in use by: %s\n", pinManager.getPinOwnerText(enPin).c_str()); return; } @@ -165,11 +165,11 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo // pin to core 0 because wled is running on core 1 xTaskCreatePinnedToCore(dmxReceiverTask, "DMX_RCV_TASK", 10240, this, 2, &task, 0); if (!task) { - USER_PRINTF("Error: Failed to create dmx rcv task"); + DEBUG_PRINTF("Error: Failed to create dmx rcv task"); } } else { - USER_PRINTLN("DMX input disabled due to rxPin, enPin or txPin not set"); + DEBUG_PRINTLN("DMX input disabled due to rxPin, enPin or txPin not set"); return; } } @@ -187,7 +187,7 @@ void DMXInput::updateInternal() if (dmx_receive(inputPortNum, &packet, DMX_TIMEOUT_TICK)) { if (!packet.err) { if(!connected) { - USER_PRINTLN("DMX Input - connected"); + DEBUG_PRINTLN("DMX Input - connected"); } connected = true; identify = isIdentifyOn(); @@ -202,7 +202,7 @@ void DMXInput::updateInternal() } else { if(connected) { - USER_PRINTLN("DMX Input - disconnected"); + DEBUG_PRINTLN("DMX Input - disconnected"); } connected = false; } diff --git a/wled00/e131.cpp b/wled00/e131.cpp index bc26a0639..c16ed9332 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -116,6 +116,11 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ // update status info realtimeIP = clientIP; + + handleDMXData(uni, dmxChannels, e131_data, mde, previousUniverses); +} + +void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8_t mde, uint8_t previousUniverses) { byte wChannel = 0; unsigned totalLen = strip.getLengthTotal(); unsigned availDMXLen = 0; @@ -130,7 +135,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ } // DMX data in Art-Net packet starts at index 0, for E1.31 at index 1 - if (protocol == P_ARTNET && dataOffset > 0) { + if (mde == REALTIME_MODE_ARTNET && dataOffset > 0) { dataOffset--; } @@ -211,7 +216,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ else dataOffset = DMXAddress; // Modify address for Art-Net data - if (protocol == P_ARTNET && dataOffset > 0) + if (mde == REALTIME_MODE_ARTNET && dataOffset > 0) dataOffset--; // Skip out of universe addresses if (dataOffset > dmxChannels - dmxEffectChannels + 1) @@ -285,7 +290,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ } } else { // All subsequent universes start at the first channel. - dmxOffset = (protocol == P_ARTNET) ? 0 : 1; + dmxOffset = (mde == REALTIME_MODE_ARTNET) ? 0 : 1; const unsigned dimmerOffset = (DMXMode == DMX_MODE_MULTIPLE_DRGB) ? 1 : 0; unsigned ledsInFirstUniverse = (((MAX_CHANNELS_PER_UNIVERSE - DMXAddress) + dmxLenOffset) - dimmerOffset) / dmxChannelsPerLed; previousLeds = ledsInFirstUniverse + (previousUniverses - 1) * ledsPerUniverse; diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index db6c6b872..c7fa9daae 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -193,6 +193,7 @@ void handleDMXInput(); //e131.cpp void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol); +void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8_t mde, uint8_t previousUniverses); void handleArtnetPollReply(IPAddress ipAddress); void prepareArtnetPollReply(ArtPollReply* reply); void sendArtnetPollReply(ArtPollReply* reply, IPAddress ipAddress, uint16_t portAddress); diff --git a/wled00/xml.cpp b/wled00/xml.cpp index aa49de022..0ed32c04b 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -442,11 +442,11 @@ void getSettingsJS(byte subPage, Print& settingsScript) #ifndef WLED_ENABLE_DMX_INPUT settingsScript.print(SET_F("hideDMXInput();")); // hide "dmx input" settings #else - settingsScript.print(SET_F("hideNoDMXInput();")); //hide "not comp iled in" message - sappend('v',SET_F("IDMT"),dmxInputTransmitPin); - sappend('v',SET_F("IDMR"),dmxInputReceivePin); - sappend('v',SET_F("IDME"),dmxInputEnablePin); - sappend('v',SET_F("IDMP"),dmxInputPort); + settingsScript.print(SET_F("hideNoDMXInput();")); //hide "not compiled in" message + printSetFormValue(settingsScript,SET_F("IDMT"),dmxInputTransmitPin); + printSetFormValue(settingsScript,SET_F("IDMR"),dmxInputReceivePin); + printSetFormValue(settingsScript,SET_F("IDME"),dmxInputEnablePin); + printSetFormValue(settingsScript,SET_F("IDMP"),dmxInputPort); #endif printSetFormValue(settingsScript,PSTR("DA"),DMXAddress); printSetFormValue(settingsScript,PSTR("XX"),DMXSegmentSpacing); From b9aeb19834aae58fc51f3bff873839a802c0f1ed Mon Sep 17 00:00:00 2001 From: Kilrah Date: Fri, 17 Jan 2025 08:01:17 +0100 Subject: [PATCH 111/125] RF433 json usermod (#4234) * RF433 remote usermod --------- Co-authored-by: Kilrah --- platformio_override.sample.ini | 11 ++ usermods/usermod_v2_RF433/readme.md | 18 ++ usermods/usermod_v2_RF433/remote433.json | 34 ++++ usermods/usermod_v2_RF433/usermod_v2_RF433.h | 183 +++++++++++++++++++ wled00/const.h | 1 + wled00/usermods_list.cpp | 9 +- 6 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 usermods/usermod_v2_RF433/readme.md create mode 100644 usermods/usermod_v2_RF433/remote433.json create mode 100644 usermods/usermod_v2_RF433/usermod_v2_RF433.h diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index cb5b43e7b..19b8c273a 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -529,3 +529,14 @@ monitor_filters = esp32_exception_decoder lib_deps = ${esp32.lib_deps} TFT_eSPI @ 2.5.33 ;; this is the last version that compiles with the WLED default framework - newer versions require platform = espressif32 @ ^6.3.2 + +# ------------------------------------------------------------------------------ +# Usermod examples +# ------------------------------------------------------------------------------ + +# 433MHz RF remote example for esp32dev +[env:esp32dev_usermod_RF433] +extends = env:esp32dev +build_flags = ${env:esp32dev.build_flags} -D USERMOD_RF433 +lib_deps = ${env:esp32dev.lib_deps} + sui77/rc-switch @ 2.6.4 diff --git a/usermods/usermod_v2_RF433/readme.md b/usermods/usermod_v2_RF433/readme.md new file mode 100644 index 000000000..43919f11b --- /dev/null +++ b/usermods/usermod_v2_RF433/readme.md @@ -0,0 +1,18 @@ +# RF433 remote usermod + +Usermod for controlling WLED using a generic 433 / 315MHz remote and simple 3-pin receiver +See for compatibility details + +## Build + +- Create a `platformio_override.ini` file at the root of the wled source directory if not already present +- Copy the `433MHz RF remote example for esp32dev` section from `platformio_override.sample.ini` into it +- Duplicate/adjust for other boards + +## Usage + +- Connect receiver to a free pin +- Set pin in Config->Usermods +- Info pane will show the last received button code +- Upload the remote433.json sample file in this folder to the ESP with the file editor at [http://\[wled-ip\]/edit](http://ip/edit) +- Edit as necessary, the key is the button number retrieved from the info pane, and the "cmd" can be either an [HTTP API](https://kno.wled.ge/interfaces/http-api/) or a [JSON API](https://kno.wled.ge/interfaces/json-api/) command. \ No newline at end of file diff --git a/usermods/usermod_v2_RF433/remote433.json b/usermods/usermod_v2_RF433/remote433.json new file mode 100644 index 000000000..d5d930a81 --- /dev/null +++ b/usermods/usermod_v2_RF433/remote433.json @@ -0,0 +1,34 @@ +{ + "13985576": { + "cmnt": "Toggle Power using HTTP API", + "cmd": "T=2" + }, + "3670817": { + "cmnt": "Force Power ON using HTTP API", + "cmd": "T=1" + }, + "13985572": { + "cmnt": "Set brightness to 200 using JSON API", + "cmd": {"bri":200} + }, + "3670818": { + "cmnt": "Run Preset 1 using JSON API", + "cmd": {"ps":1} + }, + "13985570": { + "cmnt": "Increase brightness by 40 using HTTP API", + "cmd": "A=~40" + }, + "13985569": { + "cmnt": "Decrease brightness by 40 using HTTP API", + "cmd": "A=~-40" + }, + "7608836": { + "cmnt": "Start 1min timer using JSON API", + "cmd": {"nl":{"on":true,"dur":1,"mode":0}} + }, + "7608840": { + "cmnt": "Select random effect on all segments using JSON API", + "cmd": {"seg":{"fx":"r"}} + } +} \ No newline at end of file diff --git a/usermods/usermod_v2_RF433/usermod_v2_RF433.h b/usermods/usermod_v2_RF433/usermod_v2_RF433.h new file mode 100644 index 000000000..ebaf433f1 --- /dev/null +++ b/usermods/usermod_v2_RF433/usermod_v2_RF433.h @@ -0,0 +1,183 @@ +#pragma once + +#include "wled.h" +#include "Arduino.h" +#include + +#define RF433_BUSWAIT_TIMEOUT 24 + +class RF433Usermod : public Usermod +{ +private: + RCSwitch mySwitch = RCSwitch(); + unsigned long lastCommand = 0; + unsigned long lastTime = 0; + + bool modEnabled = true; + int8_t receivePin = -1; + + static const char _modName[]; + static const char _modEnabled[]; + static const char _receivePin[]; + + bool initDone = false; + +public: + + void setup() + { + mySwitch.disableReceive(); + if (modEnabled) + { + mySwitch.enableReceive(receivePin); + } + initDone = true; + } + + /* + * connected() is called every time the WiFi is (re)connected + * Use it to initialize network interfaces + */ + void connected() + { + } + + void loop() + { + if (!modEnabled || strip.isUpdating()) + return; + + if (mySwitch.available()) + { + unsigned long receivedCommand = mySwitch.getReceivedValue(); + mySwitch.resetAvailable(); + + // Discard duplicates, limit long press repeat + if (lastCommand == receivedCommand && millis() - lastTime < 800) + return; + + lastCommand = receivedCommand; + lastTime = millis(); + + DEBUG_PRINT(F("RF433 Receive: ")); + DEBUG_PRINTLN(receivedCommand); + + if(!remoteJson433(receivedCommand)) + DEBUG_PRINTLN(F("RF433: unknown button")); + } + } + + // Add last received button to info pane + void addToJsonInfo(JsonObject &root) + { + if (!initDone) + return; // prevent crash on boot applyPreset() + JsonObject user = root["u"]; + if (user.isNull()) + user = root.createNestedObject("u"); + + JsonArray switchArr = user.createNestedArray("RF433 Last Received"); // name + switchArr.add(lastCommand); + } + + void addToConfig(JsonObject &root) + { + JsonObject top = root.createNestedObject(FPSTR(_modName)); // usermodname + top[FPSTR(_modEnabled)] = modEnabled; + JsonArray pinArray = top.createNestedArray("pin"); + pinArray.add(receivePin); + + DEBUG_PRINTLN(F(" config saved.")); + } + + bool readFromConfig(JsonObject &root) + { + JsonObject top = root[FPSTR(_modName)]; + if (top.isNull()) + { + DEBUG_PRINT(FPSTR(_modName)); + DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + return false; + } + getJsonValue(top[FPSTR(_modEnabled)], modEnabled); + getJsonValue(top["pin"][0], receivePin); + + DEBUG_PRINTLN(F("config (re)loaded.")); + + // Redo init on update + if(initDone) + setup(); + + return true; + } + + /* + * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). + * This could be used in the future for the system to determine whether your usermod is installed. + */ + uint16_t getId() + { + return USERMOD_ID_RF433; + } + + // this function follows the same principle as decodeIRJson() / remoteJson() + bool remoteJson433(int button) + { + char objKey[14]; + bool parsed = false; + + if (!requestJSONBufferLock(22)) return false; + + sprintf_P(objKey, PSTR("\"%d\":"), button); + + unsigned long start = millis(); + while (strip.isUpdating() && millis()-start < RF433_BUSWAIT_TIMEOUT) yield(); // wait for strip to finish updating, accessing FS during sendout causes glitches + + // attempt to read command from remote.json + readObjectFromFile(PSTR("/remote433.json"), objKey, pDoc); + JsonObject fdo = pDoc->as(); + if (fdo.isNull()) { + // the received button does not exist + releaseJSONBufferLock(); + return parsed; + } + + String cmdStr = fdo["cmd"].as(); + JsonObject jsonCmdObj = fdo["cmd"]; //object + + if (jsonCmdObj.isNull()) // we could also use: fdo["cmd"].is() + { + // HTTP API command + String apireq = "win"; apireq += '&'; // reduce flash string usage + if (!cmdStr.startsWith(apireq)) cmdStr = apireq + cmdStr; // if no "win&" prefix + if (!irApplyToAllSelected && cmdStr.indexOf(F("SS="))<0) { + char tmp[10]; + sprintf_P(tmp, PSTR("&SS=%d"), strip.getMainSegmentId()); + cmdStr += tmp; + } + fdo.clear(); // clear JSON buffer (it is no longer needed) + handleSet(nullptr, cmdStr, false); // no stateUpdated() call here + stateUpdated(CALL_MODE_BUTTON); + parsed = true; + } else { + // command is JSON object + if (jsonCmdObj[F("psave")].isNull()) + deserializeState(jsonCmdObj, CALL_MODE_BUTTON_PRESET); + else { + uint8_t psave = jsonCmdObj[F("psave")].as(); + char pname[33]; + sprintf_P(pname, PSTR("IR Preset %d"), psave); + fdo.clear(); + if (psave > 0 && psave < 251) savePreset(psave, pname, fdo); + } + parsed = true; + } + releaseJSONBufferLock(); + return parsed; + } +}; + +const char RF433Usermod::_modName[] PROGMEM = "RF433 Remote"; +const char RF433Usermod::_modEnabled[] PROGMEM = "Enabled"; +const char RF433Usermod::_receivePin[] PROGMEM = "RX Pin"; + diff --git a/wled00/const.h b/wled00/const.h index bdd20beba..6a023fadc 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -204,6 +204,7 @@ #define USERMOD_ID_POV_DISPLAY 53 //Usermod "usermod_pov_display.h" #define USERMOD_ID_PIXELS_DICE_TRAY 54 //Usermod "pixels_dice_tray.h" #define USERMOD_ID_DEEP_SLEEP 55 //Usermod "usermod_deep_sleep.h" +#define USERMOD_ID_RF433 56 //Usermod "usermod_v2_RF433.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index 3430e337d..15ded987d 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -242,11 +242,14 @@ #include "../usermods/LD2410_v2/usermod_ld2410.h" #endif - #ifdef USERMOD_DEEP_SLEEP #include "../usermods/deep_sleep/usermod_deep_sleep.h" #endif +#ifdef USERMOD_RF433 + #include "../usermods/usermod_v2_RF433/usermod_v2_RF433.h" +#endif + void registerUsermods() { /* @@ -479,4 +482,8 @@ void registerUsermods() #ifdef USERMOD_DEEP_SLEEP UsermodManager::add(new DeepSleepUsermod()); #endif + + #ifdef USERMOD_RF433 + UsermodManager::add(new RF433Usermod()); + #endif } From 703f84e5e1070a8006281e198ae354e3435d8e94 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:09:29 +0100 Subject: [PATCH 112/125] code robustness improvements plus minor speedup * make XY() and _setPixelColorXY_raw() const (minor speedup) * segment is a struct not a class: friend class Segment --> friend struct Segment * fix missing braces around two macros * use non-throwing "new" where possible * improve robustness of transition code --- wled00/FX.h | 10 +++++----- wled00/FX_2Dfcn.cpp | 8 ++++---- wled00/FX_fcn.cpp | 12 ++++++------ wled00/json.cpp | 2 +- wled00/playlist.cpp | 2 +- wled00/presets.cpp | 4 ++-- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 57df58549..f80ffb36d 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -79,9 +79,9 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define MAX_NUM_SEGMENTS 32 #endif #if defined(ARDUINO_ARCH_ESP32S2) - #define MAX_SEGMENT_DATA MAX_NUM_SEGMENTS*768 // 24k by default (S2 is short on free RAM) + #define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*768) // 24k by default (S2 is short on free RAM) #else - #define MAX_SEGMENT_DATA MAX_NUM_SEGMENTS*1280 // 40k by default + #define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*1280) // 40k by default #endif #endif @@ -460,7 +460,7 @@ typedef struct Segment { {} } *_t; - [[gnu::hot]] void _setPixelColorXY_raw(int& x, int& y, uint32_t& col); // set pixel without mapping (internal use only) + [[gnu::hot]] void _setPixelColorXY_raw(int& x, int& y, uint32_t& col) const; // set pixel without mapping (internal use only) public: @@ -642,7 +642,7 @@ typedef struct Segment { #endif } #ifndef WLED_DISABLE_2D - [[gnu::hot]] uint16_t XY(int x, int y); // support function to get relative index within segment + [[gnu::hot]] uint16_t XY(int x, int y) const; // support function to get relative index within segment [[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColorXY(int(x), int(y), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } @@ -936,7 +936,7 @@ class WS2812FX { // 96 bytes }; std::vector _segments; - friend class Segment; + friend struct Segment; private: volatile bool _suspend; diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index f00e7147d..2e4cdd515 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -51,7 +51,7 @@ void WS2812FX::setUpMatrix() { customMappingSize = 0; // prevent use of mapping if anything goes wrong if (customMappingTable) delete[] customMappingTable; - customMappingTable = new uint16_t[getLengthTotal()]; + customMappingTable = new(std::nothrow) uint16_t[getLengthTotal()]; if (customMappingTable) { customMappingSize = getLengthTotal(); @@ -85,7 +85,7 @@ void WS2812FX::setUpMatrix() { JsonArray map = pDoc->as(); gapSize = map.size(); if (!map.isNull() && gapSize >= matrixSize) { // not an empty map - gapTable = new int8_t[gapSize]; + gapTable = new(std::nothrow) int8_t[gapSize]; if (gapTable) for (size_t i = 0; i < gapSize; i++) { gapTable[i] = constrain(map[i], -1, 1); } @@ -146,7 +146,7 @@ void WS2812FX::setUpMatrix() { #ifndef WLED_DISABLE_2D // XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element) -uint16_t IRAM_ATTR_YN Segment::XY(int x, int y) +uint16_t IRAM_ATTR_YN Segment::XY(int x, int y) const { const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) const int vH = vHeight(); // segment height in logical pixels (is always >= 1) @@ -154,7 +154,7 @@ uint16_t IRAM_ATTR_YN Segment::XY(int x, int y) } // raw setColor function without checks (checks are done in setPixelColorXY()) -void IRAM_ATTR_YN Segment::_setPixelColorXY_raw(int& x, int& y, uint32_t& col) +void IRAM_ATTR_YN Segment::_setPixelColorXY_raw(int& x, int& y, uint32_t& col) const { const int baseX = start + x; const int baseY = startY + y; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index b9a62bb2c..90520d3d2 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -94,7 +94,7 @@ Segment::Segment(const Segment &orig) { name = nullptr; data = nullptr; _dataLen = 0; - if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } + if (orig.name) { name = new(std::nothrow) char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } } @@ -122,7 +122,7 @@ Segment& Segment::operator= (const Segment &orig) { data = nullptr; _dataLen = 0; // copy source data - if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } + if (orig.name) { name = new(std::nothrow) char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } } return *this; @@ -253,7 +253,7 @@ void Segment::startTransition(uint16_t dur) { if (isInTransition()) return; // already in transition no need to store anything // starting a transition has to occur before change so we get current values 1st - _t = new Transition(dur); // no previous transition running + _t = new(std::nothrow) Transition(dur); // no previous transition running if (!_t) return; // failed to allocate data //DEBUG_PRINTF_P(PSTR("-- Started transition: %p (%p)\n"), this, _t); @@ -380,7 +380,7 @@ void Segment::restoreSegenv(tmpsegd_t &tmpSeg) { uint8_t Segment::currentBri(bool useCct) const { unsigned prog = progress(); - if (prog < 0xFFFFU) { + if (prog < 0xFFFFU && _t) { unsigned curBri = (useCct ? cct : (on ? opacity : 0)) * prog; curBri += (useCct ? _t->_cctT : _t->_briT) * (0xFFFFU - prog); return curBri / 0xFFFFU; @@ -391,7 +391,7 @@ uint8_t Segment::currentBri(bool useCct) const { uint8_t Segment::currentMode() const { #ifndef WLED_DISABLE_MODE_BLEND unsigned prog = progress(); - if (modeBlending && prog < 0xFFFFU) return _t->_modeT; + if (modeBlending && prog < 0xFFFFU && _t) return _t->_modeT; #endif return mode; } @@ -1809,7 +1809,7 @@ bool WS2812FX::deserializeMap(unsigned n) { } if (customMappingTable) delete[] customMappingTable; - customMappingTable = new uint16_t[getLengthTotal()]; + customMappingTable = new(std::nothrow) uint16_t[getLengthTotal()]; if (customMappingTable) { DEBUG_PRINT(F("Reading LED map from ")); DEBUG_PRINTLN(fileName); diff --git a/wled00/json.cpp b/wled00/json.cpp index cc25d5f89..f284a9533 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -77,7 +77,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) if (name != nullptr) len = strlen(name); if (len > 0) { if (len > WLED_MAX_SEGNAME_LEN) len = WLED_MAX_SEGNAME_LEN; - seg.name = new char[len+1]; + seg.name = new(std::nothrow) char[len+1]; if (seg.name) strlcpy(seg.name, name, WLED_MAX_SEGNAME_LEN+1); } else { // but is empty (already deleted above) diff --git a/wled00/playlist.cpp b/wled00/playlist.cpp index 36235ab9e..7f11e2bf0 100644 --- a/wled00/playlist.cpp +++ b/wled00/playlist.cpp @@ -61,7 +61,7 @@ int16_t loadPlaylist(JsonObject playlistObj, byte presetId) { if (playlistLen == 0) return -1; if (playlistLen > 100) playlistLen = 100; - playlistEntries = new PlaylistEntry[playlistLen]; + playlistEntries = new(std::nothrow) PlaylistEntry[playlistLen]; if (playlistEntries == nullptr) return -1; byte it = 0; diff --git a/wled00/presets.cpp b/wled00/presets.cpp index 1abcb52b9..4aaa121f4 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -216,8 +216,8 @@ void handlePresets() //called from handleSet(PS=) [network callback (sObj is empty), IR (irrational), deserializeState, UDP] and deserializeState() [network callback (filedoc!=nullptr)] void savePreset(byte index, const char* pname, JsonObject sObj) { - if (!saveName) saveName = new char[33]; - if (!quickLoad) quickLoad = new char[9]; + if (!saveName) saveName = new(std::nothrow) char[33]; + if (!quickLoad) quickLoad = new(std::nothrow) char[9]; if (!saveName || !quickLoad) return; if (index == 0 || (index > 250 && index < 255)) return; From 7be868db12524835aa087ed27176a790a0ace93b Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 7 Jan 2025 20:18:42 +0100 Subject: [PATCH 113/125] bugfix: indexOf() returns -1 if string not found ... so we must use `int` instead of `unsigned` --- wled00/util.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/util.cpp b/wled00/util.cpp index d9f1c00b9..9a918a010 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -265,16 +265,16 @@ uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxL if (mode < strip.getModeCount()) { String lineBuffer = FPSTR(strip.getModeData(mode)); if (lineBuffer.length() > 0) { - unsigned start = lineBuffer.indexOf('@'); - unsigned stop = lineBuffer.indexOf(';', start); + int start = lineBuffer.indexOf('@'); // String::indexOf() returns an int, not an unsigned; -1 means "not found" + int stop = lineBuffer.indexOf(';', start); if (start>0 && stop>0) { String names = lineBuffer.substring(start, stop); // include @ - unsigned nameBegin = 1, nameEnd, nameDefault; + int nameBegin = 1, nameEnd, nameDefault; if (slider < 10) { for (size_t i=0; i<=slider; i++) { const char *tmpstr; dest[0] = '\0'; //clear dest buffer - if (nameBegin == 0) break; // there are no more names + if (nameBegin <= 0) break; // there are no more names nameEnd = names.indexOf(',', nameBegin); if (i == slider) { nameDefault = names.indexOf('=', nameBegin); // find default value From 90c2955a717cd6c0c216bb1ac3ffc31b8a9d0e42 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 7 Jan 2025 20:22:52 +0100 Subject: [PATCH 114/125] avoid using keywords for variables: module, final these are reserved names and future compilers may reject them. --- wled00/fcn_declare.h | 2 +- wled00/util.cpp | 10 +++++----- wled00/wled_server.cpp | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index a1e362914..e9e8df6a2 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -479,7 +479,7 @@ size_t printSetFormIndex(Print& settingsScript, const char* key, int index); size_t printSetClassElementHTML(Print& settingsScript, const char* key, const int index, const char* val); void prepareHostname(char* hostname); bool isAsterisksOnly(const char* str, byte maxLen); -bool requestJSONBufferLock(uint8_t module=255); +bool requestJSONBufferLock(uint8_t moduleID=255); void releaseJSONBufferLock(); uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen); uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxLen, uint8_t *var = nullptr); diff --git a/wled00/util.cpp b/wled00/util.cpp index 9a918a010..e15247f9f 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -151,7 +151,7 @@ bool isAsterisksOnly(const char* str, byte maxLen) //threading/network callback details: https://github.com/Aircoookie/WLED/pull/2336#discussion_r762276994 -bool requestJSONBufferLock(uint8_t module) +bool requestJSONBufferLock(uint8_t moduleID) { if (pDoc == nullptr) { DEBUG_PRINTLN(F("ERROR: JSON buffer not allocated!")); @@ -175,14 +175,14 @@ bool requestJSONBufferLock(uint8_t module) #endif // If the lock is still held - by us, or by another task if (jsonBufferLock) { - DEBUG_PRINTF_P(PSTR("ERROR: Locking JSON buffer (%d) failed! (still locked by %d)\n"), module, jsonBufferLock); + DEBUG_PRINTF_P(PSTR("ERROR: Locking JSON buffer (%d) failed! (still locked by %d)\n"), moduleID, jsonBufferLock); #ifdef ARDUINO_ARCH_ESP32 xSemaphoreGiveRecursive(jsonBufferLockMutex); #endif return false; } - jsonBufferLock = module ? module : 255; + jsonBufferLock = moduleID ? moduleID : 255; DEBUG_PRINTF_P(PSTR("JSON buffer locked. (%d)\n"), jsonBufferLock); pDoc->clear(); return true; @@ -470,7 +470,7 @@ um_data_t* simulateSound(uint8_t simulationId) for (int i = 0; i<16; i++) fftResult[i] = beatsin8_t(120 / (i+1), 0, 255); // fftResult[i] = (beatsin8_t(120, 0, 255) + (256/16 * i)) % 256; - volumeSmth = fftResult[8]; + volumeSmth = fftResult[8]; break; case UMS_WeWillRockYou: if (ms%2000 < 200) { @@ -507,7 +507,7 @@ um_data_t* simulateSound(uint8_t simulationId) case UMS_10_13: for (int i = 0; i<16; i++) fftResult[i] = inoise8(beatsin8_t(90 / (i+1), 0, 200)*15 + (ms>>10), ms>>3); - volumeSmth = fftResult[8]; + volumeSmth = fftResult[8]; break; case UMS_14_3: for (int i = 0; i<16; i++) diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index e8cbb41ae..96f2a705c 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -152,9 +152,9 @@ static String msgProcessor(const String& var) return String(); } -static void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) { +static void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool isFinal) { if (!correctPIN) { - if (final) request->send(401, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_unlock_cfg)); + if (isFinal) request->send(401, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_unlock_cfg)); return; } if (!index) { @@ -170,7 +170,7 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename, if (len) { request->_tempFile.write(data,len); } - if (final) { + if (isFinal) { request->_tempFile.close(); if (filename.indexOf(F("cfg.json")) >= 0) { // check for filename with or without slash doReboot = true; @@ -359,7 +359,7 @@ void initServer() server.on(F("/upload"), HTTP_POST, [](AsyncWebServerRequest *request) {}, [](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, - size_t len, bool final) {handleUpload(request, filename, index, data, len, final);} + size_t len, bool isFinal) {handleUpload(request, filename, index, data, len, isFinal);} ); createEditHandler(correctPIN); @@ -389,7 +389,7 @@ void initServer() serveMessage(request, 200, F("Update successful!"), F("Rebooting..."), 131); doReboot = true; } - },[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){ + },[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){ if (!correctPIN || otaLock) return; if(!index){ DEBUG_PRINTLN(F("OTA Update Start")); @@ -406,7 +406,7 @@ void initServer() Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000); } if(!Update.hasError()) Update.write(data, len); - if(final){ + if(isFinal){ if(Update.end(true)){ DEBUG_PRINTLN(F("Update Success")); } else { From 013684b5ca62052f79df27ab72974cf3425e6a89 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 7 Jan 2025 20:33:10 +0100 Subject: [PATCH 115/125] making some parameters `const`, plus minor improvements * changed some parameters to "pointer to const", so compiler can better optimize code size and performance - because data behind a const pointer will never be modified by the called function. * made setPixelColor `const` * fixed a few potentially uninitialized local vars (the may have random values if not initialized) * avoid shadowing "state" in handleSerial() * plus a few very minor improvements --- wled00/FX.cpp | 8 ++++---- wled00/FX.h | 14 +++++++------- wled00/FX_2Dfcn.cpp | 8 ++++---- wled00/FX_fcn.cpp | 6 +++--- wled00/bus_manager.cpp | 14 +++++++------- wled00/bus_manager.h | 12 ++++++------ wled00/colors.cpp | 6 +++--- wled00/fcn_declare.h | 16 ++++++++-------- wled00/file.cpp | 6 +++--- wled00/json.cpp | 2 +- wled00/ntp.cpp | 2 +- wled00/set.cpp | 6 +++--- wled00/udp.cpp | 4 ++-- wled00/wled_serial.cpp | 4 ++-- wled00/wled_server.cpp | 2 +- wled00/xml.cpp | 4 ++-- 16 files changed, 57 insertions(+), 57 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index dc39df816..4e7c832b8 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2363,7 +2363,7 @@ static const char _data_FX_MODE_LAKE[] PROGMEM = "Lake@!;Fx;!"; // send a meteor from begining to to the end of the strip with a trail that randomly decays. // adapted from https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/#LEDStripEffectMeteorRain uint16_t mode_meteor() { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); if (!SEGENV.allocateData(SEGLEN)) return mode_static(); //allocation failed const bool meteorSmooth = SEGMENT.check3; byte* trail = SEGENV.data; @@ -2531,7 +2531,7 @@ static uint16_t ripple_base(uint8_t blurAmount = 0) { uint16_t mode_ripple(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); if(SEGMENT.custom1 || SEGMENT.check2) // blur or overlay SEGMENT.fade_out(250); else @@ -2543,7 +2543,7 @@ static const char _data_FX_MODE_RIPPLE[] PROGMEM = "Ripple@!,Wave #,Blur,,,,Over uint16_t mode_ripple_rainbow(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); if (SEGENV.call ==0) { SEGENV.aux0 = hw_random8(); SEGENV.aux1 = hw_random8(); @@ -3984,7 +3984,7 @@ static const char _data_FX_MODE_HEARTBEAT[] PROGMEM = "Heartbeat@!,!;!,!;!;01;m1 // Modified for WLED, based on https://github.com/FastLED/FastLED/blob/master/examples/Pacifica/Pacifica.ino // // Add one layer of waves into the led array -static CRGB pacifica_one_layer(uint16_t i, CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff) +static CRGB pacifica_one_layer(uint16_t i, const CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff) { unsigned ci = cistart; unsigned waveangle = ioff; diff --git a/wled00/FX.h b/wled00/FX.h index f80ffb36d..7b06963b8 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -460,7 +460,7 @@ typedef struct Segment { {} } *_t; - [[gnu::hot]] void _setPixelColorXY_raw(int& x, int& y, uint32_t& col) const; // set pixel without mapping (internal use only) + [[gnu::hot]] void _setPixelColorXY_raw(const int& x, const int& y, uint32_t& col) const; // set pixel without mapping (internal use only) public: @@ -588,7 +588,7 @@ typedef struct Segment { inline void handleTransition() { updateTransitionProgress(); if (progress() == 0xFFFFU) stopTransition(); } #ifndef WLED_DISABLE_MODE_BLEND void swapSegenv(tmpsegd_t &tmpSegD); // copies segment data into specifed buffer, if buffer is not a transition buffer, segment data is overwritten from transition buffer - void restoreSegenv(tmpsegd_t &tmpSegD); // restores segment data from buffer, if buffer is not transition buffer, changed values are copied to transition buffer + void restoreSegenv(const tmpsegd_t &tmpSegD); // restores segment data from buffer, if buffer is not transition buffer, changed values are copied to transition buffer #endif [[gnu::hot]] void updateTransitionProgress(); // set current progression of transition inline uint16_t progress() const { return _transitionprogress; }; // transition progression between 0-65535 @@ -643,11 +643,11 @@ typedef struct Segment { } #ifndef WLED_DISABLE_2D [[gnu::hot]] uint16_t XY(int x, int y) const; // support function to get relative index within segment - [[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color - inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColorXY(int(x), int(y), c); } - inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } - inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } - inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) { setPixelColorXY(int(x), int(y), RGBW32(c.r,c.g,c.b,0)); } + [[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c) const; // set relative pixel within segment with color + inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) const { setPixelColorXY(int(x), int(y), c); } + inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) const { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } + inline void setPixelColorXY(int x, int y, CRGB c) const { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } + inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) const { setPixelColorXY(int(x), int(y), RGBW32(c.r,c.g,c.b,0)); } #ifdef WLED_USE_AA_PIXELS void setPixelColorXY(float x, float y, uint32_t c, bool aa = true); inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); } diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 2e4cdd515..a72cfde29 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -154,7 +154,7 @@ uint16_t IRAM_ATTR_YN Segment::XY(int x, int y) const } // raw setColor function without checks (checks are done in setPixelColorXY()) -void IRAM_ATTR_YN Segment::_setPixelColorXY_raw(int& x, int& y, uint32_t& col) const +void IRAM_ATTR_YN Segment::_setPixelColorXY_raw(const int& x, const int& y, uint32_t& col) const { const int baseX = start + x; const int baseY = startY + y; @@ -179,7 +179,7 @@ void IRAM_ATTR_YN Segment::_setPixelColorXY_raw(int& x, int& y, uint32_t& col) c } } -void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) +void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) const { if (!isActive()) return; // not active @@ -276,8 +276,8 @@ void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) { if (!isActive()) return; // not active const unsigned cols = vWidth(); const unsigned rows = vHeight(); - uint32_t lastnew; - uint32_t last; + uint32_t lastnew = BLACK; + uint32_t last = BLACK; if (blur_x) { const uint8_t keepx = smear ? 255 : 255 - blur_x; const uint8_t seepx = blur_x >> 1; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 90520d3d2..0a2b88acb 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -347,7 +347,7 @@ void Segment::swapSegenv(tmpsegd_t &tmpSeg) { } } -void Segment::restoreSegenv(tmpsegd_t &tmpSeg) { +void Segment::restoreSegenv(const tmpsegd_t &tmpSeg) { //DEBUG_PRINTF_P(PSTR("-- Restoring temp seg: %p->(%p) [%d->%p]\n"), &tmpSeg, this, _dataLen, data); if (_t && &(_t->_segT) != &tmpSeg) { // update possibly changed variables to keep old effect running correctly @@ -1134,8 +1134,8 @@ void Segment::blur(uint8_t blur_amount, bool smear) { uint8_t seep = blur_amount >> 1; unsigned vlength = vLength(); uint32_t carryover = BLACK; - uint32_t lastnew; - uint32_t last; + uint32_t lastnew = BLACK; + uint32_t last = BLACK; uint32_t curnew = BLACK; for (unsigned i = 0; i < vlength; i++) { uint32_t cur = getPixelColor(i); diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 2c0ba41a9..6e159a82b 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -27,7 +27,7 @@ extern bool cctICused; uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); //udp.cpp -uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte *buffer, uint8_t bri=255, bool isRGBW=false); +uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const uint8_t* buffer, uint8_t bri=255, bool isRGBW=false); // enable additional debug output #if defined(WLED_DEBUG_HOST) @@ -121,7 +121,7 @@ uint8_t *Bus::allocateData(size_t size) { } -BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) +BusDigital::BusDigital(const BusConfig &bc, uint8_t nr, const ColorOrderMap &com) : Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, (bc.refreshReq || bc.type == TYPE_TM1814)) , _skip(bc.skipAmount) //sacrificial pixels , _colorOrder(bc.colorOrder) @@ -448,7 +448,7 @@ void BusDigital::cleanup() { #endif #endif -BusPwm::BusPwm(BusConfig &bc) +BusPwm::BusPwm(const BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed, bc.refreshReq) // hijack Off refresh flag to indicate usage of dithering { if (!isPWM(bc.type)) return; @@ -646,7 +646,7 @@ void BusPwm::deallocatePins() { } -BusOnOff::BusOnOff(BusConfig &bc) +BusOnOff::BusOnOff(const BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed) , _onoffdata(0) { @@ -699,7 +699,7 @@ std::vector BusOnOff::getLEDTypes() { }; } -BusNetwork::BusNetwork(BusConfig &bc) +BusNetwork::BusNetwork(const BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite, bc.count) , _broadcastLock(false) { @@ -778,7 +778,7 @@ void BusNetwork::cleanup() { //utility to get the approx. memory usage of a given BusConfig -uint32_t BusManager::memUsage(BusConfig &bc) { +uint32_t BusManager::memUsage(const BusConfig &bc) { if (Bus::isOnOff(bc.type) || Bus::isPWM(bc.type)) return OUTPUT_MAX_PINS; unsigned len = bc.count + bc.skipAmount; @@ -803,7 +803,7 @@ uint32_t BusManager::memUsage(unsigned maxChannels, unsigned maxCount, unsigned return (maxChannels * maxCount * minBuses * multiplier); } -int BusManager::add(BusConfig &bc) { +int BusManager::add(const BusConfig &bc) { if (getNumBusses() - getNumVirtualBusses() >= WLED_MAX_BUSSES) return -1; if (Bus::isVirtual(bc.type)) { busses[numBusses] = new BusNetwork(bc); diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index d90a66151..9aed01308 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -198,7 +198,7 @@ class Bus { class BusDigital : public Bus { public: - BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com); + BusDigital(const BusConfig &bc, uint8_t nr, const ColorOrderMap &com); ~BusDigital() { cleanup(); } void show() override; @@ -250,7 +250,7 @@ class BusDigital : public Bus { class BusPwm : public Bus { public: - BusPwm(BusConfig &bc); + BusPwm(const BusConfig &bc); ~BusPwm() { cleanup(); } void setPixelColor(unsigned pix, uint32_t c) override; @@ -277,7 +277,7 @@ class BusPwm : public Bus { class BusOnOff : public Bus { public: - BusOnOff(BusConfig &bc); + BusOnOff(const BusConfig &bc); ~BusOnOff() { cleanup(); } void setPixelColor(unsigned pix, uint32_t c) override; @@ -296,7 +296,7 @@ class BusOnOff : public Bus { class BusNetwork : public Bus { public: - BusNetwork(BusConfig &bc); + BusNetwork(const BusConfig &bc); ~BusNetwork() { cleanup(); } bool canShow() const override { return !_broadcastLock; } // this should be a return value from UDP routine if it is still sending data out @@ -379,12 +379,12 @@ class BusManager { BusManager() {}; //utility to get the approx. memory usage of a given BusConfig - static uint32_t memUsage(BusConfig &bc); + static uint32_t memUsage(const BusConfig &bc); static uint32_t memUsage(unsigned channels, unsigned count, unsigned buses = 1); static uint16_t currentMilliamps() { return _milliAmpsUsed + MA_FOR_ESP; } static uint16_t ablMilliampsMax() { return _milliAmpsMax; } - static int add(BusConfig &bc); + static int add(const BusConfig &bc); static void useParallelOutput(); // workaround for inaccessible PolyBus //do not call this method from system context (network callback) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index e64cf6758..f154a1aea 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -122,7 +122,7 @@ void setRandomColor(byte* rgb) * generates a random palette based on harmonic color theory * takes a base palette as the input, it will choose one color of the base palette and keep it */ -CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette) +CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette) { CHSV palettecolors[4]; // array of colors for the new palette uint8_t keepcolorposition = hw_random8(4); // color position of current random palette to keep @@ -391,7 +391,7 @@ void colorXYtoRGB(float x, float y, byte* rgb) //coordinates to rgb (https://www rgb[2] = byte(255.0f*b); } -void colorRGBtoXY(byte* rgb, float* xy) //rgb to coordinates (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy) +void colorRGBtoXY(const byte* rgb, float* xy) //rgb to coordinates (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy) { float X = rgb[0] * 0.664511f + rgb[1] * 0.154324f + rgb[2] * 0.162028f; float Y = rgb[0] * 0.283881f + rgb[1] * 0.668433f + rgb[2] * 0.047685f; @@ -402,7 +402,7 @@ void colorRGBtoXY(byte* rgb, float* xy) //rgb to coordinates (https://www.develo #endif // WLED_DISABLE_HUESYNC //RRGGBB / WWRRGGBB order for hex -void colorFromDecOrHexString(byte* rgb, char* in) +void colorFromDecOrHexString(byte* rgb, const char* in) { if (in[0] == 0) return; char first = in[0]; diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index e9e8df6a2..48aad4d8c 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -166,7 +166,7 @@ inline uint32_t color_blend16(uint32_t c1, uint32_t c2, uint16_t b) { return col [[gnu::hot]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false); [[gnu::hot]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); [[gnu::hot]] uint32_t ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); -CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); +CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(); inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } void hsv2rgb(const CHSV32& hsv, uint32_t& rgb); @@ -177,7 +177,7 @@ void colorKtoRGB(uint16_t kelvin, byte* rgb); void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO void colorRGBtoXY(byte* rgb, float* xy); // only defined if huesync disabled TODO -void colorFromDecOrHexString(byte* rgb, char* in); +void colorFromDecOrHexString(byte* rgb, const char* in); bool colorFromHexString(byte* rgb, const char* in); uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); uint16_t approximateKelvinFromRGB(uint32_t rgb); @@ -200,14 +200,14 @@ void sendArtnetPollReply(ArtPollReply* reply, IPAddress ipAddress, uint16_t port //file.cpp bool handleFileRead(AsyncWebServerRequest*, String path); -bool writeObjectToFileUsingId(const char* file, uint16_t id, JsonDocument* content); -bool writeObjectToFile(const char* file, const char* key, JsonDocument* content); +bool writeObjectToFileUsingId(const char* file, uint16_t id, const JsonDocument* content); +bool writeObjectToFile(const char* file, const char* key, const JsonDocument* content); bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest); bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest); void updateFSInfo(); void closeFile(); -inline bool writeObjectToFileUsingId(const String &file, uint16_t id, JsonDocument* content) { return writeObjectToFileUsingId(file.c_str(), id, content); }; -inline bool writeObjectToFile(const String &file, const char* key, JsonDocument* content) { return writeObjectToFile(file.c_str(), key, content); }; +inline bool writeObjectToFileUsingId(const String &file, uint16_t id, const JsonDocument* content) { return writeObjectToFileUsingId(file.c_str(), id, content); }; +inline bool writeObjectToFile(const String &file, const char* key, const JsonDocument* content) { return writeObjectToFile(file.c_str(), key, content); }; inline bool readObjectFromFileUsingId(const String &file, uint16_t id, JsonDocument* dest) { return readObjectFromFileUsingId(file.c_str(), id, dest); }; inline bool readObjectFromFile(const String &file, const char* key, JsonDocument* dest) { return readObjectFromFile(file.c_str(), key, dest); }; @@ -248,7 +248,7 @@ void handleIR(); bool deserializeSegment(JsonObject elem, byte it, byte presetId = 0); bool deserializeState(JsonObject root, byte callMode = CALL_MODE_DIRECT_CHANGE, byte presetId = 0); -void serializeSegment(JsonObject& root, Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true); +void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true); void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true, bool selectedSegmentsOnly = false); void serializeInfo(JsonObject root); void serializeModeNames(JsonArray root); @@ -333,7 +333,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply=tru //udp.cpp void notify(byte callMode, bool followUp=false); -uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri=255, bool isRGBW=false); +uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const uint8_t* buffer, uint8_t bri=255, bool isRGBW=false); void realtimeLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC); void exitRealtime(); void handleNotifications(); diff --git a/wled00/file.cpp b/wled00/file.cpp index bc3467202..c48a300b7 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -176,7 +176,7 @@ static void writeSpace(size_t l) if (knownLargestSpace < l) knownLargestSpace = l; } -bool appendObjectToFile(const char* key, JsonDocument* content, uint32_t s, uint32_t contentLen = 0) +bool appendObjectToFile(const char* key, const JsonDocument* content, uint32_t s, uint32_t contentLen = 0) { #ifdef WLED_DEBUG_FS DEBUGFS_PRINTLN(F("Append")); @@ -255,14 +255,14 @@ bool appendObjectToFile(const char* key, JsonDocument* content, uint32_t s, uint return true; } -bool writeObjectToFileUsingId(const char* file, uint16_t id, JsonDocument* content) +bool writeObjectToFileUsingId(const char* file, uint16_t id, const JsonDocument* content) { char objKey[10]; sprintf(objKey, "\"%d\":", id); return writeObjectToFile(file, objKey, content); } -bool writeObjectToFile(const char* file, const char* key, JsonDocument* content) +bool writeObjectToFile(const char* file, const char* key, const JsonDocument* content) { uint32_t s = 0; //timing #ifdef WLED_DEBUG_FS diff --git a/wled00/json.cpp b/wled00/json.cpp index f284a9533..ad0e96ed9 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -493,7 +493,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) return stateResponse; } -void serializeSegment(JsonObject& root, Segment& seg, byte id, bool forPreset, bool segmentBounds) +void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool forPreset, bool segmentBounds) { root["id"] = id; if (segmentBounds) { diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index 8d44e634e..12b698f44 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -224,7 +224,7 @@ void sendNTPPacket() ntpUdp.endPacket(); } -static bool isValidNtpResponse(byte * ntpPacket) { +static bool isValidNtpResponse(const byte* ntpPacket) { // Perform a few validity checks on the packet // based on https://github.com/taranais/NTPClient/blob/master/NTPClient.cpp if((ntpPacket[0] & 0b11000000) == 0b11000000) return false; //reject LI=UNSYNC diff --git a/wled00/set.cpp b/wled00/set.cpp index 08a0180ad..a750072a6 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -990,18 +990,18 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) //set color from HEX or 32bit DEC pos = req.indexOf(F("CL=")); if (pos > 0) { - colorFromDecOrHexString(colIn, (char*)req.substring(pos + 3).c_str()); + colorFromDecOrHexString(colIn, (const char*)req.substring(pos + 3).c_str()); col0Changed = true; } pos = req.indexOf(F("C2=")); if (pos > 0) { - colorFromDecOrHexString(colInSec, (char*)req.substring(pos + 3).c_str()); + colorFromDecOrHexString(colInSec, (const char*)req.substring(pos + 3).c_str()); col1Changed = true; } pos = req.indexOf(F("C3=")); if (pos > 0) { byte tmpCol[4]; - colorFromDecOrHexString(tmpCol, (char*)req.substring(pos + 3).c_str()); + colorFromDecOrHexString(tmpCol, (const char*)req.substring(pos + 3).c_str()); col2 = RGBW32(tmpCol[0], tmpCol[1], tmpCol[2], tmpCol[3]); selseg.setColor(2, col2); // defined above (SS= or main) col2Changed = true; diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 8ba9a1a7a..e76ef6646 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -206,7 +206,7 @@ void notify(byte callMode, bool followUp) notificationCount = followUp ? notificationCount + 1 : 0; } -void parseNotifyPacket(uint8_t *udpIn) { +void parseNotifyPacket(const uint8_t *udpIn) { //ignore notification if received within a second after sending a notification ourselves if (millis() - notificationSentTime < 1000) return; if (udpIn[1] > 199) return; //do not receive custom versions @@ -810,7 +810,7 @@ static size_t sequenceNumber = 0; // this needs to be shared across all ou static const size_t ART_NET_HEADER_SIZE = 12; static const byte ART_NET_HEADER[] PROGMEM = {0x41,0x72,0x74,0x2d,0x4e,0x65,0x74,0x00,0x00,0x50,0x00,0x0e}; -uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri, bool isRGBW) { +uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const uint8_t* buffer, uint8_t bri, bool isRGBW) { if (!(apActive || interfacesInited) || !client[0] || !length) return 1; // network not initialised or dummy/unset IP address 031522 ajn added check for ap WiFiUDP ddpUdp; diff --git a/wled00/wled_serial.cpp b/wled00/wled_serial.cpp index ad9bb1413..a0e59c531 100644 --- a/wled00/wled_serial.cpp +++ b/wled00/wled_serial.cpp @@ -113,8 +113,8 @@ void handleSerial() //only send response if TX pin is unused for other purposes if (verboseResponse && serialCanTX) { pDoc->clear(); - JsonObject state = pDoc->createNestedObject("state"); - serializeState(state); + JsonObject stateDoc = pDoc->createNestedObject("state"); + serializeState(stateDoc); JsonObject info = pDoc->createNestedObject("info"); serializeInfo(info); diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 96f2a705c..a17a7ec3a 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -21,7 +21,7 @@ static const char s_accessdenied[] PROGMEM = "Access Denied"; static const char _common_js[] PROGMEM = "/common.js"; //Is this an IP? -static bool isIp(String str) { +static bool isIp(const String str) { for (size_t i = 0; i < str.length(); i++) { int c = str.charAt(i); if (c != '.' && (c < '0' || c > '9')) { diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 0ed32c04b..957ccebda 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -26,7 +26,7 @@ void XML_response(Print& dest) ); } -static void extractPin(Print& settingsScript, JsonObject &obj, const char *key) { +static void extractPin(Print& settingsScript, const JsonObject &obj, const char *key) { if (obj[key].is()) { JsonArray pins = obj[key].as(); for (JsonVariant pv : pins) { @@ -38,7 +38,7 @@ static void extractPin(Print& settingsScript, JsonObject &obj, const char *key) } // print used pins by scanning JsonObject (1 level deep) -static void fillUMPins(Print& settingsScript, JsonObject &mods) +static void fillUMPins(Print& settingsScript, const JsonObject &mods) { for (JsonPair kv : mods) { // kv.key() is usermod name or subobject key From b6f74287d07a7a572318ec78ee90e48399b32081 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 9 Jan 2025 12:12:53 +0100 Subject: [PATCH 116/125] implement recommendations from reviewers * simplified transition bugfix * removed cast same type * isIp parameter changed to pass-by-reference, to avoid copy constructor --- wled00/FX.h | 2 +- wled00/FX_fcn.cpp | 6 +++--- wled00/set.cpp | 6 +++--- wled00/wled_server.cpp | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 7b06963b8..fa9ebe1c8 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -591,7 +591,7 @@ typedef struct Segment { void restoreSegenv(const tmpsegd_t &tmpSegD); // restores segment data from buffer, if buffer is not transition buffer, changed values are copied to transition buffer #endif [[gnu::hot]] void updateTransitionProgress(); // set current progression of transition - inline uint16_t progress() const { return _transitionprogress; }; // transition progression between 0-65535 + inline uint16_t progress() const { return _t ? Segment::_transitionprogress : 0xFFFFU; } // transition progression between 0-65535 [[gnu::hot]] uint8_t currentBri(bool useCct = false) const; // current segment brightness/CCT (blended while in transition) uint8_t currentMode() const; // currently active effect/mode (while in transition) [[gnu::hot]] uint32_t currentColor(uint8_t slot) const; // currently active segment color (blended while in transition) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 0a2b88acb..b2f3035e2 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -380,7 +380,7 @@ void Segment::restoreSegenv(const tmpsegd_t &tmpSeg) { uint8_t Segment::currentBri(bool useCct) const { unsigned prog = progress(); - if (prog < 0xFFFFU && _t) { + if (prog < 0xFFFFU) { // progress() < 0xFFFF inplies that _t is a valid pointer unsigned curBri = (useCct ? cct : (on ? opacity : 0)) * prog; curBri += (useCct ? _t->_cctT : _t->_briT) * (0xFFFFU - prog); return curBri / 0xFFFFU; @@ -390,8 +390,8 @@ uint8_t Segment::currentBri(bool useCct) const { uint8_t Segment::currentMode() const { #ifndef WLED_DISABLE_MODE_BLEND - unsigned prog = progress(); - if (modeBlending && prog < 0xFFFFU && _t) return _t->_modeT; + unsigned prog = progress(); // progress() < 0xFFFF inplies that _t is a valid pointer + if (modeBlending && prog < 0xFFFFU) return _t->_modeT; #endif return mode; } diff --git a/wled00/set.cpp b/wled00/set.cpp index a750072a6..7a88699cd 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -990,18 +990,18 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) //set color from HEX or 32bit DEC pos = req.indexOf(F("CL=")); if (pos > 0) { - colorFromDecOrHexString(colIn, (const char*)req.substring(pos + 3).c_str()); + colorFromDecOrHexString(colIn, req.substring(pos + 3).c_str()); col0Changed = true; } pos = req.indexOf(F("C2=")); if (pos > 0) { - colorFromDecOrHexString(colInSec, (const char*)req.substring(pos + 3).c_str()); + colorFromDecOrHexString(colInSec, req.substring(pos + 3).c_str()); col1Changed = true; } pos = req.indexOf(F("C3=")); if (pos > 0) { byte tmpCol[4]; - colorFromDecOrHexString(tmpCol, (const char*)req.substring(pos + 3).c_str()); + colorFromDecOrHexString(tmpCol, req.substring(pos + 3).c_str()); col2 = RGBW32(tmpCol[0], tmpCol[1], tmpCol[2], tmpCol[3]); selseg.setColor(2, col2); // defined above (SS= or main) col2Changed = true; diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index a17a7ec3a..8768b2b4e 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -21,7 +21,7 @@ static const char s_accessdenied[] PROGMEM = "Access Denied"; static const char _common_js[] PROGMEM = "/common.js"; //Is this an IP? -static bool isIp(const String str) { +static bool isIp(const String &str) { for (size_t i = 0; i < str.length(); i++) { int c = str.charAt(i); if (c != '.' && (c < '0' || c > '9')) { From 872465df400e8304c7a20cdb58c43d87d0d9f96a Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 9 Jan 2025 12:21:56 +0100 Subject: [PATCH 117/125] typo in comments --- wled00/FX_fcn.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index b2f3035e2..bd0ece168 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -380,7 +380,7 @@ void Segment::restoreSegenv(const tmpsegd_t &tmpSeg) { uint8_t Segment::currentBri(bool useCct) const { unsigned prog = progress(); - if (prog < 0xFFFFU) { // progress() < 0xFFFF inplies that _t is a valid pointer + if (prog < 0xFFFFU) { // progress() < 0xFFFF implies that _t is a valid pointer unsigned curBri = (useCct ? cct : (on ? opacity : 0)) * prog; curBri += (useCct ? _t->_cctT : _t->_briT) * (0xFFFFU - prog); return curBri / 0xFFFFU; @@ -390,8 +390,8 @@ uint8_t Segment::currentBri(bool useCct) const { uint8_t Segment::currentMode() const { #ifndef WLED_DISABLE_MODE_BLEND - unsigned prog = progress(); // progress() < 0xFFFF inplies that _t is a valid pointer - if (modeBlending && prog < 0xFFFFU) return _t->_modeT; + unsigned prog = progress(); + if (modeBlending && prog < 0xFFFFU) return _t->_modeT; // progress() < 0xFFFF implies that _t is a valid pointer #endif return mode; } From 01c463c8e8bd687a439222ffeafca9d1799914f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Thu, 9 Jan 2025 13:29:06 +0100 Subject: [PATCH 118/125] More tuning - replaced POD new/delete with malloc/free - some more SEGLEN <= 1 - some gnu::pure - more const attributes - some static attributes --- wled00/FX.cpp | 70 ++++++++++++++++++++++---------------------- wled00/FX.h | 51 ++++++++++++++++---------------- wled00/FX_2Dfcn.cpp | 14 ++++----- wled00/FX_fcn.cpp | 20 ++++++------- wled00/fcn_declare.h | 22 +++++++------- wled00/file.cpp | 2 +- wled00/json.cpp | 6 ++-- wled00/mqtt.cpp | 8 ++--- wled00/presets.cpp | 12 ++++---- wled00/set.cpp | 2 +- wled00/udp.cpp | 2 +- wled00/util.cpp | 8 ++--- 12 files changed, 109 insertions(+), 108 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 4e7c832b8..9fc9dbffb 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -197,7 +197,7 @@ static const char _data_FX_MODE_STROBE_RAINBOW[] PROGMEM = "Strobe Rainbow@!;,!; * if (bool rev == true) then LEDs are turned off in reverse order */ uint16_t color_wipe(bool rev, bool useRandomColors) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); uint32_t cycleTime = 750 + (255 - SEGMENT.speed)*150; uint32_t perc = strip.now % cycleTime; unsigned prog = (perc * 65535) / cycleTime; @@ -410,7 +410,7 @@ static const char _data_FX_MODE_FADE[] PROGMEM = "Fade@!;!,!;!;01"; * Scan mode parent function */ uint16_t scan(bool dual) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); uint32_t cycleTime = 750 + (255 - SEGMENT.speed)*150; uint32_t perc = strip.now % cycleTime; int prog = (perc * 65535) / cycleTime; @@ -1017,7 +1017,7 @@ static const char _data_FX_MODE_COLORFUL[] PROGMEM = "Colorful@!,Saturation;1,2, * Emulates a traffic light. */ uint16_t mode_traffic_light(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); for (unsigned i=0; i < SEGLEN; i++) SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); uint32_t mdelay = 500; @@ -1050,7 +1050,7 @@ static const char _data_FX_MODE_TRAFFIC_LIGHT[] PROGMEM = "Traffic Light@!,US st */ #define FLASH_COUNT 4 uint16_t mode_chase_flash(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); unsigned flash_step = SEGENV.call % ((FLASH_COUNT * 2) + 1); for (unsigned i = 0; i < SEGLEN; i++) { @@ -1080,7 +1080,7 @@ static const char _data_FX_MODE_CHASE_FLASH[] PROGMEM = "Chase Flash@!;Bg,Fx;!"; * Prim flashes running, followed by random color. */ uint16_t mode_chase_flash_random(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); unsigned flash_step = SEGENV.call % ((FLASH_COUNT * 2) + 1); for (int i = 0; i < SEGENV.aux1; i++) { @@ -1162,7 +1162,7 @@ static const char _data_FX_MODE_RUNNING_RANDOM[] PROGMEM = "Stream@!,Zone size;; * K.I.T.T. */ uint16_t mode_larson_scanner(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); const unsigned speed = FRAMETIME * map(SEGMENT.speed, 0, 255, 96, 2); // map into useful range const unsigned pixels = SEGLEN / speed; // how many pixels to advance per frame @@ -1220,7 +1220,7 @@ static const char _data_FX_MODE_DUAL_LARSON_SCANNER[] PROGMEM = "Scanner Dual@!, * Firing comets from one end. "Lighthouse" */ uint16_t mode_comet(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); unsigned counter = (strip.now * ((SEGMENT.speed >>2) +1)) & 0xFFFF; unsigned index = (counter * SEGLEN) >> 16; if (SEGENV.call == 0) SEGENV.aux0 = index; @@ -1248,7 +1248,7 @@ static const char _data_FX_MODE_COMET[] PROGMEM = "Lighthouse@!,Fade rate;!,!;!" * Fireworks function. */ uint16_t mode_fireworks() { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); const uint16_t width = SEGMENT.is2D() ? SEG_W : SEGLEN; const uint16_t height = SEG_H; @@ -1290,7 +1290,7 @@ static const char _data_FX_MODE_FIREWORKS[] PROGMEM = "Fireworks@,Frequency;!,!; //Twinkling LEDs running. Inspired by https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/Rain.h uint16_t mode_rain() { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); const unsigned width = SEG_W; const unsigned height = SEG_H; SEGENV.step += FRAMETIME; @@ -1356,7 +1356,7 @@ static const char _data_FX_MODE_FIRE_FLICKER[] PROGMEM = "Fire Flicker@!,!;!;!;0 * Gradient run base function */ uint16_t gradient_base(bool loading) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); uint16_t counter = strip.now * ((SEGMENT.speed >> 2) + 1); uint16_t pp = (counter * SEGLEN) >> 16; if (SEGENV.call == 0) pp = 0; @@ -1401,7 +1401,7 @@ static const char _data_FX_MODE_LOADING[] PROGMEM = "Loading@!,Fade;!,!;!;;ix=16 * Two dots running */ uint16_t mode_two_dots() { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); unsigned delay = 1 + (FRAMETIME<<3) / SEGLEN; // longer segments should change faster uint32_t it = strip.now / map(SEGMENT.speed, 0, 255, delay<<4, delay); unsigned offset = it % SEGLEN; @@ -1852,7 +1852,7 @@ static const char _data_FX_MODE_OSCILLATE[] PROGMEM = "Oscillate"; //TODO uint16_t mode_lightning(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); unsigned ledstart = hw_random16(SEGLEN); // Determine starting location of flash unsigned ledlen = 1 + hw_random16(SEGLEN -ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1) uint8_t bri = 255/hw_random8(1, 3); @@ -1938,7 +1938,7 @@ static const char _data_FX_MODE_PRIDE_2015[] PROGMEM = "Pride 2015@!;;"; //eight colored dots, weaving in and out of sync with each other uint16_t mode_juggle(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); SEGMENT.fadeToBlackBy(192 - (3*SEGMENT.intensity/4)); CRGB fastled_col; @@ -2083,7 +2083,7 @@ static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Shift,Size,Rotation // feel of your fire: COOLING (used in step 1 above) (Speed = COOLING), and SPARKING (used // in step 3 above) (Effect Intensity = Sparking). uint16_t mode_fire_2012() { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); const unsigned strips = SEGMENT.nrOfVStrips(); if (!SEGENV.allocateData(strips * SEGLEN)) return mode_static(); //allocation failed byte* heat = SEGENV.data; @@ -2430,7 +2430,7 @@ static const char _data_FX_MODE_METEOR[] PROGMEM = "Meteor@!,Trail,,,,Gradient,, //Railway Crossing / Christmas Fairy lights uint16_t mode_railway() { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); unsigned dur = (256 - SEGMENT.speed) * 40; uint16_t rampdur = (dur * SEGMENT.intensity) >> 8; if (SEGENV.step > dur) @@ -2721,7 +2721,7 @@ uint16_t mode_halloween_eyes() uint32_t blinkEndTime; }; - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); const unsigned maxWidth = strip.isMatrix ? SEG_W : SEGLEN; const unsigned HALLOWEEN_EYE_SPACE = MAX(2, strip.isMatrix ? SEG_W>>4: SEGLEN>>5); const unsigned HALLOWEEN_EYE_WIDTH = HALLOWEEN_EYE_SPACE/2; @@ -2906,7 +2906,7 @@ static const char _data_FX_MODE_TRI_STATIC_PATTERN[] PROGMEM = "Solid Pattern Tr static uint16_t spots_base(uint16_t threshold) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); unsigned maxZones = SEGLEN >> 2; @@ -2962,7 +2962,7 @@ typedef struct Ball { * Bouncing Balls Effect */ uint16_t mode_bouncing_balls(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); //allocate segment data const unsigned strips = SEGMENT.nrOfVStrips(); // adapt for 2D const size_t maxNumBalls = 16; @@ -3140,7 +3140,7 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b * Sinelon stolen from FASTLED examples */ static uint16_t sinelon_base(bool dual, bool rainbow=false) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); SEGMENT.fade_out(SEGMENT.intensity); unsigned pos = beatsin16_t(SEGMENT.speed/10,0,SEGLEN-1); if (SEGENV.call == 0) SEGENV.aux0 = pos; @@ -3245,7 +3245,7 @@ typedef struct Spark { * modified from https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/Popcorn.h */ uint16_t mode_popcorn(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); //allocate segment data unsigned strips = SEGMENT.nrOfVStrips(); unsigned usablePopcorns = maxNumPopcorn; @@ -3420,7 +3420,7 @@ typedef struct particle { } star; uint16_t mode_starburst(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); unsigned maxData = FAIR_DATA_PER_SEG; //ESP8266: 256 ESP32: 640 unsigned segs = strip.getActiveSegmentsNum(); if (segs <= (strip.getMaxSegments() /2)) maxData *= 2; //ESP8266: 512 if <= 8 segs ESP32: 1280 if <= 16 segs @@ -3539,7 +3539,7 @@ static const char _data_FX_MODE_STARBURST[] PROGMEM = "Fireworks Starburst@Chanc */ uint16_t mode_exploding_fireworks(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); const int cols = SEGMENT.is2D() ? SEG_W : 1; const int rows = SEGMENT.is2D() ? SEG_H : SEGLEN; @@ -3677,7 +3677,7 @@ static const char _data_FX_MODE_EXPLODING_FIREWORKS[] PROGMEM = "Fireworks 1D@Gr */ uint16_t mode_drip(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); //allocate segment data unsigned strips = SEGMENT.nrOfVStrips(); const int maxNumDrops = 4; @@ -3773,7 +3773,7 @@ typedef struct Tetris { } tetris; uint16_t mode_tetrix(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); unsigned strips = SEGMENT.nrOfVStrips(); // allow running on virtual strips (columns in 2D segment) unsigned dataSize = sizeof(tetris); if (!SEGENV.allocateData(dataSize * strips)) return mode_static(); //allocation failed @@ -4080,7 +4080,7 @@ static const char _data_FX_MODE_PACIFICA[] PROGMEM = "Pacifica@!,Angle;;!;;pal=5 * Mode simulates a gradual sunrise */ uint16_t mode_sunrise() { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); //speed 0 - static sun //speed 1 - 60: sunrise time in minutes //speed 60 - 120 : sunset time in minutes - 60; @@ -4287,7 +4287,7 @@ static const char _data_FX_MODE_FLOW[] PROGMEM = "Flow@!,Zones;;!;;m12=1"; //ver */ uint16_t mode_chunchun(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); SEGMENT.fade_out(254); // add a bit of trail unsigned counter = strip.now * (6 + (SEGMENT.speed >> 4)); unsigned numBirds = 2 + (SEGLEN >> 3); // 2 + 1/8 of a segment @@ -4338,7 +4338,7 @@ typedef struct Spotlight { */ uint16_t mode_dancing_shadows(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); unsigned numSpotlights = map(SEGMENT.intensity, 0, 255, 2, SPOT_MAX_COUNT); // 49 on 32 segment ESP32, 17 on 16 segment ESP8266 bool initialize = SEGENV.aux0 != numSpotlights; SEGENV.aux0 = numSpotlights; @@ -4800,7 +4800,7 @@ static const char _data_FX_MODE_AURORA[] PROGMEM = "Aurora@!,!;1,2,3;!;;sx=24,pa // 16 bit perlinmove. Use Perlin Noise instead of sinewaves for movement. By Andrew Tuline. // Controls are speed, # of pixels, faderate. uint16_t mode_perlinmove(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); SEGMENT.fade_out(255-SEGMENT.custom1); for (int i = 0; i < SEGMENT.intensity/16 + 1; i++) { unsigned locn = inoise16(strip.now*128/(260-SEGMENT.speed)+i*15000, strip.now*128/(260-SEGMENT.speed)); // Get a new pixel location from moving noise. @@ -4836,7 +4836,7 @@ static const char _data_FX_MODE_WAVESINS[] PROGMEM = "Wavesins@!,Brightness vari ////////////////////////////// // By: ldirko https://editor.soulmatelights.com/gallery/392-flow-led-stripe , modifed by: Andrew Tuline uint16_t mode_FlowStripe(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); const int hl = SEGLEN * 10 / 13; uint8_t hue = strip.now / (SEGMENT.speed+1); uint32_t t = strip.now / (SEGMENT.intensity/8+1); @@ -6598,7 +6598,7 @@ static const char _data_FX_MODE_MATRIPIX[] PROGMEM = "Matripix@!,Brightness;!,!; // * MIDNOISE // ////////////////////// uint16_t mode_midnoise(void) { // Midnoise. By Andrew Tuline. - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); // Changing xdist to SEGENV.aux0 and ydist to SEGENV.aux1. um_data_t *um_data = getAudioData(); @@ -6689,7 +6689,7 @@ static const char _data_FX_MODE_NOISEMETER[] PROGMEM = "Noisemeter@Fade rate,Wid // * PIXELWAVE // ////////////////////// uint16_t mode_pixelwave(void) { // Pixelwave. By Andrew Tuline. - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment if (SEGENV.call == 0) { @@ -6757,7 +6757,7 @@ static const char _data_FX_MODE_PLASMOID[] PROGMEM = "Plasmoid@Phase,# of pixels ////////////////////// // Puddles/Puddlepeak By Andrew Tuline. Merged by @dedehai uint16_t mode_puddles_base(bool peakdetect) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); unsigned size = 0; uint8_t fadeVal = map(SEGMENT.speed, 0, 255, 224, 254); unsigned pos = hw_random16(SEGLEN); // Set a random starting position. @@ -6807,7 +6807,7 @@ static const char _data_FX_MODE_PUDDLES[] PROGMEM = "Puddles@Fade rate,Puddle si // * PIXELS // ////////////////////// uint16_t mode_pixels(void) { // Pixels. By Andrew Tuline. - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); if (!SEGENV.allocateData(32*sizeof(uint8_t))) return mode_static(); //allocation failed uint8_t *myVals = reinterpret_cast(SEGENV.data); // Used to store a pile of samples because WLED frame rate and WLED sample rate are not synchronized. Frame rate is too low. @@ -6835,7 +6835,7 @@ static const char _data_FX_MODE_PIXELS[] PROGMEM = "Pixels@Fade rate,# of pixels // ** Blurz // ////////////////////// uint16_t mode_blurz(void) { // Blurz. By Andrew Tuline. - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment um_data_t *um_data = getAudioData(); @@ -6899,7 +6899,7 @@ static const char _data_FX_MODE_DJLIGHT[] PROGMEM = "DJ Light@Speed;;;01f;m12=2, // ** Freqmap // //////////////////// uint16_t mode_freqmap(void) { // Map FFT_MajorPeak to SEGLEN. Would be better if a higher framerate. - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); // Start frequency = 60 Hz and log10(60) = 1.78 // End frequency = MAX_FREQUENCY in Hz and lo10(MAX_FREQUENCY) = MAX_FREQ_LOG10 diff --git a/wled00/FX.h b/wled00/FX.h index fa9ebe1c8..289896d2d 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -79,7 +79,7 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define MAX_NUM_SEGMENTS 32 #endif #if defined(ARDUINO_ARCH_ESP32S2) - #define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*768) // 24k by default (S2 is short on free RAM) + #define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*768) // 24k by default (S2 is short on free RAM) #else #define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*1280) // 40k by default #endif @@ -518,7 +518,7 @@ typedef struct Segment { //if (data) Serial.printf(" %d->(%p)", (int)_dataLen, data); //Serial.println(); #endif - if (name) { delete[] name; name = nullptr; } + if (name) { free(name); name = nullptr; } stopTransition(); deallocateData(); } @@ -534,7 +534,6 @@ typedef struct Segment { inline bool isSelected() const { return selected; } inline bool isInTransition() const { return _t != nullptr; } inline bool isActive() const { return stop > start; } - inline bool is2D() const { return (width()>1 && height()>1); } inline bool hasRGB() const { return _isRGB; } inline bool hasWhite() const { return _hasW; } inline bool isCCT() const { return _isCCT; } @@ -599,14 +598,14 @@ typedef struct Segment { // 1D strip [[gnu::hot]] uint16_t virtualLength() const; - [[gnu::hot]] void setPixelColor(int n, uint32_t c); // set relative pixel within segment with color - inline void setPixelColor(unsigned n, uint32_t c) { setPixelColor(int(n), c); } - inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } - inline void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } + [[gnu::hot]] void setPixelColor(int n, uint32_t c) const; // set relative pixel within segment with color + inline void setPixelColor(unsigned n, uint32_t c) const { setPixelColor(int(n), c); } + inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) const { setPixelColor(n, RGBW32(r,g,b,w)); } + inline void setPixelColor(int n, CRGB c) const { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } #ifdef WLED_USE_AA_PIXELS - void setPixelColor(float i, uint32_t c, bool aa = true); - inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) { setPixelColor(i, RGBW32(r,g,b,w), aa); } - inline void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } + void setPixelColor(float i, uint32_t c, bool aa = true) const; + inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) const { setPixelColor(i, RGBW32(r,g,b,w), aa); } + inline void setPixelColor(float i, CRGB c, bool aa = true) const { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } #endif [[gnu::hot]] uint32_t getPixelColor(int i) const; // 1D support functions (some implement 2D as well) @@ -642,16 +641,17 @@ typedef struct Segment { #endif } #ifndef WLED_DISABLE_2D - [[gnu::hot]] uint16_t XY(int x, int y) const; // support function to get relative index within segment + inline bool is2D() const { return (width()>1 && height()>1); } + [[gnu::hot]] int XY(int x, int y) const; // support function to get relative index within segment [[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c) const; // set relative pixel within segment with color inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) const { setPixelColorXY(int(x), int(y), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) const { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } inline void setPixelColorXY(int x, int y, CRGB c) const { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) const { setPixelColorXY(int(x), int(y), RGBW32(c.r,c.g,c.b,0)); } #ifdef WLED_USE_AA_PIXELS - void setPixelColorXY(float x, float y, uint32_t c, bool aa = true); - inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); } - inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); } + void setPixelColorXY(float x, float y, uint32_t c, bool aa = true) const; + inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) const { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); } + inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) const { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); } #endif [[gnu::hot]] uint32_t getPixelColorXY(int x, int y) const; // 2D support functions @@ -678,7 +678,8 @@ typedef struct Segment { void wu_pixel(uint32_t x, uint32_t y, CRGB c); inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } #else - inline uint16_t XY(int x, int y) { return x; } + inline constexpr bool is2D() const { return false; } + inline int XY(int x, int y) const { return x; } inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); } inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColor(int(x), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColor(x, RGBW32(r,g,b,w)); } @@ -778,7 +779,7 @@ class WS2812FX { // 96 bytes } ~WS2812FX() { - if (customMappingTable) delete[] customMappingTable; + if (customMappingTable) free(customMappingTable); _mode.clear(); _modeData.clear(); _segments.clear(); @@ -804,7 +805,7 @@ class WS2812FX { // 96 bytes resetSegments(), // marks all segments for reset makeAutoSegments(bool forceReset = false), // will create segments based on configured outputs fixInvalidSegments(), // fixes incorrect segment configuration - setPixelColor(unsigned n, uint32_t c), // paints absolute strip pixel with index n and color c + setPixelColor(unsigned n, uint32_t c) const, // paints absolute strip pixel with index n and color c show(), // initiates LED output setTargetFps(unsigned fps), setupEffectData(); // add default effects to the list; defined in FX.cpp @@ -812,9 +813,9 @@ class WS2812FX { // 96 bytes inline void resetTimebase() { timebase = 0UL - millis(); } inline void restartRuntime() { for (Segment &seg : _segments) { seg.markForReset().resetIfRequired(); } } inline void setTransitionMode(bool t) { for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); } - inline void setPixelColor(unsigned n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } - inline void setPixelColor(unsigned n, CRGB c) { setPixelColor(n, c.red, c.green, c.blue); } - inline void fill(uint32_t c) { for (unsigned i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline) + inline void setPixelColor(unsigned n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) const { setPixelColor(n, RGBW32(r,g,b,w)); } + inline void setPixelColor(unsigned n, CRGB c) const { setPixelColor(n, c.red, c.green, c.blue); } + inline void fill(uint32_t c) const { for (unsigned i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline) inline void trigger() { _triggered = true; } // Forces the next frame to be computed on all active segments. inline void setShowCallback(show_callback cb) { _callback = cb; } inline void setTransition(uint16_t t) { _transitionDur = t; } // sets transition time (in ms) @@ -824,7 +825,7 @@ class WS2812FX { // 96 bytes bool paletteFade, - checkSegmentAlignment(), + checkSegmentAlignment() const, hasRGBWBus() const, hasCCTBus() const, deserializeMap(unsigned n = 0); @@ -918,11 +919,11 @@ class WS2812FX { // 96 bytes void setUpMatrix(); // sets up automatic matrix ledmap from panel configuration // outsmart the compiler :) by correctly overloading - inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor((unsigned)(y * Segment::maxWidth + x), c); } - inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } - inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } + inline void setPixelColorXY(int x, int y, uint32_t c) const { setPixelColor((unsigned)(y * Segment::maxWidth + x), c); } + inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) const { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } + inline void setPixelColorXY(int x, int y, CRGB c) const { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } - inline uint32_t getPixelColorXY(int x, int y) const { return getPixelColor(isMatrix ? y * Segment::maxWidth + x : x); } + inline uint32_t getPixelColorXY(int x, int y) const { return getPixelColor(isMatrix ? y * Segment::maxWidth + x : x); } // end 2D support diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index a72cfde29..ffaf53bb7 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -50,8 +50,8 @@ void WS2812FX::setUpMatrix() { customMappingSize = 0; // prevent use of mapping if anything goes wrong - if (customMappingTable) delete[] customMappingTable; - customMappingTable = new(std::nothrow) uint16_t[getLengthTotal()]; + if (customMappingTable) free(customMappingTable); + customMappingTable = static_cast(malloc(sizeof(uint16_t)*getLengthTotal())); if (customMappingTable) { customMappingSize = getLengthTotal(); @@ -68,7 +68,7 @@ void WS2812FX::setUpMatrix() { // content of the file is just raw JSON array in the form of [val1,val2,val3,...] // there are no other "key":"value" pairs in it // allowed values are: -1 (missing pixel/no LED attached), 0 (inactive/unused pixel), 1 (active/used pixel) - char fileName[32]; strcpy_P(fileName, PSTR("/2d-gaps.json")); // reduce flash footprint + char fileName[32]; strcpy_P(fileName, PSTR("/2d-gaps.json")); bool isFile = WLED_FS.exists(fileName); size_t gapSize = 0; int8_t *gapTable = nullptr; @@ -85,7 +85,7 @@ void WS2812FX::setUpMatrix() { JsonArray map = pDoc->as(); gapSize = map.size(); if (!map.isNull() && gapSize >= matrixSize) { // not an empty map - gapTable = new(std::nothrow) int8_t[gapSize]; + gapTable = static_cast(malloc(gapSize)); if (gapTable) for (size_t i = 0; i < gapSize; i++) { gapTable[i] = constrain(map[i], -1, 1); } @@ -113,7 +113,7 @@ void WS2812FX::setUpMatrix() { } // delete gap array as we no longer need it - if (gapTable) delete[] gapTable; + if (gapTable) free(gapTable); #ifdef WLED_DEBUG DEBUG_PRINT(F("Matrix ledmap:")); @@ -146,7 +146,7 @@ void WS2812FX::setUpMatrix() { #ifndef WLED_DISABLE_2D // XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element) -uint16_t IRAM_ATTR_YN Segment::XY(int x, int y) const +int IRAM_ATTR_YN Segment::XY(int x, int y) const { const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) const int vH = vHeight(); // segment height in logical pixels (is always >= 1) @@ -215,7 +215,7 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) const #ifdef WLED_USE_AA_PIXELS // anti-aliased version of setPixelColorXY() -void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) +void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) const { if (!isActive()) return; // not active if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index bd0ece168..433c828a7 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -94,7 +94,7 @@ Segment::Segment(const Segment &orig) { name = nullptr; data = nullptr; _dataLen = 0; - if (orig.name) { name = new(std::nothrow) char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } + if (orig.name) { name = static_cast(malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); } if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } } @@ -113,7 +113,7 @@ Segment& Segment::operator= (const Segment &orig) { //DEBUG_PRINTF_P(PSTR("-- Copying segment: %p -> %p\n"), &orig, this); if (this != &orig) { // clean destination - if (name) { delete[] name; name = nullptr; } + if (name) { free(name); name = nullptr; } stopTransition(); deallocateData(); // copy source @@ -122,7 +122,7 @@ Segment& Segment::operator= (const Segment &orig) { data = nullptr; _dataLen = 0; // copy source data - if (orig.name) { name = new(std::nothrow) char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } + if (orig.name) { name = static_cast(malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); } if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } } return *this; @@ -132,7 +132,7 @@ Segment& Segment::operator= (const Segment &orig) { Segment& Segment::operator= (Segment &&orig) noexcept { //DEBUG_PRINTF_P(PSTR("-- Moving segment: %p -> %p\n"), &orig, this); if (this != &orig) { - if (name) { delete[] name; name = nullptr; } // free old name + if (name) { free(name); name = nullptr; } // free old name stopTransition(); deallocateData(); // free old runtime data memcpy((void*)this, (void*)&orig, sizeof(Segment)); @@ -869,7 +869,7 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) #ifdef WLED_USE_AA_PIXELS // anti-aliased normalized version of setPixelColor() -void Segment::setPixelColor(float i, uint32_t col, bool aa) +void Segment::setPixelColor(float i, uint32_t col, bool aa) const { if (!isActive()) return; // not active int vStrip = int(i/10.0f); // hack to allow running on virtual strips (2D segment columns/rows) @@ -1413,7 +1413,7 @@ void WS2812FX::service() { #endif } -void IRAM_ATTR WS2812FX::setPixelColor(unsigned i, uint32_t col) { +void IRAM_ATTR WS2812FX::setPixelColor(unsigned i, uint32_t col) const { i = getMappedPixelIndex(i); if (i >= _length) return; BusManager::setPixelColor(i, col); @@ -1694,9 +1694,9 @@ void WS2812FX::fixInvalidSegments() { //true if all segments align with a bus, or if a segment covers the total length //irrelevant in 2D set-up -bool WS2812FX::checkSegmentAlignment() { +bool WS2812FX::checkSegmentAlignment() const { bool aligned = false; - for (segment &seg : _segments) { + for (const segment &seg : _segments) { for (unsigned b = 0; bgetStart() && seg.stop == bus->getStart() + bus->getLength()) aligned = true; @@ -1808,8 +1808,8 @@ bool WS2812FX::deserializeMap(unsigned n) { Segment::maxHeight = min(max(root[F("height")].as(), 1), 128); } - if (customMappingTable) delete[] customMappingTable; - customMappingTable = new(std::nothrow) uint16_t[getLengthTotal()]; + if (customMappingTable) free(customMappingTable); + customMappingTable = static_cast(malloc(sizeof(uint16_t)*getLengthTotal())); if (customMappingTable) { DEBUG_PRINT(F("Reading LED map from ")); DEBUG_PRINTLN(fileName); diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 48aad4d8c..d30230f23 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -161,11 +161,11 @@ class NeoGammaWLEDMethod { }; #define gamma32(c) NeoGammaWLEDMethod::Correct32(c) #define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c) -[[gnu::hot]] uint32_t color_blend(uint32_t c1, uint32_t c2 , uint8_t blend); +[[gnu::hot, gnu::pure]] uint32_t color_blend(uint32_t c1, uint32_t c2 , uint8_t blend); inline uint32_t color_blend16(uint32_t c1, uint32_t c2, uint16_t b) { return color_blend(c1, c2, b >> 8); }; -[[gnu::hot]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false); -[[gnu::hot]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); -[[gnu::hot]] uint32_t ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); +[[gnu::hot, gnu::pure]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false); +[[gnu::hot, gnu::pure]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); +[[gnu::hot, gnu::pure]] uint32_t ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(); inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } @@ -176,7 +176,7 @@ inline CHSV rgb2hsv(const CRGB c) { CHSV32 hsv; rgb2hsv((uint32_t((byte(c.r) << void colorKtoRGB(uint16_t kelvin, byte* rgb); void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO -void colorRGBtoXY(byte* rgb, float* xy); // only defined if huesync disabled TODO +void colorRGBtoXY(const byte* rgb, float* xy); // only defined if huesync disabled TODO void colorFromDecOrHexString(byte* rgb, const char* in); bool colorFromHexString(byte* rgb, const char* in); uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); @@ -467,10 +467,10 @@ void userLoop(); #include "soc/wdev_reg.h" #define HW_RND_REGISTER REG_READ(WDEV_RND_REG) #endif -int getNumVal(const String* req, uint16_t pos); +[[gnu::pure]] int getNumVal(const String* req, uint16_t pos); void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255); -bool getVal(JsonVariant elem, byte* val, byte minv=0, byte maxv=255); // getVal supports inc/decrementing and random ("X~Y(r|~[w][-][Z])" form) -bool getBoolVal(JsonVariant elem, bool dflt); +bool getVal(JsonVariant elem, byte* val, byte minv=0, byte maxv=255); // getVal supports inc/decrementing and random ("X~Y(r|[w]~[-][Z])" form) +[[gnu::pure]] bool getBoolVal(const JsonVariant &elem, bool dflt); bool updateVal(const char* req, const char* key, byte* val, byte minv=0, byte maxv=255); size_t printSetFormCheckbox(Print& settingsScript, const char* key, int val); size_t printSetFormValue(Print& settingsScript, const char* key, int val); @@ -478,7 +478,7 @@ size_t printSetFormValue(Print& settingsScript, const char* key, const char* val size_t printSetFormIndex(Print& settingsScript, const char* key, int index); size_t printSetClassElementHTML(Print& settingsScript, const char* key, const int index, const char* val); void prepareHostname(char* hostname); -bool isAsterisksOnly(const char* str, byte maxLen); +[[gnu::pure]] bool isAsterisksOnly(const char* str, byte maxLen); bool requestJSONBufferLock(uint8_t moduleID=255); void releaseJSONBufferLock(); uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen); @@ -491,8 +491,8 @@ uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest = 0, uint16_t hig uint8_t beatsin8_t(accum88 beats_per_minute, uint8_t lowest = 0, uint8_t highest = 255, uint32_t timebase = 0, uint8_t phase_offset = 0); um_data_t* simulateSound(uint8_t simulationId); void enumerateLedmaps(); -uint8_t get_random_wheel_index(uint8_t pos); -float mapf(float x, float in_min, float in_max, float out_min, float out_max); +[[gnu::hot]] uint8_t get_random_wheel_index(uint8_t pos); +[[gnu::hot, gnu::pure]] float mapf(float x, float in_min, float in_max, float out_min, float out_max); // fast (true) random numbers using hardware RNG, all functions return values in the range lowerlimit to upperlimit-1 // note: for true random numbers with high entropy, do not call faster than every 200ns (5MHz) diff --git a/wled00/file.cpp b/wled00/file.cpp index c48a300b7..46df8d380 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -176,7 +176,7 @@ static void writeSpace(size_t l) if (knownLargestSpace < l) knownLargestSpace = l; } -bool appendObjectToFile(const char* key, const JsonDocument* content, uint32_t s, uint32_t contentLen = 0) +static bool appendObjectToFile(const char* key, const JsonDocument* content, uint32_t s, uint32_t contentLen = 0) { #ifdef WLED_DEBUG_FS DEBUGFS_PRINTLN(F("Append")); diff --git a/wled00/json.cpp b/wled00/json.cpp index ad0e96ed9..c63da0854 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -68,7 +68,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) if (elem["n"]) { // name field exists if (seg.name) { //clear old name - delete[] seg.name; + free(seg.name); seg.name = nullptr; } @@ -77,7 +77,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) if (name != nullptr) len = strlen(name); if (len > 0) { if (len > WLED_MAX_SEGNAME_LEN) len = WLED_MAX_SEGNAME_LEN; - seg.name = new(std::nothrow) char[len+1]; + seg.name = static_cast(malloc(len+1)); if (seg.name) strlcpy(seg.name, name, WLED_MAX_SEGNAME_LEN+1); } else { // but is empty (already deleted above) @@ -86,7 +86,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) } else if (start != seg.start || stop != seg.stop) { // clearing or setting segment without name field if (seg.name) { - delete[] seg.name; + free(seg.name); seg.name = nullptr; } } diff --git a/wled00/mqtt.cpp b/wled00/mqtt.cpp index 38afeffe3..8cbe79093 100644 --- a/wled00/mqtt.cpp +++ b/wled00/mqtt.cpp @@ -68,8 +68,8 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp } if (index == 0) { // start (1st partial packet or the only packet) - if (payloadStr) delete[] payloadStr; // fail-safe: release buffer - payloadStr = new char[total+1]; // allocate new buffer + if (payloadStr) free(payloadStr); // fail-safe: release buffer + payloadStr = static_cast(malloc(total+1)); // allocate new buffer } if (payloadStr == nullptr) return; // buffer not allocated @@ -94,7 +94,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp } else { // Non-Wled Topic used here. Probably a usermod subscribed to this topic. UsermodManager::onMqttMessage(topic, payloadStr); - delete[] payloadStr; + free(payloadStr); payloadStr = nullptr; return; } @@ -124,7 +124,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp // topmost topic (just wled/MAC) parseMQTTBriPayload(payloadStr); } - delete[] payloadStr; + free(payloadStr); payloadStr = nullptr; } diff --git a/wled00/presets.cpp b/wled00/presets.cpp index 4aaa121f4..15eed3e46 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -76,8 +76,8 @@ static void doSaveState() { // clean up saveLedmap = -1; presetToSave = 0; - delete[] saveName; - delete[] quickLoad; + free(saveName); + free(quickLoad); saveName = nullptr; quickLoad = nullptr; playlistSave = false; @@ -216,8 +216,8 @@ void handlePresets() //called from handleSet(PS=) [network callback (sObj is empty), IR (irrational), deserializeState, UDP] and deserializeState() [network callback (filedoc!=nullptr)] void savePreset(byte index, const char* pname, JsonObject sObj) { - if (!saveName) saveName = new(std::nothrow) char[33]; - if (!quickLoad) quickLoad = new(std::nothrow) char[9]; + if (!saveName) saveName = static_cast(malloc(33)); + if (!quickLoad) quickLoad = static_cast(malloc(9)); if (!saveName || !quickLoad) return; if (index == 0 || (index > 250 && index < 255)) return; @@ -263,8 +263,8 @@ void savePreset(byte index, const char* pname, JsonObject sObj) presetsModifiedTime = toki.second(); //unix time updateFSInfo(); } - delete[] saveName; - delete[] quickLoad; + free(saveName); + free(quickLoad); saveName = nullptr; quickLoad = nullptr; } else { diff --git a/wled00/set.cpp b/wled00/set.cpp index 7a88699cd..27ac6b805 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -209,7 +209,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) // actual finalization is done in WLED::loop() (removing old busses and adding new) // this may happen even before this loop is finished so we do "doInitBusses" after the loop if (busConfigs[s] != nullptr) delete busConfigs[s]; - busConfigs[s] = new BusConfig(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, useGlobalLedBuffer, maPerLed, maMax); + busConfigs[s] = new(std::nothrow) BusConfig(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, useGlobalLedBuffer, maPerLed, maMax); busesChanged = true; } //doInitBusses = busesChanged; // we will do that below to ensure all input data is processed diff --git a/wled00/udp.cpp b/wled00/udp.cpp index e76ef6646..b7be591e7 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -206,7 +206,7 @@ void notify(byte callMode, bool followUp) notificationCount = followUp ? notificationCount + 1 : 0; } -void parseNotifyPacket(const uint8_t *udpIn) { +static void parseNotifyPacket(const uint8_t *udpIn) { //ignore notification if received within a second after sending a notification ourselves if (millis() - notificationSentTime < 1000) return; if (udpIn[1] > 199) return; //do not receive custom versions diff --git a/wled00/util.cpp b/wled00/util.cpp index e15247f9f..ed38ae180 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -73,7 +73,7 @@ bool getVal(JsonVariant elem, byte* val, byte vmin, byte vmax) { } -bool getBoolVal(JsonVariant elem, bool dflt) { +bool getBoolVal(const JsonVariant &elem, bool dflt) { if (elem.is() && elem.as()[0] == 't') { return !dflt; } else { @@ -538,7 +538,7 @@ void enumerateLedmaps() { #ifndef ESP8266 if (ledmapNames[i-1]) { //clear old name - delete[] ledmapNames[i-1]; + free(ledmapNames[i-1]); ledmapNames[i-1] = nullptr; } #endif @@ -556,7 +556,7 @@ void enumerateLedmaps() { const char *name = root["n"].as(); if (name != nullptr) len = strlen(name); if (len > 0 && len < 33) { - ledmapNames[i-1] = new char[len+1]; + ledmapNames[i-1] = static_cast(malloc(len+1)); if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], name, 33); } } @@ -564,7 +564,7 @@ void enumerateLedmaps() { char tmp[33]; snprintf_P(tmp, 32, s_ledmap_tmpl, i); len = strlen(tmp); - ledmapNames[i-1] = new char[len+1]; + ledmapNames[i-1] = static_cast(malloc(len+1)); if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], tmp, 33); } } From ed3ec66d33e9579f670f1539ade4501a982afaf3 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 9 Jan 2025 14:36:02 +0100 Subject: [PATCH 119/125] fix compile error "const" was missing in the function implementation --- wled00/FX_fcn.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 433c828a7..0c493f23a 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -696,7 +696,7 @@ uint16_t Segment::virtualLength() const { return vLength; } -void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) +void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) const { if (!isActive() || i < 0) return; // not active or invalid index #ifndef WLED_DISABLE_2D From cd52d7bcf6723f4b48e67e6176a78fb89b624295 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 9 Jan 2025 14:39:54 +0100 Subject: [PATCH 120/125] align some function declariations with their implementation This is purely a "clean code" thing, no impact on function - it helps to avoid confusion when reading the code. C++ allows declaration and implementation to use different variable names. --- wled00/FX.h | 6 +++--- wled00/button.cpp | 10 +++++----- wled00/fcn_declare.h | 6 +++--- wled00/lx_parser.cpp | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 289896d2d..2f42614ab 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -598,7 +598,7 @@ typedef struct Segment { // 1D strip [[gnu::hot]] uint16_t virtualLength() const; - [[gnu::hot]] void setPixelColor(int n, uint32_t c) const; // set relative pixel within segment with color + [[gnu::hot]] void setPixelColor(int i, uint32_t c) const; // set relative pixel within segment with color inline void setPixelColor(unsigned n, uint32_t c) const { setPixelColor(int(n), c); } inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) const { setPixelColor(n, RGBW32(r,g,b,w)); } inline void setPixelColor(int n, CRGB c) const { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } @@ -805,7 +805,7 @@ class WS2812FX { // 96 bytes resetSegments(), // marks all segments for reset makeAutoSegments(bool forceReset = false), // will create segments based on configured outputs fixInvalidSegments(), // fixes incorrect segment configuration - setPixelColor(unsigned n, uint32_t c) const, // paints absolute strip pixel with index n and color c + setPixelColor(unsigned i, uint32_t c) const, // paints absolute strip pixel with index n and color c show(), // initiates LED output setTargetFps(unsigned fps), setupEffectData(); // add default effects to the list; defined in FX.cpp @@ -870,7 +870,7 @@ class WS2812FX { // 96 bytes }; unsigned long now, timebase; - uint32_t getPixelColor(unsigned) const; + uint32_t getPixelColor(unsigned i) const; inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call diff --git a/wled00/button.cpp b/wled00/button.cpp index 4e063c120..5144f09f2 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -90,12 +90,12 @@ void doublePressAction(uint8_t b) #endif } -bool isButtonPressed(uint8_t i) +bool isButtonPressed(uint8_t b) { - if (btnPin[i]<0) return false; - unsigned pin = btnPin[i]; + if (btnPin[b]<0) return false; + unsigned pin = btnPin[b]; - switch (buttonType[i]) { + switch (buttonType[b]) { case BTN_TYPE_NONE: case BTN_TYPE_RESERVED: break; @@ -113,7 +113,7 @@ bool isButtonPressed(uint8_t i) #ifdef SOC_TOUCH_VERSION_2 //ESP32 S2 and S3 provide a function to check touch state (state is updated in interrupt) if (touchInterruptGetLastStatus(pin)) return true; #else - if (digitalPinToTouchChannel(btnPin[i]) >= 0 && touchRead(pin) <= touchThreshold) return true; + if (digitalPinToTouchChannel(btnPin[b]) >= 0 && touchRead(pin) <= touchThreshold) return true; #endif #endif break; diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index d30230f23..fcfa1bdcc 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -251,8 +251,8 @@ bool deserializeState(JsonObject root, byte callMode = CALL_MODE_DIRECT_CHANGE, void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true); void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true, bool selectedSegmentsOnly = false); void serializeInfo(JsonObject root); -void serializeModeNames(JsonArray root); -void serializeModeData(JsonArray root); +void serializeModeNames(JsonArray arr); +void serializeModeData(JsonArray fxdata); void serveJson(AsyncWebServerRequest* request); #ifdef WLED_ENABLE_JSONLIVE bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0); @@ -469,7 +469,7 @@ void userLoop(); #endif [[gnu::pure]] int getNumVal(const String* req, uint16_t pos); void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255); -bool getVal(JsonVariant elem, byte* val, byte minv=0, byte maxv=255); // getVal supports inc/decrementing and random ("X~Y(r|[w]~[-][Z])" form) +bool getVal(JsonVariant elem, byte* val, byte vmin=0, byte vmax=255); // getVal supports inc/decrementing and random ("X~Y(r|[w]~[-][Z])" form) [[gnu::pure]] bool getBoolVal(const JsonVariant &elem, bool dflt); bool updateVal(const char* req, const char* key, byte* val, byte minv=0, byte maxv=255); size_t printSetFormCheckbox(Print& settingsScript, const char* key, int val); diff --git a/wled00/lx_parser.cpp b/wled00/lx_parser.cpp index 7fd91ea02..1318686ce 100644 --- a/wled00/lx_parser.cpp +++ b/wled00/lx_parser.cpp @@ -22,7 +22,7 @@ bool parseLx(int lxValue, byte* rgbw) } else if ((lxValue >= 200000000) && (lxValue <= 201006500)) { // Loxone Lumitech ok = true; - float tmpBri = floor((lxValue - 200000000) / 10000); ; + float tmpBri = floor((lxValue - 200000000) / 10000); uint16_t ct = (lxValue - 200000000) - (((uint8_t)tmpBri) * 10000); tmpBri *= 2.55f; From 566c5057f961524376e45ec7dad7d5550b16d303 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 16 Jan 2025 17:56:11 +0100 Subject: [PATCH 121/125] optimizations as per reviewer recommendations * removed unneeded initializations in blur() and blur2D() * remove check for _t in progress() * code readability: if (_t) --> if(isInTransition()) * add `isInTransition()` checks to currentBri() and currentMode() * added missing `_transitionprogress = 0xFFFFU` in stopTransition() --- wled00/FX.h | 2 +- wled00/FX_2Dfcn.cpp | 4 ++-- wled00/FX_fcn.cpp | 19 ++++++++++--------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 2f42614ab..f54b3ba5c 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -590,7 +590,7 @@ typedef struct Segment { void restoreSegenv(const tmpsegd_t &tmpSegD); // restores segment data from buffer, if buffer is not transition buffer, changed values are copied to transition buffer #endif [[gnu::hot]] void updateTransitionProgress(); // set current progression of transition - inline uint16_t progress() const { return _t ? Segment::_transitionprogress : 0xFFFFU; } // transition progression between 0-65535 + inline uint16_t progress() const { return Segment::_transitionprogress; } // transition progression between 0-65535 [[gnu::hot]] uint8_t currentBri(bool useCct = false) const; // current segment brightness/CCT (blended while in transition) uint8_t currentMode() const; // currently active effect/mode (while in transition) [[gnu::hot]] uint32_t currentColor(uint8_t slot) const; // currently active segment color (blended while in transition) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index ffaf53bb7..0d38578a3 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -276,8 +276,8 @@ void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) { if (!isActive()) return; // not active const unsigned cols = vWidth(); const unsigned rows = vHeight(); - uint32_t lastnew = BLACK; - uint32_t last = BLACK; + uint32_t lastnew; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration + uint32_t last; if (blur_x) { const uint8_t keepx = smear ? 255 : 255 - blur_x; const uint8_t seepx = blur_x >> 1; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 0c493f23a..5ad2314df 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -296,6 +296,7 @@ void Segment::stopTransition() { delete _t; _t = nullptr; } + _transitionprogress = 0xFFFFU; // stop means stop - transition has ended } // transition progression between 0-65535 @@ -326,7 +327,7 @@ void Segment::swapSegenv(tmpsegd_t &tmpSeg) { tmpSeg._callT = call; tmpSeg._dataT = data; tmpSeg._dataLenT = _dataLen; - if (_t && &tmpSeg != &(_t->_segT)) { + if (isInTransition() && &tmpSeg != &(_t->_segT)) { // swap SEGENV with transitional data options = _t->_segT._optionsT; for (size_t i=0; i_segT._colorT[i]; @@ -349,7 +350,7 @@ void Segment::swapSegenv(tmpsegd_t &tmpSeg) { void Segment::restoreSegenv(const tmpsegd_t &tmpSeg) { //DEBUG_PRINTF_P(PSTR("-- Restoring temp seg: %p->(%p) [%d->%p]\n"), &tmpSeg, this, _dataLen, data); - if (_t && &(_t->_segT) != &tmpSeg) { + if (isInTransition() && &(_t->_segT) != &tmpSeg) { // update possibly changed variables to keep old effect running correctly _t->_segT._aux0T = aux0; _t->_segT._aux1T = aux1; @@ -379,7 +380,7 @@ void Segment::restoreSegenv(const tmpsegd_t &tmpSeg) { #endif uint8_t Segment::currentBri(bool useCct) const { - unsigned prog = progress(); + unsigned prog = isInTransition() ? progress() : 0xFFFFU; if (prog < 0xFFFFU) { // progress() < 0xFFFF implies that _t is a valid pointer unsigned curBri = (useCct ? cct : (on ? opacity : 0)) * prog; curBri += (useCct ? _t->_cctT : _t->_briT) * (0xFFFFU - prog); @@ -390,7 +391,7 @@ uint8_t Segment::currentBri(bool useCct) const { uint8_t Segment::currentMode() const { #ifndef WLED_DISABLE_MODE_BLEND - unsigned prog = progress(); + unsigned prog = isInTransition() ? progress() : 0xFFFFU; if (modeBlending && prog < 0xFFFFU) return _t->_modeT; // progress() < 0xFFFF implies that _t is a valid pointer #endif return mode; @@ -411,18 +412,18 @@ void Segment::beginDraw() { _vHeight = virtualHeight(); _vLength = virtualLength(); _segBri = currentBri(); + unsigned prog = isInTransition() ? progress() : 0xFFFFU; // transition progress; 0xFFFFU = no transition active // adjust gamma for effects for (unsigned i = 0; i < NUM_COLORS; i++) { #ifndef WLED_DISABLE_MODE_BLEND - uint32_t col = isInTransition() ? color_blend16(_t->_segT._colorT[i], colors[i], progress()) : colors[i]; + uint32_t col = isInTransition() ? color_blend16(_t->_segT._colorT[i], colors[i], prog) : colors[i]; #else - uint32_t col = isInTransition() ? color_blend16(_t->_colorT[i], colors[i], progress()) : colors[i]; + uint32_t col = isInTransition() ? color_blend16(_t->_colorT[i], colors[i], prog) : colors[i]; #endif _currentColors[i] = gamma32(col); } // load palette into _currentPalette loadPalette(_currentPalette, palette); - unsigned prog = progress(); if (strip.paletteFade && prog < 0xFFFFU) { // blend palettes // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) @@ -1134,8 +1135,8 @@ void Segment::blur(uint8_t blur_amount, bool smear) { uint8_t seep = blur_amount >> 1; unsigned vlength = vLength(); uint32_t carryover = BLACK; - uint32_t lastnew = BLACK; - uint32_t last = BLACK; + uint32_t lastnew; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration + uint32_t last; uint32_t curnew = BLACK; for (unsigned i = 0; i < vlength; i++) { uint32_t cur = getPixelColor(i); From aab29cb0abf2047fef8205910a17c9a2472aa452 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 19 Jan 2025 09:03:14 +0100 Subject: [PATCH 122/125] consolidated colorwaves and pride into one base function the two FX are almost identical in code with just a few lines difference. --- wled00/FX.cpp | 105 ++++++++++++++++++++------------------------------ 1 file changed, 41 insertions(+), 64 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 9fc9dbffb..2655d7daa 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1893,48 +1893,72 @@ uint16_t mode_lightning(void) { } static const char _data_FX_MODE_LIGHTNING[] PROGMEM = "Lightning@!,!,,,,,Overlay;!,!;!"; - -// Pride2015 -// Animated, ever-changing rainbows. -// by Mark Kriegsman: https://gist.github.com/kriegsman/964de772d64c502760e5 -uint16_t mode_pride_2015(void) { +// combined function from original pride and colorwaves +uint16_t mode_colorwaves_pride_base(bool isPride2015) { unsigned duration = 10 + SEGMENT.speed; unsigned sPseudotime = SEGENV.step; unsigned sHue16 = SEGENV.aux0; - uint8_t sat8 = beatsin88_t( 87, 220, 250); - uint8_t brightdepth = beatsin88_t( 341, 96, 224); - unsigned brightnessthetainc16 = beatsin88_t( 203, (25 * 256), (40 * 256)); + uint8_t sat8 = isPride2015 ? beatsin88_t(87, 220, 250) : 255; + unsigned brightdepth = beatsin88_t(341, 96, 224); + unsigned brightnessthetainc16 = beatsin88_t(203, (25 * 256), (40 * 256)); unsigned msmultiplier = beatsin88_t(147, 23, 60); - unsigned hue16 = sHue16;//gHue * 256; - unsigned hueinc16 = beatsin88_t(113, 1, 3000); + unsigned hue16 = sHue16; + unsigned hueinc16 = isPride2015 ? beatsin88_t(113, 1, 3000) : + beatsin88_t(113, 60, 300) * SEGMENT.intensity * 10 / 255; sPseudotime += duration * msmultiplier; - sHue16 += duration * beatsin88_t( 400, 5,9); + sHue16 += duration * beatsin88_t(400, 5, 9); unsigned brightnesstheta16 = sPseudotime; - for (unsigned i = 0 ; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { hue16 += hueinc16; - uint8_t hue8 = hue16 >> 8; + uint8_t hue8; - brightnesstheta16 += brightnessthetainc16; - unsigned b16 = sin16_t( brightnesstheta16 ) + 32768; + if (isPride2015) { + hue8 = hue16 >> 8; + } else { + unsigned h16_128 = hue16 >> 7; + hue8 = (h16_128 & 0x100) ? (255 - (h16_128 >> 1)) : (h16_128 >> 1); + } + brightnesstheta16 += brightnessthetainc16; + unsigned b16 = sin16_t(brightnesstheta16) + 32768; unsigned bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536; uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536; bri8 += (255 - brightdepth); - CRGB newcolor = CHSV(hue8, sat8, bri8); - SEGMENT.blendPixelColor(i, newcolor, 64); + if (isPride2015) { + CRGB newcolor = CHSV(hue8, sat8, bri8); + SEGMENT.blendPixelColor(i, newcolor, 64); + } else { + SEGMENT.blendPixelColor(i, SEGMENT.color_from_palette(hue8, false, PALETTE_SOLID_WRAP, 0, bri8), 128); + } } + SEGENV.step = sPseudotime; SEGENV.aux0 = sHue16; return FRAMETIME; } + +// Pride2015 +// Animated, ever-changing rainbows. +// by Mark Kriegsman: https://gist.github.com/kriegsman/964de772d64c502760e5 +uint16_t mode_pride_2015(void) { + return mode_colorwaves_pride_base(true); +} static const char _data_FX_MODE_PRIDE_2015[] PROGMEM = "Pride 2015@!;;"; +// ColorWavesWithPalettes by Mark Kriegsman: https://gist.github.com/kriegsman/8281905786e8b2632aeb +// This function draws color waves with an ever-changing, +// widely-varying set of parameters, using a color palette. +uint16_t mode_colorwaves() { + return mode_colorwaves_pride_base(false); +} +static const char _data_FX_MODE_COLORWAVES[] PROGMEM = "Colorwaves@!,Hue;!;!;;pal=26"; + //eight colored dots, weaving in and out of sync with each other uint16_t mode_juggle(void) { @@ -2141,53 +2165,6 @@ uint16_t mode_fire_2012() { } static const char _data_FX_MODE_FIRE_2012[] PROGMEM = "Fire 2012@Cooling,Spark rate,,2D Blur,Boost;;!;1;pal=35,sx=64,ix=160,m12=1,c2=128"; // bars - -// ColorWavesWithPalettes by Mark Kriegsman: https://gist.github.com/kriegsman/8281905786e8b2632aeb -// This function draws color waves with an ever-changing, -// widely-varying set of parameters, using a color palette. -uint16_t mode_colorwaves() { - unsigned duration = 10 + SEGMENT.speed; - unsigned sPseudotime = SEGENV.step; - unsigned sHue16 = SEGENV.aux0; - - unsigned brightdepth = beatsin88_t(341, 96, 224); - unsigned brightnessthetainc16 = beatsin88_t( 203, (25 * 256), (40 * 256)); - unsigned msmultiplier = beatsin88_t(147, 23, 60); - - unsigned hue16 = sHue16;//gHue * 256; - unsigned hueinc16 = beatsin88_t(113, 60, 300)*SEGMENT.intensity*10/255; // Use the Intensity Slider for the hues - - sPseudotime += duration * msmultiplier; - sHue16 += duration * beatsin88_t(400, 5, 9); - unsigned brightnesstheta16 = sPseudotime; - - for (unsigned i = 0 ; i < SEGLEN; i++) { - hue16 += hueinc16; - uint8_t hue8 = hue16 >> 8; - unsigned h16_128 = hue16 >> 7; - if ( h16_128 & 0x100) { - hue8 = 255 - (h16_128 >> 1); - } else { - hue8 = h16_128 >> 1; - } - - brightnesstheta16 += brightnessthetainc16; - unsigned b16 = sin16_t(brightnesstheta16) + 32768; - - unsigned bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536; - uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536; - bri8 += (255 - brightdepth); - - SEGMENT.blendPixelColor(i, SEGMENT.color_from_palette(hue8, false, PALETTE_SOLID_WRAP, 0, bri8), 128); // 50/50 mix - } - SEGENV.step = sPseudotime; - SEGENV.aux0 = sHue16; - - return FRAMETIME; -} -static const char _data_FX_MODE_COLORWAVES[] PROGMEM = "Colorwaves@!,Hue;!;!;;pal=26"; - - // colored stripes pulsing at a defined Beats-Per-Minute (BPM) uint16_t mode_bpm() { uint32_t stp = (strip.now / 20) & 0xFF; From a421a90e0ad2432ee2621c41ace63a2702b13452 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Mon, 20 Jan 2025 05:51:04 +0100 Subject: [PATCH 123/125] replacement for fastled sqrt16() (#4426) * added bitwise operation based sqrt16 - replacement for fastled, it is about 10% slower for numbers smaller 128 but faster for larger numbers. speed difference is irrelevant to WLED but it saves some flash. * updated to 32bit, improved for typical WLED use - making it 32bits allows for larger numbers - added another initial condition check for medium sized numbers - increased the "small number" optimization to larger numbers: the function is currently only used to calculate sqrt(x^2+y^2) which even for small segments is larger than the initially used 64, so optimizing for 1024 makes more sense, although the value is arbitrarily chosen --- wled00/FX.cpp | 6 +++--- wled00/FX_fcn.cpp | 4 ++-- wled00/fcn_declare.h | 1 + wled00/wled_math.cpp | 24 ++++++++++++++++++++++++ 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 2655d7daa..9fffe4d09 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5446,15 +5446,15 @@ uint16_t mode_2Dmetaballs(void) { // Metaballs by Stefan Petrick. Cannot have // and add them together with weightening unsigned dx = abs(x - x1); unsigned dy = abs(y - y1); - unsigned dist = 2 * sqrt16((dx * dx) + (dy * dy)); + unsigned dist = 2 * sqrt32_bw((dx * dx) + (dy * dy)); dx = abs(x - x2); dy = abs(y - y2); - dist += sqrt16((dx * dx) + (dy * dy)); + dist += sqrt32_bw((dx * dx) + (dy * dy)); dx = abs(x - x3); dy = abs(y - y3); - dist += sqrt16((dx * dx) + (dy * dy)); + dist += sqrt32_bw((dx * dx) + (dy * dy)); // inverse result int color = dist ? 1000 / dist : 255; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 5ad2314df..20d99519d 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -679,7 +679,7 @@ uint16_t Segment::virtualLength() const { vLen = max(vW,vH); // get the longest dimension break; case M12_pArc: - vLen = sqrt16(vH*vH + vW*vW); // use diagonal + vLen = sqrt32_bw(vH*vH + vW*vW); // use diagonal break; case M12_sPinwheel: vLen = getPinwheelLength(vW, vH); @@ -922,7 +922,7 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const break; } case M12_pArc: if (i >= vW && i >= vH) { - unsigned vI = sqrt16(i*i/2); + unsigned vI = sqrt32_bw(i*i/2); return getPixelColorXY(vI,vI); // use diagonal } case M12_pCorner: diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index fcfa1bdcc..c8b1f05ab 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -552,6 +552,7 @@ float asin_t(float x); template T atan_t(T x); float floor_t(float x); float fmod_t(float num, float denom); +uint32_t sqrt32_bw(uint32_t x); #define sin_t sin_approx #define cos_t cos_approx #define tan_t tan_approx diff --git a/wled00/wled_math.cpp b/wled00/wled_math.cpp index a8ec55400..43c593080 100644 --- a/wled00/wled_math.cpp +++ b/wled00/wled_math.cpp @@ -220,3 +220,27 @@ float fmod_t(float num, float denom) { #endif return res; } + +// bit-wise integer square root calculation (exact) +uint32_t sqrt32_bw(uint32_t x) { + uint32_t res = 0; + uint32_t bit; + uint32_t num = x; // use 32bit for faster calculation + + if(num < 1 << 10) bit = 1 << 10; // speed optimization for small numbers < 32^2 + else if (num < 1 << 20) bit = 1 << 20; // speed optimization for medium numbers < 1024^2 + else bit = 1 << 30; // start with highest power of 4 <= 2^32 + + while (bit > num) bit >>= 2; // reduce iterations + + while (bit != 0) { + if (num >= res + bit) { + num -= res + bit; + res = (res >> 1) + bit; + } else { + res >>= 1; + } + bit >>= 2; + } + return res; +} From 01a71132d5ee57d53cb083fd08907a4821bf3147 Mon Sep 17 00:00:00 2001 From: Ryan Ross Date: Sun, 19 Jan 2025 21:12:12 -0800 Subject: [PATCH 124/125] connect the seven segment reloaded usermod to BH1750 usermod (#4503) --- .../seven_segment_display_reloaded/readme.md | 6 ++--- .../usermod_seven_segment_reloaded.h | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/usermods/seven_segment_display_reloaded/readme.md b/usermods/seven_segment_display_reloaded/readme.md index a3398c3e5..94788df7e 100644 --- a/usermods/seven_segment_display_reloaded/readme.md +++ b/usermods/seven_segment_display_reloaded/readme.md @@ -9,7 +9,7 @@ Very loosely based on the existing usermod "seven segment display". Add the compile-time option `-D USERMOD_SSDR` to your `platformio.ini` (or `platformio_override.ini`) or use `#define USERMOD_SSDR` in `my_config.h`. -For the auto brightness option, the usermod SN_Photoresistor has to be installed as well. See SN_Photoresistor/readme.md for instructions. +For the auto brightness option, the usermod SN_Photoresistor or BH1750_V2 has to be installed as well. See SN_Photoresistor/readme.md or BH1750_V2/readme.md for instructions. ## Settings All settings can be controlled via the usermod settings page. @@ -28,10 +28,10 @@ Enables the blinking colon(s) if they are defined Shows the leading zero of the hour if it exists (i.e. shows `07` instead of `7`) ### enable-auto-brightness -Enables the auto brightness feature. Can be used only when the usermod SN_Photoresistor is installed. +Enables the auto brightness feature. Can be used only when the usermods SN_Photoresistor or BH1750_V2 are installed. ### auto-brightness-min / auto-brightness-max -The lux value calculated from usermod SN_Photoresistor will be mapped to the values defined here. +The lux value calculated from usermod SN_Photoresistor or BH1750_V2 will be mapped to the values defined here. The mapping, 0 - 1000 lux, will be mapped to auto-brightness-min and auto-brightness-max WLED current protection will override the calculated value if it is too high. diff --git a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h index 1436f8fc4..72f4c2dd6 100644 --- a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h +++ b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h @@ -97,6 +97,11 @@ private: #else void* ptr = nullptr; #endif +#ifdef USERMOD_BH1750 + Usermod_BH1750* bh1750 = nullptr; +#else + void* bh1750 = nullptr; +#endif void _overlaySevenSegmentDraw() { int displayMaskLen = static_cast(umSSDRDisplayMask.length()); @@ -387,6 +392,9 @@ public: #ifdef USERMOD_SN_PHOTORESISTOR ptr = (Usermod_SN_Photoresistor*) UsermodManager::lookup(USERMOD_ID_SN_PHOTORESISTOR); #endif + #ifdef USERMOD_BH1750 + bh1750 = (Usermod_BH1750*) UsermodManager::lookup(USERMOD_ID_BH1750); + #endif DEBUG_PRINTLN(F("Setup done")); } @@ -410,6 +418,20 @@ public: umSSDRLastRefresh = millis(); } #endif + #ifdef USERMOD_BH1750 + if(bri != 0 && umSSDREnableLDR && (millis() - umSSDRLastRefresh > umSSDRResfreshTime)) { + if (bh1750 != nullptr) { + float lux = bh1750->getIlluminance(); + uint16_t brightness = map(lux, 0, 1000, umSSDRBrightnessMin, umSSDRBrightnessMax); + if (bri != brightness) { + DEBUG_PRINTF("Adjusting brightness based on lux value: %.2f lx, new brightness: %d\n", lux, brightness); + bri = brightness; + stateUpdated(1); + } + } + umSSDRLastRefresh = millis(); + } + #endif } void handleOverlayDraw() { From 4951be6999316e74c75770fd160b86514a275ecb Mon Sep 17 00:00:00 2001 From: 5chubrakete <163564943+5chubrakete@users.noreply.github.com> Date: Mon, 20 Jan 2025 06:24:10 +0100 Subject: [PATCH 125/125] Added some date and time formatting options to scrolling text effect. (#4195) Updated to nonbreaking change and auto uppercasing according to review. --- wled00/FX.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 9fffe4d09..1459c4a0f 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -6094,13 +6094,23 @@ uint16_t mode_2Dscrollingtext(void) { if (!strlen(text)) { // fallback if empty segment name: display date and time sprintf_P(text, PSTR("%s %d, %d %d:%02d%s"), monthShortStr(month(localTime)), day(localTime), year(localTime), AmPmHour, minute(localTime), sec); } else { + if (text[0] == '#') for (auto &c : text) c = std::toupper(c); if (!strncmp_P(text,PSTR("#DATE"),5)) sprintf_P(text, zero?PSTR("%02d.%02d.%04d"):PSTR("%d.%d.%d"), day(localTime), month(localTime), year(localTime)); else if (!strncmp_P(text,PSTR("#DDMM"),5)) sprintf_P(text, zero?PSTR("%02d.%02d") :PSTR("%d.%d"), day(localTime), month(localTime)); else if (!strncmp_P(text,PSTR("#MMDD"),5)) sprintf_P(text, zero?PSTR("%02d/%02d") :PSTR("%d/%d"), month(localTime), day(localTime)); else if (!strncmp_P(text,PSTR("#TIME"),5)) sprintf_P(text, zero?PSTR("%02d:%02d%s") :PSTR("%2d:%02d%s"), AmPmHour, minute(localTime), sec); else if (!strncmp_P(text,PSTR("#HHMM"),5)) sprintf_P(text, zero?PSTR("%02d:%02d") :PSTR("%d:%02d"), AmPmHour, minute(localTime)); - else if (!strncmp_P(text,PSTR("#HH"),3)) sprintf_P(text, zero?PSTR("%02d") :PSTR("%d"), AmPmHour); - else if (!strncmp_P(text,PSTR("#MM"),3)) sprintf_P(text, zero?PSTR("%02d") :PSTR("%d"), minute(localTime)); + else if (!strncmp_P(text,PSTR("#HH"),3)) sprintf (text, zero? ("%02d") : ("%d"), AmPmHour); + else if (!strncmp_P(text,PSTR("#MM"),3)) sprintf (text, zero? ("%02d") : ("%d"), minute(localTime)); + else if (!strncmp_P(text,PSTR("#SS"),3)) sprintf (text, ("%02d") , second(localTime)); + else if (!strncmp_P(text,PSTR("#DD"),3)) sprintf (text, zero? ("%02d") : ("%d"), day(localTime)); + else if (!strncmp_P(text,PSTR("#DAY"),4)) sprintf (text, ("%s") , dayShortStr(day(localTime))); + else if (!strncmp_P(text,PSTR("#DDDD"),5)) sprintf (text, ("%s") , dayStr(day(localTime))); + else if (!strncmp_P(text,PSTR("#MO"),3)) sprintf (text, zero? ("%02d") : ("%d"), month(localTime)); + else if (!strncmp_P(text,PSTR("#MON"),4)) sprintf (text, ("%s") , monthShortStr(month(localTime))); + else if (!strncmp_P(text,PSTR("#MMMM"),5)) sprintf (text, ("%s") , monthStr(month(localTime))); + else if (!strncmp_P(text,PSTR("#YY"),3)) sprintf (text, ("%02d") , year(localTime)%100); + else if (!strncmp_P(text,PSTR("#YYYY"),5)) sprintf_P(text, zero?PSTR("%04d") : ("%d"), year(localTime)); } const int numberOfLetters = strlen(text);