Merge branch 'main' into usermod-libs

This commit is contained in:
Will Tatam 2025-03-11 22:31:52 +00:00
commit 0ba80ce61e
32 changed files with 853 additions and 2127 deletions

View File

@ -8,6 +8,9 @@
runs-on: ubuntu-latest
steps:
- name: Send Discord notification
shell: bash
env:
DISCORD_WEBHOOK_BETA_TESTERS: ${{ secrets.DISCORD_WEBHOOK_BETA_TESTERS }}
if: github.event.pull_request.merged == true
run: |
curl -H "Content-Type: application/json" -d '{"content": "Pull Request #{{ github.event.pull_request.number }} merged by {{ github.actor }}"}' ${{ secrets.DISCORD_WEBHOOK_BETA_TESTERS }}
curl -H "Content-Type: application/json" -d '{"content": "Pull Request #{{ github.event.pull_request.number }} merged by {{ github.actor }}"}' $DISCORD_WEBHOOK_BETA_TESTERS

1899
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -25,10 +25,10 @@
"dependencies": {
"clean-css": "^5.3.3",
"html-minifier-terser": "^7.2.0",
"inliner": "^1.13.1",
"nodemon": "^3.1.7"
"web-resource-inliner": "^7.0.0",
"nodemon": "^3.1.9"
},
"engines": {
"node": ">=20.0.0"
}
}
}

View File

@ -17,7 +17,7 @@
const fs = require("node:fs");
const path = require("path");
const inliner = require("inliner");
const inline = require("web-resource-inliner");
const zlib = require("node:zlib");
const CleanCSS = require("clean-css");
const minifyHtml = require("html-minifier-terser").minify;
@ -128,21 +128,26 @@ async function minify(str, type = "plain") {
async function writeHtmlGzipped(sourceFile, resultFile, page) {
console.info("Reading " + sourceFile);
new inliner(sourceFile, async function (error, html) {
if (error) throw error;
inline.html({
fileContent: fs.readFileSync(sourceFile, "utf8"),
relativeTo: path.dirname(sourceFile),
strict: true,
},
async function (error, html) {
if (error) throw error;
html = adoptVersionAndRepo(html);
const originalLength = html.length;
html = await minify(html, "html-minify");
const result = zlib.gzipSync(html, { level: zlib.constants.Z_BEST_COMPRESSION });
console.info("Minified and compressed " + sourceFile + " from " + originalLength + " to " + result.length + " bytes");
const array = hexdump(result);
let src = singleHeader;
src += `const uint16_t PAGE_${page}_L = ${result.length};\n`;
src += `const uint8_t PAGE_${page}[] PROGMEM = {\n${array}\n};\n\n`;
console.info("Writing " + resultFile);
fs.writeFileSync(resultFile, src);
});
html = adoptVersionAndRepo(html);
const originalLength = html.length;
html = await minify(html, "html-minify");
const result = zlib.gzipSync(html, { level: zlib.constants.Z_BEST_COMPRESSION });
console.info("Minified and compressed " + sourceFile + " from " + originalLength + " to " + result.length + " bytes");
const array = hexdump(result);
let src = singleHeader;
src += `const uint16_t PAGE_${page}_L = ${result.length};\n`;
src += `const uint8_t PAGE_${page}[] PROGMEM = {\n${array}\n};\n\n`;
console.info("Writing " + resultFile);
fs.writeFileSync(resultFile, src);
});
}
async function specToChunk(srcDir, s) {

View File

@ -37,6 +37,7 @@ Open Usermod Settings in WLED to change settings:
No special requirements.
## Change Log
2021-07
* Upgraded to work with the latest WLED code, and make settings configurable in Usermod Settings
- 2021-07<br>
Upgraded to work with the latest WLED code, and make settings configurable in Usermod Settings
- 2025-03<br>
Upgraded to work with the latest WLED code

View File

@ -93,9 +93,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)
@ -104,9 +104,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)
@ -118,9 +118,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)
@ -129,9 +129,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)

View File

@ -516,7 +516,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();
@ -918,17 +918,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; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue;
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
seg.colors[0] = RGBW32(colPri[0], colPri[1], colPri[2], colPri[3]);
}
} else {
Segment& seg = strip.getSegment(strip.getMainSegmentId());
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
seg.colors[0] = RGBW32(colPri[0], colPri[1], colPri[2], colPri[3]);
}
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY
@ -948,16 +948,16 @@ void RotaryEncoderUIUsermod::changeSat(bool increase){
display->updateRedrawTime();
#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<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue;
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
seg.colors[0] = RGBW32(colPri[0], colPri[1], colPri[2], colPri[3]);
}
} else {
Segment& seg = strip.getSegment(strip.getMainSegmentId());
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
seg.colors[0] = RGBW32(colPri[0], colPri[1], colPri[2], colPri[3]);
}
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY

View File

@ -676,8 +676,10 @@ typedef struct Segment {
[[gnu::hot]] uint32_t getPixelColor(int i) const;
// 1D support functions (some implement 2D as well)
void blur(uint8_t, bool smear = false);
void clear();
void fill(uint32_t c);
void fade_out(uint8_t r);
void fadeToSecondaryBy(uint8_t fadeBy);
void fadeToBlackBy(uint8_t fadeBy);
inline void blendPixelColor(int n, uint32_t color, uint8_t blend) { setPixelColor(n, color_blend(getPixelColor(n), color, blend)); }
inline void blendPixelColor(int n, CRGB c, uint8_t blend) { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); }

View File

