From 0c8d9d5614a2e4f0013604cfcac8552cf1646279 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 3 Apr 2024 18:38:06 +0200 Subject: [PATCH 01/92] 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 1089a0b8bc..44c5e1549a 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 7aecd22716..7e14192930 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 f97268f9ba..989809d026 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 4a532abb7a..dc14317675 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 4ad2044ad8..1a50b6a3b6 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 f1b013e99b..9a843dbf46 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 fd1527a219..bde6c36c73 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 ad7e4b6701..eabd3e3837 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 35b99260a0..a58e1ceabf 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 02/92] 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 989809d026..bd5758e314 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 03/92] 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 46f6df2de6..0d86c8b2da 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 f712316b86..707479df17 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 c1264ebfb2..0000000000 --- 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 14341f5b99..e75e20cd2d 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 44c5e1549a..fd0bb297b5 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 7e14192930..a37e5d11ff 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 00196e38e2..4ef110e070 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 82e81a3877..02a76f69d1 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 c128f8c099..cdd1e82ff9 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 530777ab54..01c407f950 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 dddedd471d..2a5267825c 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -834,12 +834,7 @@

Defaults

Use Gamma value:

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 010ad3a53b..85893dfae4 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 269d8a7f69..2e00ec09c9 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 23c8d03c57..acfa3ac36e 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 67c4f60494..225102da64 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 a2e884c817..c5b9f262c0 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 100ace1663..d2f49144ab 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 eb78608516..8f64a10e96 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 5a9b8dcf5a..8e21343f30 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 4f2c14d474..a43c23e8d3 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 3915d9b0e5..e9c1fac46c 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 04/92] 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 e75e20cd2d..14341f5b99 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 05/92] Use transition style for palette and color change - as requested by @willmmiles & @tkadauke --- wled00/FX.h | 2 ++ wled00/FX_fcn.cpp | 61 +++++++++++++++++++++++++++++++---------------- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index cb9cafb238..acca0b20db 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 29302bfb2c..df06e56ad0 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(); + if (prog < 0xFFFFU) { #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 + 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 - 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 + { + // 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 06/92] 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 07/92] 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 e91bc10ba5..d7ef3707ac 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 4cb7079651..1f8a916168 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 08/92] 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 3ebf0c4483..af9b3218c4 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 09/92] Push variants --- wled00/FX.h | 28 +++++++++++--------- wled00/FX_2Dfcn.cpp | 61 ++++++++++++++++++++++++++++++++++++++----- wled00/FX_fcn.cpp | 30 ++++++++++++++++++--- wled00/data/index.htm | 28 +++++++++++--------- 4 files changed, 113 insertions(+), 34 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 136f8e18bf..3dc69e987a 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_COUNT 14 +#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 18 typedef enum mapping1D2D { diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index a62aa330ec..846c786758 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 4af961764c..583496de01 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 (i >= vL || i < 0 || isPixelClipped(i)) return 0; // handle clipping on 1D - if (reverse) i = virtualLength() - i - 1; + 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 d7ef3707ac..df867d3a58 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 10/92] 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 3dc69e987a..3abc811665 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 846c786758..493cb8963a 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 583496de01..dbbd24b7d3 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 11/92] 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 36d2038b94..7aec73cadf 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 12/92] 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 a5e007e89d..f825cecd3b 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 e16d3bf040a20d104c29fe870639d04e018a7b8d Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 13 Dec 2024 07:40:04 +0100 Subject: [PATCH 13/92] 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 3d8c27acac..e883c5fbd1 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 9bc5430c01..de386932a6 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 394da6783e..229d363b39 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 14/92] 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 e883c5fbd1..2e965a9af5 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 de386932a6..8787369c0a 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 60774d7010..5173842a68 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 229d363b39..b7f4ad7d6e 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 15/92] 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 8787369c0a..c3325ab98b 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 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 16/92] 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 fd2118fd06..295f2e9a80 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 17/92] 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 394b5df0d4..da7b0c4ba5 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 68169509da..8b34bbf0ca 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 5b5e4157e3dbf170416f9e7b18fe7398798d15b3 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Mon, 13 Jan 2025 11:12:14 +0000 Subject: [PATCH 18/92] Add esp32dev_v4 env --- platformio.ini | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 9a72b75d68..b7395dded4 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 19/92] Swap to tasmota/platform-espressif32 --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index b7395dded4..f3e11ce3c3 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 20/92] Remove platform_package override --- platformio.ini | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/platformio.ini b/platformio.ini index f3e11ce3c3..6fdaef3210 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 21/92] Remove platform_package override --- platformio.ini | 6 ------ 1 file changed, 6 deletions(-) diff --git a/platformio.ini b/platformio.ini index 6fdaef3210..73a76f05e0 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 22/92] 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 73a76f05e0..d6788a0f5e 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 23/92] 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 b7395dded4..63433ca7b6 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 24/92] Add env:esp8266_2m_tasmota --- platformio.ini | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index d6788a0f5e..b27525ab15 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 25/92] 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 b27525ab15..6db26b2e0c 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 26/92] Update comment --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 6db26b2e0c..9e221724c4 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 27/92] 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 9e221724c4..701c90d7b6 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 28/92] Use flash_mode = dio --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 701c90d7b6..59709eb1bb 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 29/92] 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 59709eb1bb..76783dba8f 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 bd00d012e22796a5a3580328f4cb3ace93de9230 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Tue, 14 Jan 2025 12:29:03 +0000 Subject: [PATCH 30/92] 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 59709eb1bb..069d9c5368 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 31/92] 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 48b8c152dc..33e14799c8 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 32/92] 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 33e14799c8..9b9b693f54 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 33/92] 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 0000000000..7f55f0bde2 --- /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 34/92] 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 7bf054581d..dba59a5ce8 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 35/92] 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 627fa6308a..3430e337d3 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 36/92] 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 37/92] 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 9b9b693f54..6d959fe121 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 38/92] 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 38e804ed97..1105077ca6 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 dbe70f2aac..305d5130d3 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 160eb48f07..9a12cdc215 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 1f978a39b4..8144b0cd41 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 533b120632..8adbd1ba5c 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 2a19cdfabc..62de534250 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 39/92] 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 305d5130d3..1bb1b250dd 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 40/92] 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 1bb1b250dd..f2f7d50337 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 37ebd41ec0..6f16523010 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 c8fb165ced..b285b6ee5d 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 8144b0cd41..a8748162ef 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 62de534250..5caa7159c4 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 41/92] 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 f2f7d50337..aa05c0113d 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 0000000000..8ed1d7ac33 --- /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 cb21e8c2ed..c3ada1a578 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 a8748162ef..5e15394790 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 42/92] 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 8ed1d7ac33..009112b737 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 43/92] fix merge error --- wled00/wled.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 5e15394790..7b49b8b5da 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 44/92] 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 009112b737..dcb04781cf 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 0000000000..ac67bc206d --- /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 7b49b8b5da..f9dfa55f30 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 8adbd1ba5c..b366e82a15 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 45/92] 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 dcb04781cf..28c3372589 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 ac67bc206d..b33c2a16ff 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 46/92] 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 f9dfa55f30..376cb6b28d 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 47/92] 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 1105077ca6..d3a8a0c6d3 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 34b9fc6cdb..16bd3f7f68 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -151,6 +151,18 @@

Realtime

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 48/92] 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 28c3372589..a150fa780b 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 b33c2a16ff..b96077c1bd 100644 --- a/wled00/dmx_input.h +++ b/wled00/dmx_input.h @@ -12,6 +12,10 @@ class DMXInput 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 49/92] 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 a150fa780b..d46e3bebbe 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 b96077c1bd..27425d0d46 100644 --- a/wled00/dmx_input.h +++ b/wled00/dmx_input.h @@ -17,6 +17,8 @@ class DMXInput 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 50/92] 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 d46e3bebbe..e60be42002 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 27425d0d46..1871176e97 100644 --- a/wled00/dmx_input.h +++ b/wled00/dmx_input.h @@ -20,6 +20,11 @@ class DMXInput /// @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 51/92] 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 e60be42002..b60f56cdd4 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 1871176e97..88b1755c17 100644 --- a/wled00/dmx_input.h +++ b/wled00/dmx_input.h @@ -28,6 +28,7 @@ class DMXInput /// 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 52/92] 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 b60f56cdd4..3cb9090b2d 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 88b1755c17..df6c31d6f4 100644 --- a/wled00/dmx_input.h +++ b/wled00/dmx_input.h @@ -29,6 +29,11 @@ class DMXInput 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 53/92] 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 3cb9090b2d..4294a3483d 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 54/92] 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 4294a3483d..0de1337e2a 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 df6c31d6f4..b762e59be2 100644 --- a/wled00/dmx_input.h +++ b/wled00/dmx_input.h @@ -30,10 +30,14 @@ class DMXInput 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 376cb6b28d..8b2b33762f 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 55/92] 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 0de1337e2a..f90515fd08 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 56/92] 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 16bd3f7f68..8e142dc9be 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -156,6 +156,7 @@

