diff --git a/usermods/usermod_rotary_brightness_color/usermod_rotary_brightness_color.h b/usermods/usermod_rotary_brightness_color/usermod_rotary_brightness_color.h index 85a9a1605..c3e1ce17b 100644 --- a/usermods/usermod_rotary_brightness_color/usermod_rotary_brightness_color.h +++ b/usermods/usermod_rotary_brightness_color/usermod_rotary_brightness_color.h @@ -95,9 +95,9 @@ public: } else { - fastled_col.red = col[0]; - fastled_col.green = col[1]; - fastled_col.blue = col[2]; + fastled_col.red = colPri[0]; + fastled_col.green = colPri[1]; + fastled_col.blue = colPri[2]; prim_hsv = rgb2hsv_approximate(fastled_col); new_val = (int16_t)prim_hsv.h + fadeAmount; if (new_val > 255) @@ -106,9 +106,9 @@ public: new_val += 255; // roll-over if smaller than 0 prim_hsv.h = (byte)new_val; hsv2rgb_rainbow(prim_hsv, fastled_col); - col[0] = fastled_col.red; - col[1] = fastled_col.green; - col[2] = fastled_col.blue; + colPri[0] = fastled_col.red; + colPri[1] = fastled_col.green; + colPri[2] = fastled_col.blue; } } else if (Enc_B == LOW) @@ -120,9 +120,9 @@ public: } else { - fastled_col.red = col[0]; - fastled_col.green = col[1]; - fastled_col.blue = col[2]; + fastled_col.red = colPri[0]; + fastled_col.green = colPri[1]; + fastled_col.blue = colPri[2]; prim_hsv = rgb2hsv_approximate(fastled_col); new_val = (int16_t)prim_hsv.h - fadeAmount; if (new_val > 255) @@ -131,9 +131,9 @@ public: new_val += 255; // roll-over if smaller than 0 prim_hsv.h = (byte)new_val; hsv2rgb_rainbow(prim_hsv, fastled_col); - col[0] = fastled_col.red; - col[1] = fastled_col.green; - col[2] = fastled_col.blue; + colPri[0] = fastled_col.red; + colPri[1] = fastled_col.green; + colPri[2] = fastled_col.blue; } } //call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification) 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 383c1193e..9533fa21b 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 @@ -518,7 +518,7 @@ void RotaryEncoderUIUsermod::setup() loopTime = millis(); - currentCCT = (approximateKelvinFromRGB(RGBW32(col[0], col[1], col[2], col[3])) - 1900) >> 5; + currentCCT = (approximateKelvinFromRGB(RGBW32(colPri[0], colPri[1], colPri[2], colPri[3])) - 1900) >> 5; if (!initDone) sortModesAndPalettes(); @@ -920,17 +920,17 @@ void RotaryEncoderUIUsermod::changeHue(bool increase){ display->updateRedrawTime(); #endif currentHue1 = max(min((increase ? currentHue1+fadeAmount : currentHue1-fadeAmount), 255), 0); - colorHStoRGB(currentHue1*256, currentSat1, col); + colorHStoRGB(currentHue1*256, currentSat1, colPri); stateChanged = true; if (applyToAll) { for (unsigned i=0; iupdateRedrawTime(); #endif currentSat1 = max(min((increase ? currentSat1+fadeAmount : currentSat1-fadeAmount), 255), 0); - colorHStoRGB(currentHue1*256, currentSat1, col); + colorHStoRGB(currentHue1*256, currentSat1, colPri); if (applyToAll) { for (unsigned i=0; i= (int)vWidth() || y0 < 0 || y0 >= (int)vHeight()) continue; // drawing off-screen if (((bits>>(j+(8-w))) & 0x01)) { // bit set - setPixelColorXY(x0, y0, c); + setPixelColorXY(x0, y0, c.color32); } } _colorScaled = false; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 65cb2640e..9acc36e7c 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -270,7 +270,7 @@ void Segment::startTransition(uint16_t dur) { _t->_briT = on ? opacity : 0; _t->_cctT = cct; #ifndef WLED_DISABLE_MODE_BLEND - swapSegenv(_t->_segT); + swapSegenv(_t->_segT); // copy runtime data to temporary _t->_modeT = mode; _t->_segT._dataLenT = 0; _t->_segT._dataT = nullptr; @@ -282,6 +282,13 @@ void Segment::startTransition(uint16_t dur) { _t->_segT._dataLenT = _dataLen; } } + DEBUG_PRINTF_P(PSTR("-- pal: %d, bri: %d, C:[%08X,%08X,%08X], m: %d\n"), + (int)_t->_palTid, + (int)_t->_briT, + _t->_segT._colorT[0], + _t->_segT._colorT[1], + _t->_segT._colorT[2], + (int)_t->_modeT); #else for (size_t i=0; i_colorT[i] = colors[i]; #endif @@ -502,21 +509,17 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui #ifndef WLED_DISABLE_2D if (Segment::maxHeight>1) boundsUnchanged &= (startY == i1Y && stopY == i2Y); // 2D #endif + + if (stop && (spc > 0 || m12 != map1D2D)) clear(); +/* if (boundsUnchanged && (!grp || (grouping == grp && spacing == spc)) && (ofs == UINT16_MAX || ofs == offset) && (m12 == map1D2D) ) return; - +*/ stateChanged = true; // send UDP/WS broadcast - if (stop || spc != spacing || m12 != map1D2D) { - _vWidth = virtualWidth(); - _vHeight = virtualHeight(); - _vLength = virtualLength(); - _segBri = currentBri(); - fill(BLACK); // turn old segment range off or clears pixels if changing spacing (requires _vWidth/_vHeight/_vLength/_segBri) - } if (grp) { // prevent assignment of 0 grouping = grp; spacing = spc; @@ -527,10 +530,7 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui if (ofs < UINT16_MAX) offset = ofs; map1D2D = constrain(m12, 0, 7); - DEBUG_PRINT(F("setUp segment: ")); DEBUG_PRINT(i1); - DEBUG_PRINT(','); DEBUG_PRINT(i2); - DEBUG_PRINT(F(" -> ")); DEBUG_PRINT(i1Y); - DEBUG_PRINT(','); DEBUG_PRINTLN(i2Y); + DEBUG_PRINTF_P(PSTR("Segment geometry: %d,%d -> %d,%d\n"), (int)i1, (int)i2, (int)i1Y, (int)i2Y); markForReset(); if (boundsUnchanged) return; @@ -1156,6 +1156,26 @@ void Segment::refreshLightCapabilities() { _capabilities = capabilities; } +/* + * Fills segment with black + */ +void Segment::clear() { + if (!isActive()) return; // not active + unsigned oldVW = _vWidth; + unsigned oldVH = _vHeight; + unsigned oldVL = _vLength; + unsigned oldSB = _segBri; + _vWidth = virtualWidth(); + _vHeight = virtualHeight(); + _vLength = virtualLength(); + _segBri = currentBri(); + fill(BLACK); + _vWidth = oldVW; + _vHeight = oldVH; + _vLength = oldVL; + _segBri = oldSB; +} + /* * Fills segment with color */ @@ -1175,42 +1195,45 @@ void Segment::fill(uint32_t c) { /* * fade out function, higher rate = quicker fade + * fading is highly dependant on frame rate (higher frame rates, faster fading) + * each frame will fade at max 9% or as little as 0.8% */ void Segment::fade_out(uint8_t rate) { if (!isActive()) return; // not active const int cols = is2D() ? vWidth() : vLength(); const int rows = vHeight(); // will be 1 for 1D - rate = (255-rate) >> 1; - float mappedRate = 1.0f / (float(rate) + 1.1f); - - uint32_t color = colors[1]; // SEGCOLOR(1); // target color - int w2 = W(color); - int r2 = R(color); - int g2 = G(color); - int b2 = B(color); + rate = (256-rate) >> 1; + const int mappedRate = 256 / (rate + 1); for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { - color = is2D() ? getPixelColorXY(x, y) : getPixelColor(x); + uint32_t color = is2D() ? getPixelColorXY(x, y) : getPixelColor(x); if (color == colors[1]) continue; // already at target color - int w1 = W(color); - int r1 = R(color); - int g1 = G(color); - int b1 = B(color); + for (int i = 0; i < 32; i += 8) { + uint8_t c2 = (colors[1]>>i); // get background channel + uint8_t c1 = (color>>i); // get foreground channel + // we can't use bitshift since we are using int + int delta = (c2 - c1) * mappedRate / 256; + // if fade isn't complete, make sure delta is at least 1 (fixes rounding issues) + if (delta == 0) delta += (c2 == c1) ? 0 : (c2 > c1) ? 1 : -1; + // stuff new value back into color + color &= ~(0xFF< w1) ? 1 : -1; - rdelta += (r2 == r1) ? 0 : (r2 > r1) ? 1 : -1; - gdelta += (g2 == g1) ? 0 : (g2 > g1) ? 1 : -1; - bdelta += (b2 == b1) ? 0 : (b2 > b1) ? 1 : -1; - - if (is2D()) setPixelColorXY(x, y, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); - else setPixelColor(x, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); + for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { + if (is2D()) setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), colors[1], fadeBy)); + else setPixelColor(x, color_blend(getPixelColor(x), colors[1], fadeBy)); } } @@ -1288,12 +1311,12 @@ uint32_t Segment::color_wheel(uint8_t pos) const { * Gets a single color from the currently selected palette. * @param i Palette Index (if mapping is true, the full palette will be _virtualSegmentLength long, if false, 255). Will wrap around automatically. * @param mapping if true, LED position in segment is considered for color - * @param wrap FastLED palettes will usually wrap back to the start smoothly. Set false to get a hard edge + * @param moving FastLED palettes will usually wrap back to the start smoothly. Set to true if effect has moving palette and you want wrap. * @param mcol If the default palette 0 is selected, return the standard color 0, 1 or 2 instead. If >2, Party palette is used instead * @param pbri Value to scale the brightness of the returned color by. Default is 255. (no scaling) * @returns Single color from palette */ -uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) const { +uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool moving, uint8_t mcol, uint8_t pbri) const { uint32_t color = getCurrentColor(mcol < NUM_COLORS ? mcol : 0); // default palette or no RGB support on segment if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) { @@ -1303,9 +1326,15 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_ const int vL = vLength(); unsigned paletteIndex = i; if (mapping && vL > 1) paletteIndex = (i*255)/(vL -1); - // 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" - CRGBW palcol = ColorFromPaletteWLED(_currentPalette, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global + // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined/no interpolation of palette entries) + // ColorFromPalette interpolations are: NOBLEND, LINEARBLEND, LINEARBLEND_NOWRAP + TBlendType blend = NOBLEND; + switch (strip.paletteBlend) { // NOTE: paletteBlend should be global + case 0: blend = moving ? LINEARBLEND : LINEARBLEND_NOWRAP; break; + case 1: blend = LINEARBLEND; break; + case 2: blend = LINEARBLEND_NOWRAP; break; + } + CRGBW palcol = ColorFromPalette(_currentPalette, paletteIndex, pbri, blend); palcol.w = W(color); return palcol.color32; @@ -1487,7 +1516,7 @@ void WS2812FX::service() { _segment_index = 0; for (segment &seg : _segments) { - if (_suspend) return; // immediately stop processing segments if suspend requested during service() + if (_suspend) break; // immediately stop processing segments if suspend requested during service() // process transition (mode changes in the middle of transition) seg.handleTransition(); @@ -1518,6 +1547,11 @@ void WS2812FX::service() { #ifndef WLED_DISABLE_MODE_BLEND Segment::setClippingRect(0, 0); // disable clipping (just in case) if (seg.isInTransition()) { + // a hack to determine if effect has changed + uint8_t m = seg.currentMode(); + Segment::modeBlend(true); // set semaphore + bool sameEffect = (m == seg.currentMode()); + Segment::modeBlend(false); // clear semaphore // set clipping rectangle // new mode is run inside clipping area and old mode outside clipping area unsigned p = seg.progress(); @@ -1526,7 +1560,20 @@ void WS2812FX::service() { 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) + if (w*h == 1) blendingStyle = BLEND_STYLE_FADE; // disable style for single pixel segments (use fade instead) + else if (sameEffect && (blendingStyle & BLEND_STYLE_PUSH_MASK)) { + // when effect stays the same push will look awful, change it to swipe + switch (blendingStyle) { + case BLEND_STYLE_PUSH_BR: + case BLEND_STYLE_PUSH_TR: + case BLEND_STYLE_PUSH_RIGHT: blendingStyle = BLEND_STYLE_SWIPE_RIGHT; break; + case BLEND_STYLE_PUSH_BL: + case BLEND_STYLE_PUSH_TL: + case BLEND_STYLE_PUSH_LEFT: blendingStyle = BLEND_STYLE_SWIPE_LEFT; break; + case BLEND_STYLE_PUSH_DOWN: blendingStyle = BLEND_STYLE_SWIPE_DOWN; break; + case BLEND_STYLE_PUSH_UP: blendingStyle = BLEND_STYLE_SWIPE_UP; break; + } + } switch (blendingStyle) { case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) Segment::setClippingRect(0, w, 0, h); @@ -1572,7 +1619,7 @@ void WS2812FX::service() { Segment::setClippingRect(0, dw, h - dh, h); break; } - frameDelay = (*_mode[seg.currentMode()])(); // run new/current mode + frameDelay = (*_mode[m])(); // run new/current mode // now run old/previous mode Segment::tmpsegd_t _tmpSegData; Segment::modeBlend(true); // set semaphore @@ -1608,8 +1655,8 @@ void WS2812FX::service() { if (doShow) { yield(); Segment::handleRandomPalette(); // slowly transition random palette; move it into for loop when each segment has individual random palette - show(); _lastServiceShow = nowUp; // update timestamp, for precise FPS control + if (!_suspend) show(); } #ifdef WLED_DEBUG if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); diff --git a/wled00/button.cpp b/wled00/button.cpp index 5144f09f2..cf8fabe42 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -40,7 +40,7 @@ void longPressAction(uint8_t b) { if (!macroLongPress[b]) { switch (b) { - case 0: setRandomColor(col); colorUpdated(CALL_MODE_BUTTON); break; + case 0: setRandomColor(colPri); colorUpdated(CALL_MODE_BUTTON); break; case 1: if(buttonBriDirection) { if (bri == 255) break; // avoid unnecessary updates to brightness @@ -230,7 +230,7 @@ void handleAnalog(uint8_t b) effectPalette = constrain(effectPalette, 0, strip.getPaletteCount()-1); // map is allowed to "overshoot", so we need to contrain the result } else if (macroDoublePress[b] == 200) { // primary color, hue, full saturation - colorHStoRGB(aRead*256,255,col); + colorHStoRGB(aRead*256,255,colPri); } else { // otherwise use "double press" for segment selection Segment& seg = strip.getSegment(macroDoublePress[b]); diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 7bfd71c29..ba38d5a15 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -10,12 +10,13 @@ */ uint32_t color_blend(uint32_t color1, uint32_t color2, uint8_t blend) { // min / max blend checking is omitted: calls with 0 or 255 are rare, checking lowers overall performance - uint32_t rb1 = color1 & 0x00FF00FF; - uint32_t wg1 = (color1>>8) & 0x00FF00FF; - uint32_t rb2 = color2 & 0x00FF00FF; - uint32_t wg2 = (color2>>8) & 0x00FF00FF; - uint32_t rb3 = ((((rb1 << 8) | rb2) + (rb2 * blend) - (rb1 * blend)) >> 8) & 0x00FF00FF; - uint32_t wg3 = ((((wg1 << 8) | wg2) + (wg2 * blend) - (wg1 * blend))) & 0xFF00FF00; + const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; // mask for R and B channels or W and G if negated (poorman's SIMD; https://github.com/wled/WLED/pull/4568#discussion_r1986587221) + uint32_t rb1 = color1 & TWO_CHANNEL_MASK; // extract R & B channels from color1 + uint32_t wg1 = (color1 >> 8) & TWO_CHANNEL_MASK; // extract W & G channels from color1 (shifted for multiplication later) + uint32_t rb2 = color2 & TWO_CHANNEL_MASK; // extract R & B channels from color2 + uint32_t wg2 = (color2 >> 8) & TWO_CHANNEL_MASK; // extract W & G channels from color2 (shifted for multiplication later) + uint32_t rb3 = ((((rb1 << 8) | rb2) + (rb2 * blend) - (rb1 * blend)) >> 8) & TWO_CHANNEL_MASK; // blend red and blue + uint32_t wg3 = ((((wg1 << 8) | wg2) + (wg2 * blend) - (wg1 * blend))) & ~TWO_CHANNEL_MASK; // negated mask for white and green return rb3 | wg3; } @@ -28,8 +29,9 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool preserveCR) { if (c1 == BLACK) return c2; if (c2 == BLACK) return c1; - uint32_t rb = (c1 & 0x00FF00FF) + (c2 & 0x00FF00FF); // mask and add two colors at once - uint32_t wg = ((c1>>8) & 0x00FF00FF) + ((c2>>8) & 0x00FF00FF); + const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; // mask for R and B channels or W and G if negated + uint32_t rb = ( c1 & TWO_CHANNEL_MASK) + ( c2 & TWO_CHANNEL_MASK); // mask and add two colors at once + uint32_t wg = ((c1>>8) & TWO_CHANNEL_MASK) + ((c2>>8) & TWO_CHANNEL_MASK); uint32_t r = rb >> 16; // extract single color values uint32_t b = rb & 0xFFFF; uint32_t w = wg >> 16; @@ -44,10 +46,10 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool preserveCR) //max = b > max ? b : max; //max = w > max ? w : max; if (max > 255) { - uint32_t scale = (uint32_t(255)<<8) / max; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead - rb = ((rb * scale) >> 8) & 0x00FF00FF; // - wg = (wg * scale) & 0xFF00FF00; - } else wg = wg << 8; //shift white and green back to correct position + const uint32_t scale = (uint32_t(255)<<8) / max; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead + rb = ((rb * scale) >> 8) & TWO_CHANNEL_MASK; + wg = (wg * scale) & ~TWO_CHANNEL_MASK; + } else wg <<= 8; //shift white and green back to correct position return rb | wg; } else { r = r > 255 ? 255 : r; @@ -77,8 +79,9 @@ uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) addRemains |= B(c1) ? 0x00000001 : 0; addRemains |= W(c1) ? 0x01000000 : 0; } - uint32_t rb = (((c1 & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; // scale red and blue - uint32_t wg = (((c1 & 0xFF00FF00) >> 8) * scale) & 0xFF00FF00; // scale white and green + const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; + uint32_t rb = (((c1 & TWO_CHANNEL_MASK) * scale) >> 8) & TWO_CHANNEL_MASK; // scale red and blue + uint32_t wg = (((c1 >> 8) & TWO_CHANNEL_MASK) * scale) & ~TWO_CHANNEL_MASK; // scale white and green scaledcolor = (rb | wg) + addRemains; return scaledcolor; } @@ -95,7 +98,7 @@ uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t unsigned red1 = entry->r; unsigned green1 = entry->g; unsigned blue1 = entry->b; - if(lo4 && blendType != NOBLEND) { + if (lo4 && blendType != NOBLEND) { if (hi4 == 15) entry = &(pal[0]); else ++entry; unsigned f2 = (lo4 << 4); @@ -105,6 +108,7 @@ uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8; } if (brightness < 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted + // actually color_fade(c1, brightness) uint32_t scale = brightness + 1; // adjust for rounding (bitshift) red1 = (red1 * scale) >> 8; // note: using color_fade() is 30% slower green1 = (green1 * scale) >> 8; diff --git a/wled00/data/index.js b/wled00/data/index.js index f3648d3b5..147abf389 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -409,7 +409,9 @@ function pName(i) function isPlaylist(i) { - return pJson[i].playlist && pJson[i].playlist.ps; + if (isNumeric(i)) return pJson[i].playlist && pJson[i].playlist.ps; + if (isObj(i)) return i.playlist && i.playlist.ps; + return false; } function papiVal(i) @@ -775,8 +777,8 @@ function populateSegments(s) } let segp = `
`+ - ``+ - `
`+ + ``+ + `
`+ ``+ `
`+ `
`+ @@ -814,7 +816,7 @@ function populateSegments(s) cn += `
`+ ``+ `
`+ `&#x${inst.frz ? (li.live && li.liveseg==i?'e410':'e0e8') : 'e325'};`+ @@ -1666,13 +1668,17 @@ function setEffectParameters(idx) paOnOff[0] = paOnOff[0].substring(0,dPos); } if (paOnOff.length>0 && paOnOff[0] != "!") text = paOnOff[0]; + gId("adPal").classList.remove("hide"); + if (lastinfo.cpalcount>0) gId("rmPal").classList.remove("hide"); } else { // disable palette list text += ' not used'; palw.style.display = "none"; + gId("adPal").classList.add("hide"); + gId("rmPal").classList.add("hide"); // Close palette dialog if not available - if (gId("palw").lastElementChild.tagName == "DIALOG") { - gId("palw").lastElementChild.close(); + if (palw.lastElementChild.tagName == "DIALOG") { + palw.lastElementChild.close(); } } pall.innerHTML = icon + text; @@ -1887,7 +1893,7 @@ function makeSeg() function resetUtil(off=false) { gId('segutil').innerHTML = `
` - + '' + + '' + `
Add segment
` + '
' + `` @@ -1901,15 +1907,16 @@ function resetUtil(off=false) if (lSeg>2) d.querySelectorAll("#Segments .pop").forEach((e)=>{e.classList.remove("hide");}); } -function makePlSel(el, incPl=false) +function makePlSel(p, el) { var plSelContent = ""; delete pJson["0"]; // remove filler preset Object.entries(pJson).sort(cmpP).forEach((a)=>{ var n = a[1].n ? a[1].n : "Preset " + a[0]; + if (isPlaylist(a[1])) n += ' ▶'; // mark playlist if (cfg.comp.idsort) n = a[0] + ' ' + n; - if (!(!incPl && a[1].playlist && a[1].playlist.ps)) // skip playlists, sub-playlists not yet supported - plSelContent += ``; + // skip endless playlists and itself + if (!isPlaylist(a[1]) || (a[1].playlist.repeat > 0 && a[0]!=p)) plSelContent += ``; }); return plSelContent; } @@ -1934,21 +1941,23 @@ function refreshPlE(p) }); } -// p: preset ID, i: ps index +// p: preset ID, i: playlist item index function addPl(p,i) { - plJson[p].ps.splice(i+1,0,0); - plJson[p].dur.splice(i+1,0,plJson[p].dur[i]); - plJson[p].transition.splice(i+1,0,plJson[p].transition[i]); + const pl = plJson[p]; + pl.ps.splice(i+1,0,1); + pl.dur.splice(i+1,0,pl.dur[i]); + pl.transition.splice(i+1,0,pl.transition[i]); refreshPlE(p); } function delPl(p,i) { - if (plJson[p].ps.length < 2) return; - plJson[p].ps.splice(i,1); - plJson[p].dur.splice(i,1); - plJson[p].transition.splice(i,1); + const pl = plJson[p]; + if (pl.ps.length < 2) return; + pl.ps.splice(i,1); + pl.dur.splice(i,1); + pl.transition.splice(i,1); refreshPlE(p); } @@ -1965,6 +1974,13 @@ function pleDur(p,i,field) function pleTr(p,i,field) { + const du = gId(`pl${p}du${i}`); + const dv = parseFloat(du.value); + if (dv > 0) { + field.max = dv; + if (parseFloat(field.value) > dv) + field.value = du.value; + } if (field.validity.valid) plJson[p].transition[i] = Math.floor(field.value*10); } @@ -1984,6 +2000,17 @@ function plR(p) } } +function plM(p) +{ + const man = gId(`pl${p}manual`).checked; + plJson[p].dur.forEach((e,i)=>{ + const d = gId(`pl${p}du${i}`); + plJson[p].dur[i] = e = man ? 0 : 100; + d.value = e/10; // 10s default + d.readOnly = man; + }); +} + function makeP(i,pl) { var content = ""; @@ -1997,12 +2024,17 @@ function makeP(i,pl) r: false, end: 0 }; - var rep = plJson[i].repeat ? plJson[i].repeat : 0; + const rep = plJson[i].repeat ? plJson[i].repeat : 0; + const man = plJson[i].dur == 0; content = `
+
`; @@ -2086,25 +2118,26 @@ function makePUtil() function makePlEntry(p,i) { + const man = gId(`pl${p}manual`).checked; return `
- + - - + +
DurationDuration (0=inf.) Transition #${i+1}
ssss
@@ -2657,28 +2690,28 @@ function fromRgb() var g = gId('sliderG').value; var b = gId('sliderB').value; setPicker(`rgb(${r},${g},${b})`); - let cd = gId('csl').children; // color slots - cd[csel].dataset.r = r; - cd[csel].dataset.g = g; - cd[csel].dataset.b = b; - setCSL(cd[csel]); + let cd = gId('csl').children[csel]; // color slots + cd.dataset.r = r; + cd.dataset.g = g; + cd.dataset.b = b; + setCSL(cd); } function fromW() { let w = gId('sliderW'); - let cd = gId('csl').children; // color slots - cd[csel].dataset.w = w.value; - setCSL(cd[csel]); + let cd = gId('csl').children[csel]; // color slots + cd.dataset.w = w.value; + setCSL(cd); updateTrail(w); } // sr 0: from RGB sliders, 1: from picker, 2: from hex function setColor(sr) { - var cd = gId('csl').children; // color slots - let cdd = cd[csel].dataset; - let w = 0, r,g,b; + var cd = gId('csl').children[csel]; // color slots + let cdd = cd.dataset; + let w = parseInt(cdd.w), r = parseInt(cdd.r), g = parseInt(cdd.g), b = parseInt(cdd.b); if (sr == 1 && isRgbBlack(cdd)) cpick.color.setChannel('hsv', 'v', 100); if (sr != 2 && hasWhite) w = parseInt(gId('sliderW').value); var col = cpick.color.rgb; @@ -2686,7 +2719,7 @@ function setColor(sr) cdd.g = g = hasRGB ? col.g : w; cdd.b = b = hasRGB ? col.b : w; cdd.w = w; - setCSL(cd[csel]); + setCSL(cd); var obj = {"seg": {"col": [[],[],[]]}}; obj.seg.col[csel] = [r, g, b, w]; requestJson(obj); diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 5effe0c6d..a45ed050e 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -139,7 +139,7 @@ gId("ppldis").style.display = ppl ? 'inline' : 'none'; // set PPL minimum value and clear actual PPL limit if ABL is disabled d.Sf.querySelectorAll("#mLC input[name^=MA]").forEach((i,x)=>{ - var n = String.fromCharCode((x<10?48:55)+x); + var n = chrID(x); gId("PSU"+n).style.display = ppl ? "inline" : "none"; const t = parseInt(d.Sf["LT"+n].value); // LED type SELECT const c = parseInt(d.Sf["LC"+n].value); //get LED count @@ -170,7 +170,7 @@ // select appropriate LED current d.Sf.querySelectorAll("#mLC select[name^=LAsel]").forEach((sel,x)=>{ sel.value = 0; // set custom - var n = String.fromCharCode((x<10?48:55)+x); + var n = chrID(x); if (en) switch (parseInt(d.Sf["LA"+n].value)) { case 0: break; // disable ABL @@ -401,7 +401,7 @@ } function lastEnd(i) { if (i-- < 1) return 0; - var s = String.fromCharCode((i<10?48:55)+i); + var s = chrID(i); v = parseInt(d.getElementsByName("LS"+s)[0].value) + parseInt(d.getElementsByName("LC"+s)[0].value); var t = parseInt(d.getElementsByName("LT"+s)[0].value); if (isPWM(t)) v = 1; //PWM busses @@ -508,7 +508,7 @@ mA/LED:   @@ -562,7 +562,7 @@ Swap: `; c += `  function checkSi() { //on load, checks whether there are custom start fields var cs = false; for (var i=1; i < gEBCN("iST").length; i++) { - var v = parseInt(gId("ls"+(i-1)).value) + parseInt(gN("LC"+(i-1)).value); - if (v != parseInt(gId("ls"+i).value)) {cs = true; startsDirty[i] = true;} + var s = chrID(i); + var p = chrID(i-1); // cover edge case 'A' previous char being '9' + var v = parseInt(gId("ls"+p).value) + parseInt(gN("LC"+p).value); + if (v != parseInt(gId("ls"+s).value)) {cs = true; startsDirty[i] = true;} } if (gId("ls0") && parseInt(gId("ls0").value) != 0) {cs = true; startsDirty[0] = true;} gId("si").checked = cs; diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 6584d524e..cabbde95a 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -327,6 +327,7 @@ void serializePlaylist(JsonObject obj); //presets.cpp const char *getPresetsFileName(bool persistent = true); +bool presetNeedsSaving(); void initPresetsFile(); void handlePresets(); bool applyPreset(byte index, byte callMode = CALL_MODE_DIRECT_CHANGE); diff --git a/wled00/hue.cpp b/wled00/hue.cpp index 950c54896..d5fcb7cb9 100644 --- a/wled00/hue.cpp +++ b/wled00/hue.cpp @@ -195,9 +195,9 @@ void onHueData(void* arg, AsyncClient* client, void *data, size_t len) { switch(hueColormode) { - case 1: if (hueX != hueXLast || hueY != hueYLast) colorXYtoRGB(hueX,hueY,col); hueXLast = hueX; hueYLast = hueY; break; - case 2: if (hueHue != hueHueLast || hueSat != hueSatLast) colorHStoRGB(hueHue,hueSat,col); hueHueLast = hueHue; hueSatLast = hueSat; break; - case 3: if (hueCt != hueCtLast) colorCTtoRGB(hueCt,col); hueCtLast = hueCt; break; + case 1: if (hueX != hueXLast || hueY != hueYLast) colorXYtoRGB(hueX,hueY,colPri); hueXLast = hueX; hueYLast = hueY; break; + case 2: if (hueHue != hueHueLast || hueSat != hueSatLast) colorHStoRGB(hueHue,hueSat,colPri); hueHueLast = hueHue; hueSatLast = hueSat; break; + case 3: if (hueCt != hueCtLast) colorCTtoRGB(hueCt,colPri); hueCtLast = hueCt; break; } } hueReceived = true; diff --git a/wled00/json.cpp b/wled00/json.cpp index 200307464..24988be15 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -91,14 +91,20 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) } } - uint16_t grp = elem["grp"] | seg.grouping; - uint16_t spc = elem[F("spc")] | seg.spacing; - uint16_t of = seg.offset; - uint8_t soundSim = elem["si"] | seg.soundSim; - uint8_t map1D2D = elem["m12"] | seg.map1D2D; - uint8_t set = elem[F("set")] | seg.set; - seg.set = constrain(set, 0, 3); - seg.soundSim = constrain(soundSim, 0, 3); + uint16_t grp = elem["grp"] | seg.grouping; + uint16_t spc = elem[F("spc")] | seg.spacing; + uint16_t of = seg.offset; + uint8_t soundSim = elem["si"] | seg.soundSim; + uint8_t map1D2D = elem["m12"] | seg.map1D2D; + uint8_t set = elem[F("set")] | seg.set; + bool selected = getBoolVal(elem["sel"], seg.selected); + bool reverse = getBoolVal(elem["rev"], seg.reverse); + bool mirror = getBoolVal(elem["mi"] , seg.mirror); + #ifndef WLED_DISABLE_2D + bool reverse_y = getBoolVal(elem["rY"] , seg.reverse_y); + bool mirror_y = getBoolVal(elem["mY"] , seg.mirror_y); + bool transpose = getBoolVal(elem[F("tp")], seg.transpose); + #endif int len = (stop > start) ? stop - start : 1; int offset = elem[F("of")] | INT32_MAX; @@ -200,20 +206,16 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) } #endif + //seg.map1D2D = constrain(map1D2D, 0, 7); // done in setGeometry() + seg.set = constrain(set, 0, 3); + seg.soundSim = constrain(soundSim, 0, 3); + seg.selected = selected; + seg.reverse = reverse; + seg.mirror = mirror; #ifndef WLED_DISABLE_2D - bool reverse = seg.reverse; - bool mirror = seg.mirror; - #endif - seg.selected = getBoolVal(elem["sel"], seg.selected); - seg.reverse = getBoolVal(elem["rev"], seg.reverse); - seg.mirror = getBoolVal(elem["mi"] , seg.mirror); - #ifndef WLED_DISABLE_2D - bool reverse_y = seg.reverse_y; - bool mirror_y = seg.mirror_y; - seg.reverse_y = getBoolVal(elem["rY"] , seg.reverse_y); - seg.mirror_y = getBoolVal(elem["mY"] , seg.mirror_y); - seg.transpose = getBoolVal(elem[F("tp")], seg.transpose); - if (seg.is2D() && seg.map1D2D == M12_pArc && (reverse != seg.reverse || reverse_y != seg.reverse_y || mirror != seg.mirror || mirror_y != seg.mirror_y)) seg.fill(BLACK); // clear entire segment (in case of Arc 1D to 2D expansion) + seg.reverse_y = reverse_y; + seg.mirror_y = mirror_y; + seg.transpose = transpose; #endif byte fx = seg.mode; @@ -392,35 +394,38 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) int it = 0; JsonVariant segVar = root["seg"]; - if (!segVar.isNull()) strip.suspend(); - if (segVar.is()) - { - int id = segVar["id"] | -1; - //if "seg" is not an array and ID not specified, apply to all selected/checked segments - if (id < 0) { - //apply all selected segments - //bool didSet = false; - for (size_t s = 0; s < strip.getSegmentsNum(); s++) { - Segment &sg = strip.getSegment(s); - if (sg.isActive() && sg.isSelected()) { - deserializeSegment(segVar, s, presetId); - //didSet = true; + if (!segVar.isNull()) { + // we may be called during strip.service() so we must not modify segments while effects are executing + strip.suspend(); + const unsigned long start = millis(); + while (strip.isServicing() && millis() - start < strip.getFrameTime()) yield(); // wait until frame is over + #ifdef WLED_DEBUG + if (millis() - start > 0) DEBUG_PRINTLN(F("JSON: Waited for strip to finish servicing.")); + #endif + if (segVar.is()) { + int id = segVar["id"] | -1; + //if "seg" is not an array and ID not specified, apply to all selected/checked segments + if (id < 0) { + //apply all selected segments + for (size_t s = 0; s < strip.getSegmentsNum(); s++) { + Segment &sg = strip.getSegment(s); + if (sg.isActive() && sg.isSelected()) { + deserializeSegment(segVar, s, presetId); + } } + } else { + deserializeSegment(segVar, id, presetId); //apply only the segment with the specified ID } - //TODO: not sure if it is good idea to change first active but unselected segment - //if (!didSet) deserializeSegment(segVar, strip.getMainSegmentId(), presetId); } else { - deserializeSegment(segVar, id, presetId); //apply only the segment with the specified ID + size_t deleted = 0; + JsonArray segs = segVar.as(); + for (JsonObject elem : segs) { + if (deserializeSegment(elem, it++, presetId) && !elem["stop"].isNull() && elem["stop"]==0) deleted++; + } + if (strip.getSegmentsNum() > 3 && deleted >= strip.getSegmentsNum()/2U) strip.purgeSegments(); // batch deleting more than half segments } - } else { - size_t deleted = 0; - JsonArray segs = segVar.as(); - for (JsonObject elem : segs) { - if (deserializeSegment(elem, it++, presetId) && !elem["stop"].isNull() && elem["stop"]==0) deleted++; - } - if (strip.getSegmentsNum() > 3 && deleted >= strip.getSegmentsNum()/2U) strip.purgeSegments(); // batch deleting more than half segments + strip.resume(); } - strip.resume(); UsermodManager::readFromJsonState(root); diff --git a/wled00/led.cpp b/wled00/led.cpp index 9731da655..2d2f5b6f2 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -9,10 +9,10 @@ void setValuesFromFirstSelectedSeg() { setValuesFromSegment(strip.getFirstSelect void setValuesFromSegment(uint8_t s) { Segment& seg = strip.getSegment(s); - col[0] = R(seg.colors[0]); - col[1] = G(seg.colors[0]); - col[2] = B(seg.colors[0]); - col[3] = W(seg.colors[0]); + colPri[0] = R(seg.colors[0]); + colPri[1] = G(seg.colors[0]); + colPri[2] = B(seg.colors[0]); + colPri[3] = W(seg.colors[0]); colSec[0] = R(seg.colors[1]); colSec[1] = G(seg.colors[1]); colSec[2] = B(seg.colors[1]); @@ -39,7 +39,7 @@ void applyValuesToSelectedSegs() if (effectIntensity != selsegPrev.intensity) {seg.intensity = effectIntensity; stateChanged = true;} if (effectPalette != selsegPrev.palette) {seg.setPalette(effectPalette);} if (effectCurrent != selsegPrev.mode) {seg.setMode(effectCurrent);} - uint32_t col0 = RGBW32( col[0], col[1], col[2], col[3]); + uint32_t col0 = RGBW32(colPri[0], colPri[1], colPri[2], colPri[3]); uint32_t col1 = RGBW32(colSec[0], colSec[1], colSec[2], colSec[3]); if (col0 != selsegPrev.colors[0]) {seg.setColor(0, col0);} if (col1 != selsegPrev.colors[1]) {seg.setColor(1, col1);} @@ -112,10 +112,11 @@ void stateUpdated(byte callMode) { } } + unsigned long now = millis(); if (callMode != CALL_MODE_NO_NOTIFY && nightlightActive && (nightlightMode == NL_MODE_FADE || nightlightMode == NL_MODE_COLORFADE)) { briNlT = bri; - nightlightDelayMs -= (millis() - nightlightStartTime); - nightlightStartTime = millis(); + nightlightDelayMs -= (now - nightlightStartTime); + nightlightStartTime = now; } if (briT == 0) { if (callMode != CALL_MODE_NOTIFICATION) strip.resetTimebase(); //effect start from beginning @@ -141,7 +142,7 @@ void stateUpdated(byte callMode) { } else strip.setTransitionMode(true); // force all segments to transition mode transitionActive = true; - transitionStartTime = millis(); + transitionStartTime = now; } @@ -150,14 +151,14 @@ void updateInterfaces(uint8_t callMode) { sendDataWs(); lastInterfaceUpdate = millis(); - interfaceUpdateCallMode = 0; //disable further updates + interfaceUpdateCallMode = CALL_MODE_INIT; //disable further updates if (callMode == CALL_MODE_WS_SEND) return; #ifndef WLED_DISABLE_ALEXA if (espalexaDevice != nullptr && callMode != CALL_MODE_ALEXA) { espalexaDevice->setValue(bri); - espalexaDevice->setColor(col[0], col[1], col[2]); + espalexaDevice->setColor(colPri[0], colPri[1], colPri[2]); } #endif #ifndef WLED_DISABLE_MQTT @@ -211,7 +212,7 @@ void handleNightlight() { nightlightDelayMs = (unsigned)(nightlightDelayMins*60000); nightlightActiveOld = true; briNlT = bri; - for (unsigned i=0; i<4; i++) colNlT[i] = col[i]; // remember starting color + for (unsigned i=0; i<4; i++) colNlT[i] = colPri[i]; // remember starting color if (nightlightMode == NL_MODE_SUN) { //save current @@ -236,7 +237,7 @@ void handleNightlight() { bri = briNlT + ((nightlightTargetBri - briNlT)*nper); if (nightlightMode == NL_MODE_COLORFADE) // color fading only is enabled with "NF=2" { - for (unsigned i=0; i<4; i++) col[i] = colNlT[i]+ ((colSec[i] - colNlT[i])*nper); // fading from actual color to secondary color + for (unsigned i=0; i<4; i++) colPri[i] = colNlT[i]+ ((colSec[i] - colNlT[i])*nper); // fading from actual color to secondary color } colorUpdated(CALL_MODE_NO_NOTIFY); } diff --git a/wled00/mqtt.cpp b/wled00/mqtt.cpp index 8cbe79093..a462881ec 100644 --- a/wled00/mqtt.cpp +++ b/wled00/mqtt.cpp @@ -103,7 +103,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp //Prefix is stripped from the topic at this point if (strcmp_P(topic, PSTR("/col")) == 0) { - colorFromDecOrHexString(col, payloadStr); + colorFromDecOrHexString(colPri, payloadStr); colorUpdated(CALL_MODE_DIRECT_CHANGE); } else if (strcmp_P(topic, PSTR("/api")) == 0) { if (requestJSONBufferLock(15)) { @@ -169,7 +169,7 @@ void publishMqtt() strcat_P(subuf, PSTR("/g")); mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) - sprintf_P(s, PSTR("#%06X"), (col[3] << 24) | (col[0] << 16) | (col[1] << 8) | (col[2])); + sprintf_P(s, PSTR("#%06X"), (colPri[3] << 24) | (colPri[0] << 16) | (colPri[1] << 8) | (colPri[2])); strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(subuf, PSTR("/c")); mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) diff --git a/wled00/playlist.cpp b/wled00/playlist.cpp index 1908334d7..2e51503e3 100644 --- a/wled00/playlist.cpp +++ b/wled00/playlist.cpp @@ -10,19 +10,19 @@ typedef struct PlaylistEntry { uint16_t tr; //Duration of the transition TO this entry (in tenths of seconds) } ple; -byte playlistRepeat = 1; //how many times to repeat the playlist (0 = infinitely) -byte playlistEndPreset = 0; //what preset to apply after playlist end (0 = stay on last preset) -byte playlistOptions = 0; //bit 0: shuffle playlist after each iteration. bits 1-7 TBD +static byte playlistRepeat = 1; //how many times to repeat the playlist (0 = infinitely) +static byte playlistEndPreset = 0; //what preset to apply after playlist end (0 = stay on last preset) +static byte playlistOptions = 0; //bit 0: shuffle playlist after each iteration. bits 1-7 TBD -PlaylistEntry *playlistEntries = nullptr; -byte playlistLen; //number of playlist entries -int8_t playlistIndex = -1; -uint16_t playlistEntryDur = 0; //duration of the current entry in tenths of seconds +static PlaylistEntry *playlistEntries = nullptr; +static byte playlistLen; //number of playlist entries +static int8_t playlistIndex = -1; +static uint16_t playlistEntryDur = 0; //duration of the current entry in tenths of seconds //values we need to keep about the parent playlist while inside sub-playlist -//int8_t parentPlaylistIndex = -1; -//byte parentPlaylistRepeat = 0; -//byte parentPlaylistPresetId = 0; //for re-loading +static int16_t parentPlaylistIndex = -1; +static byte parentPlaylistRepeat = 0; +static byte parentPlaylistPresetId = 0; //for re-loading void shufflePlaylist() { @@ -54,6 +54,12 @@ void unloadPlaylist() { int16_t loadPlaylist(JsonObject playlistObj, byte presetId) { + if (currentPlaylist > 0 && parentPlaylistPresetId > 0) return -1; // we are already in nested playlist, do nothing + if (currentPlaylist > 0) { + parentPlaylistIndex = playlistIndex; + parentPlaylistRepeat = playlistRepeat; + parentPlaylistPresetId = currentPlaylist; + } unloadPlaylist(); JsonArray presets = playlistObj["ps"]; @@ -79,7 +85,7 @@ int16_t loadPlaylist(JsonObject playlistObj, byte presetId) { } else { for (int dur : durations) { if (it >= playlistLen) break; - playlistEntries[it].dur = (dur > 1) ? dur : 100; + playlistEntries[it].dur = constrain(dur, 0, 65530); it++; } } @@ -117,6 +123,19 @@ int16_t loadPlaylist(JsonObject playlistObj, byte presetId) { shuffle = shuffle || playlistObj["r"]; if (shuffle) playlistOptions |= PL_OPTION_SHUFFLE; + if (parentPlaylistPresetId == 0 && parentPlaylistIndex > -1) { + // we are re-loading playlist when returning from nested playlist + playlistIndex = parentPlaylistIndex; + playlistRepeat = parentPlaylistRepeat; + parentPlaylistIndex = -1; + parentPlaylistRepeat = 0; + } else if (rep == 0) { + // endless playlist will never return to parent so erase parent information if it was called from it + parentPlaylistPresetId = 0; + parentPlaylistIndex = -1; + parentPlaylistRepeat = 0; + } + currentPlaylist = presetId; DEBUG_PRINTLN(F("Playlist loaded.")); return currentPlaylist; @@ -127,7 +146,7 @@ void handlePlaylist() { static unsigned long presetCycledTime = 0; if (currentPlaylist < 0 || playlistEntries == nullptr) return; -if (millis() - presetCycledTime > (100 * playlistEntryDur) || doAdvancePlaylist) { + if ((playlistEntryDur < UINT16_MAX && millis() - presetCycledTime > 100 * playlistEntryDur) || doAdvancePlaylist) { presetCycledTime = millis(); if (bri == 0 || nightlightActive) return; @@ -137,7 +156,10 @@ if (millis() - presetCycledTime > (100 * playlistEntryDur) || doAdvancePlaylist) if (!playlistIndex) { if (playlistRepeat == 1) { //stop if all repetitions are done unloadPlaylist(); - if (playlistEndPreset) applyPresetFromPlaylist(playlistEndPreset); + if (parentPlaylistPresetId > 0) { + applyPresetFromPlaylist(parentPlaylistPresetId); // reload previous playlist (unfortunately asynchronous) + parentPlaylistPresetId = 0; // reset previous playlist but do not reset Index or Repeat (they will be loaded & reset in loadPlaylist()) + } else if (playlistEndPreset) applyPresetFromPlaylist(playlistEndPreset); return; } if (playlistRepeat > 1) playlistRepeat--; // decrease repeat count on each index reset if not an endless playlist @@ -147,7 +169,7 @@ if (millis() - presetCycledTime > (100 * playlistEntryDur) || doAdvancePlaylist) jsonTransitionOnce = true; strip.setTransition(playlistEntries[playlistIndex].tr * 100); - playlistEntryDur = playlistEntries[playlistIndex].dur; + playlistEntryDur = playlistEntries[playlistIndex].dur > 0 ? playlistEntries[playlistIndex].dur : UINT16_MAX; applyPresetFromPlaylist(playlistEntries[playlistIndex].preset); doAdvancePlaylist = false; } diff --git a/wled00/presets.cpp b/wled00/presets.cpp index 15eed3e46..54f052637 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -22,6 +22,10 @@ const char *getPresetsFileName(bool persistent) { return persistent ? presets_json : tmp_json; } +bool presetNeedsSaving() { + return presetToSave; +} + static void doSaveState() { bool persist = (presetToSave < 251); @@ -269,7 +273,7 @@ void savePreset(byte index, const char* pname, JsonObject sObj) quickLoad = nullptr; } else { // store playlist - // WARNING: playlist will be loaded in json.cpp after this call and will have repeat counter increased by 1 + // WARNING: playlist will be loaded in json.cpp after this call and will have repeat counter increased by 1 it will also be randomised if selected includeBri = true; // !sObj["on"].isNull(); playlistSave = true; } diff --git a/wled00/set.cpp b/wled00/set.cpp index c37f24533..00333788d 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -146,7 +146,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) bool busesChanged = false; for (int s = 0; s < 36; s++) { // theoretical limit is 36 : "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" - int offset = s < 10 ? 48 : 55; + int offset = s < 10 ? '0' : 'A'; char lp[4] = "L0"; lp[2] = offset+s; lp[3] = 0; //ascii 0-9 //strip data pin char lc[4] = "LC"; lc[2] = offset+s; lc[3] = 0; //strip length char co[4] = "CO"; co[2] = offset+s; co[3] = 0; //strip color order @@ -165,7 +165,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) break; } for (int i = 0; i < 5; i++) { - lp[1] = offset+i; + lp[1] = '0'+i; if (!request->hasArg(lp)) break; pins[i] = (request->arg(lp).length() > 0) ? request->arg(lp).toInt() : 255; } @@ -220,7 +220,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) // we will not bother with pre-allocating ColorOrderMappings vector BusManager::getColorOrderMap().reset(); for (int s = 0; s < WLED_MAX_COLOR_ORDER_MAPPINGS; s++) { - int offset = s < 10 ? 48 : 55; + int offset = s < 10 ? '0' : 'A'; char xs[4] = "XS"; xs[2] = offset+s; xs[3] = 0; //start LED char xc[4] = "XC"; xc[2] = offset+s; xc[3] = 0; //strip length char xo[4] = "XO"; xo[2] = offset+s; xo[3] = 0; //color order @@ -259,7 +259,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) disablePullUp = (bool)request->hasArg(F("IP")); touchThreshold = request->arg(F("TT")).toInt(); for (int i = 0; i < WLED_MAX_BUTTONS; i++) { - int offset = i < 10 ? 48 : 55; + int offset = i < 10 ? '0' : 'A'; char bt[4] = "BT"; bt[2] = offset+i; bt[3] = 0; // button pin (use A,B,C,... if WLED_MAX_BUTTONS>10) char be[4] = "BE"; be[2] = offset+i; be[3] = 0; // button type (use A,B,C,... if WLED_MAX_BUTTONS>10) int hw_btn_pin = request->arg(bt).toInt(); @@ -1193,7 +1193,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) } // you can add more if you need - // global col[], effectCurrent, ... are updated in stateChanged() + // global colPri[], effectCurrent, ... are updated in stateChanged() if (!apply) return true; // when called by JSON API, do not call colorUpdated() here pos = req.indexOf(F("&NN")); //do not send UDP notifications this time diff --git a/wled00/wled.cpp b/wled00/wled.cpp index bafdf58a9..7bfd2d5da 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -109,7 +109,6 @@ void WLED::loop() if (WLED_CONNECTED && aOtaEnabled && !otaLock && correctPIN) ArduinoOTA.handle(); #endif handleNightlight(); - handlePlaylist(); yield(); #ifndef WLED_DISABLE_HUESYNC @@ -117,6 +116,10 @@ void WLED::loop() yield(); #endif + if (!presetNeedsSaving()) { + handlePlaylist(); + yield(); + } handlePresets(); yield(); @@ -464,7 +467,7 @@ void WLED::setup() #endif // fill in unique mdns default - if (strcmp(cmDNS, "x") == 0) sprintf_P(cmDNS, PSTR("wled-%*s"), 6, escapedMac.c_str() + 6); + if (strcmp(cmDNS, DEFAULT_MDNS_NAME) == 0) sprintf_P(cmDNS, PSTR("wled-%*s"), 6, escapedMac.c_str() + 6); #ifndef WLED_DISABLE_MQTT if (mqttDeviceTopic[0] == 0) sprintf_P(mqttDeviceTopic, PSTR("wled/%*s"), 6, escapedMac.c_str() + 6); if (mqttClientID[0] == 0) sprintf_P(mqttClientID, PSTR("WLED-%*s"), 6, escapedMac.c_str() + 6); @@ -547,16 +550,16 @@ void WLED::beginStrip() Segment &seg = strip.getSegment(i); if (seg.isActive()) seg.colors[0] = BLACK; } - col[0] = col[1] = col[2] = col[3] = 0; // needed for colorUpdated() + colPri[0] = colPri[1] = colPri[2] = colPri[3] = 0; // needed for colorUpdated() } briLast = briS; bri = 0; strip.fill(BLACK); strip.show(); } + colorUpdated(CALL_MODE_INIT); // will not send notification but will initiate transition if (bootPreset > 0) { applyPreset(bootPreset, CALL_MODE_INIT); } - colorUpdated(CALL_MODE_INIT); // will not send notification // init relay pin if (rlyPin >= 0) { diff --git a/wled00/wled.h b/wled00/wled.h index 8ece2c356..ea40c5dfe 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -419,7 +419,7 @@ WLED_GLOBAL bool gammaCorrectCol _INIT(true); // use gamma correction on col WLED_GLOBAL bool gammaCorrectBri _INIT(false); // use gamma correction on brightness WLED_GLOBAL float gammaCorrectVal _INIT(2.8f); // gamma correction value -WLED_GLOBAL byte col[] _INIT_N(({ 255, 160, 0, 0 })); // current RGB(W) primary color. col[] should be updated if you want to change the color. +WLED_GLOBAL byte colPri[] _INIT_N(({ 255, 160, 0, 0 })); // current RGB(W) primary color. colPri[] should be updated if you want to change the color. WLED_GLOBAL byte colSec[] _INIT_N(({ 0, 0, 0, 0 })); // current RGB(W) secondary color WLED_GLOBAL byte nightlightTargetBri _INIT(0); // brightness after nightlight is over diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 3c15ee085..19868d01d 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -11,7 +11,7 @@ void XML_response(Print& dest) dest.printf_P(PSTR("%d"), (nightlightActive && nightlightMode > NL_MODE_SET) ? briT : bri); for (int i = 0; i < 3; i++) { - dest.printf_P(PSTR("%d"), col[i]); + dest.printf_P(PSTR("%d"), colPri[i]); } for (int i = 0; i < 3; i++) { @@ -20,7 +20,7 @@ void XML_response(Print& dest) dest.printf_P(PSTR("%d%d%d%d%d%d%d%d%d%d%d%d%d%d%s%s%d"), notifyDirect, receiveGroups!=0, nightlightActive, nightlightMode > NL_MODE_SET, nightlightDelayMins, nightlightTargetBri, effectCurrent, effectSpeed, effectIntensity, effectPalette, - strip.hasWhiteChannel() ? col[3] : -1, colSec[3], currentPreset, currentPlaylist >= 0, + strip.hasWhiteChannel() ? colPri[3] : -1, colSec[3], currentPreset, currentPlaylist >= 0, serverDescription, realtimeMode ? PSTR(" (live)") : "", strip.getFirstSelectedSegId() ); @@ -296,9 +296,9 @@ void getSettingsJS(byte subPage, Print& settingsScript) unsigned sumMa = 0; for (int s = 0; s < BusManager::getNumBusses(); s++) { - const Bus *bus = BusManager::getBus(s); + const Bus* bus = BusManager::getBus(s); if (!bus || !bus->isOk()) break; // should not happen but for safety - int offset = s < 10 ? 48 : 55; + int offset = s < 10 ? '0' : 'A'; char lp[4] = "L0"; lp[2] = offset+s; lp[3] = 0; //ascii 0-9 //strip data pin char lc[4] = "LC"; lc[2] = offset+s; lc[3] = 0; //strip length char co[4] = "CO"; co[2] = offset+s; co[3] = 0; //strip color order