diff --git a/CHANGELOG.md b/CHANGELOG.md index f81e5cb0b..6751de2c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ ### Builds after release 0.12.0 +#### Build 2112070 + +- Added new effect "Fairy", replacing "Police All" +- Added new effect "Fairytwinkle", replacing "Two Areas" +- Static single JSON buffer (performance and stability improvement) (PR #2336) + +#### Build 2112030 + +- Fixed ESP32 crash on Colortwinkles brightness change +- Fixed setting picker to black resetting hue and saturation +- Fixed auto white mode not saved to config + #### Build 2111300 - Added CCT and white balance correction support (PR #2285) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index a2b6a9596..de7ecc5dd 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1216,12 +1216,13 @@ uint16_t WS2812FX::mode_loading(void) { //American Police Light with all LEDs Red and Blue -uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2, uint16_t width) +uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2) { uint16_t delay = 1 + (FRAMETIME<<3) / SEGLEN; // longer segments should change faster uint32_t it = now / map(SEGMENT.speed, 0, 255, delay<<4, delay); uint16_t offset = it % SEGLEN; + uint16_t width = ((SEGLEN*(SEGMENT.intensity+1))>>9); //max width is half the strip if (!width) width = 1; for (uint16_t i = 0; i < width; i++) { uint16_t indexR = (offset + i) % SEGLEN; @@ -1233,26 +1234,11 @@ uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2, uint16_t width) } -//American Police Light with all LEDs Red and Blue -uint16_t WS2812FX::mode_police_all() -{ - return police_base(RED, BLUE, (SEGLEN>>1)); -} - - //Police Lights Red and Blue uint16_t WS2812FX::mode_police() { fill(SEGCOLOR(1)); - return police_base(RED, BLUE, ((SEGLEN*(SEGMENT.intensity+1))>>9)); // max width is half the strip -} - - -//Police All with custom colors -uint16_t WS2812FX::mode_two_areas() -{ - fill(SEGCOLOR(2)); - return police_base(SEGCOLOR(0), SEGCOLOR(1), ((SEGLEN*(SEGMENT.intensity+1))>>9)); // max width is half the strip + return police_base(RED, BLUE); } @@ -1262,7 +1248,142 @@ uint16_t WS2812FX::mode_two_dots() fill(SEGCOLOR(2)); uint32_t color2 = (SEGCOLOR(1) == SEGCOLOR(2)) ? SEGCOLOR(0) : SEGCOLOR(1); - return police_base(SEGCOLOR(0), color2, ((SEGLEN*(SEGMENT.intensity+1))>>9)); // max width is half the strip + return police_base(SEGCOLOR(0), color2); +} + + +/* + * Fairy, inspired by https://www.youtube.com/watch?v=zeOw5MZWq24 + */ +//4 bytes +typedef struct Flasher { + uint16_t stateStart; + uint8_t stateDur; + bool stateOn; +} flasher; + +#define FLASHERS_PER_ZONE 6 +#define MAX_SHIMMER 92 + +uint16_t WS2812FX::mode_fairy() { + //set every pixel to a 'random' color from palette (using seed so it doesn't change between frames) + uint16_t PRNG16 = 5100 + _segment_index; + for (uint16_t i = 0; i < SEGLEN; i++) { + PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number + setPixelColor(i, color_from_palette(PRNG16 >> 8, false, false, 0)); + } + + //amount of flasher pixels depending on intensity (0: none, 255: every LED) + if (SEGMENT.intensity == 0) return FRAMETIME; + uint8_t flasherDistance = ((255 - SEGMENT.intensity) / 28) +1; //1-10 + uint16_t numFlashers = (SEGLEN / flasherDistance) +1; + + uint16_t dataSize = sizeof(flasher) * numFlashers; + if (!SEGENV.allocateData(dataSize)) return FRAMETIME; //allocation failed + Flasher* flashers = reinterpret_cast(SEGENV.data); + uint16_t now16 = now & 0xFFFF; + + //Up to 11 flashers in one brightness zone, afterwards a new zone for every 6 flashers + uint16_t zones = numFlashers/FLASHERS_PER_ZONE; + if (!zones) zones = 1; + uint8_t flashersInZone = numFlashers/zones; + uint8_t flasherBri[FLASHERS_PER_ZONE*2 -1]; + + for (uint16_t z = 0; z < zones; z++) { + uint16_t flasherBriSum = 0; + uint16_t firstFlasher = z*flashersInZone; + if (z == zones-1) flashersInZone = numFlashers-(flashersInZone*(zones-1)); + + for (uint16_t f = firstFlasher; f < firstFlasher + flashersInZone; f++) { + uint16_t stateTime = now16 - flashers[f].stateStart; + //random on/off time reached, switch state + if (stateTime > flashers[f].stateDur * 10) { + flashers[f].stateOn = !flashers[f].stateOn; + if (flashers[f].stateOn) { + flashers[f].stateDur = 12 + random8(12 + ((255 - SEGMENT.speed) >> 2)); //*10, 250ms to 1250ms + } else { + flashers[f].stateDur = 20 + random8(6 + ((255 - SEGMENT.speed) >> 2)); //*10, 250ms to 1250ms + } + //flashers[f].stateDur = 51 + random8(2 + ((255 - SEGMENT.speed) >> 1)); + flashers[f].stateStart = now16; + if (stateTime < 255) { + flashers[f].stateStart -= 255 -stateTime; //start early to get correct bri + flashers[f].stateDur += 26 - stateTime/10; + stateTime = 255 - stateTime; + } else { + stateTime = 0; + } + } + if (stateTime > 255) stateTime = 255; //for flasher brightness calculation, fades in first 255 ms of state + //flasherBri[f - firstFlasher] = (flashers[f].stateOn) ? 255-gamma8((510 - stateTime) >> 1) : gamma8((510 - stateTime) >> 1); + flasherBri[f - firstFlasher] = (flashers[f].stateOn) ? stateTime : 255 - (stateTime >> 0); + flasherBriSum += flasherBri[f - firstFlasher]; + } + //dim factor, to create "shimmer" as other pixels get less voltage if a lot of flashers are on + uint8_t avgFlasherBri = flasherBriSum / flashersInZone; + uint8_t globalPeakBri = 255 - ((avgFlasherBri * MAX_SHIMMER) >> 8); //183-255, suitable for 1/5th of LEDs flashers + + for (uint16_t f = firstFlasher; f < firstFlasher + flashersInZone; f++) { + uint8_t bri = (flasherBri[f - firstFlasher] * globalPeakBri) / 255; + PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number + uint16_t flasherPos = f*flasherDistance; + setPixelColor(flasherPos, color_blend(SEGCOLOR(1), color_from_palette(PRNG16 >> 8, false, false, 0), bri)); + for (uint16_t i = flasherPos+1; i < flasherPos+flasherDistance && i < SEGLEN; i++) { + PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number + setPixelColor(i, color_from_palette(PRNG16 >> 8, false, false, 0, globalPeakBri)); + } + } + } + return FRAMETIME; +} + + +/* + * Fairytwinkle. Like Colortwinkle, but starting from all lit and not relying on getPixelColor + * Warning: Uses 4 bytes of segment data per pixel + */ +uint16_t WS2812FX::mode_fairytwinkle() { + uint16_t dataSize = sizeof(flasher) * SEGLEN; + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + Flasher* flashers = reinterpret_cast(SEGENV.data); + uint16_t now16 = now & 0xFFFF; + uint16_t PRNG16 = 5100 + _segment_index; + + uint16_t riseFallTime = 400 + (255-SEGMENT.speed)*3; + uint16_t maxDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + 13 + ((255 - SEGMENT.intensity) >> 1); + + for (uint16_t f = 0; f < SEGLEN; f++) { + uint16_t stateTime = now16 - flashers[f].stateStart; + //random on/off time reached, switch state + if (stateTime > flashers[f].stateDur * 100) { + flashers[f].stateOn = !flashers[f].stateOn; + bool init = !flashers[f].stateDur; + if (flashers[f].stateOn) { + flashers[f].stateDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + random8(12 + ((255 - SEGMENT.intensity) >> 1)) +1; + } else { + flashers[f].stateDur = riseFallTime/100 + random8(3 + ((255 - SEGMENT.speed) >> 6)) +1; + } + flashers[f].stateStart = now16; + stateTime = 0; + if (init) { + flashers[f].stateStart -= riseFallTime; //start lit + flashers[f].stateDur = riseFallTime/100 + random8(12 + ((255 - SEGMENT.intensity) >> 1)) +5; //fire up a little quicker + stateTime = riseFallTime; + } + } + if (flashers[f].stateOn && flashers[f].stateDur > maxDur) flashers[f].stateDur = maxDur; //react more quickly on intensity change + if (stateTime > riseFallTime) stateTime = riseFallTime; //for flasher brightness calculation, fades in first 255 ms of state + uint8_t fadeprog = 255 - ((stateTime * 255) / riseFallTime); + uint8_t flasherBri = (flashers[f].stateOn) ? 255-gamma8(fadeprog) : gamma8(fadeprog); + uint16_t lastR = PRNG16; + uint16_t diff = 0; + while (diff < 0x4000) { //make sure colors of two adjacent LEDs differ enough + PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number + diff = (PRNG16 > lastR) ? PRNG16 - lastR : lastR - PRNG16; + } + setPixelColor(f, color_blend(SEGCOLOR(1), color_from_palette(PRNG16 >> 8, false, false, 0), flasherBri)); + } + return FRAMETIME; } diff --git a/wled00/FX.h b/wled00/FX.h index f43e9765f..954f6656f 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -161,14 +161,14 @@ #define FX_MODE_COMET 41 #define FX_MODE_FIREWORKS 42 #define FX_MODE_RAIN 43 -#define FX_MODE_TETRIX 44 +#define FX_MODE_TETRIX 44 //was Merry Christmas prior to 0.12.0 (use "Chase 2" with Red/Green) #define FX_MODE_FIRE_FLICKER 45 #define FX_MODE_GRADIENT 46 #define FX_MODE_LOADING 47 #define FX_MODE_POLICE 48 // candidate for removal (after below three) -#define FX_MODE_POLICE_ALL 49 // candidate for removal +#define FX_MODE_FAIRY 49 //was Police All prior to 0.13.0-b6 (use "Two Dots" with Red/Blue and full intensity) #define FX_MODE_TWO_DOTS 50 -#define FX_MODE_TWO_AREAS 51 // candidate for removal +#define FX_MODE_FAIRYTWINKLE 51 //was Two Areas prior to 0.13.0-b6 (use "Two Dots" with full intensity) #define FX_MODE_RUNNING_DUAL 52 #define FX_MODE_HALLOWEEN 53 // candidate for removal #define FX_MODE_TRICOLOR_CHASE 54 @@ -550,9 +550,9 @@ class WS2812FX { _mode[FX_MODE_GRADIENT] = &WS2812FX::mode_gradient; _mode[FX_MODE_LOADING] = &WS2812FX::mode_loading; _mode[FX_MODE_POLICE] = &WS2812FX::mode_police; - _mode[FX_MODE_POLICE_ALL] = &WS2812FX::mode_police_all; + _mode[FX_MODE_FAIRY] = &WS2812FX::mode_fairy; _mode[FX_MODE_TWO_DOTS] = &WS2812FX::mode_two_dots; - _mode[FX_MODE_TWO_AREAS] = &WS2812FX::mode_two_areas; + _mode[FX_MODE_FAIRYTWINKLE] = &WS2812FX::mode_fairytwinkle; _mode[FX_MODE_RUNNING_DUAL] = &WS2812FX::mode_running_dual; _mode[FX_MODE_HALLOWEEN] = &WS2812FX::mode_halloween; _mode[FX_MODE_TRICOLOR_CHASE] = &WS2812FX::mode_tricolor_chase; @@ -773,9 +773,9 @@ class WS2812FX { mode_gradient(void), mode_loading(void), mode_police(void), - mode_police_all(void), + mode_fairy(void), mode_two_dots(void), - mode_two_areas(void), + mode_fairytwinkle(void), mode_running_dual(void), mode_bicolor_chase(void), mode_tricolor_chase(void), @@ -878,7 +878,7 @@ class WS2812FX { chase(uint32_t, uint32_t, uint32_t, bool), gradient_base(bool), ripple_base(bool), - police_base(uint32_t, uint32_t, uint16_t), + police_base(uint32_t, uint32_t), running(uint32_t, uint32_t, bool theatre=false), tricolor_chase(uint32_t, uint32_t), twinklefox_base(bool), @@ -993,9 +993,9 @@ const char JSON_mode_names[] PROGMEM = R"=====([ "Gradient", "Loading", "Police@!,Width;;", -"Police All@!,Width;;", +"Fairy", "Two Dots@!,Dot size;1,2,Bg;!", -"Two Areas@!,Size;1,2,Bg;!", +"Fairy Twinkle", "Running Dual", "Halloween", "Chase 3@!,Size;1,2,3;", diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 62abc7870..b5416d526 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -442,14 +442,14 @@ void WS2812FX::setBrightness(uint8_t b) { if (gammaCorrectBri) b = gamma8(b); if (_brightness == b) return; _brightness = b; - _segment_index = 0; if (_brightness == 0) { //unfreeze all segments on power off for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { _segments[i].setOption(SEG_OPTION_FREEZE, false); } } - if (SEGENV.next_time > millis() + 22 && millis() - _lastShow > MIN_SHOW_DELAY) show();//apply brightness change immediately if no refresh soon + unsigned long t = millis(); + if (_segment_runtimes[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) show(); //apply brightness change immediately if no refresh soon } uint8_t WS2812FX::getMode(void) { @@ -701,14 +701,35 @@ bool WS2812FX::checkSegmentAlignment() { } //After this function is called, setPixelColor() will use that segment (offsets, grouping, ... will apply) +//Note: If called in an interrupt (e.g. JSON API), it must be reset with "setPixelColor(255)", +//otherwise it can lead to a crash on ESP32 because _segment_index is modified while in use by the main thread +#ifdef ARDUINO_ARCH_ESP32 +uint8_t _segment_index_prev = 0; +uint16_t _virtualSegmentLength_prev = 0; +bool _ps_set = false; +#endif + void WS2812FX::setPixelSegment(uint8_t n) { if (n < MAX_NUM_SEGMENTS) { + #ifdef ARDUINO_ARCH_ESP32 + if (!_ps_set) { + _segment_index_prev = _segment_index; + _virtualSegmentLength_prev = _virtualSegmentLength; + _ps_set = true; + } + #endif _segment_index = n; - _virtualSegmentLength = SEGMENT.length(); + _virtualSegmentLength = SEGMENT.virtualLength(); } else { - _segment_index = 0; - _virtualSegmentLength = 0; + _virtualSegmentLength = 0; + #ifdef ARDUINO_ARCH_ESP32 + if (_ps_set) { + _segment_index = _segment_index_prev; + _virtualSegmentLength = _virtualSegmentLength_prev; + _ps_set = false; + } + #endif } } @@ -735,13 +756,13 @@ void WS2812FX::setTransition(uint16_t t) void WS2812FX::setTransitionMode(bool t) { - unsigned long waitMax = millis() + 20; //refresh after 20 ms if transition enabled + unsigned long waitMax = millis() + 20; //refresh after 20 ms if transition enabled for (uint16_t i = 0; i < MAX_NUM_SEGMENTS; i++) { - _segment_index = i; - SEGMENT.setOption(SEG_OPTION_TRANSITIONAL, t); + _segments[i].setOption(SEG_OPTION_TRANSITIONAL, t); - if (t && SEGMENT.mode == FX_MODE_STATIC && SEGENV.next_time > waitMax) SEGENV.next_time = waitMax; + if (t && _segments[i].mode == FX_MODE_STATIC && _segment_runtimes[i].next_time > waitMax) + _segment_runtimes[i].next_time = waitMax; } } @@ -1097,7 +1118,7 @@ void WS2812FX::deserializeMap(uint8_t n) { #ifdef WLED_USE_DYNAMIC_JSON DynamicJsonDocument doc(JSON_BUFFER_SIZE); #else - if (!requestJSONBufferLock(5)) return; + if (!requestJSONBufferLock(7)) return; #endif DEBUG_PRINT(F("Reading LED map from ")); diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index dd36d724a..c449ffabb 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -318,16 +318,21 @@ class BusPwm : public Bus { cct = (approximateKelvinFromRGB(c) - 1900) >> 5; } - //0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold) uint8_t ww, cw; - if (cct < _cctBlend) ww = 255; - else ww = ((255-cct) * 255) / (255 - _cctBlend); + #ifdef WLED_USE_IC_CCT + ww = w; + cw = cct; + #else + //0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold) + if (cct < _cctBlend) ww = 255; + else ww = ((255-cct) * 255) / (255 - _cctBlend); if ((255-cct) < _cctBlend) cw = 255; else cw = (cct * 255) / (255 - _cctBlend); ww = (w * ww) / 255; //brightness scaling cw = (w * cw) / 255; + #endif switch (_type) { case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation @@ -448,7 +453,7 @@ class BusNetwork : public Bus { void setPixelColor(uint16_t pix, uint32_t c) { if (!_valid || pix >= _len) return; - if (_rgbw) c = autoWhiteCalc(c); + if (isRgbw()) c = autoWhiteCalc(c); if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT uint16_t offset = pix * _UDPchannels; _data[offset] = R(c); diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 62ae75eda..d650777f7 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -86,8 +86,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { Bus::setAutoWhiteMode(hw_led[F("rgbwm")] | Bus::getAutoWhiteMode()); CJSON(correctWB, hw_led["cct"]); CJSON(cctFromRgb, hw_led[F("cr")]); - CJSON(strip.cctBlending, hw_led[F("cb")]); - Bus::setCCTBlend(strip.cctBlending); + CJSON(strip.cctBlending, hw_led[F("cb")]); + Bus::setCCTBlend(strip.cctBlending); JsonArray ins = hw_led["ins"]; @@ -542,7 +542,7 @@ void serializeConfig() { hw_led[F("ledma")] = strip.milliampsPerLed; hw_led["cct"] = correctWB; hw_led[F("cr")] = cctFromRgb; - hw_led[F("cb")] = strip.cctBlending; + hw_led[F("cb")] = strip.cctBlending; hw_led[F("rgbwm")] = Bus::getAutoWhiteMode(); JsonArray hw_led_ins = hw_led.createNestedArray("ins"); diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 5fea9cb59..a513f33a8 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -240,7 +240,7 @@ void colorRGBtoRGBW(byte* rgb) //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_M float low = minf(rgb[0],minf(rgb[1],rgb[2])); float high = maxf(rgb[0],maxf(rgb[1],rgb[2])); if (high < 0.1f) return; - float sat = 100.0f * ((high - low) / high);; // maximum saturation is 100 (corrected from 255) + float sat = 100.0f * ((high - low) / high); // maximum saturation is 100 (corrected from 255) rgb[3] = (byte)((255.0f - sat) / 255.0f * (rgb[0] + rgb[1] + rgb[2]) / 3); } */ diff --git a/wled00/ir.cpp b/wled00/ir.cpp index fa9a51a35..a73a0db5d 100644 --- a/wled00/ir.cpp +++ b/wled00/ir.cpp @@ -575,11 +575,10 @@ void decodeIRJson(uint32_t code) JsonObject fdo; JsonObject jsonCmdObj; - DEBUG_PRINTLN(F("IR JSON buffer requested.")); #ifdef WLED_USE_DYNAMIC_JSON DynamicJsonDocument doc(JSON_BUFFER_SIZE); #else - if (!requestJSONBufferLock(6)) return; + if (!requestJSONBufferLock(13)) return; #endif sprintf_P(objKey, PSTR("\"0x%lX\":"), (unsigned long)code); @@ -593,12 +592,12 @@ void decodeIRJson(uint32_t code) lastValidCode = 0; if (fdo.isNull()) { //the received code does not exist - releaseJSONBufferLock(); if (!WLED_FS.exists("/ir.json")) errorFlag = ERR_FS_IRLOAD; //warn if IR file itself doesn't exist + releaseJSONBufferLock(); return; } - cmdStr = fdo["cmd"].as();; + cmdStr = fdo["cmd"].as(); jsonCmdObj = fdo["cmd"]; //object // command is JSON object @@ -638,9 +637,9 @@ void decodeIRJson(uint32_t code) } colorUpdated(CALL_MODE_BUTTON); } else if (!jsonCmdObj.isNull()) { + // command is JSON object deserializeState(jsonCmdObj, CALL_MODE_BUTTON); } - //fileDoc = nullptr; releaseJSONBufferLock(); } @@ -669,7 +668,8 @@ void handleIR() { if (results.value != 0) // only print results if anything is received ( != 0 ) { - DEBUG_PRINTF("IR recv: 0x%lX\n", (unsigned long)results.value); + if (!pinManager.isPinAllocated(1)) //GPIO 1 - Serial TX pin + Serial.printf_P(PSTR("IR recv: 0x%lX\n"), (unsigned long)results.value); } decodeIR(results.value); irrecv->resume(); diff --git a/wled00/json.cpp b/wled00/json.cpp index 5dfb981af..7111fda6e 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -67,7 +67,7 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId) uint16_t grp = elem["grp"] | seg.grouping; uint16_t spc = elem[F("spc")] | seg.spacing; - strip.setSegment(id, start, stop, grp, spc); + uint16_t of = seg.offset; uint16_t len = 1; if (stop > start) len = stop - start; @@ -76,9 +76,10 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId) int offsetAbs = abs(offset); if (offsetAbs > len - 1) offsetAbs %= len; if (offset < 0) offsetAbs = len - offsetAbs; - seg.offset = offsetAbs; + of = offsetAbs; } - if (stop > start && seg.offset > len -1) seg.offset = len -1; + if (stop > start && of > len -1) of = len -1; + strip.setSegment(id, start, stop, grp, spc, of); byte segbri = 0; if (getVal(elem["bri"], &segbri)) { @@ -159,17 +160,19 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId) //temporary, strip object gets updated via colorUpdated() if (id == strip.getMainSegmentId()) { + byte effectPrev = effectCurrent; if (getVal(elem["fx"], &effectCurrent, 1, strip.getModeCount())) { //load effect ('r' random, '~' inc/dec, 1-255 exact value) - if (!presetId) unloadPlaylist(); //stop playlist if active and FX changed manually + if (!presetId && effectCurrent != effectPrev) unloadPlaylist(); //stop playlist if active and FX changed manually } effectSpeed = elem[F("sx")] | effectSpeed; effectIntensity = elem[F("ix")] | effectIntensity; getVal(elem["pal"], &effectPalette, 1, strip.getPaletteCount()); } else { //permanent byte fx = seg.mode; + byte fxPrev = fx; if (getVal(elem["fx"], &fx, 1, strip.getModeCount())) { //load effect ('r' random, '~' inc/dec, 1-255 exact value) strip.setMode(id, fx); - if (!presetId) unloadPlaylist(); //stop playlist if active and FX changed manually + if (!presetId && seg.mode != fxPrev) unloadPlaylist(); //stop playlist if active and FX changed manually } seg.speed = elem[F("sx")] | seg.speed; seg.intensity = elem[F("ix")] | seg.intensity; @@ -345,11 +348,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) usermods.readFromJsonState(root); - int8_t ledmap = root[F("ledmap")] | -1; - if (ledmap >= 0) { - //strip.deserializeMap(ledmap); // requires separate JSON buffer - loadLedmap = ledmap; - } + loadLedmap = root[F("ledmap")] | loadLedmap; byte ps = root[F("psave")]; if (ps > 0) { @@ -960,7 +959,7 @@ void serveJson(AsyncWebServerRequest* request) #ifdef WLED_USE_DYNAMIC_JSON AsyncJsonResponse* response = new AsyncJsonResponse(JSON_BUFFER_SIZE, subJson==6); #else - if (!requestJSONBufferLock(7)) return; + if (!requestJSONBufferLock(17)) return; AsyncJsonResponse *response = new AsyncJsonResponse(&doc, subJson==6); #endif diff --git a/wled00/mqtt.cpp b/wled00/mqtt.cpp index 9ef3a22fb..d24992572 100644 --- a/wled00/mqtt.cpp +++ b/wled00/mqtt.cpp @@ -91,22 +91,20 @@ void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties colorUpdated(CALL_MODE_DIRECT_CHANGE); } else if (strcmp_P(topic, PSTR("/api")) == 0) { DEBUG_PRINTLN(F("MQTT JSON buffer requested.")); - #ifdef WLED_USE_DYNAMIC_JSON - DynamicJsonDocument doc(JSON_BUFFER_SIZE); - #else - if (!requestJSONBufferLock(8)) return; - #endif if (payload[0] == '{') { //JSON API + #ifdef WLED_USE_DYNAMIC_JSON + DynamicJsonDocument doc(JSON_BUFFER_SIZE); + #else + if (!requestJSONBufferLock(15)) return; + #endif deserializeJson(doc, payloadStr); - //fileDoc = &doc; // used for applying presets (presets.cpp) deserializeState(doc.as()); - //fileDoc = nullptr; + releaseJSONBufferLock(); } else { //HTTP API String apireq = "win&"; apireq += (char*)payloadStr; handleSet(nullptr, apireq); } - releaseJSONBufferLock(); } else if (strlen(topic) != 0) { // non standard topic, check with usermods usermods.onMqttMessage(topic, payloadStr); diff --git a/wled00/presets.cpp b/wled00/presets.cpp index 114f593db..0e65000b0 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -27,7 +27,6 @@ bool applyPreset(byte index, byte callMode) #else if (!requestJSONBufferLock(9)) return false; #endif - errorFlag = readObjectFromFileUsingId(filename, index, &doc) ? ERR_NONE : ERR_FS_PLOAD; JsonObject fdo = doc.as(); if (fdo["ps"] == index) fdo.remove("ps"); @@ -59,7 +58,6 @@ void savePreset(byte index, bool persist, const char* pname, JsonObject saveobj) #else if (!requestJSONBufferLock(10)) return; #endif - sObj = doc.to(); if (pname) sObj["n"] = pname; diff --git a/wled00/set.cpp b/wled00/set.cpp index a9ff07eee..0bd9ffd04 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -420,7 +420,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) #ifdef WLED_USE_DYNAMIC_JSON DynamicJsonDocument doc(JSON_BUFFER_SIZE); #else - if (!requestJSONBufferLock(11)) return; + if (!requestJSONBufferLock(5)) return; #endif JsonObject um = doc.createNestedObject("um"); diff --git a/wled00/util.cpp b/wled00/util.cpp index 388f30102..67260e3ed 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -202,6 +202,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) { unsigned long now = millis(); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 7d1ec2e5b..1fb1565a9 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -286,8 +286,10 @@ void WLED::setup() WiFi.onEvent(WiFiEvent); #endif - #ifdef WLED_ENABLE_ADALIGHT // reserve GPIO3 (RX) pin for ADALight - if (!pinManager.isPinAllocated(3)) { + #ifdef WLED_ENABLE_ADALIGHT + //Serial RX (Adalight, Improv, Serial JSON) only possible if GPIO3 unused + //Serial TX (Debug, Improv, Serial JSON) only possible if GPIO1 unused + if (!pinManager.isPinAllocated(3) && !pinManager.isPinAllocated(1)) { Serial.println(F("Ada")); pinManager.allocatePin(3,false); } else { diff --git a/wled00/wled.h b/wled00/wled.h index 39af56eb8..4cace88a9 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2112031 +#define VERSION 2112072 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG @@ -527,7 +527,7 @@ WLED_GLOBAL byte presetCycMax _INIT(5); // realtime WLED_GLOBAL byte realtimeMode _INIT(REALTIME_MODE_INACTIVE); WLED_GLOBAL byte realtimeOverride _INIT(REALTIME_OVERRIDE_NONE); -WLED_GLOBAL IPAddress realtimeIP _INIT_N(((0, 0, 0, 0)));; +WLED_GLOBAL IPAddress realtimeIP _INIT_N(((0, 0, 0, 0))); WLED_GLOBAL unsigned long realtimeTimeout _INIT(0); WLED_GLOBAL uint8_t tpmPacketCount _INIT(0); WLED_GLOBAL uint16_t tpmPayloadFrameSize _INIT(0); @@ -609,8 +609,8 @@ WLED_GLOBAL int8_t loadLedmap _INIT(-1); // Usermod manager WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager()); -// global ArduinoJson buffer #ifndef WLED_USE_DYNAMIC_JSON +// global ArduinoJson buffer WLED_GLOBAL StaticJsonDocument doc; #endif WLED_GLOBAL volatile uint8_t jsonBufferLock _INIT(0); diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp index 691ea81de..dd9a5c95b 100644 --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -385,7 +385,7 @@ void deEEP() { #ifdef WLED_USE_DYNAMIC_JSON DynamicJsonDocument doc(JSON_BUFFER_SIZE); #else - if (!requestJSONBufferLock(12)) return; + if (!requestJSONBufferLock(8)) return; #endif JsonObject sObj = doc.to(); diff --git a/wled00/wled_serial.cpp b/wled00/wled_serial.cpp index 13cb78878..dedc5d451 100644 --- a/wled00/wled_serial.cpp +++ b/wled00/wled_serial.cpp @@ -48,11 +48,10 @@ void handleSerial() Serial.print("WLED"); Serial.write(' '); Serial.println(VERSION); } else if (next == '{') { //JSON API bool verboseResponse = false; - DEBUG_PRINTLN(F("Serial JSON buffer requested.")); #ifdef WLED_USE_DYNAMIC_JSON DynamicJsonDocument doc(JSON_BUFFER_SIZE); #else - if (!requestJSONBufferLock(13)) return; + if (!requestJSONBufferLock(16)) return; #endif Serial.setTimeout(100); DeserializationError error = deserializeJson(doc, Serial); @@ -60,10 +59,7 @@ void handleSerial() releaseJSONBufferLock(); return; } - //fileDoc = &doc; // used for applying presets (presets.cpp) verboseResponse = deserializeState(doc.as()); - //fileDoc = nullptr; - //only send response if TX pin is unused for other purposes if (verboseResponse && !pinManager.isPinAllocated(1)) { doc.clear(); diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index efef0f0df..680ed1b31 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -63,11 +63,11 @@ void initServer() DefaultHeaders::Instance().addHeader(F("Access-Control-Allow-Methods"), "*"); DefaultHeaders::Instance().addHeader(F("Access-Control-Allow-Headers"), "*"); - #ifdef WLED_ENABLE_WEBSOCKETS + #ifdef WLED_ENABLE_WEBSOCKETS server.on("/liveview", HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, "text/html", PAGE_liveviewws); }); - #else + #else server.on("/liveview", HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, "text/html", PAGE_liveview); }); @@ -110,7 +110,6 @@ void initServer() bool verboseResponse = false; bool isConfig = false; { //scope JsonDocument so it releases its buffer - DEBUG_PRINTLN(F("HTTP JSON buffer requested.")); #ifdef WLED_USE_DYNAMIC_JSON DynamicJsonDocument doc(JSON_BUFFER_SIZE); #else @@ -132,9 +131,7 @@ void initServer() serializeJson(root,Serial); DEBUG_PRINTLN(); #endif - //fileDoc = &doc; // used for applying presets (presets.cpp) verboseResponse = deserializeState(root); - //fileDoc = nullptr; } else { verboseResponse = deserializeConfig(root); //use verboseResponse to determine whether cfg change should be saved immediately } diff --git a/wled00/ws.cpp b/wled00/ws.cpp index 451c15cb0..0cb9d5392 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -40,7 +40,7 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp #ifdef WLED_USE_DYNAMIC_JSON DynamicJsonDocument doc(JSON_BUFFER_SIZE); #else - if (!requestJSONBufferLock(15)) return; + if (!requestJSONBufferLock(11)) return; #endif DeserializationError error = deserializeJson(doc, data, len); @@ -49,13 +49,6 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp releaseJSONBufferLock(); return; } - /* - #ifdef WLED_DEBUG - DEBUG_PRINT(F("Incoming WS: ")); - serializeJson(root,Serial); - DEBUG_PRINTLN(); - #endif - */ if (root["v"] && root.size() == 1) { //if the received value is just "{"v":true}", send only to this client verboseResponse = true; @@ -63,15 +56,13 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp { wsLiveClientId = root["lv"] ? client->id() : 0; } else { - //fileDoc = &doc; // used for applying presets (presets.cpp) verboseResponse = deserializeState(root); - //fileDoc = nullptr; if (!interfaceUpdateCallMode) { //special case, only on playlist load, avoid sending twice in rapid succession if (millis() - lastInterfaceUpdate > 1700) verboseResponse = false; } } - releaseJSONBufferLock(); + releaseJSONBufferLock(); // will clean fileDoc } //update if it takes longer than 300ms until next "broadcast" if (verboseResponse && (millis() - lastInterfaceUpdate < 1700 || !interfaceUpdateCallMode)) sendDataWs(client); @@ -117,7 +108,7 @@ void sendDataWs(AsyncWebSocketClient * client) #ifdef WLED_USE_DYNAMIC_JSON DynamicJsonDocument doc(JSON_BUFFER_SIZE); #else - if (!requestJSONBufferLock(16)) return; + if (!requestJSONBufferLock(12)) return; #endif JsonObject state = doc.createNestedObject("state"); @@ -131,13 +122,6 @@ void sendDataWs(AsyncWebSocketClient * client) releaseJSONBufferLock(); return; //out of memory } -/* - #ifdef WLED_DEBUG - DEBUG_PRINT(F("Outgoing WS: ")); - serializeJson(doc,Serial); - DEBUG_PRINTLN(); - #endif -*/ serializeJson(doc, (char *)buffer->get(), len +1); releaseJSONBufferLock(); } diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 5c75a15a8..5db1cf30a 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -253,17 +253,18 @@ void getSettingsJS(byte subPage, char* dest) // add reserved and usermod pins as d.um_p array oappend(SET_F("d.um_p=[6,7,8,9,10,11")); + { // scope so buffer can be released earlier #ifdef WLED_USE_DYNAMIC_JSON - DynamicJsonDocument doc(2048); // 2k is enough for usermods + DynamicJsonDocument doc(3072); #else - if (!requestJSONBufferLock(17)) return; + if (!requestJSONBufferLock(6)) return; #endif - + JsonObject mods = doc.createNestedObject(F("um")); usermods.addToConfig(mods); if (!mods.isNull()) fillUMPins(mods); - releaseJSONBufferLock(); + } #ifdef WLED_ENABLE_DMX oappend(SET_F(",2")); // DMX hardcoded pin