Realtime

DMX RX:
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 9a12cdc215..08a0180ad5 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 8b2b33762f..341036abf7 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 b366e82a15..b7f1ae710b 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 5caa7159c4..aa49de0228 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 57/92] 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 c5d3c6aa52..b9f29694cc 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 d3a8a0c6d3..4d8918db4d 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 bdd20beba1..e0e4916d33 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 aa05c0113d..efdd902ec1 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 bc26a0639e..98ba5dfd95 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 08a0180ad5..7ffd806538 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 341036abf7..e19361a18b 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 b7f1ae710b..af4a0a84d6 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 8582b49df9..99995debd5 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 e8cbb41ae5..327cff2c33 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 aa49de0228..b3ba4a0e81 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 58/92] 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 59/92] 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 efdd902ec1..1c883c15a8 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 c3ada1a578..831cc68f84 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 e19361a18b..27f74af026 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 60/92] 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 1c883c15a8..12095965e1 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 831cc68f84..db6c6b872a 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 27f74af026..44e2faf5aa 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 61/92] 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 44e2faf5aa..46d156e79f 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 62/92] 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 f90515fd08..4ccfbab76b 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 b762e59be2..7a6266c50e 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 @@ class DMXInput /// 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 @@ class DMXInput 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 63/92] 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 4ccfbab76b..902303bb77 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 64/92] 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 902303bb77..af0bb679dc 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 65/92] 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 7a6266c50e..0a02f2d113 100644 --- a/wled00/dmx_input.h +++ b/wled00/dmx_input.h @@ -56,7 +56,7 @@ class DMXInput 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 12095965e1..a868c78980 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 66/92] 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 b9f29694cc..c5d3c6aa52 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 4d8918db4d..d3a8a0c6d3 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 e0e4916d33..bdd20beba1 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 a868c78980..eace2145e6 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 98ba5dfd95..bc26a0639e 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 7ffd806538..08a0180ad5 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 46d156e79f..564f3e8c29 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 af4a0a84d6..b7f1ae710b 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 99995debd5..8582b49df9 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 327cff2c33..e8cbb41ae5 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 b3ba4a0e81..aa49de0228 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 67/92] 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 8e142dc9be..5502884886 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -152,10 +152,10 @@