@ -684,9 +684,7 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w,
case 60: bits = pgm_read_byte_near(&console_font_5x12[(chr * h) + i]); break; // 5x12 font
default: return;
}
uint32_t c = ColorFromPaletteWLED(grad, (i+1)*255/h, 255, NOBLEND);
// pre-scale color for all pixels
c = color_fade(c, _segBri);
CRGBW c = ColorFromPalette(grad, (i+1)*255/h, _segBri, LINEARBLEND_NOWRAP);
_colorScaled = true;
for (int j = 0; j<w; j++) { // character width
int x0, y0;
@ -699,7 +697,7 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w,
}
if (x0 < 0 || x0 >= (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;

View File

@ -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<NUM_COLORS; i++) _t->_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;
@ -1138,12 +1138,9 @@ void Segment::refreshLightCapabilities() {
}
for (unsigned b = 0; b < BusManager::getNumBusses(); b++) {
Bus *bus = BusManager::getBus(b);
if (bus == nullptr || bus->getLength()==0) break;
if (!bus->isOk()) continue;
if (bus->getStart() >= segStopIdx) continue;
if (bus->getStart() + bus->getLength() <= segStartIdx) continue;
const Bus *bus = BusManager::getBus(b);
if (!bus || !bus->isOk()) break;
if (bus->getStart() >= segStopIdx || bus->getStart() + bus->getLength() <= segStartIdx) continue;
if (bus->hasRGB() || (strip.cctFromRgb && bus->hasCCT())) capabilities |= SEG_CAPABILITY_RGB;
if (!strip.cctFromRgb && bus->hasCCT()) capabilities |= SEG_CAPABILITY_CCT;
if (strip.correctWB && (bus->hasRGB() || bus->hasCCT())) capabilities |= SEG_CAPABILITY_CCT; //white balance correction (CCT slider)
@ -1159,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
*/
@ -1178,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<<i);
color |= ((c1 + delta) & 0xFF) << i;
}
if (is2D()) setPixelColorXY(x, y, color);
else setPixelColor(x, color);
}
}
int wdelta = (w2 - w1) * mappedRate;
int rdelta = (r2 - r1) * mappedRate;
int gdelta = (g2 - g1) * mappedRate;
int bdelta = (b2 - b1) * mappedRate;
// fades all pixels to secondary color
void Segment::fadeToSecondaryBy(uint8_t fadeBy) {
if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply
const int cols = is2D() ? vWidth() : vLength();
const int rows = vHeight(); // will be 1 for 1D
// if fade isn't complete, make sure delta is at least 1 (fixes rounding issues)
wdelta += (w2 == w1) ? 0 : (w2 > 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));
}
}
@ -1291,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) {
@ -1306,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;
@ -1449,8 +1475,7 @@ void WS2812FX::finalizeInit() {
_length = 0;
for (int i=0; i<BusManager::getNumBusses(); i++) {
Bus *bus = BusManager::getBus(i);
if (bus == nullptr) continue;
if (bus->getStart() + bus->getLength() > MAX_LEDS) break;
if (!bus || !bus->isOk() || bus->getStart() + bus->getLength() > MAX_LEDS) break;
//RGBW mode is enabled if at least one of the strips is RGBW
_hasWhiteChannel |= bus->hasWhite();
//refresh is required to remain off if at least one of the strips requires the refresh.
@ -1460,6 +1485,7 @@ void WS2812FX::finalizeInit() {
// This must be done after all buses have been created, as some kinds (parallel I2S) interact
bus->begin();
bus->setBrightness(bri);
}
DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), ESP.getFreeHeap());
@ -1490,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();
@ -1521,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();
@ -1529,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);
@ -1575,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
@ -1611,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);
@ -1747,8 +1791,8 @@ uint16_t WS2812FX::getLengthPhysical() const {
//not influenced by auto-white mode, also true if white slider does not affect output white channel
bool WS2812FX::hasRGBWBus() const {
for (size_t b = 0; b < BusManager::getNumBusses(); b++) {
Bus *bus = BusManager::getBus(b);
if (bus == nullptr || bus->getLength()==0) break;
const Bus *bus = BusManager::getBus(b);
if (!bus || !bus->isOk()) break;
if (bus->hasRGB() && bus->hasWhite()) return true;
}
return false;
@ -1757,8 +1801,8 @@ bool WS2812FX::hasRGBWBus() const {
bool WS2812FX::hasCCTBus() const {
if (cctFromRgb && !correctWB) return false;
for (size_t b = 0; b < BusManager::getNumBusses(); b++) {
Bus *bus = BusManager::getBus(b);
if (bus == nullptr || bus->getLength()==0) break;
const Bus *bus = BusManager::getBus(b);
if (!bus || !bus->isOk()) break;
if (bus->hasCCT()) return true;
}
return false;
@ -1811,10 +1855,11 @@ void WS2812FX::makeAutoSegments(bool forceReset) {
#endif
for (size_t i = s; i < BusManager::getNumBusses(); i++) {
Bus* b = BusManager::getBus(i);
const Bus *bus = BusManager::getBus(i);
if (!bus || !bus->isOk()) break;
segStarts[s] = b->getStart();
segStops[s] = segStarts[s] + b->getLength();
segStarts[s] = bus->getStart();
segStops[s] = segStarts[s] + bus->getLength();
#ifndef WLED_DISABLE_2D
if (isMatrix && segStops[s] <= Segment::maxWidth*Segment::maxHeight) continue; // ignore buses comprising matrix
@ -1904,7 +1949,8 @@ bool WS2812FX::checkSegmentAlignment() const {
bool aligned = false;
for (const segment &seg : _segments) {
for (unsigned b = 0; b<BusManager::getNumBusses(); b++) {
Bus *bus = BusManager::getBus(b);
const Bus *bus = BusManager::getBus(b);
if (!bus || !bus->isOk()) break;
if (seg.start == bus->getStart() && seg.stop == bus->getStart() + bus->getLength()) aligned = true;
}
if (seg.start == 0 && seg.stop == _length) aligned = true;

View File

@ -43,6 +43,8 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const
#define W(c) (byte((c) >> 24))
static ColorOrderMap _colorOrderMap = {};
bool ColorOrderMap::add(uint16_t start, uint16_t len, uint8_t colorOrder) {
if (count() >= WLED_MAX_COLOR_ORDER_MAPPINGS || len == 0 || (colorOrder & 0x0F) > COL_ORDER_MAX) return false; // upper nibble contains W swap information
_mappings.push_back({start,len,colorOrder});
@ -53,10 +55,8 @@ bool ColorOrderMap::add(uint16_t start, uint16_t len, uint8_t colorOrder) {
uint8_t IRAM_ATTR ColorOrderMap::getPixelColorOrder(uint16_t pix, uint8_t defaultColorOrder) const {
// upper nibble contains W swap information
// when ColorOrderMap's upper nibble contains value >0 then swap information is used from it, otherwise global swap is used
for (unsigned i = 0; i < count(); i++) {
if (pix >= _mappings[i].start && pix < (_mappings[i].start + _mappings[i].len)) {
return _mappings[i].colorOrder | ((_mappings[i].colorOrder >> 4) ? 0 : (defaultColorOrder & 0xF0));
}
for (const auto& map : _mappings) {
if (pix >= map.start && pix < (map.start + map.len)) return map.colorOrder | ((map.colorOrder >> 4) ? 0 : (defaultColorOrder & 0xF0));
}
return defaultColorOrder;
}
@ -72,7 +72,7 @@ void Bus::calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw) {
} else {
cct = (approximateKelvinFromRGB(c) - 1900) >> 5; // convert K (from RGB value) to relative format
}
//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);
@ -99,23 +99,14 @@ uint32_t Bus::autoWhiteCalc(uint32_t c) const {
return RGBW32(r, g, b, w);
}
uint8_t *Bus::allocateData(size_t size) {
freeData(); // should not happen, but for safety
return _data = (uint8_t *)(size>0 ? calloc(size, sizeof(uint8_t)) : nullptr);
}
void Bus::freeData() {
if (_data) free(_data);
_data = nullptr;
}
BusDigital::BusDigital(const BusConfig &bc, uint8_t nr, const ColorOrderMap &com)
BusDigital::BusDigital(const BusConfig &bc, uint8_t nr)
: Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, (bc.refreshReq || bc.type == TYPE_TM1814))
, _skip(bc.skipAmount) //sacrificial pixels
, _colorOrder(bc.colorOrder)
, _milliAmpsPerLed(bc.milliAmpsPerLed)
, _milliAmpsMax(bc.milliAmpsMax)
, _colorOrderMap(com)
, _data(nullptr)
{
DEBUGBUS_PRINTLN(F("Bus: Creating digital bus."));
if (!isDigital(bc.type) || !bc.count) { DEBUGBUS_PRINTLN(F("Not digial or empty bus!")); return; }
@ -136,12 +127,14 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr, const ColorOrderMap &com
_hasRgb = hasRGB(bc.type);
_hasWhite = hasWhite(bc.type);
_hasCCT = hasCCT(bc.type);
if (bc.doubleBuffer && !allocateData(bc.count * Bus::getNumberOfChannels(bc.type))) { DEBUGBUS_PRINTLN(F("Buffer allocation failed!")); return; }
//_buffering = bc.doubleBuffer;
if (bc.doubleBuffer) {
_data = (uint8_t*)calloc(_len, Bus::getNumberOfChannels(_type));
if (!_data) DEBUGBUS_PRINTLN(F("Bus: Buffer allocation failed!"));
}
uint16_t lenToCreate = bc.count;
if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus
_busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr);
_valid = (_busPtr != nullptr);
_valid = (_busPtr != nullptr) && bc.count > 0;
DEBUGBUS_PRINTF_P(PSTR("Bus: %successfully inited #%u (len:%u, type:%u (RGB:%d, W:%d, CCT:%d), pins:%u,%u [itype:%u] mA=%d/%d)\n"),
_valid?"S":"Uns",
(int)nr,
@ -268,7 +261,7 @@ void BusDigital::show() {
}
}
}
PolyBus::show(_busPtr, _iType, !_data); // faster if buffer consistency is not important (use !_buffering this causes 20% FPS drop)
PolyBus::show(_busPtr, _iType, !_data); // faster if buffer consistency is not important
// restore bus brightness to its original value
// this is done right after show, so this is only OK if LED updates are completed before show() returns
// or async show has a separate buffer (ESP32 RMT and I2S are ok)
@ -421,11 +414,11 @@ void BusDigital::begin() {
void BusDigital::cleanup() {
DEBUGBUS_PRINTLN(F("Digital Cleanup."));
PolyBus::cleanup(_busPtr, _iType);
free(_data);
_data = nullptr;
_iType = I_NONE;
_valid = false;
_busPtr = nullptr;
freeData();
//PinManager::deallocateMultiplePins(_pins, 2, PinOwner::BusDigital);
PinManager::deallocatePin(_pins[1], PinOwner::BusDigital);
PinManager::deallocatePin(_pins[0], PinOwner::BusDigital);
}
@ -449,7 +442,7 @@ void BusDigital::cleanup() {
#else
#ifdef SOC_LEDC_TIMER_BIT_WIDE_NUM
// C6/H2/P4: 20 bit, S2/S3/C2/C3: 14 bit
#define MAX_BIT_WIDTH SOC_LEDC_TIMER_BIT_WIDE_NUM
#define MAX_BIT_WIDTH SOC_LEDC_TIMER_BIT_WIDE_NUM
#else
// ESP32: 20 bit (but in reality we would never go beyond 16 bit as the frequency would be to low)
#define MAX_BIT_WIDTH 14
@ -497,7 +490,6 @@ BusPwm::BusPwm(const BusConfig &bc)
_hasRgb = hasRGB(bc.type);
_hasWhite = hasWhite(bc.type);
_hasCCT = hasCCT(bc.type);
_data = _pwmdata; // avoid malloc() and use already allocated memory
_valid = true;
DEBUGBUS_PRINTF_P(PSTR("%successfully inited PWM strip with type %u, frequency %u, bit depth %u and pins %u,%u,%u,%u,%u\n"), _valid?"S":"Uns", bc.type, _frequency, _depth, _pins[0], _pins[1], _pins[2], _pins[3], _pins[4]);
}
@ -561,7 +553,7 @@ uint32_t BusPwm::getPixelColor(unsigned pix) const {
void BusPwm::show() {
if (!_valid) return;
const unsigned numPins = getPins();
const size_t numPins = getPins();
#ifdef ESP8266
const unsigned analogPeriod = F_CPU / _frequency;
const unsigned maxBri = analogPeriod; // compute to clock cycle accuracy
@ -571,7 +563,7 @@ void BusPwm::show() {
// if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor)
// https://github.com/wled-dev/WLED/pull/4115 and https://github.com/zalatnaicsongor/WLED/pull/1)
const bool dithering = _needsRefresh; // avoid working with bitfield
const unsigned maxBri = (1<<_depth); // possible values: 16384 (14), 8192 (13), 4096 (12), 2048 (11), 1024 (10), 512 (9) and 256 (8)
const unsigned maxBri = (1<<_depth); // possible values: 16384 (14), 8192 (13), 4096 (12), 2048 (11), 1024 (10), 512 (9) and 256 (8)
const unsigned bitShift = dithering * 4; // if dithering, _depth is 12 bit but LEDC channel is set to 8 bit (using 4 fractional bits)
#endif
// use CIE brightness formula (linear + cubic) to approximate human eye perceived brightness
@ -587,7 +579,7 @@ void BusPwm::show() {
[[maybe_unused]] unsigned hPoint = 0; // phase shift (0 - maxBri)
// we will be phase shifting every channel by previous pulse length (plus dead time if required)
// phase shifting is only mandatory when using H-bridge to drive reverse-polarity PWM CCT (2 wire) LED type
// phase shifting is only mandatory when using H-bridge to drive reverse-polarity PWM CCT (2 wire) LED type
// CCT additive blending must be 0 (WW & CW will not overlap) otherwise signals *will* overlap
// for all other cases it will just try to "spread" the load on PSU
// Phase shifting requires that LEDC timers are synchronised (see setup()). For PWM CCT (and H-bridge) it is
@ -648,7 +640,7 @@ std::vector<LEDType> BusPwm::getLEDTypes() {
}
void BusPwm::deallocatePins() {
unsigned numPins = getPins();
size_t numPins = getPins();
for (unsigned i = 0; i < numPins; i++) {
PinManager::deallocatePin(_pins[i], PinOwner::BusPwm);
if (!PinManager::isPinOk(_pins[i])) continue;
@ -666,7 +658,7 @@ void BusPwm::deallocatePins() {
BusOnOff::BusOnOff(const BusConfig &bc)
: Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed)
, _onoffdata(0)
, _data(0)
{
if (!Bus::isOnOff(bc.type)) return;
@ -679,7 +671,6 @@ BusOnOff::BusOnOff(const BusConfig &bc)
_hasRgb = false;
_hasWhite = false;
_hasCCT = false;
_data = &_onoffdata; // avoid malloc() and use stack
_valid = true;
DEBUGBUS_PRINTF_P(PSTR("%successfully inited On/Off strip with pin %u\n"), _valid?"S":"Uns", _pin);
}
@ -691,17 +682,17 @@ void BusOnOff::setPixelColor(unsigned pix, uint32_t c) {
uint8_t g = G(c);
uint8_t b = B(c);
uint8_t w = W(c);
_data[0] = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0;
_data = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0;
}
uint32_t BusOnOff::getPixelColor(unsigned pix) const {
if (!_valid) return 0;
return RGBW32(_data[0], _data[0], _data[0], _data[0]);
return RGBW32(_data, _data, _data, _data);
}
void BusOnOff::show() {
if (!_valid) return;
digitalWrite(_pin, _reversed ? !(bool)_data[0] : (bool)_data[0]);
digitalWrite(_pin, _reversed ? !(bool)_data : (bool)_data);
}
unsigned BusOnOff::getPins(uint8_t* pinArray) const {
@ -740,7 +731,8 @@ BusNetwork::BusNetwork(const BusConfig &bc)
_hasCCT = false;
_UDPchannels = _hasWhite + 3;
_client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
_valid = (allocateData(_len * _UDPchannels) != nullptr);
_data = (uint8_t*)calloc(_len, _UDPchannels);
_valid = (_data != nullptr);
DEBUGBUS_PRINTF_P(PSTR("%successfully inited virtual strip with type %u and IP %u.%u.%u.%u\n"), _valid?"S":"Uns", bc.type, bc.pins[0], bc.pins[1], bc.pins[2], bc.pins[3]);
}
@ -790,9 +782,10 @@ std::vector<LEDType> BusNetwork::getLEDTypes() {
void BusNetwork::cleanup() {
DEBUGBUS_PRINTLN(F("Virtual Cleanup."));
free(_data);
_data = nullptr;
_type = I_NONE;
_valid = false;
freeData();
}
@ -844,17 +837,17 @@ int BusManager::add(const BusConfig &bc) {
unsigned numDigital = 0;
for (const auto &bus : busses) if (bus->isDigital() && !bus->is2Pin()) numDigital++;
if (Bus::isVirtual(bc.type)) {
//busses.push_back(std::make_unique<BusNetwork>(bc)); // when C++ >11
busses.push_back(new BusNetwork(bc));
busses.push_back(make_unique<BusNetwork>(bc));
//busses.push_back(new BusNetwork(bc));
} else if (Bus::isDigital(bc.type)) {
//busses.push_back(std::make_unique<BusDigital>(bc, numDigital, colorOrderMap));
busses.push_back(new BusDigital(bc, numDigital, colorOrderMap));
busses.push_back(make_unique<BusDigital>(bc, numDigital));
//busses.push_back(new BusDigital(bc, numDigital));
} else if (Bus::isOnOff(bc.type)) {
//busses.push_back(std::make_unique<BusOnOff>(bc));
busses.push_back(new BusOnOff(bc));
busses.push_back(make_unique<BusOnOff>(bc));
//busses.push_back(new BusOnOff(bc));
} else {
//busses.push_back(std::make_unique<BusPwm>(bc));
busses.push_back(new BusPwm(bc));
busses.push_back(make_unique<BusPwm>(bc));
//busses.push_back(new BusPwm(bc));
}
return busses.size();
}
@ -898,7 +891,7 @@ void BusManager::removeAll() {
DEBUGBUS_PRINTLN(F("Removing all."));
//prevents crashes due to deleting busses while in use.
while (!canAllShow()) yield();
for (auto &bus : busses) delete bus; // needed when not using std::unique_ptr C++ >11
//for (auto &bus : busses) delete bus; // needed when not using std::unique_ptr C++ >11
busses.clear();
PolyBus::setParallelI2S1Output(false);
}
@ -949,8 +942,8 @@ void BusManager::on() {
uint8_t pins[2] = {255,255};
if (bus->isDigital() && bus->getPins(pins)) {
if (pins[0] == LED_BUILTIN || pins[1] == LED_BUILTIN) {
BusDigital *b = static_cast<BusDigital*>(bus);
b->begin();
BusDigital &b = static_cast<BusDigital&>(*bus);
b.begin();
break;
}
}
@ -978,17 +971,13 @@ void BusManager::off() {
}
void BusManager::show() {
_milliAmpsUsed = 0;
_gMilliAmpsUsed = 0;
for (auto &bus : busses) {
bus->show();
_milliAmpsUsed += bus->getUsedCurrent();
_gMilliAmpsUsed += bus->getUsedCurrent();
}
}
void BusManager::setStatusPixel(uint32_t c) {
for (auto &bus : busses) bus->setStatusPixel(c);
}
void IRAM_ATTR BusManager::setPixelColor(unsigned pix, uint32_t c) {
for (auto &bus : busses) {
unsigned bstart = bus->getStart();
@ -997,10 +986,6 @@ void IRAM_ATTR BusManager::setPixelColor(unsigned pix, uint32_t c) {
}
}
void BusManager::setBrightness(uint8_t b) {
for (auto &bus : busses) bus->setBrightness(b);
}
void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) {
if (cct > 255) cct = 255;
if (cct >= 0) {
@ -1024,17 +1009,8 @@ bool BusManager::canAllShow() {
return true;
}
Bus* BusManager::getBus(uint8_t busNr) {
if (busNr >= busses.size()) return nullptr;
return busses[busNr];
}
ColorOrderMap& BusManager::getColorOrderMap() { return _colorOrderMap; }
//semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit())
uint16_t BusManager::getTotalLength() {
unsigned len = 0;
for (const auto &bus : busses) len += bus->getLength();
return len;
}
bool PolyBus::_useParallelI2S = false;
@ -1045,8 +1021,7 @@ uint8_t Bus::_gAWM = 255;
uint16_t BusDigital::_milliAmpsTotal = 0;
//std::vector<std::unique_ptr<Bus>> BusManager::busses;
std::vector<Bus*> BusManager::busses;
ColorOrderMap BusManager::colorOrderMap = {};
uint16_t BusManager::_milliAmpsUsed = 0;
uint16_t BusManager::_milliAmpsMax = ABL_MILLIAMPS_DEFAULT;
std::vector<std::unique_ptr<Bus>> BusManager::busses;
//std::vector<Bus*> BusManager::busses;
uint16_t BusManager::_gMilliAmpsUsed = 0;
uint16_t BusManager::_gMilliAmpsMax = ABL_MILLIAMPS_DEFAULT;

View File

@ -11,6 +11,18 @@
#include <vector>
#include <memory>
#if __cplusplus >= 201402L
using std::make_unique;
#else
// Really simple C++11 shim for non-array case; implementation from cppreference.com
template<class T, class... Args>
std::unique_ptr<T>
make_unique(Args&&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
#endif
// enable additional debug output
#if defined(WLED_DEBUG_HOST)
#include "net_debug.h"
@ -94,11 +106,10 @@ class Bus {
: _type(type)
, _bri(255)
, _start(start)
, _len(len)
, _len(std::max(len,(uint16_t)1))
, _reversed(reversed)
, _valid(false)
, _needsRefresh(refresh)
, _data(nullptr) // keep data access consistent across all types of buses
{
_autoWhiteMode = Bus::hasWhite(type) ? aw : RGBW_MODE_MANUAL_ONLY;
};
@ -202,7 +213,6 @@ class Bus {
bool _hasCCT;// : 1;
//} __attribute__ ((packed));
uint8_t _autoWhiteMode;
uint8_t *_data;
// global Auto White Calculation override
static uint8_t _gAWM;
// _cct has the following menaings (see calculateCCT() & BusManager::setSegmentCCT()):
@ -217,14 +227,12 @@ class Bus {
static uint8_t _cctBlend;
uint32_t autoWhiteCalc(uint32_t c) const;
uint8_t *allocateData(size_t size = 1);
void freeData();
};
class BusDigital : public Bus {
public:
BusDigital(const BusConfig &bc, uint8_t nr, const ColorOrderMap &com);
BusDigital(const BusConfig &bc, uint8_t nr);
~BusDigital() { cleanup(); }
void show() override;
@ -248,15 +256,15 @@ class BusDigital : public Bus {
static std::vector<LEDType> getLEDTypes();
private:
uint8_t _skip;
uint8_t _colorOrder;
uint8_t _pins[2];
uint8_t _iType;
uint8_t _skip;
uint8_t _colorOrder;
uint8_t _pins[2];
uint8_t _iType;
uint16_t _frequencykHz;
uint8_t _milliAmpsPerLed;
uint8_t _milliAmpsPerLed;
uint16_t _milliAmpsMax;
void * _busPtr;
const ColorOrderMap &_colorOrderMap;
uint8_t *_data;
void *_busPtr;
static uint16_t _milliAmpsTotal; // is overwitten/recalculated on each show()
@ -286,13 +294,13 @@ class BusPwm : public Bus {
uint16_t getFrequency() const override { return _frequency; }
unsigned getBusSize() const override { return sizeof(BusPwm); }
void show() override;
inline void cleanup() { deallocatePins(); _data = nullptr; }
inline void cleanup() { deallocatePins(); }
static std::vector<LEDType> getLEDTypes();
private:
uint8_t _pins[OUTPUT_MAX_PINS];
uint8_t _pwmdata[OUTPUT_MAX_PINS];
uint8_t _data[OUTPUT_MAX_PINS];
#ifdef ARDUINO_ARCH_ESP32
uint8_t _ledcStart;
#endif
@ -313,13 +321,13 @@ class BusOnOff : public Bus {
unsigned getPins(uint8_t* pinArray) const override;
unsigned getBusSize() const override { return sizeof(BusOnOff); }
void show() override;
inline void cleanup() { PinManager::deallocatePin(_pin, PinOwner::BusOnOff); _data = nullptr; }
inline void cleanup() { PinManager::deallocatePin(_pin, PinOwner::BusOnOff); }
static std::vector<LEDType> getLEDTypes();
private:
uint8_t _pin;
uint8_t _onoffdata;
uint8_t _data;
};
@ -343,6 +351,7 @@ class BusNetwork : public Bus {
uint8_t _UDPtype;
uint8_t _UDPchannels;
bool _broadcastLock;
uint8_t *_data;
};
@ -363,7 +372,7 @@ struct BusConfig {
uint16_t milliAmpsMax;
BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, bool dblBfr=false, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT)
: count(len)
: count(std::max(len,(uint16_t)1))
, start(pstart)
, colorOrder(pcolorOrder)
, reversed(rev)
@ -416,59 +425,58 @@ struct BusConfig {
#endif
#endif
class BusManager {
public:
BusManager() {};
namespace BusManager {
static unsigned memUsage();
static uint16_t currentMilliamps() { return _milliAmpsUsed + MA_FOR_ESP; }
static uint16_t ablMilliampsMax() { return _milliAmpsMax; }
extern std::vector<std::unique_ptr<Bus>> busses;
//extern std::vector<Bus*> busses;
extern uint16_t _gMilliAmpsUsed;
extern uint16_t _gMilliAmpsMax;
static int add(const BusConfig &bc);
static void useParallelOutput(); // workaround for inaccessible PolyBus
static bool hasParallelOutput(); // workaround for inaccessible PolyBus
#ifdef ESP32_DATA_IDLE_HIGH
void esp32RMTInvertIdle() ;
#endif
inline size_t getNumVirtualBusses() {
size_t j = 0;
for (const auto &bus : busses) j += bus->isVirtual();
return j;
}
//do not call this method from system context (network callback)
static void removeAll();
size_t memUsage();
inline uint16_t currentMilliamps() { return _gMilliAmpsUsed + MA_FOR_ESP; }
//inline uint16_t ablMilliampsMax() { unsigned sum = 0; for (auto &bus : busses) sum += bus->getMaxCurrent(); return sum; }
inline uint16_t ablMilliampsMax() { return _gMilliAmpsMax; } // used for compatibility reasons (and enabling virtual global ABL)
inline void setMilliampsMax(uint16_t max) { _gMilliAmpsMax = max;}
static void on();
static void off();
void useParallelOutput(); // workaround for inaccessible PolyBus
bool hasParallelOutput(); // workaround for inaccessible PolyBus
static void show();
static bool canAllShow();
static void setStatusPixel(uint32_t c);
[[gnu::hot]] static void setPixelColor(unsigned pix, uint32_t c);
static void setBrightness(uint8_t b);
// for setSegmentCCT(), cct can only be in [-1,255] range; allowWBCorrection will convert it to K
// WARNING: setSegmentCCT() is a misleading name!!! much better would be setGlobalCCT() or just setCCT()
static void setSegmentCCT(int16_t cct, bool allowWBCorrection = false);
static inline void setMilliampsMax(uint16_t max) { _milliAmpsMax = max;}
[[gnu::hot]] static uint32_t getPixelColor(unsigned pix);
static inline int16_t getSegmentCCT() { return Bus::getCCT(); }
//do not call this method from system context (network callback)
void removeAll();
int add(const BusConfig &bc);
static Bus* getBus(uint8_t busNr);
void on();
void off();
//semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit())
static uint16_t getTotalLength();
static inline uint8_t getNumBusses() { return busses.size(); }
static String getLEDTypesJSONString();
[[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c);
[[gnu::hot]] uint32_t getPixelColor(unsigned pix);
void show();
bool canAllShow();
inline void setStatusPixel(uint32_t c) { for (auto &bus : busses) bus->setStatusPixel(c);}
inline void setBrightness(uint8_t b) { for (auto &bus : busses) bus->setBrightness(b); }
// for setSegmentCCT(), cct can only be in [-1,255] range; allowWBCorrection will convert it to K
// WARNING: setSegmentCCT() is a misleading name!!! much better would be setGlobalCCT() or just setCCT()
void setSegmentCCT(int16_t cct, bool allowWBCorrection = false);
inline int16_t getSegmentCCT() { return Bus::getCCT(); }
inline Bus* getBus(size_t busNr) { return busNr < busses.size() ? busses[busNr].get() : nullptr; }
inline size_t getNumBusses() { return busses.size(); }
static inline ColorOrderMap& getColorOrderMap() { return colorOrderMap; }
private:
//static std::vector<std::unique_ptr<Bus>> busses; // we'd need C++ >11
static std::vector<Bus*> busses;
static ColorOrderMap colorOrderMap;
static uint16_t _milliAmpsUsed;
static uint16_t _milliAmpsMax;
#ifdef ESP32_DATA_IDLE_HIGH
static void esp32RMTInvertIdle() ;
#endif
static uint8_t getNumVirtualBusses() {
int j = 0;
for (const auto &bus : busses) j += bus->isVirtual();
return j;
}
//semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit())
inline uint16_t getTotalLength(bool onlyPhysical = false) {
unsigned len = 0;
for (const auto &bus : busses) if (!(bus->isVirtual() && onlyPhysical)) len += bus->getLength();
return len;
}
String getLEDTypesJSONString();
ColorOrderMap& getColorOrderMap();
};
#endif

View File

@ -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]);

View File

@ -170,7 +170,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (fromFS) BusManager::removeAll(); // can't safely manipulate busses directly in network callback
for (JsonObject elm : ins) {
if (s >= WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES) break;
if (s >= WLED_MAX_BUSSES) break;
uint8_t pins[5] = {255, 255, 255, 255, 255};
JsonArray pinArr = elm["pin"];
if (pinArr.size() == 0) continue;
@ -199,12 +199,13 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
}
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
busConfigs.push_back(std::move(BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer, maPerLed, maMax)));
//busConfigs.push_back(std::move(BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer, maPerLed, maMax)));
busConfigs.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer, maPerLed, maMax);
doInitBusses = true; // finalization done in beginStrip()
s++;
if (!Bus::isVirtual(ledType)) s++; // have as many virtual buses as you want
}
}
if (hw_led["rev"]) BusManager::getBus(0)->setReversed(true); //set 0.11 global reversed setting for first bus
if (hw_led["rev"] && BusManager::getNumBusses()) BusManager::getBus(0)->setReversed(true); //set 0.11 global reversed setting for first bus
// read color order map configuration
JsonArray hw_com = hw[F("com")];
@ -824,8 +825,8 @@ void serializeConfig() {
for (size_t s = 0; s < BusManager::getNumBusses(); s++) {
DEBUG_PRINTF_P(PSTR("Cfg: Saving bus #%u\n"), s);
Bus *bus = BusManager::getBus(s);
if (!bus || bus->getLength()==0) break;
const Bus *bus = BusManager::getBus(s);
if (!bus || !bus->isOk()) break;
DEBUG_PRINTF_P(PSTR(" (%d-%d, type:%d, CO:%d, rev:%d, skip:%d, AW:%d kHz:%d, mA:%d/%d)\n"),
(int)bus->getStart(), (int)(bus->getStart()+bus->getLength()),
(int)(bus->getType() & 0x7F),
@ -838,28 +839,27 @@ void serializeConfig() {
);
JsonObject ins = hw_led_ins.createNestedObject();
ins["start"] = bus->getStart();
ins["len"] = bus->getLength();
ins["len"] = bus->getLength();
JsonArray ins_pin = ins.createNestedArray("pin");
uint8_t pins[5];
uint8_t nPins = bus->getPins(pins);
for (int i = 0; i < nPins; i++) ins_pin.add(pins[i]);
ins[F("order")] = bus->getColorOrder();
ins["rev"] = bus->isReversed();
ins[F("skip")] = bus->skippedLeds();
ins["type"] = bus->getType() & 0x7F;
ins["ref"] = bus->isOffRefreshRequired();
ins[F("rgbwm")] = bus->getAutoWhiteMode();
ins[F("freq")] = bus->getFrequency();
ins[F("order")] = bus->getColorOrder();
ins["rev"] = bus->isReversed();
ins[F("skip")] = bus->skippedLeds();
ins["type"] = bus->getType() & 0x7F;
ins["ref"] = bus->isOffRefreshRequired();
ins[F("rgbwm")] = bus->getAutoWhiteMode();
ins[F("freq")] = bus->getFrequency();
ins[F("maxpwr")] = bus->getMaxCurrent();
ins[F("ledma")] = bus->getLEDCurrent();
ins[F("ledma")] = bus->getLEDCurrent();
}
JsonArray hw_com = hw.createNestedArray(F("com"));
const ColorOrderMap& com = BusManager::getColorOrderMap();
for (size_t s = 0; s < com.count(); s++) {
const ColorOrderMapEntry *entry = com.get(s);
if (!entry) break;
if (!entry || !entry->len) break;
JsonObject co = hw_com.createNestedObject();
co["start"] = entry->start;
co["len"] = entry->len;

View File

@ -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;

View File

@ -49,31 +49,31 @@
#define WLED_MAX_DIGITAL_CHANNELS 3
#define WLED_MAX_ANALOG_CHANNELS 5
#define WLED_MAX_BUSSES 4 // will allow 3 digital & 1 analog RGB
#define WLED_MIN_VIRTUAL_BUSSES 3
#define WLED_MIN_VIRTUAL_BUSSES 3 // no longer used for bus creation but used to distinguish S2/S3 in UI
#else
#define WLED_MAX_ANALOG_CHANNELS (LEDC_CHANNEL_MAX*LEDC_SPEED_MODE_MAX)
#if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, 6 LEDC, only has 1 I2S but NPB does not support it ATM
#define WLED_MAX_BUSSES 6 // will allow 2 digital & 2 analog RGB or 6 PWM white
#define WLED_MAX_DIGITAL_CHANNELS 2
//#define WLED_MAX_ANALOG_CHANNELS 6
#define WLED_MIN_VIRTUAL_BUSSES 4
#define WLED_MIN_VIRTUAL_BUSSES 4 // no longer used for bus creation but used to distinguish S2/S3 in UI
#elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, 8 LEDC, only has 1 I2S bus, supported in NPB
// the 5th bus (I2S) will prevent Audioreactive usermod from functioning (it is last used though)
#define WLED_MAX_BUSSES 14 // will allow 12 digital & 2 analog RGB
#define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT + x1/x8 I2S0
#define WLED_MAX_BUSSES 7 // will allow 5 digital & 2 analog RGB
#define WLED_MAX_DIGITAL_CHANNELS 5
//#define WLED_MAX_ANALOG_CHANNELS 8
#define WLED_MIN_VIRTUAL_BUSSES 4
#define WLED_MIN_VIRTUAL_BUSSES 4 // no longer used for bus creation but used to distinguish S2/S3 in UI
#elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, 8 LEDC, has 2 I2S but NPB supports parallel x8 LCD on I2S1
#define WLED_MAX_BUSSES 14 // will allow 12 digital & 2 analog RGB
#define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT + x8 I2S-LCD
//#define WLED_MAX_ANALOG_CHANNELS 8
#define WLED_MIN_VIRTUAL_BUSSES 6
#define WLED_MIN_VIRTUAL_BUSSES 6 // no longer used for bus creation but used to distinguish S2/S3 in UI
#else
// the last digital bus (I2S0) will prevent Audioreactive usermod from functioning
#define WLED_MAX_BUSSES 19 // will allow 16 digital & 3 analog RGB
#define WLED_MAX_DIGITAL_CHANNELS 16 // x1/x8 I2S1 + x8 RMT
//#define WLED_MAX_ANALOG_CHANNELS 16
#define WLED_MIN_VIRTUAL_BUSSES 6
#define WLED_MIN_VIRTUAL_BUSSES 6 // no longer used for bus creation but used to distinguish S2/S3 in UI
#endif
#endif
#else
@ -87,7 +87,7 @@
#ifndef WLED_MAX_DIGITAL_CHANNELS
#error You must also define WLED_MAX_DIGITAL_CHANNELS.
#endif
#define WLED_MIN_VIRTUAL_BUSSES (5-WLED_MAX_BUSSES)
#define WLED_MIN_VIRTUAL_BUSSES 3
#else
#if WLED_MAX_BUSSES > 20
#error Maximum number of buses is 20.
@ -98,7 +98,11 @@
#ifndef WLED_MAX_DIGITAL_CHANNELS
#error You must also define WLED_MAX_DIGITAL_CHANNELS.
#endif
#define WLED_MIN_VIRTUAL_BUSSES (20-WLED_MAX_BUSSES)
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
#define WLED_MIN_VIRTUAL_BUSSES 4
#else
#define WLED_MIN_VIRTUAL_BUSSES 6
#endif
#endif
#endif

View File

@ -6,6 +6,7 @@
<meta name="theme-color" content="#222222">
<meta content="yes" name="apple-mobile-web-app-capable">
<link rel="shortcut icon" href="data:image/x-icon;base64,AAABAAEAEBAAAAEAGACGAAAAFgAAAIlQTkcNChoKAAAADUlIRFIAAAAQAAAAEAgGAAAAH/P/YQAAAE1JREFUOI1j/P//PwOxgNGeAUMxE9G6cQCKDWAhpADZ2f8PMjBS3QW08QK20KaZC2gfC9hCnqouoNgARgY7zMxAyNlUdQHlXiAlO2MDAD63EVqNHAe0AAAAAElFTkSuQmCC"/>
<link rel="apple-touch-icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAMAAAAKE/YAAAAANlBMVEUAAAAAKacAPv8APv8APv8APv8APv8AKacAQv8AJZsAKacAKacAPv8APv8AL8MAPv8APv8AQv/mTD7uAAAAEHRSTlMAf1mvpgzzka9/Vy3wnX9ic6GVaQAAAQhJREFUeNrt3EtuwkAQRdGGYAyYT9j/ZqN4GNkv9gSV4ZwVXJU8cVerGwAAAAAAUNTl9LXY6dJq6J/fiz37VsNuTfSu1SB6hmjRgWjRgWjRgWjRgWjRgehf5y1G70r/2Ha3w5T7foWhvVg3Pbxzq6w7lv5MRa8iWnQgWnQgWnQgWnQgWnQgenQtHT3spzxqT7p/+/MX0aJFj0SLDkSLDkSLDkSLDkSL/s/n7OhXRR8fNXb0q6KvrbSZSXetMtEzRIsORIsORIsORIsORIsORIdr/i+PHvYr3A9Tbsui7egn+2pf83//ky7RokWPRIsORIsORIsORIsORG/2gbhNPsUHAAAAAAD89QMg+PZb+jO0tAAAAABJRU5ErkJggg==" sizes="180x180"/>
<title>WLED</title>
<link rel="stylesheet" href="index.css">
</head>

View File

@ -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 = `<div id="segp${i}" class="sbs">`+
`<i class="icons slider-icon pwr ${inst.on ? "act":""}" id="seg${i}pwr" onclick="setSegPwr(${i})">&#xe08f;</i>`+
`<div class="sliderwrap il">`+
`<i class="icons slider-icon pwr ${inst.on ? "act":""}" id="seg${i}pwr" title="Power" onclick="setSegPwr(${i})">&#xe08f;</i>`+
`<div class="sliderwrap il" title="Opacity/Brightness">`+
`<input id="seg${i}bri" class="noslide" onchange="setSegBri(${i})" oninput="updateTrail(this)" max="255" min="1" type="range" value="${inst.bri}" />`+
`<div class="sliderdisplay"></div>`+
`</div>`+
@ -814,7 +816,7 @@ function populateSegments(s)
cn += `<div class="seg lstI ${i==s.mainseg && !simplifiedUI ? 'selected' : ''} ${exp ? "expanded":""}" id="seg${i}" data-set="${inst.set}">`+
`<label class="check schkl ${smpl}">`+
`<input type="checkbox" id="seg${i}sel" onchange="selSeg(${i})" ${inst.sel ? "checked":""}>`+
`<span class="checkmark"></span>`+
`<span class="checkmark" title="Select"></span>`+
`</label>`+
`<div class="segname ${smpl}" onclick="selSegEx(${i})">`+
`<i class="icons e-icon frz" id="seg${i}frz" title="(un)Freeze" onclick="event.preventDefault();tglFreeze(${i});">&#x${inst.frz ? (li.live && li.liveseg==i?'e410':'e0e8') : 'e325'};</i>`+
@ -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 = `<div class="seg btn btn-s${off?' off':''}" style="padding:0;margin-bottom:12px;">`
+ '<label class="check schkl"><input type="checkbox" id="selall" onchange="selSegAll(this)"><span class="checkmark"></span></label>'
+ '<label class="check schkl"><input type="checkbox" id="selall" onchange="selSegAll(this)"><span class="checkmark" title="Select all"></span></label>'
+ `<div class="segname" ${off?'':'onclick="makeSeg()"'}><i class="icons btn-icon">&#xe18a;</i>Add segment</div>`
+ '<div class="pop hide" onclick="event.stopPropagation();">'
+ `<i class="icons g-icon" title="Select group" onclick="this.nextElementSibling.classList.toggle('hide');">&#xE34B;</i>`
@ -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 += ' &#9654;'; // 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 += `<option value="${a[0]}" ${a[0]==el?"selected":""}>${n}</option>`;
// skip endless playlists and itself
if (!isPlaylist(a[1]) || (a[1].playlist.repeat > 0 && a[0]!=p)) plSelContent += `<option value="${a[0]}" ${a[0]==el?"selected":""}>${n}</option>`;
});
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 =
`<div id="ple${i}" style="margin-top:10px;"></div><label class="check revchkl">Shuffle
<input type="checkbox" id="pl${i}rtgl" onchange="plR(${i})" ${plJson[i].r||rep<0?"checked":""}>
<span class="checkmark"></span>
</label>
<label class="check revchkl">Manual advance
<input type="checkbox" id="pl${i}manual" onchange="plM(${i})" ${man?"checked":""}>
<span class="checkmark"></span>
</label>
<label class="check revchkl">Repeat indefinitely
<input type="checkbox" id="pl${i}rptgl" onchange="plR(${i})" ${rep>0?"":"checked"}>
<span class="checkmark"></span>
@ -2013,7 +2045,7 @@ function makeP(i,pl)
<div class="sel-p"><select class="sel-ple" id="pl${i}selEnd" onchange="plR(${i})" data-val=${plJson[i].end?plJson[i].end:0}>
<option value="0">None</option>
<option value="255" ${plJson[i].end && plJson[i].end==255?"selected":""}>Restore preset</option>
${makePlSel(plJson[i].end?plJson[i].end:0, true)}
${makePlSel(i, plJson[i].end?plJson[i].end:0)}
</select></div></div>
</div>
<div class="c"><button class="btn btn-p" onclick="testPl(${i}, this)"><i class='icons btn-icon'>&#xe139;</i>Test</button></div>`;
@ -2086,25 +2118,26 @@ function makePUtil()
function makePlEntry(p,i)
{
const man = gId(`pl${p}manual`).checked;
return `<div class="plentry">
<div class="hrz"></div>
<table>
<tr>
<td width="80%" colspan=2>
<div class="sel-p"><select class="sel-pl" onchange="plePs(${p},${i},this)" data-val="${plJson[p].ps[i]}" data-index="${i}">
${makePlSel(plJson[p].ps[i])}
${makePlSel(p, plJson[p].ps[i])}
</select></div>
</td>
<td class="c"><button class="btn btn-pl-add" onclick="addPl(${p},${i})"><i class="icons btn-icon">&#xe18a;</i></button></td>
</tr>
<tr>
<td class="c">Duration</td>
<td class="c">Duration <i style="font-size:70%;">(0=inf.)</i></td>
<td class="c">Transition</td>
<td class="c">#${i+1}</td>
</tr>
<tr>
<td class="c" width="40%"><input class="segn" type="number" placeholder="Duration" max=6553.0 min=0.2 step=0.1 oninput="pleDur(${p},${i},this)" value="${plJson[p].dur[i]/10.0}">s</td>
<td class="c" width="40%"><input class="segn" type="number" placeholder="Transition" max=65.0 min=0.0 step=0.1 oninput="pleTr(${p},${i},this)" value="${plJson[p].transition[i]/10.0}">s</td>
<td class="c" width="40%"><input class="segn" type="number" placeholder="Duration" max=6553.0 min=0.0 step=0.1 oninput="pleDur(${p},${i},this)" value="${plJson[p].dur[i]/10.0}" id="pl${p}du${i}" ${man?"readonly":""}>s</td>
<td class="c" width="40%"><input class="segn" type="number" placeholder="Transition" max=65.0 min=0.0 step=0.1 oninput="pleTr(${p},${i},this)" onfocus="pleTr(${p},${i},this)" value="${plJson[p].transition[i]/10.0}">s</td>
<td class="c"><button class="btn btn-pl-del" onclick="delPl(${p},${i})"><i class="icons btn-icon">&#xe037;</i></button></div></td>
</tr>
</table>
@ -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);

View File

@ -24,6 +24,7 @@
function is16b(t) { return !!(gT(t).c & 0x10); } // is digital 16 bit type
function mustR(t) { return !!(gT(t).c & 0x20); } // Off refresh is mandatory
function numPins(t){ return Math.max(gT(t).t.length, 1); } // type length determines number of GPIO pins
function chrID(x) { return String.fromCharCode((x<10?48:55)+x); }
function S() {
getLoc();
loadJS(getURL('/settings/s.js?p=2'), false, ()=>{
@ -42,10 +43,10 @@
if (loc) d.Sf.action = getURL('/settings/leds');
}
function bLimits(b,v,p,m,l,o=5,d=2,a=6) {
oMaxB = maxB = b; // maxB - max buses (can be changed if using ESP32 parallel I2S): 19 - ESP32, 14 - S3/S2, 6 - C3, 4 - 8266
maxD = d; // maxD - max digital channels (can be changed if using ESP32 parallel I2S): 16 - ESP32, 12 - S3/S2, 2 - C3, 3 - 8266
oMaxB = maxB = b; // maxB - max buses (can be changed if using ESP32 parallel I2S): 20 - ESP32, 14 - S3/S2, 6 - C3, 4 - 8266
maxD = d; // maxD - max digital channels (can be changed if using ESP32 parallel I2S): 17 - ESP32, 12 - S3/S2, 2 - C3, 3 - 8266
maxA = a; // maxA - max analog channels: 16 - ESP32, 8 - S3/S2, 6 - C3, 5 - 8266
maxV = v; // maxV - min virtual buses: 4 - ESP32/S3, 3 - S2/C3, 2 - ESP8266
maxV = v; // maxV - min virtual buses: 6 - ESP32/S3, 4 - S2/C3, 3 - ESP8266 (only used to distinguish S2/S3)
maxPB = p; // maxPB - max LEDs per bus
maxM = m; // maxM - max LED memory
maxL = l; // maxL - max LEDs (will serve to determine ESP >1664 == ESP32)
@ -138,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
@ -169,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
@ -359,8 +360,9 @@
gId("prl").classList.add("hide");
} else
gId("prl").classList.remove("hide");
// S2 supports mono I2S as well as parallel so we need to take that into account; S3 only supports parallel
maxD = (S2 || S3 ? 4 : 8) + (d.Sf["PR"].checked ? 8 : S2); // TODO: use bLimits() : 4/8RMT + (x1/x8 parallel) I2S1
maxB = oMaxB - (d.Sf["PR"].checked ? 0 : 7 + S3); // S2 (maxV==3) does support single I2S
maxB = oMaxB - (d.Sf["PR"].checked ? 0 : 7 + S3); // S2 (maxV==4) does support mono I2S
}
// distribute ABL current if not using PPL
enPPL(sDI);
@ -399,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
@ -421,8 +423,8 @@
if (isVir(t)) virtB++;
});
if ((n==1 && i>=maxB+maxV) || (n==-1 && i==0)) return;
var s = String.fromCharCode((i<10?48:55)+i);
if ((n==1 && i>=36) || (n==-1 && i==0)) return; // used to be i>=maxB+maxV when virtual buses were limited (now :"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")
var s = chrID(i);
if (n==1) {
// npm run build has trouble minimizing spaces inside string
@ -495,7 +497,7 @@ mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
o[i].querySelector("[name^=LT]").disabled = false;
}
gId("+").style.display = (i<maxB+maxV-1) ? "inline":"none";
gId("+").style.display = (i<35) ? "inline":"none"; // was maxB+maxV-1 when virtual buses were limited (now :"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")
gId("-").style.display = (i>0) ? "inline":"none";
if (!init) {
@ -506,7 +508,7 @@ mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
function addCOM(start=0,len=1,co=0) {
var i = gEBCN("com_entry").length;
if (i >= maxCO) return;
var s = String.fromCharCode((i<10?48:55)+i);
var s = chrID(i);
var b = `<div class="com_entry">
<hr class="sml">
${i+1}: Start: <input type="number" name="XS${s}" id="xs${s}" class="l starts" min="0" max="65535" value="${start}" oninput="UI();" required="">&nbsp;
@ -560,7 +562,7 @@ Swap: <select id="xw${s}" name="XW${s}">
function addBtn(i,p,t) {
var c = gId("btns").innerHTML;
var s = String.fromCharCode((i<10?48:55)+i);
var s = chrID(i);
c += `Button ${i} GPIO: <input type="number" name="BT${s}" onchange="UI()" class="xs" value="${p}">`;
c += `&nbsp;<select name="BE${s}">`
c += `<option value="0" ${t==0?"selected":""}>Disabled</option>`;
@ -584,8 +586,10 @@ Swap: <select id="xw${s}" name="XW${s}">
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;
@ -614,22 +618,32 @@ Swap: <select id="xw${s}" name="XW${s}">
function receivedText(e) {
let lines = e.target.result;
var c = JSON.parse(lines);
let c = JSON.parse(lines);
if (c.hw) {
if (c.hw.led) {
for (var i=0; i<10; i++) addLEDs(-1);
var l = c.hw.led;
// remove all existing outputs
for (const i=0; i<36; i++) addLEDs(-1); // was i<maxb+maxV when number of virtual buses was limited (now :"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")
let l = c.hw.led;
l.ins.forEach((v,i,a)=>{
addLEDs(1);
for (var j=0; j<v.pin.length; j++) d.getElementsByName(`L${j}${i}`)[0].value = v.pin[j];
d.getElementsByName("LT"+i)[0].value = v.type;
d.getElementsByName("LS"+i)[0].value = v.start;
d.getElementsByName("LC"+i)[0].value = v.len;
d.getElementsByName("CO"+i)[0].value = v.order;
d.getElementsByName("SL"+i)[0].value = v.skip;
d.getElementsByName("LT"+i)[0].value = v.type;
d.getElementsByName("LS"+i)[0].value = v.start;
d.getElementsByName("LC"+i)[0].value = v.len;
d.getElementsByName("CO"+i)[0].value = v.order & 0x0F;
d.getElementsByName("SL"+i)[0].value = v.skip;
d.getElementsByName("RF"+i)[0].checked = v.ref;
d.getElementsByName("CV"+i)[0].checked = v.rev;
d.getElementsByName("AW"+i)[0].value = v.rgbwm;
d.getElementsByName("WO"+i)[0].value = (v.order>>4) & 0x0F;
d.getElementsByName("SP"+i)[0].value = v.freq;
d.getElementsByName("LA"+i)[0].value = v.ledma;
d.getElementsByName("MA"+i)[0].value = v.maxpwr;
});
d.getElementsByName("PR")[0].checked = l.prl | 0;
d.getElementsByName("LD")[0].checked = l.ld;
d.getElementsByName("MA")[0].value = l.maxpwr;
d.getElementsByName("ABL")[0].checked = l.maxpwr > 0;
}
if(c.hw.com) {
resetCOM();
@ -637,22 +651,28 @@ Swap: <select id="xw${s}" name="XW${s}">
addCOM(e.start, e.len, e.order);
});
}
if (c.hw.btn) {
var b = c.hw.btn;
let b = c.hw.btn;
if (b) {
if (Array.isArray(b.ins)) gId("btns").innerHTML = "";
b.ins.forEach((v,i,a)=>{
addBtn(i,v.pin[0],v.type);
});
d.getElementsByName("TT")[0].value = b.tt;
}
if (c.hw.ir) {
d.getElementsByName("IR")[0].value = c.hw.ir.pin;
d.getElementsByName("IT")[0].value = c.hw.ir.type;
let ir = c.hw.ir;
if (ir) {
d.getElementsByName("IR")[0].value = ir.pin;
d.getElementsByName("IT")[0].value = ir.type;
}
if (c.hw.relay) {
d.getElementsByName("RL")[0].value = c.hw.relay.pin;
d.getElementsByName("RM")[0].checked = c.hw.relay.rev;
d.getElementsByName("RO")[0].checked = c.hw.relay.odrain;
let rl = c.hw.relay;
if (rl) {
d.getElementsByName("RL")[0].value = rl.pin;
d.getElementsByName("RM")[0].checked = rl.rev;
d.getElementsByName("RO")[0].checked = rl.odrain;
}
let li = c.light;
if (li) {
d.getElementsByName("MS")[0].checked = li.aseg;
}
UI();
}

View File

@ -19,6 +19,9 @@ public:
void disable();
void enable();
/// True if dmx is currently connected
bool isConnected() const { return connected; }
private:
/// @return true if rdm identify is active
bool isIdentifyOn() const;

View File

@ -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);

View File

@ -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;

View File

@ -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<JsonObject>())
{
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<JsonObject>()) {
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<JsonArray>();
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<JsonArray>();
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);

View File

@ -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);
}

View File

@ -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)

View File

@ -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;
}

View File

@ -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;
}

View File

@ -145,8 +145,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
#endif
bool busesChanged = false;
for (int s = 0; s < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; s++) {
int offset = s < 10 ? 48 : 55;
for (int s = 0; s < 36; s++) { // theoretical limit is 36 : "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
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
@ -161,11 +161,11 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
char la[4] = "LA"; la[2] = offset+s; la[3] = 0; //LED mA
char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max mA
if (!request->hasArg(lp)) {
DEBUG_PRINTF_P(PSTR("No data for %d\n"), s);
DEBUG_PRINTF_P(PSTR("# of buses: %d\n"), s+1);
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;
}
@ -212,7 +212,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
type |= request->hasArg(rf) << 7; // off refresh override
// actual finalization is done in WLED::loop() (removing old busses and adding new)
// this may happen even before this loop is finished so we do "doInitBusses" after the loop
busConfigs.push_back(std::move(BusConfig(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, useGlobalLedBuffer, maPerLed, maMax)));
busConfigs.emplace_back(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, useGlobalLedBuffer, maPerLed, maMax);
busesChanged = true;
}
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed
@ -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

View File

@ -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();
@ -461,7 +464,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);
@ -544,16 +547,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) {

View File

@ -217,6 +217,10 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
#define WLED_AP_PASS DEFAULT_AP_PASS
#endif
#ifndef WLED_PIN
#define WLED_PIN ""
#endif
#ifndef SPIFFS_EDITOR_AIRCOOOKIE
#error You are not using the Aircoookie fork of the ESPAsyncWebserver library.\
Using upstream puts your WiFi password at risk of being served by the filesystem.\
@ -277,7 +281,11 @@ WLED_GLOBAL char releaseString[] _INIT(WLED_RELEASE_NAME); // must include the q
// AP and OTA default passwords (for maximum security change them!)
WLED_GLOBAL char apPass[65] _INIT(WLED_AP_PASS);
#ifdef WLED_OTA_PASS
WLED_GLOBAL char otaPass[33] _INIT(WLED_OTA_PASS);
#else
WLED_GLOBAL char otaPass[33] _INIT(DEFAULT_OTA_PASS);
#endif
// Hardware and pin config
#ifndef BTNPIN
@ -411,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
@ -570,11 +578,15 @@ WLED_GLOBAL byte macroLongPress[WLED_MAX_BUTTONS] _INIT({0});
WLED_GLOBAL byte macroDoublePress[WLED_MAX_BUTTONS] _INIT({0});
// Security CONFIG
WLED_GLOBAL bool otaLock _INIT(false); // prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks
WLED_GLOBAL bool wifiLock _INIT(false); // prevents access to WiFi settings when OTA lock is enabled
WLED_GLOBAL bool aOtaEnabled _INIT(true); // ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on
WLED_GLOBAL char settingsPIN[5] _INIT(""); // PIN for settings pages
WLED_GLOBAL bool correctPIN _INIT(true);
#ifdef WLED_OTA_PASS
WLED_GLOBAL bool otaLock _INIT(true); // prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks
#else
WLED_GLOBAL bool otaLock _INIT(false); // prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks
#endif
WLED_GLOBAL bool wifiLock _INIT(false); // prevents access to WiFi settings when OTA lock is enabled
WLED_GLOBAL bool aOtaEnabled _INIT(true); // ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on
WLED_GLOBAL char settingsPIN[5] _INIT(WLED_PIN); // PIN for settings pages
WLED_GLOBAL bool correctPIN _INIT(!strlen(settingsPIN));
WLED_GLOBAL unsigned long lastEditTime _INIT(0);
WLED_GLOBAL uint16_t userVar0 _INIT(0), userVar1 _INIT(0); //available for use in usermod
@ -895,12 +907,11 @@ WLED_GLOBAL ESPAsyncE131 ddp _INIT_N(((handleE131Packet)));
WLED_GLOBAL bool e131NewData _INIT(false);
// led fx library object
WLED_GLOBAL BusManager busses _INIT(BusManager());
WLED_GLOBAL WS2812FX strip _INIT(WS2812FX());
WLED_GLOBAL std::vector<BusConfig> busConfigs; //temporary, to remember values from network callback until after
WLED_GLOBAL bool doInitBusses _INIT(false);
WLED_GLOBAL int8_t loadLedmap _INIT(-1);
WLED_GLOBAL uint8_t currentLedmap _INIT(0);
WLED_GLOBAL WS2812FX strip _INIT(WS2812FX());
WLED_GLOBAL std::vector<BusConfig> busConfigs; //temporary, to remember values from network callback until after
WLED_GLOBAL bool doInitBusses _INIT(false);
WLED_GLOBAL int8_t loadLedmap _INIT(-1);
WLED_GLOBAL uint8_t currentLedmap _INIT(0);
#ifndef ESP8266
WLED_GLOBAL char *ledmapNames[WLED_MAX_LEDMAPS-1] _INIT_N(({nullptr}));
#endif

View File

@ -567,13 +567,14 @@ void serveSettings(AsyncWebServerRequest* request, bool post) {
//else if (url.indexOf("/edit") >= 0) subPage = 10;
else subPage = SUBPAGE_WELCOME;
if (!correctPIN && strlen(settingsPIN) > 0 && (subPage > 0 && subPage < 11)) {
bool pinRequired = !correctPIN && strlen(settingsPIN) > 0 && (subPage > (WLED_WIFI_CONFIGURED ? SUBPAGE_MENU : SUBPAGE_WIFI) && subPage < SUBPAGE_LOCK);
if (pinRequired) {
originalSubPage = subPage;
subPage = SUBPAGE_PINREQ; // require PIN
}
// if OTA locked or too frequent PIN entry requests fail hard
if ((subPage == SUBPAGE_WIFI && wifiLock && otaLock) || (post && !correctPIN && millis()-lastEditTime < PIN_RETRY_COOLDOWN))
if ((subPage == SUBPAGE_WIFI && wifiLock && otaLock) || (post && pinRequired && millis()-lastEditTime < PIN_RETRY_COOLDOWN))
{
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_ota), 254); return;
}
@ -609,7 +610,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) {
if (!s2[0]) strcpy_P(s2, s_redirecting);
bool redirectAfter9s = (subPage == SUBPAGE_WIFI || ((subPage == SUBPAGE_SEC || subPage == SUBPAGE_UM) && doReboot));
serveMessage(request, (correctPIN ? 200 : 401), s, s2, redirectAfter9s ? 129 : (correctPIN ? 1 : 3));
serveMessage(request, (!pinRequired ? 200 : 401), s, s2, redirectAfter9s ? 129 : (!pinRequired ? 1 : 3));
return;
}
}

View File

@ -11,7 +11,7 @@ void XML_response(Print& dest)
dest.printf_P(PSTR("<?xml version=\"1.0\" ?><vs><ac>%d</ac>"), (nightlightActive && nightlightMode > NL_MODE_SET) ? briT : bri);
for (int i = 0; i < 3; i++)
{
dest.printf_P(PSTR("<cl>%d</cl>"), col[i]);
dest.printf_P(PSTR("<cl>%d</cl>"), colPri[i]);
}
for (int i = 0; i < 3; i++)
{
@ -20,7 +20,7 @@ void XML_response(Print& dest)
dest.printf_P(PSTR("<ns>%d</ns><nr>%d</nr><nl>%d</nl><nf>%d</nf><nd>%d</nd><nt>%d</nt><fx>%d</fx><sx>%d</sx><ix>%d</ix><fp>%d</fp><wv>%d</wv><ws>%d</ws><ps>%d</ps><cy>%d</cy><ds>%s%s</ds><ss>%d</ss></vs>"),
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()
);
@ -275,7 +275,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
// set limits
settingsScript.printf_P(PSTR("bLimits(%d,%d,%d,%d,%d,%d,%d,%d);"),
WLED_MAX_BUSSES,
WLED_MIN_VIRTUAL_BUSSES,
WLED_MIN_VIRTUAL_BUSSES, // irrelevant, but kept to distinguish S2/S3 in UI
MAX_LEDS_PER_BUS,
MAX_LED_MEMORY,
MAX_LEDS,
@ -296,9 +296,9 @@ void getSettingsJS(byte subPage, Print& settingsScript)
unsigned sumMa = 0;
for (int s = 0; s < BusManager::getNumBusses(); s++) {
Bus* bus = BusManager::getBus(s);
if (bus == nullptr) continue;
int offset = s < 10 ? 48 : 55;
const Bus* bus = BusManager::getBus(s);
if (!bus || !bus->isOk()) break; // should not happen but for safety
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
@ -316,7 +316,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
uint8_t pins[5];
int nPins = bus->getPins(pins);
for (int i = 0; i < nPins; i++) {
lp[1] = offset+i;
lp[1] = '0'+i;
if (PinManager::isPinOk(pins[i]) || bus->isVirtual()) printSetFormValue(settingsScript,lp,pins[i]);
}
printSetFormValue(settingsScript,lc,bus->getLength());
@ -361,7 +361,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
const ColorOrderMap& com = BusManager::getColorOrderMap();
for (int s = 0; s < com.count(); s++) {
const ColorOrderMapEntry* entry = com.get(s);
if (entry == nullptr) break;
if (!entry || !entry->len) break;
settingsScript.printf_P(PSTR("addCOM(%d,%d,%d);"), entry->start, entry->len, entry->colorOrder);
}