From 5ed7b44d1586f43ff980b0ef0ee357d18ed5e54e Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 21 Oct 2025 20:52:21 +0200 Subject: [PATCH 01/15] WIP: change effect IDs from 8bit to 16bit it compiles. --- wled00/FX.cpp | 9 +++++--- wled00/FX.h | 25 +++++++++++----------- wled00/FX_fcn.cpp | 8 ++++---- wled00/e131.cpp | 2 +- wled00/fcn_declare.h | 11 +++++++--- wled00/json.cpp | 6 +++--- wled00/set.cpp | 4 ++-- wled00/udp.cpp | 4 ++-- wled00/util.cpp | 49 ++++++++++++++++++++++++++++++++++++-------- wled00/wled.h | 2 +- 10 files changed, 80 insertions(+), 40 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 138e9690be..19ae0135fb 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -11772,9 +11772,12 @@ static const char _data_RESERVED[] PROGMEM = "RSVD"; // add (or replace reserved) effect mode and data into vector // use id==255 to find unallocated gaps (with "Reserved" data string) // if vector size() is smaller than id (single) data is appended at the end (regardless of id) -void WS2812FX::addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name) { - if (id == 255) { // find empty slot - for (size_t i=1; i<_mode.size(); i++) if (_modeData[i] == _data_RESERVED) { id = i; break; } +void WS2812FX::addEffect(uint16_t id, mode_ptr mode_fn, const char *mode_name) { + if ((id == MODE_AUTO) || (id == MODE_AUTO_LEGACY)) { // find empty slot // WLEDMM need to make sure that slot 255 is always skipped + for (size_t i=1; i<_mode.size(); i++) { + if ((_modeData[i] == _data_RESERVED) && (id != MODE_AUTO) && (id == MODE_AUTO_LEGACY)) { + id = i; break; // style hint: break is a goto in disguise + } } } if (id < _mode.size()) { if (_modeData[id] != _data_RESERVED) return; // do not overwrite alerady added effect diff --git a/wled00/FX.h b/wled00/FX.h index 7559b09e31..19448ef104 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -371,6 +371,8 @@ bool strip_uses_global_leds(void) __attribute__((pure)); // WLEDMM implemented #define FX_MODE_PS1DSPRINGY 227 #define MODE_COUNT 228 +#define MODE_AUTO 65000 // magic value to add/remove effects at runtime +#define MODE_AUTO_LEGACY 255 // magic value #2, for legacy code still using addEffect(255, .... typedef enum mapping1D2D { M12_Pixels = 0, @@ -392,7 +394,7 @@ typedef struct Segment { uint8_t speed; uint8_t intensity; uint8_t palette; - uint8_t mode; + uint16_t mode; union { uint16_t options; //bit pattern: msb first: [transposed mirrorY reverseY] transitional (tbd) paused needspixelstate mirrored on reverse selected struct { @@ -481,7 +483,7 @@ typedef struct Segment { uint8_t _cctT; // temporary CCT CRGBPalette16 _palT; // temporary palette uint8_t _prevPaletteBlends; // number of previous palette blends (there are max 255 blends possible) - uint8_t _modeP; // previous mode/effect + uint16_t _modeP; // previous mode/effect //uint16_t _aux0, _aux1; // previous mode/effect runtime data //uint32_t _step, _call; // previous mode/effect runtime data //byte *_data; // previous mode/effect runtime data @@ -616,7 +618,7 @@ typedef struct Segment { void setCCT(uint16_t k); void setOpacity(uint8_t o); void setOption(uint8_t n, bool val); - void setMode(uint8_t fx, bool loadDefaults = false, bool sliderDefaultsOnly = false); + void setMode(uint16_t fx, bool loadDefaults = false, bool sliderDefaultsOnly = false); void setPalette(uint8_t pal); uint8_t differs(Segment& b) const; void refreshLightCapabilities(void); @@ -659,7 +661,7 @@ typedef struct Segment { } } - uint8_t currentMode(uint8_t modeNew); + uint16_t currentMode(uint16_t modeNew); uint32_t currentColor(uint8_t slot, uint32_t colorNew); CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal) const; void setCurrentPalette(void); @@ -862,10 +864,10 @@ class WS2812FX { // 96 bytes typedef uint16_t (*mode_ptr)(void); // pointer to mode function typedef void (*show_callback)(void); // pre show callback typedef struct ModeData { - uint8_t _id; // mode (effect) id + uint16_t _id; // mode (effect) id mode_ptr _fcn; // mode (effect) function const char *_data; // mode (effect) name and its UI control data - ModeData(uint8_t id, uint16_t (*fcn)(void), const char *data) : _id(id), _fcn(fcn), _data(data) {} + ModeData(uint16_t id, uint16_t (*fcn)(void), const char *data) : _id(id), _fcn(fcn), _data(data) {} } mode_data_t; static WS2812FX* instance; @@ -944,7 +946,7 @@ class WS2812FX { // 96 bytes finalizeInit(), waitUntilIdle(void), // WLEDMM service(void), - setMode(uint8_t segid, uint8_t m), + setMode(uint8_t segid, uint16_t m), setColor(uint8_t slot, uint32_t c), setCCT(uint16_t k), setBrightness(uint8_t b, bool direct = false), @@ -964,7 +966,7 @@ class WS2812FX { // 96 bytes 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)); } void fill(uint32_t c) { for (int i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline) - void addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name); // add effect to the list; defined in FX.cpp + void addEffect(uint16_t id, mode_ptr mode_fn, const char *mode_name); // add effect to the list; defined in FX.cpp void setupEffectData(void); // add default effects to the list; defined in FX.cpp // outsmart the compiler :) by correctly overloading @@ -1004,7 +1006,7 @@ class WS2812FX { // 96 bytes inline uint8_t getCurrSegmentId(void) const { return _segment_index; } inline uint8_t getMainSegmentId(void) const { return _mainSegment; } inline uint8_t getTargetFps() const { return _targetFps; } - inline uint8_t getModeCount() const { return _modeCount; } + inline uint16_t getModeCount() const { return _modeCount; } inline static constexpr uint8_t getMaxSegments(void) { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) inline static constexpr uint8_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT; } // will only return built-in palette count @@ -1031,8 +1033,7 @@ class WS2812FX { // 96 bytes inline uint32_t segColor(uint8_t i) const { return _colors_t[i]; } const char * - getModeData(uint8_t id = 0) const { return (id && id<_modeCount) ? _modeData[id] : PSTR("Solid"); } - + getModeData(uint16_t id = 0) const { return (id && id<_modeCount) ? _modeData[id] : PSTR("Solid"); } const char ** getModeDataSrc(void) { return &(_modeData[0]); } // vectors use arrays for underlying data @@ -1144,7 +1145,7 @@ class WS2812FX { // 96 bytes bool _triggered : 1; }; - uint8_t _modeCount; + uint16_t _modeCount; std::vector _mode; // SRAM footprint: 4 bytes per element std::vector _modeData; // mode (effect) name and its slider control data array diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 4941529f18..d68aada1f6 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -440,7 +440,7 @@ void Segment::startTransition(uint16_t dur) { uint8_t _briT = currentBri(on ? opacity : 0); uint8_t _cctT = currentBri(cct, true); CRGBPalette16 _palT = CRGBPalette16(DEFAULT_COLOR); loadPalette(_palT, palette); - uint8_t _modeP = mode; + auto _modeP = mode; uint32_t _colorT[NUM_COLORS]; for (size_t i=0; i32767U) ? newMode : (_t ? _t->_modeP : newMode); // change effect in the middle of transition } @@ -590,7 +590,7 @@ void Segment::setOption(uint8_t n, bool val) { if (!(n == SEG_OPTION_SELECTED || n == SEG_OPTION_RESET || n == SEG_OPTION_TRANSITIONAL)) stateChanged = true; // send UDP/WS broadcast } -void Segment::setMode(uint8_t fx, bool loadDefaults, bool sliderDefaultsOnly) { +void Segment::setMode(uint16_t fx, bool loadDefaults, bool sliderDefaultsOnly) { //WLEDMM: return to old setting if not explicitly set static int16_t oldMap = -1; static int16_t oldSim = -1; @@ -2144,7 +2144,7 @@ void WS2812FX::setTargetFps(uint8_t fps) { if (fps >= FPS_UNLIMITED) _frametime = 2; // WLEDMM unlimited mode } -void WS2812FX::setMode(uint8_t segid, uint8_t m) { +void WS2812FX::setMode(uint8_t segid, uint16_t m) { if (segid >= _segments.size()) return; if (m >= getModeCount()) m = getModeCount() - 1; diff --git a/wled00/e131.cpp b/wled00/e131.cpp index a6123e7a50..cfe2fe94ab 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -231,7 +231,7 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8 return; if (e131_data[dataOffset+1] < strip.getModeCount()) - if (e131_data[dataOffset+1] != seg.mode) seg.setMode( e131_data[dataOffset+1]); + if (e131_data[dataOffset+1] != seg.mode) seg.setMode( e131_data[dataOffset+1]); // WLEDMM Only one byte for mode, so DMX cannot control effects with id>255 if (e131_data[dataOffset+2] != seg.speed) seg.speed = e131_data[dataOffset+2]; if (e131_data[dataOffset+3] != seg.intensity) seg.intensity = e131_data[dataOffset+3]; if (e131_data[dataOffset+4] != seg.palette) seg.setPalette(e131_data[dataOffset+4]); diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 1a9247a2d9..768683af4a 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -386,6 +386,11 @@ 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); bool updateVal(const char* req, const char* key, byte* val, byte minv=0, byte maxv=255); + +void parseNumber16(const char* str, uint16_t* val, uint16_t minv=0, uint16_t maxv=255); // the real thing in 16bit +bool getVal16(JsonVariant elem, uint16_t* val, uint16_t vmin, uint16_t vmax=255); // same as above, with 2byte output buffer +bool updateVal16(const char* req, const char* key, uint16_t* val, uint16_t minv=0, uint16_t maxv=255); + void oappendUseDeflate(bool OnOff); // enable / disable string squeezing bool oappend(const char* txt); // append new c string to temp buffer efficiently bool oappendi(int i); // append new number to temp buffer efficiently @@ -395,9 +400,9 @@ void prepareHostname(char* hostname); bool isAsterisksOnly(const char* str, byte maxLen) __attribute__((pure)); bool requestJSONBufferLock(uint8_t module=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); -int16_t extractModeDefaults(uint8_t mode, const char *segVar); +uint16_t extractModeName(uint16_t mode, const char *src, char *dest, uint16_t maxLen); +uint16_t extractModeSlider(uint16_t mode, uint8_t slider, char *dest, uint16_t maxLen, uint8_t *var = nullptr); +int16_t extractModeDefaults(uint16_t mode, const char *segVar); void checkSettingsPIN(const char *pin); uint16_t __attribute__((pure)) crc16(const unsigned char* data_p, size_t length); // WLEDMM: added attribute pure diff --git a/wled00/json.cpp b/wled00/json.cpp index c51deefaf6..55fa7da79d 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -292,15 +292,15 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) if (seg.is2D() && (seg.map1D2D == M12_pArc || seg.map1D2D == M12_sCircle) && (reverse != seg.reverse || reverse_y != seg.reverse_y || mirror != seg.mirror || mirror_y != seg.mirror_y)) seg.markForBlank(); // clear entire segment (in case of Arc 1D to 2D expansion) WLEDMM: also Circle #endif - byte fx = seg.mode; - byte last = strip.getModeCount(); + auto fx = seg.mode; + auto last = strip.getModeCount(); // partial fix for #3605 if (!elem["fx"].isNull() && elem["fx"].is()) { const char *tmp = elem["fx"].as(); if (strlen(tmp) > 3 && (strchr(tmp,'r') || strchr(tmp,'~') != strrchr(tmp,'~'))) last = 0; // we have "X~Y(r|[w]~[-])" form } // end fix - if (getVal(elem["fx"], &fx, 0, last)) { //load effect ('r' random, '~' inc/dec, 0-255 exact value, 5~10r pick random between 5 & 10) + if (getVal16(elem["fx"], &fx, 0, last)) { //load effect ('r' random, '~' inc/dec, 0-255 exact value, 5~10r pick random between 5 & 10) if (!presetId && currentPlaylist>=0) unloadPlaylist(); if (fx != seg.mode) seg.setMode(fx, elem[F("fxdef")], elem[F("fxdef2")]); // WLEDMM fxdef2 added } diff --git a/wled00/set.cpp b/wled00/set.cpp index 560de9b653..21d3e36ab3 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -857,7 +857,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) uint32_t col1 = selseg.colors[1]; byte colIn[4] = {R(col0), G(col0), B(col0), W(col0)}; byte colInSec[4] = {R(col1), G(col1), B(col1), W(col1)}; - byte effectIn = selseg.mode; + auto effectIn = selseg.mode; byte speedIn = selseg.speed; byte intensityIn = selseg.intensity; byte paletteIn = selseg.palette; @@ -1049,7 +1049,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) bool fxModeChanged = false, speedChanged = false, intensityChanged = false, paletteChanged = false; bool custom1Changed = false, custom2Changed = false, custom3Changed = false, check1Changed = false, check2Changed = false, check3Changed = false; // set effect parameters - if (updateVal(req.c_str(), "FX=", &effectIn, 0, strip.getModeCount()-1)) { + if (updateVal16(req.c_str(), "FX=", &effectIn, 0, strip.getModeCount()-1)) { if (request != nullptr) unloadPlaylist(); // unload playlist if changing FX using web request fxModeChanged = true; } diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 3fc630e9a1..20d7943e34 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -37,7 +37,7 @@ void notify(byte callMode, bool followUp) udpOut[5] = B(col); udpOut[6] = nightlightActive; udpOut[7] = nightlightDelayMins; - udpOut[8] = mainseg.mode; + udpOut[8] = byte(mainseg.mode); // WLEDMM toDo: need workaround for 16bit mode numbers udpOut[9] = mainseg.speed; udpOut[10] = W(col); //compatibilityVersionByte: @@ -107,7 +107,7 @@ void notify(byte callMode, bool followUp) udpOut[8 +ofs] = selseg.offset & 0xFF; udpOut[9 +ofs] = selseg.options & 0x8F; //only take into account selected, mirrored, on, reversed, reverse_y (for 2D); ignore freeze, reset, transitional udpOut[10+ofs] = selseg.opacity; - udpOut[11+ofs] = selseg.mode; + udpOut[11+ofs] = byte(selseg.mode); // WLEDMM toDo: need workaround for 16bit mode numbers udpOut[12+ofs] = selseg.speed; udpOut[13+ofs] = selseg.intensity; udpOut[14+ofs] = selseg.palette; diff --git a/wled00/util.cpp b/wled00/util.cpp index c31b20b692..6951d086c1 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -10,11 +10,18 @@ int getNumVal(const String* req, uint16_t pos) } +// wrapper for parseNumber16 to suppport byte target buffer +void parseNumber(const char* str, byte* val, byte minv, byte maxv) { // wrapper for 8bit + uint16_t temp = 0; + parseNumber16(str, &temp, (uint16_t)minv, (uint16_t)maxv); + *val = constrain(temp, 0, 255); +} + //helper to get int value with in/decrementing support via ~ syntax -void parseNumber(const char* str, byte* val, byte minv, byte maxv) +void parseNumber16(const char* str, uint16_t* val, uint16_t minv, uint16_t maxv) // the real thing in 16bit { if (str == nullptr || str[0] == '\0') return; - if (str[0] == 'r') {*val = random8(minv,maxv?maxv:255); return;} // maxv for random cannot be 0 + if (str[0] == 'r') {*val = uint16_t(hw_random(minv,maxv?maxv:255)); return;} // maxv for random cannot be 0 bool wrap = false; if (str[0] == 'w' && strlen(str) > 1) {str++; wrap = true;} if (str[0] == '~') { @@ -38,18 +45,18 @@ void parseNumber(const char* str, byte* val, byte minv, byte maxv) } return; } else if (minv == maxv && minv == 0) { // limits "unset" i.e. both 0 - byte p1 = atoi(str); + uint16_t p1 = atoi(str); const char* str2 = strchr(str,'~'); // min/max range (for preset cycle, e.g. "1~5~") if (str2) { - byte p2 = atoi(++str2); // skip ~ + uint16_t p2 = atoi(++str2); // skip ~ if (p2 > 0) { while (isdigit(*(++str2))); // skip digits - parseNumber(str2, val, p1, p2); + parseNumber16(str2, val, p1, p2); return; } } } - *val = atoi(str); + *val = uint16_t(atoi(str)); } @@ -68,6 +75,21 @@ bool getVal(JsonVariant elem, byte* val, byte vmin, byte vmax) { return false; //key does not exist } +bool getVal16(JsonVariant elem, uint16_t* val, uint16_t vmin, uint16_t vmax) { // same as above, with 2byte output buffer + if (elem.is()) { + if (elem < 0) return false; //ignore e.g. {"ps":-1} + *val = elem; + return true; + } else if (elem.is()) { + const char* str = elem; + size_t len = strnlen(str, 12); + if (len == 0 || len > 10) return false; + parseNumber16(str, val, vmin, vmax); + return true; + } + return false; //key does not exist +} + bool updateVal(const char* req, const char* key, byte* val, byte minv, byte maxv) { @@ -78,6 +100,15 @@ bool updateVal(const char* req, const char* key, byte* val, byte minv, byte maxv return true; } +bool updateVal16(const char* req, const char* key, uint16_t* val, uint16_t minv, uint16_t maxv) +{ + const char *v = strstr(req, key); + if (v) v += strlen(key); + else return false; + parseNumber16(v, val, minv, maxv); + return true; +} + //append a numeric setting to string buffer void sappend(char stype, const char* key, int val) @@ -248,7 +279,7 @@ void releaseJSONBufferLock() // extracts effect mode (or palette) name from names serialized string // caller must provide large enough buffer for name (including SR extensions)! -uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen) +uint16_t extractModeName(uint16_t mode, const char *src, char *dest, uint16_t maxLen) { if (src == JSON_mode_names || src == nullptr) { if (mode < strip.getModeCount()) { @@ -305,7 +336,7 @@ uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLe // extracts effect slider data (1st group after @) -uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxLen, uint8_t *var) +uint16_t extractModeSlider(uint16_t mode, uint8_t slider, char *dest, uint16_t maxLen, uint8_t *var) { dest[0] = '\0'; // start by clearing buffer @@ -380,7 +411,7 @@ uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxL // extracts mode parameter defaults from last section of mode data (e.g. "Juggle@!,Trail;!,!,;!;sx=16,ix=240,1d") -int16_t extractModeDefaults(uint8_t mode, const char *segVar) +int16_t extractModeDefaults(uint16_t mode, const char *segVar) { if (mode < strip.getModeCount()) { char lineBuffer[256] = { '\0' }; diff --git a/wled00/wled.h b/wled00/wled.h index 442473dae8..11ad4b245d 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -619,7 +619,7 @@ WLED_GLOBAL byte notificationSentCallMode _INIT(CALL_MODE_INIT); WLED_GLOBAL uint8_t notificationCount _INIT(0); // effects -WLED_GLOBAL byte effectCurrent _INIT(0); +WLED_GLOBAL uint16_t effectCurrent _INIT(0); WLED_GLOBAL byte effectSpeed _INIT(128); WLED_GLOBAL byte effectIntensity _INIT(128); WLED_GLOBAL byte effectPalette _INIT(0); From 82910a684e35c9f6e6a6965d8a4a6e9c606b67a9 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 21 Oct 2025 21:32:09 +0200 Subject: [PATCH 02/15] small bugfix in addEffect --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 19ae0135fb..44da425fa5 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -11775,7 +11775,7 @@ static const char _data_RESERVED[] PROGMEM = "RSVD"; void WS2812FX::addEffect(uint16_t id, mode_ptr mode_fn, const char *mode_name) { if ((id == MODE_AUTO) || (id == MODE_AUTO_LEGACY)) { // find empty slot // WLEDMM need to make sure that slot 255 is always skipped for (size_t i=1; i<_mode.size(); i++) { - if ((_modeData[i] == _data_RESERVED) && (id != MODE_AUTO) && (id == MODE_AUTO_LEGACY)) { + if ((_modeData[i] == _data_RESERVED) && (i != MODE_AUTO) && (i != MODE_AUTO_LEGACY)) { id = i; break; // style hint: break is a goto in disguise } } } From 894052f20e822f6fa96513e2f922186ba92e6bf8 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 21 Oct 2025 21:55:49 +0200 Subject: [PATCH 03/15] bugfix for parseNumber *val is an InOut parameter --- wled00/util.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/util.cpp b/wled00/util.cpp index 6951d086c1..c9bb270c76 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -11,8 +11,8 @@ int getNumVal(const String* req, uint16_t pos) // wrapper for parseNumber16 to suppport byte target buffer -void parseNumber(const char* str, byte* val, byte minv, byte maxv) { // wrapper for 8bit - uint16_t temp = 0; +void parseNumber(const char* str, byte* val, byte minv, byte maxv) { // wrapper for 8bit buffer + uint16_t temp = *val; parseNumber16(str, &temp, (uint16_t)minv, (uint16_t)maxv); *val = constrain(temp, 0, 255); } From 68aad22044d4955d9278501a10afce0e2653fe82 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Wed, 22 Oct 2025 12:50:28 +0200 Subject: [PATCH 04/15] parseNumber 8bit "r" (random) improvement the problem was that "r" with min=max=0 did not behave proprly in 16bit. This fix makes it work in 16bit, and tries to keep the 8bit variant working, too. --- wled00/util.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wled00/util.cpp b/wled00/util.cpp index c9bb270c76..c1fdd7473c 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -15,13 +15,14 @@ void parseNumber(const char* str, byte* val, byte minv, byte maxv) { // wrapper uint16_t temp = *val; parseNumber16(str, &temp, (uint16_t)minv, (uint16_t)maxv); *val = constrain(temp, 0, 255); + if ((temp > 255) && (minv == 0) && (maxv == 0)) *val = temp & 0x00FF; // 8bit support for "r" with min=max=0 } //helper to get int value with in/decrementing support via ~ syntax void parseNumber16(const char* str, uint16_t* val, uint16_t minv, uint16_t maxv) // the real thing in 16bit { if (str == nullptr || str[0] == '\0') return; - if (str[0] == 'r') {*val = uint16_t(hw_random(minv,maxv?maxv:255)); return;} // maxv for random cannot be 0 + if (str[0] == 'r') {*val = uint16_t(hw_random(minv,maxv?maxv:65535)); return;} // maxv for random cannot be 0, use full range bool wrap = false; if (str[0] == 'w' && strlen(str) > 1) {str++; wrap = true;} if (str[0] == '~') { From e7831a4d44d706e3f518ea20e151662cd37d79fe Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Wed, 22 Oct 2025 13:03:30 +0200 Subject: [PATCH 05/15] addEffect: protect legacy "auto" slot 255 --- wled00/FX.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 44da425fa5..dff1100cba 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -11779,14 +11779,14 @@ void WS2812FX::addEffect(uint16_t id, mode_ptr mode_fn, const char *mode_name) { id = i; break; // style hint: break is a goto in disguise } } } - if (id < _mode.size()) { + if ((id < _mode.size()) && (id != MODE_AUTO) && (id != MODE_AUTO_LEGACY)) { // do not overwrite legacy "auto" slot 255 if (_modeData[id] != _data_RESERVED) return; // do not overwrite alerady added effect _mode[id] = mode_fn; _modeData[id] = mode_name; } else { _mode.push_back(mode_fn); _modeData.push_back(mode_name); - if (_modeCount < _mode.size()) _modeCount++; + if (_modeCount < _mode.size()) _modeCount++; // toDo: check if this works when _modeCount goes from 254 (max 8bit) to 256 (first 16bit) } } From 8cad7aa95970c8f47c545652712120475781a305 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Wed, 22 Oct 2025 13:25:24 +0200 Subject: [PATCH 06/15] extractModeName safety code when mode>255 this prevents negative numbers in palette names, if mode>255 --- wled00/util.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wled00/util.cpp b/wled00/util.cpp index c1fdd7473c..7c644109f9 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -300,7 +300,8 @@ uint16_t extractModeName(uint16_t mode, const char *src, char *dest, uint16_t ma } if (src == JSON_palette_names && mode > (GRADIENT_PALETTE_COUNT + 13)) { - snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), 255-mode); + if (mode <= 255) snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), 255-mode); // hmmm ... this function is abused to generate palette names + else snprintf_P(dest, maxLen, PSTR("~ Custom +%u ~"), mode - 255); // fallback for mode > 255 ... this should not happen for palettes, better safe than sorry dest[maxLen-1] = '\0'; return strlen(dest); } From 03bb24b1c447d9e072330af71c2d6c23891bafc8 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:09:20 +0200 Subject: [PATCH 07/15] better workaround for UDP still no solution. But now, every effect >255 will lead to udpOut[8]=255, causing less confusion. --- wled00/udp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 20d7943e34..3c6a50bb7d 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -37,7 +37,7 @@ void notify(byte callMode, bool followUp) udpOut[5] = B(col); udpOut[6] = nightlightActive; udpOut[7] = nightlightDelayMins; - udpOut[8] = byte(mainseg.mode); // WLEDMM toDo: need workaround for 16bit mode numbers + udpOut[8] = min(mainseg.mode, uint16_t(255)); // WLEDMM toDo: need workaround for 16bit mode numbers - critical as this affects webUI udpOut[9] = mainseg.speed; udpOut[10] = W(col); //compatibilityVersionByte: @@ -107,7 +107,7 @@ void notify(byte callMode, bool followUp) udpOut[8 +ofs] = selseg.offset & 0xFF; udpOut[9 +ofs] = selseg.options & 0x8F; //only take into account selected, mirrored, on, reversed, reverse_y (for 2D); ignore freeze, reset, transitional udpOut[10+ofs] = selseg.opacity; - udpOut[11+ofs] = byte(selseg.mode); // WLEDMM toDo: need workaround for 16bit mode numbers + udpOut[11+ofs] = min(selseg.mode, uint16_t(255)); // WLEDMM toDo: need workaround for 16bit mode numbers udpOut[12+ofs] = selseg.speed; udpOut[13+ofs] = selseg.intensity; udpOut[14+ofs] = selseg.palette; From 733c798a5feba260e5fd66ee763fd0d4ddcc223a Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:11:33 +0200 Subject: [PATCH 08/15] more changes for 16bit mode number compatibility I think there will be more, especially inside usermods. --- usermods/ST7789_display/ST7789_display.h | 2 +- usermods/usermod_v2_auto_save/usermod_v2_auto_save.h | 4 ++-- .../usermod_v2_rotary_encoder_ui_ALT.h | 4 ++-- wled00/fcn_declare.h | 2 +- wled00/ir.cpp | 4 ++-- wled00/presets.cpp | 2 +- wled00/remote.cpp | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/usermods/ST7789_display/ST7789_display.h b/usermods/ST7789_display/ST7789_display.h index 93700c9182..d90ed9d7b7 100644 --- a/usermods/ST7789_display/ST7789_display.h +++ b/usermods/ST7789_display/ST7789_display.h @@ -65,7 +65,7 @@ class St7789DisplayUsermod : public Usermod { String knownSsid = ""; IPAddress knownIp; uint8_t knownBrightness = 0; - uint8_t knownMode = 0; + uint16_t knownMode = 0; uint8_t knownPalette = 0; uint8_t knownEffectSpeed = 0; uint8_t knownEffectIntensity = 0; diff --git a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h index 1dd0a69a54..408393d7fa 100644 --- a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h +++ b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h @@ -57,7 +57,7 @@ class AutoSaveUsermod : public Usermod { uint8_t knownBrightness = 0; uint8_t knownEffectSpeed = 0; uint8_t knownEffectIntensity = 0; - uint8_t knownMode = 0; + uint16_t knownMode = 0; uint8_t knownPalette = 0; #ifdef USERMOD_FOUR_LINE_DISPLAY @@ -133,7 +133,7 @@ class AutoSaveUsermod : public Usermod { if (!autoSaveAfterSec || !enabled || strip.isUpdating() || currentPreset>0) return; // setting 0 as autosave seconds disables autosave unsigned long now = millis(); - uint8_t currentMode = strip.getMainSegment().mode; + uint16_t currentMode = strip.getMainSegment().mode; uint8_t currentPalette = strip.getMainSegment().palette; unsigned long wouldAutoSaveAfter = now + autoSaveAfterSec*1000; diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h index 66a1401a1d..d5ac7b72c5 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h @@ -151,9 +151,9 @@ class RotaryEncoderUIUsermod : public Usermod { unsigned char Enc_A_prev = 0; bool currentEffectAndPaletteInitialized = false; - uint8_t effectCurrentIndex = 0; + uint16_t effectCurrentIndex = 0; uint8_t effectPaletteIndex = 0; - uint8_t knownMode = 0; + uint16_t knownMode = 0; uint8_t knownPalette = 0; uint8_t currentCCT = 128; diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 768683af4a..a02f8d64fa 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -245,7 +245,7 @@ bool presetsActionPending(void); // WLEDMM true if presetToApply, presetToSave, void initPresetsFile(); void handlePresets(); bool applyPreset(byte index, byte callMode = CALL_MODE_DIRECT_CHANGE); -void applyPresetWithFallback(uint8_t presetID, uint8_t callMode, uint8_t effectID = 0, uint8_t paletteID = 0); +void applyPresetWithFallback(uint8_t presetID, uint8_t callMode, uint16_t effectID = 0, uint8_t paletteID = 0); inline bool applyTemporaryPreset() {return applyPreset(255);}; void savePreset(byte index, const char* pname = nullptr, JsonObject saveobj = JsonObject()); inline void saveTemporaryPreset() {savePreset(255);}; diff --git a/wled00/ir.cpp b/wled00/ir.cpp index bfb7926433..c8b24d263d 100644 --- a/wled00/ir.cpp +++ b/wled00/ir.cpp @@ -71,7 +71,7 @@ void decBrightness() } } -void presetFallback(uint8_t presetID, uint8_t effectID, uint8_t paletteID) +void presetFallback(uint8_t presetID, uint16_t effectID, uint8_t paletteID) { //USER_PRINTF("presetFallback1 %d %d %d\n", presetID, effectID, paletteID); //applyPreset(presetID, CALL_MODE_BUTTON_PRESET); @@ -91,7 +91,7 @@ byte relativeChange(byte property, int8_t amount, byte lowerBoundary, byte highe return (byte)constrain(new_val, 0, 255); } -void changeEffect(uint8_t fx) +void changeEffect(uint16_t fx) { if (irApplyToAllSelected) { for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) { diff --git a/wled00/presets.cpp b/wled00/presets.cpp index 3e5b4142f5..3446d90158 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -135,7 +135,7 @@ bool applyPreset(byte index, byte callMode) } // apply preset or fallback to a effect and palette if it doesn't exist -void applyPresetWithFallback(uint8_t index, uint8_t callMode, uint8_t effectID, uint8_t paletteID) +void applyPresetWithFallback(uint8_t index, uint8_t callMode, uint16_t effectID, uint8_t paletteID) { applyPreset(index, callMode); //these two will be overwritten if preset exists in handlePresets() diff --git a/wled00/remote.cpp b/wled00/remote.cpp index 64c668c579..c0abe1f8b6 100644 --- a/wled00/remote.cpp +++ b/wled00/remote.cpp @@ -119,7 +119,7 @@ static void setOff() { } } -static void presetWithFallback(uint8_t presetID, uint8_t effectID, uint8_t paletteID) { +static void presetWithFallback(uint8_t presetID, uint16_t effectID, uint8_t paletteID) { resetNightMode(); unloadPlaylist(); applyPresetWithFallback(presetID, CALL_MODE_BUTTON_PRESET, effectID, paletteID); From 1bf6be44dee644fe9aa36e20475e4dcd9e62b7c2 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Wed, 22 Oct 2025 22:51:27 +0200 Subject: [PATCH 09/15] addEffect improvements * addEffect: explicitly add entry 255 when expanding mode vector (tested and works) * if the target slot is already in use, search for a new free slot (instead of failing silently) * add some debug statements, to trace where effects are really "landing" * add some more room (MODE_COUNT) when both ANIMARTRIX and ParticleFX are enabled --- wled00/FX.cpp | 25 ++++++++++++++++++++++++- wled00/FX.h | 7 ++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index dff1100cba..db1760d9ae 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -11773,21 +11773,44 @@ static const char _data_RESERVED[] PROGMEM = "RSVD"; // use id==255 to find unallocated gaps (with "Reserved" data string) // if vector size() is smaller than id (single) data is appended at the end (regardless of id) void WS2812FX::addEffect(uint16_t id, mode_ptr mode_fn, const char *mode_name) { + if ((id < _mode.size()) && (_modeData[id] != _data_RESERVED)) { + DEBUG_PRINTF("addEffect(%d) -> ", id); + DEBUG_PRINTF(" already in use, finding a new slot for -> %s\n", mode_name); + id = MODE_AUTO; + } + if ((id >= _mode.size()) && (id != MODE_AUTO) && (id != MODE_AUTO_LEGACY)) { + DEBUG_PRINTF("!addEffect(%d) -> slot not existing, adding new slot\n", id); + } + if ((id == MODE_AUTO) || (id == MODE_AUTO_LEGACY)) { // find empty slot // WLEDMM need to make sure that slot 255 is always skipped for (size_t i=1; i<_mode.size(); i++) { if ((_modeData[i] == _data_RESERVED) && (i != MODE_AUTO) && (i != MODE_AUTO_LEGACY)) { id = i; break; // style hint: break is a goto in disguise } } } + if ((id < _mode.size()) && (id != MODE_AUTO) && (id != MODE_AUTO_LEGACY)) { // do not overwrite legacy "auto" slot 255 - if (_modeData[id] != _data_RESERVED) return; // do not overwrite alerady added effect + if (_modeData[id] != _data_RESERVED) { // do not overwrite alerady added effect + USER_PRINTF("!addEffect(%d) failed - existing effect cannot be replaced. <=> %s\n", id, mode_name); + return; + } // all good, insert into existing RSVD slot _mode[id] = mode_fn; _modeData[id] = mode_name; } else { + if (_modeCount == MODE_AUTO_LEGACY && ((id == MODE_AUTO) || (id == MODE_AUTO_LEGACY))) { + // add dummy entry to protect slot 255 = MODE_AUTO_LEGACY + DEBUG_PRINTF("+addEffect(%d) creating dummy effect 255, modecount = %d.\n", id, _modeCount); + _mode.push_back(&mode_static); + _modeData.push_back(_data_RESERVED); + if (_modeCount < _mode.size()) _modeCount++; + } + // new slot needed -> append to end of vector _mode.push_back(mode_fn); _modeData.push_back(mode_name); + id = _modeCount; if (_modeCount < _mode.size()) _modeCount++; // toDo: check if this works when _modeCount goes from 254 (max 8bit) to 256 (first 16bit) } + DEBUG_PRINTF("addEffect(%d) => %s\n", id, mode_name); } void WS2812FX::setupEffectData() { diff --git a/wled00/FX.h b/wled00/FX.h index 19448ef104..cf1273ca22 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -370,7 +370,12 @@ bool strip_uses_global_leds(void) __attribute__((pure)); // WLEDMM implemented #define FX_MODE_PS1DSONICBOOM 226 #define FX_MODE_PS1DSPRINGY 227 -#define MODE_COUNT 228 +#if defined(USERMOD_ANIMARTRIX) && !defined(WLED_DISABLE_PARTICLESYSTEM2D) +#define MODE_COUNT 275 // keep some room for animartix effects +#else +#define MODE_COUNT 228 // default including ParticleFX +#endif + #define MODE_AUTO 65000 // magic value to add/remove effects at runtime #define MODE_AUTO_LEGACY 255 // magic value #2, for legacy code still using addEffect(255, .... From ea1b97f193d9e7711ba75a7680410c8b87e7c724 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Wed, 22 Oct 2025 23:12:28 +0200 Subject: [PATCH 10/15] fix for "Upper bound off by one when parsing FX" see discussion https://github.com/MoonModules/WLED-MM/pull/270#pullrequestreview-3362552229 --- wled00/json.cpp | 4 ++-- wled00/util.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index 55fa7da79d..d2bdba3a8d 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -292,8 +292,8 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) if (seg.is2D() && (seg.map1D2D == M12_pArc || seg.map1D2D == M12_sCircle) && (reverse != seg.reverse || reverse_y != seg.reverse_y || mirror != seg.mirror || mirror_y != seg.mirror_y)) seg.markForBlank(); // clear entire segment (in case of Arc 1D to 2D expansion) WLEDMM: also Circle #endif - auto fx = seg.mode; - auto last = strip.getModeCount(); + uint16_t fx = seg.mode; + uint16_t last = min(strip.getModeCount()-1, 0); // WLEDMM bugfix: make sure that fx= cannot go out of range (effects IDs start at 0) // partial fix for #3605 if (!elem["fx"].isNull() && elem["fx"].is()) { const char *tmp = elem["fx"].as(); diff --git a/wled00/util.cpp b/wled00/util.cpp index 7c644109f9..a1460ebf21 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -64,7 +64,7 @@ void parseNumber16(const char* str, uint16_t* val, uint16_t minv, uint16_t maxv) bool getVal(JsonVariant elem, byte* val, byte vmin, byte vmax) { if (elem.is()) { if (elem < 0) return false; //ignore e.g. {"ps":-1} - *val = elem; + *val = elem; // ToDO: check if we need a bounds test [vmin ... vmax] before assigning the result return true; } else if (elem.is()) { const char* str = elem; @@ -79,7 +79,7 @@ bool getVal(JsonVariant elem, byte* val, byte vmin, byte vmax) { bool getVal16(JsonVariant elem, uint16_t* val, uint16_t vmin, uint16_t vmax) { // same as above, with 2byte output buffer if (elem.is()) { if (elem < 0) return false; //ignore e.g. {"ps":-1} - *val = elem; + *val = elem; // ToDO: check if we need a bounds test [vmin ... vmax] before assigning the result return true; } else if (elem.is()) { const char* str = elem; From 0ecd4604935de31375355aba10da6586dffbc057 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Wed, 22 Oct 2025 23:52:12 +0200 Subject: [PATCH 11/15] oopsie confused min and max --- wled00/json.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index d2bdba3a8d..76cae4e551 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -293,7 +293,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) #endif uint16_t fx = seg.mode; - uint16_t last = min(strip.getModeCount()-1, 0); // WLEDMM bugfix: make sure that fx= cannot go out of range (effects IDs start at 0) + uint16_t last = max(strip.getModeCount()-1, 0); // WLEDMM bugfix: make sure that fx= cannot go out of range (effects IDs start at 0) // partial fix for #3605 if (!elem["fx"].isNull() && elem["fx"].is()) { const char *tmp = elem["fx"].as(); From b4e900cf789fd980eae4e234240ff86e5d2376d5 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 23 Oct 2025 00:37:10 +0200 Subject: [PATCH 12/15] effectID: a few more 16bit fixes * proper default MaxVal for parseNumber16, getVal16, updateVal16 * more fixes for IR.cpp * json robustness improvement --- wled00/FX.cpp | 7 +++++++ wled00/fcn_declare.h | 6 +++--- wled00/ir.cpp | 24 +++++++++++++++++------- wled00/json.cpp | 3 ++- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index db1760d9ae..282ddbe1a0 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -11772,6 +11772,13 @@ static const char _data_RESERVED[] PROGMEM = "RSVD"; // add (or replace reserved) effect mode and data into vector // use id==255 to find unallocated gaps (with "Reserved" data string) // if vector size() is smaller than id (single) data is appended at the end (regardless of id) +///// +// WLEDMM extended to use 16bit effect IDs +// PoC: if id >= _mode.size(), we currently append at the tail and ignore the explicit id. +// TODO(WLED‑MM): honor explicit IDs by resizing/backfilling up to `id` and placing the effect +// at exactly that slot; update _modeCount = max(_modeCount, id+1).// PoC: if id >= _mode.size(), we currently append at the tail and ignore the explicit id. +// TODO(WLED‑MM): honor explicit IDs by resizing/backfilling up to `id` and placing the effect +// at exactly that slot; update _modeCount = max(_modeCount, id+1). void WS2812FX::addEffect(uint16_t id, mode_ptr mode_fn, const char *mode_name) { if ((id < _mode.size()) && (_modeData[id] != _data_RESERVED)) { DEBUG_PRINTF("addEffect(%d) -> ", id); diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index a02f8d64fa..179a72b92f 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -387,9 +387,9 @@ 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); bool updateVal(const char* req, const char* key, byte* val, byte minv=0, byte maxv=255); -void parseNumber16(const char* str, uint16_t* val, uint16_t minv=0, uint16_t maxv=255); // the real thing in 16bit -bool getVal16(JsonVariant elem, uint16_t* val, uint16_t vmin, uint16_t vmax=255); // same as above, with 2byte output buffer -bool updateVal16(const char* req, const char* key, uint16_t* val, uint16_t minv=0, uint16_t maxv=255); +void parseNumber16(const char* str, uint16_t* val, uint16_t minv=0, uint16_t maxv=65535); // the real thing in 16bit +bool getVal16(JsonVariant elem, uint16_t* val, uint16_t vmin, uint16_t vmax=65535); // same as above, with 2byte output buffer +bool updateVal16(const char* req, const char* key, uint16_t* val, uint16_t minv=0, uint16_t maxv=65535); void oappendUseDeflate(bool OnOff); // enable / disable string squeezing bool oappend(const char* txt); // append new c string to temp buffer efficiently diff --git a/wled00/ir.cpp b/wled00/ir.cpp index c8b24d263d..a7b318b35e 100644 --- a/wled00/ir.cpp +++ b/wled00/ir.cpp @@ -91,6 +91,16 @@ byte relativeChange(byte property, int8_t amount, byte lowerBoundary, byte highe return (byte)constrain(new_val, 0, 255); } +// 16‑bit variant for effect IDs +uint16_t relativeChange16(uint16_t property, int16_t amount, uint16_t lowerBoundary, uint16_t higherBoundary) +{ + int32_t new_val = (int32_t)property + amount; + if (lowerBoundary >= higherBoundary) return property; + if (new_val > higherBoundary) new_val = higherBoundary; + if (new_val < lowerBoundary) new_val = lowerBoundary; + return (uint16_t)new_val; +} + void changeEffect(uint16_t fx) { if (irApplyToAllSelected) { @@ -508,8 +518,8 @@ void decodeIR44(uint32_t code) case IR44_WARMWHITE : changeColor(COLOR_WARMWHITE, 63); changeEffect(FX_MODE_STATIC); break; case IR44_COLDWHITE : changeColor(COLOR_COLDWHITE, 191); changeEffect(FX_MODE_STATIC); break; case IR44_COLDWHITE2 : changeColor(COLOR_COLDWHITE2, 255); changeEffect(FX_MODE_STATIC); break; - case IR44_REDPLUS : changeEffect(relativeChange(effectCurrent, 1, 0, strip.getModeCount() -1)); break; - case IR44_REDMINUS : changeEffect(relativeChange(effectCurrent, -1, 0, strip.getModeCount() -1)); break; + case IR44_REDPLUS : changeEffect(relativeChange16(effectCurrent, 1, 0, strip.getModeCount() -1)); break; + case IR44_REDMINUS : changeEffect(relativeChange16(effectCurrent, -1, 0, strip.getModeCount() -1)); break; case IR44_GREENPLUS : changePalette(relativeChange(effectPalette, 1, 0, strip.getPaletteCount() -1)); break; case IR44_GREENMINUS : changePalette(relativeChange(effectPalette, -1, 0, strip.getPaletteCount() -1)); break; case IR44_BLUEPLUS : changeEffectIntensity( 16); break; @@ -568,7 +578,7 @@ void decodeIR6(uint32_t code) case IR6_POWER: toggleOnOff(); break; case IR6_CHANNEL_UP: incBrightness(); break; case IR6_CHANNEL_DOWN: decBrightness(); break; - case IR6_VOLUME_UP: changeEffect(relativeChange(effectCurrent, 1, 0, strip.getModeCount() -1)); break; + case IR6_VOLUME_UP: changeEffect(relativeChange16(effectCurrent, 1, 0, strip.getModeCount() -1)); break; case IR6_VOLUME_DOWN: changePalette(relativeChange(effectPalette, 1, 0, strip.getPaletteCount() -1)); switch(lastIR6ColourIdx) { case 0: changeColor(COLOR_RED); break; @@ -606,7 +616,7 @@ void decodeIR9(uint32_t code) case IR9_DOWN : decBrightness(); break; case IR9_LEFT : changeEffectSpeed(-16); break; case IR9_RIGHT : changeEffectSpeed(16); break; - case IR9_SELECT : changeEffect(relativeChange(effectCurrent, 1, 0, strip.getModeCount() -1)); break; + case IR9_SELECT : changeEffect(relativeChange16(effectCurrent, 1, 0, strip.getModeCount() -1)); break; default: return; } lastValidCode = code; @@ -621,8 +631,8 @@ void decodeIR24MC(uint32_t code) case IR24_MC_OFF : if (bri > 0) briLast = bri; bri = 0; break; case IR24_MC_AUTO : changeEffect(FX_MODE_FADE); break; case IR24_MC_ON : bri = briLast; break; - case IR24_MC_MODES : changeEffect(relativeChange(effectCurrent, 1, 0, strip.getModeCount() -1)); break; //WLEDMM: sound and non sound modes - case IR24_MC_MODE : changeEffect(relativeChange(effectCurrent, -1, 0, strip.getModeCount() -1)); break; //WLEDMM: sound and non sound modes + case IR24_MC_MODES : changeEffect(relativeChange16(effectCurrent, 1, 0, strip.getModeCount() -1)); break; //WLEDMM: sound and non sound modes + case IR24_MC_MODE : changeEffect(relativeChange16(effectCurrent, -1, 0, strip.getModeCount() -1)); break; //WLEDMM: sound and non sound modes case IR24_MC_BRIGHTER : incBrightness(); break; case IR24_MC_DARKER : decBrightness(); break; case IR24_MC_QUICK : changeEffectSpeed( 16); break; @@ -711,7 +721,7 @@ void decodeIRJson(uint32_t code) decBrightness(); } else if (cmdStr.startsWith(F("!presetF"))) { //!presetFallback uint8_t p1 = fdo["PL"] | 1; - uint8_t p2 = fdo["FX"] | random8(strip.getModeCount() -1); + uint16_t p2 = fdo["FX"] | random16(strip.getModeCount() -1); uint8_t p3 = fdo["FP"] | 0; presetFallback(p1, p2, p3); } diff --git a/wled00/json.cpp b/wled00/json.cpp index 76cae4e551..e0db0412c9 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -293,7 +293,8 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) #endif uint16_t fx = seg.mode; - uint16_t last = max(strip.getModeCount()-1, 0); // WLEDMM bugfix: make sure that fx= cannot go out of range (effects IDs start at 0) + uint16_t fxModeCount = strip.getModeCount(); + uint16_t last = fxModeCount ? (uint16_t)(fxModeCount - 1) : 0; // WLEDMM bugfix: make sure that fx= cannot go out of range (effects IDs start at 0) // partial fix for #3605 if (!elem["fx"].isNull() && elem["fx"].is()) { const char *tmp = elem["fx"].as(); From 9bedbd2b2c63adfab9978b47779090ff6438761f Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 23 Oct 2025 00:57:41 +0200 Subject: [PATCH 13/15] comments (ToDo) --- wled00/udp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 3c6a50bb7d..dfff52aaa9 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -431,7 +431,7 @@ void handleNotifications() selseg.options = (selseg.options & 0x0071U) | (udpIn[9 +ofs] & 0x0E); // ignore selected, freeze, reset & transitional selseg.setOpacity(udpIn[10+ofs]); if (applyEffects) { - strip.setMode(id, udpIn[11+ofs]); + strip.setMode(id, udpIn[11+ofs]); // WLEDMM ToDo: need to add support for 16bit effect IDs selseg.speed = udpIn[12+ofs]; selseg.intensity = udpIn[13+ofs]; selseg.palette = udpIn[14+ofs]; @@ -472,7 +472,7 @@ void handleNotifications() for (size_t i = 0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); if (!seg.isActive() || !seg.isSelected()) continue; - seg.setMode(udpIn[8]); + seg.setMode(udpIn[8]); // WLEDMM ToDo: need to add support for 16bit effect IDs seg.speed = udpIn[9]; if (version > 2) seg.intensity = udpIn[16]; if (version > 4) seg.setPalette(udpIn[19]); From e0c0bb5106711b3297e5df78a8006c1822acae2a Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:10:27 +0200 Subject: [PATCH 14/15] preserve legacy behavior of getVal() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit reverting a "fix" that came from AI chat 😅, in favor of preserving previous behavior. to really solve the potential for "off by one" and "below by one" in parseNumber, we would need two copies of the code and fix only parseNumber16() --- wled00/json.cpp | 5 ++--- wled00/util.cpp | 10 +++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index e0db0412c9..5d32d41ce0 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -293,15 +293,14 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) #endif uint16_t fx = seg.mode; - uint16_t fxModeCount = strip.getModeCount(); - uint16_t last = fxModeCount ? (uint16_t)(fxModeCount - 1) : 0; // WLEDMM bugfix: make sure that fx= cannot go out of range (effects IDs start at 0) + uint16_t last = strip.getModeCount(); // WLEDMM safe, because strip.getModeCount() is always >0 // partial fix for #3605 if (!elem["fx"].isNull() && elem["fx"].is()) { const char *tmp = elem["fx"].as(); if (strlen(tmp) > 3 && (strchr(tmp,'r') || strchr(tmp,'~') != strrchr(tmp,'~'))) last = 0; // we have "X~Y(r|[w]~[-])" form } // end fix - if (getVal16(elem["fx"], &fx, 0, last)) { //load effect ('r' random, '~' inc/dec, 0-255 exact value, 5~10r pick random between 5 & 10) + if (getVal16(elem["fx"], &fx, 0, last)) { //load effect ('r' random, '~' inc/dec, 0-65535 exact value, 5~10r pick random between 5 & 10) if (!presetId && currentPlaylist>=0) unloadPlaylist(); if (fx != seg.mode) seg.setMode(fx, elem[F("fxdef")], elem[F("fxdef2")]); // WLEDMM fxdef2 added } diff --git a/wled00/util.cpp b/wled00/util.cpp index a1460ebf21..0df14ecae6 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -11,15 +11,15 @@ int getNumVal(const String* req, uint16_t pos) // wrapper for parseNumber16 to suppport byte target buffer -void parseNumber(const char* str, byte* val, byte minv, byte maxv) { // wrapper for 8bit buffer +void parseNumber(const char* str, byte* val, byte minv, byte maxv) { // wrapper for 8bit buffer; maxv is "exclusive" uint16_t temp = *val; parseNumber16(str, &temp, (uint16_t)minv, (uint16_t)maxv); - *val = constrain(temp, 0, 255); - if ((temp > 255) && (minv == 0) && (maxv == 0)) *val = temp & 0x00FF; // 8bit support for "r" with min=max=0 + //*val = constrain(temp, 0, 255); // unfortunately this is not compatible with legacy 8bit "r" = random + *val = temp & 0x00FF; // always works correctly, assuming *str is strictly 8bit } //helper to get int value with in/decrementing support via ~ syntax -void parseNumber16(const char* str, uint16_t* val, uint16_t minv, uint16_t maxv) // the real thing in 16bit +void parseNumber16(const char* str, uint16_t* val, uint16_t minv, uint16_t maxv) // the real thing in 16bit; maxv is "exclusive" { if (str == nullptr || str[0] == '\0') return; if (str[0] == 'r') {*val = uint16_t(hw_random(minv,maxv?maxv:65535)); return;} // maxv for random cannot be 0, use full range @@ -687,7 +687,7 @@ char *cleanUpName(char *in) { return(in); } -// 32 bit hardware random number generator, inlining uses more code, use hw_random16() if speed is critical (see fcn_declare.h) +// 32 bit hardware random number generator, inlining uses more code, use hw_random16() if speed is critical (see fcn_declare.h). results are "exclusive" upperlimit uint32_t hw_random(uint32_t upperlimit) { uint32_t rnd = hw_random(); uint64_t scaled = uint64_t(rnd) * uint64_t(upperlimit); From 16c05d6d725e6119e196e96f18cb40c28d30619d Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 23 Oct 2025 21:50:22 +0200 Subject: [PATCH 15/15] four-line-display+rotary: avoid infinite loops changed two loop counters from byte to uint16_t, to avoid infinite loops - "i < strip.getModeCount()" will always be true when the type of i is too small. --- .../usermod_v2_four_line_display_ALT.h | 2 +- .../usermod_v2_rotary_encoder_ui_ALT.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h index 898545716d..f18daa5c01 100644 --- a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h +++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h @@ -158,7 +158,7 @@ class FourLineDisplayUsermod : public Usermod { uint8_t knownBrightness = 0; uint8_t knownEffectSpeed = 0; uint8_t knownEffectIntensity = 0; - uint8_t knownMode = 0; + uint16_t knownMode = 0; uint8_t knownPalette = 0; uint8_t knownMinute = 99; uint8_t knownHour = 99; diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h index d5ac7b72c5..1f50fda4f0 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h @@ -353,7 +353,7 @@ void RotaryEncoderUIUsermod::sortModesAndPalettes() { byte *RotaryEncoderUIUsermod::re_initIndexArray(int numModes) { byte *indexes = (byte *)malloc(sizeof(byte) * numModes); - for (byte i = 0; i < numModes; i++) { + for (uint16_t i = 0; i < numModes; i++) { // WLEDMM changed to uint16_t to avoid infinite loop with 16bit mode IDs indexes[i] = i; } return indexes; @@ -630,7 +630,7 @@ void RotaryEncoderUIUsermod::displayNetworkInfo() { void RotaryEncoderUIUsermod::findCurrentEffectAndPalette() { if (modes_alpha_indexes == nullptr) return; // WLEDMM bugfix currentEffectAndPaletteInitialized = true; - for (uint8_t i = 0; i < strip.getModeCount(); i++) { + for (uint16_t i = 0; i < strip.getModeCount(); i++) { // WLEDMM changed to uint16_t to avoid infinite loop with 16bit mode IDs if (modes_alpha_indexes[i] == effectCurrent) { effectCurrentIndex = i; break;