Realtime

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 0a02f2d113..7845778d78 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 68/92] 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 5502884886..fabb2d5dd4 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -156,7 +156,7 @@

Wired DMX Input Pins

DMX RX: 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 69/92] 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 d3a8a0c6d3..443f16c73b 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 fabb2d5dd4..5502884886 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -156,7 +156,7 @@

Wired DMX Input Pins

DMX RX: 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 af0bb679dc..facafe17c5 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 70/92] 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 facafe17c5..ffdea63216 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 71/92] Add DMX Input support to builds --- platformio.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/platformio.ini b/platformio.ini index 9b9b693f54..18a2db8aa8 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 72/92] 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 bdd20beba1..b5eb20f814 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 5502884886..775f87a964 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -151,17 +151,17 @@

Realtime

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 ffdea63216..afbc9f0d0b 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 bc26a0639e..c16ed9332e 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 db6c6b872a..c7fa9daaea 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 aa49de0228..0ed32c04b9 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 73/92] 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 cb5b43e7b8..19b8c273a0 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 0000000000..43919f11b5 --- /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 0000000000..d5d930a819 --- /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 0000000000..ebaf433f16 --- /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 bdd20beba1..6a023fadc7 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 3430e337d3..15ded987df 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 c92dbb10ac79058c96d8bfd7863b83bc63249f78 Mon Sep 17 00:00:00 2001 From: Malachi Soord Date: Wed, 25 Dec 2024 21:31:47 +0100 Subject: [PATCH 74/92] Use proper devcontainers schema for vscode customisations --- .devcontainer/devcontainer.json | 43 +++++++++++++++++---------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 241acd79d3..5886418245 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -24,29 +24,30 @@ // risk to running the build directly on the host. // "runArgs": ["--privileged", "-v", "/dev/bus/usb:/dev/bus/usb", "--group-add", "dialout"], - // Set *default* container specific settings.json values on container create. - "settings": { - "terminal.integrated.shell.linux": "/bin/bash", - "python.pythonPath": "/usr/local/bin/python", - "python.linting.enabled": true, - "python.linting.pylintEnabled": true, - "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", - "python.formatting.blackPath": "/usr/local/py-utils/bin/black", - "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", - "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", - "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", - "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", - "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", - "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", - "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.shell.linux": "/bin/bash", + "python.pythonPath": "/usr/local/bin/python", + "python.linting.enabled": true, + "python.linting.pylintEnabled": true, + "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", + "python.formatting.blackPath": "/usr/local/py-utils/bin/black", + "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", + "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", + "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", + "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", + "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", + "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", + "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" + }, + "extensions": [ + "ms-python.python", + "platformio.platformio-ide" + ] + } }, - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "ms-python.python", - "platformio.platformio-ide" - ], - // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], 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 75/92] 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 57df58549b..f80ffb36de 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 f00e7147d2..2e4cdd515e 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 b9a62bb2ca..90520d3d2d 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 cc25d5f89d..f284a95334 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 36235ab9ea..7f11e2bf0f 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 1abcb52b93..4aaa121f45 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 76/92] 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 d9f1c00b94..9a918a0105 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 77/92] 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 a1e3629141..e9e8df6a2e 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 9a918a0105..e15247f9f0 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 e8cbb41ae5..96f2a705c9 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 78/92] 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 dc39df8164..4e7c832b84 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 f80ffb36de..7b06963b85 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 2e4cdd515e..a72cfde290 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 90520d3d2d..0a2b88acba 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 2c0ba41a9a..6e159a82b2 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 d90a661511..9aed013089 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 e64cf67588..f154a1aea0 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 e9e8df6a2e..48aad4d8c2 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 bc34672023..c48a300b7f 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 f284a95334..ad0e96ed90 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 8d44e634ec..12b698f445 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 08a0180ad5..a750072a68 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 8ba9a1a7ab..e76ef66464 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 ad9bb1413c..a0e59c531f 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 96f2a705c9..a17a7ec3af 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 0ed32c04b9..957ccebdaa 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 79/92] 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 7b06963b85..fa9ebe1c8f 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 0a2b88acba..b2f3035e2f 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 a750072a68..7a88699cdf 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 a17a7ec3af..8768b2b4e7 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 80/92] 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 b2f3035e2f..bd0ece1688 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 81/92] 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 4e7c832b84..9fc9dbffba 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 fa9ebe1c8f..289896d2df 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 a72cfde290..ffaf53bb7f 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 bd0ece1688..433c828a78 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 48aad4d8c2..d30230f23b 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 c48a300b7f..46df8d380c 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 ad0e96ed90..c63da08546 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 38afeffe31..8cbe790936 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 4aaa121f45..15eed3e466 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 7a88699cdf..27ac6b8050 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 e76ef66464..b7be591e77 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 e15247f9f0..ed38ae180b 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 82/92] 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 433c828a78..0c493f23a4 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 83/92] 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 289896d2df..2f42614ab1 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 4e063c120d..5144f09f29 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 d30230f23b..fcfa1bdcc0 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 7fd91ea029..1318686ceb 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 84/92] 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 2f42614ab1..f54b3ba5c7 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 ffaf53bb7f..0d38578a32 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 0c493f23a4..5ad2314df8 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 85/92] 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 9fc9dbffba..2655d7daab 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 86/92] 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 2655d7daab..9fffe4d094 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 5ad2314df8..20d99519d3 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 fcfa1bdcc0..c8b1f05ab7 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 a8ec55400f..43c593080e 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 87/92] 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 a3398c3e59..94788df7e4 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 1436f8fc4c..72f4c2dd6d 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 @@ class UsermodSSDR : public Usermod { #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 @@ class UsermodSSDR : public Usermod { #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 @@ class UsermodSSDR : public Usermod { 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 88/92] 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 9fffe4d094..1459c4a0fa 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); From 39512da74e0f52faa8730d4591d1a62fe497f253 Mon Sep 17 00:00:00 2001 From: Miroslav Lichvar Date: Tue, 21 Jan 2025 19:58:37 +0100 Subject: [PATCH 89/92] fix reproduction in game of life A typo caused broken counting of the most common color in neighbouring cells and blocked reproduction in some directions. --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 1459c4a0fa..0ada5f28e5 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5137,7 +5137,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: neighbors++; bool colorFound = false; int k; - for (k=0; k<9 && colorsCount[i].count != 0; k++) + for (k=0; k<9 && colorsCount[k].count != 0; k++) if (colorsCount[k].color == prevLeds[xy]) { colorsCount[k].count++; colorFound = true; From f2caf14d6a785923089b867f0920b05af88d09ae Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 22 Jan 2025 20:33:15 +0000 Subject: [PATCH 90/92] Fix missing hideDMXInput and hideNoDMXInput functions --- wled00/data/settings_sync.htm | 2 ++ wled00/dmx_input.cpp | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index 775f87a964..ca6c0fb59c 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -40,6 +40,8 @@ function getURL(path) { return (loc ? locproto + "//" + locip : "") + path; } + function hideDMXInput(){gId("dmxInput").style.display="none";} + function hideNoDMXInput(){gId("dmxInputOff").style.display="none";} diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index afbc9f0d0b..3197375f13 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -151,9 +151,9 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo const bool pinsAllocated = PinManager::allocateMultiplePins(pins, 3, PinOwner::DMX_INPUT); if (!pinsAllocated) { 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()); + DEBUG_PRINTF("rx in use by: %s\n", PinManager::getPinOwner(rxPin)); + DEBUG_PRINTF("tx in use by: %s\n", PinManager::getPinOwner(txPin)); + DEBUG_PRINTF("en in use by: %s\n", PinManager::getPinOwner(enPin)); return; } From e7c0ce794b76ec1286a920534be2ff222f4d78c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Fri, 24 Jan 2025 10:10:14 +0100 Subject: [PATCH 91/92] Merge conflict fix - updated blending style constants --- wled00/data/index.htm | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/wled00/data/index.htm b/wled00/data/index.htm index 6027eeb516..aa06b51227 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -128,7 +128,7 @@
- +

Color palette

@@ -273,20 +273,20 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + +

From a778ff01f613a92974ba04447adaa0e2490d7e6b Mon Sep 17 00:00:00 2001 From: "Christian W. Zuckschwerdt" Date: Fri, 24 Jan 2025 23:17:01 +0100 Subject: [PATCH 92/92] Nightly release - Add bin.gz artifacts --- .github/workflows/nightly.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 1387300582..a5c80f22d5 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -37,4 +37,5 @@ jobs: prerelease: true body: ${{ steps.changelog.outputs.changelog }} files: | - ./*.bin \ No newline at end of file + *.bin + *.bin.gz