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 runs-on: ubuntu-latest
steps: steps:
- name: Send Discord notification - name: Send Discord notification
shell: bash
env:
DISCORD_WEBHOOK_BETA_TESTERS: ${{ secrets.DISCORD_WEBHOOK_BETA_TESTERS }}
if: github.event.pull_request.merged == true if: github.event.pull_request.merged == true
run: | 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,8 +25,8 @@
"dependencies": { "dependencies": {
"clean-css": "^5.3.3", "clean-css": "^5.3.3",
"html-minifier-terser": "^7.2.0", "html-minifier-terser": "^7.2.0",
"inliner": "^1.13.1", "web-resource-inliner": "^7.0.0",
"nodemon": "^3.1.7" "nodemon": "^3.1.9"
}, },
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=20.0.0"

View File

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

View File

@ -37,6 +37,7 @@ Open Usermod Settings in WLED to change settings:
No special requirements. No special requirements.
## Change Log ## Change Log
- 2021-07<br>
2021-07 Upgraded to work with the latest WLED code, and make settings configurable in Usermod Settings
* 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 else
{ {
fastled_col.red = col[0]; fastled_col.red = colPri[0];
fastled_col.green = col[1]; fastled_col.green = colPri[1];
fastled_col.blue = col[2]; fastled_col.blue = colPri[2];
prim_hsv = rgb2hsv_approximate(fastled_col); prim_hsv = rgb2hsv_approximate(fastled_col);
new_val = (int16_t)prim_hsv.h + fadeAmount; new_val = (int16_t)prim_hsv.h + fadeAmount;
if (new_val > 255) if (new_val > 255)
@ -104,9 +104,9 @@ public:
new_val += 255; // roll-over if smaller than 0 new_val += 255; // roll-over if smaller than 0
prim_hsv.h = (byte)new_val; prim_hsv.h = (byte)new_val;
hsv2rgb_rainbow(prim_hsv, fastled_col); hsv2rgb_rainbow(prim_hsv, fastled_col);
col[0] = fastled_col.red; colPri[0] = fastled_col.red;
col[1] = fastled_col.green; colPri[1] = fastled_col.green;
col[2] = fastled_col.blue; colPri[2] = fastled_col.blue;
} }
} }
else if (Enc_B == LOW) else if (Enc_B == LOW)
@ -118,9 +118,9 @@ public:
} }
else else
{ {
fastled_col.red = col[0]; fastled_col.red = colPri[0];
fastled_col.green = col[1]; fastled_col.green = colPri[1];
fastled_col.blue = col[2]; fastled_col.blue = colPri[2];
prim_hsv = rgb2hsv_approximate(fastled_col); prim_hsv = rgb2hsv_approximate(fastled_col);
new_val = (int16_t)prim_hsv.h - fadeAmount; new_val = (int16_t)prim_hsv.h - fadeAmount;
if (new_val > 255) if (new_val > 255)
@ -129,9 +129,9 @@ public:
new_val += 255; // roll-over if smaller than 0 new_val += 255; // roll-over if smaller than 0
prim_hsv.h = (byte)new_val; prim_hsv.h = (byte)new_val;
hsv2rgb_rainbow(prim_hsv, fastled_col); hsv2rgb_rainbow(prim_hsv, fastled_col);
col[0] = fastled_col.red; colPri[0] = fastled_col.red;
col[1] = fastled_col.green; colPri[1] = fastled_col.green;
col[2] = fastled_col.blue; colPri[2] = fastled_col.blue;
} }
} }
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification) //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(); 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(); if (!initDone) sortModesAndPalettes();
@ -918,17 +918,17 @@ void RotaryEncoderUIUsermod::changeHue(bool increase){
display->updateRedrawTime(); display->updateRedrawTime();
#endif #endif
currentHue1 = max(min((increase ? currentHue1+fadeAmount : currentHue1-fadeAmount), 255), 0); currentHue1 = max(min((increase ? currentHue1+fadeAmount : currentHue1-fadeAmount), 255), 0);
colorHStoRGB(currentHue1*256, currentSat1, col); colorHStoRGB(currentHue1*256, currentSat1, colPri);
stateChanged = true; stateChanged = true;
if (applyToAll) { if (applyToAll) {
for (unsigned i=0; i<strip.getSegmentsNum(); i++) { for (unsigned i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i); Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue; 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 { } else {
Segment& seg = strip.getSegment(strip.getMainSegmentId()); 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(); lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
@ -948,16 +948,16 @@ void RotaryEncoderUIUsermod::changeSat(bool increase){
display->updateRedrawTime(); display->updateRedrawTime();
#endif #endif
currentSat1 = max(min((increase ? currentSat1+fadeAmount : currentSat1-fadeAmount), 255), 0); currentSat1 = max(min((increase ? currentSat1+fadeAmount : currentSat1-fadeAmount), 255), 0);
colorHStoRGB(currentHue1*256, currentSat1, col); colorHStoRGB(currentHue1*256, currentSat1, colPri);
if (applyToAll) { if (applyToAll) {
for (unsigned i=0; i<strip.getSegmentsNum(); i++) { for (unsigned i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i); Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue; 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 { } else {
Segment& seg = strip.getSegment(strip.getMainSegmentId()); 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(); lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY

View File

@ -676,8 +676,10 @@ typedef struct Segment {
[[gnu::hot]] uint32_t getPixelColor(int i) const; [[gnu::hot]] uint32_t getPixelColor(int i) const;
// 1D support functions (some implement 2D as well) // 1D support functions (some implement 2D as well)
void blur(uint8_t, bool smear = false); void blur(uint8_t, bool smear = false);
void clear();
void fill(uint32_t c); void fill(uint32_t c);
void fade_out(uint8_t r); void fade_out(uint8_t r);
void fadeToSecondaryBy(uint8_t fadeBy);
void fadeToBlackBy(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, 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); } 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 case 60: bits = pgm_read_byte_near(&console_font_5x12[(chr * h) + i]); break; // 5x12 font
default: return; default: return;
} }
uint32_t c = ColorFromPaletteWLED(grad, (i+1)*255/h, 255, NOBLEND); CRGBW c = ColorFromPalette(grad, (i+1)*255/h, _segBri, LINEARBLEND_NOWRAP);
// pre-scale color for all pixels
c = color_fade(c, _segBri);
_colorScaled = true; _colorScaled = true;
for (int j = 0; j<w; j++) { // character width for (int j = 0; j<w; j++) { // character width
int x0, y0; 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 (x0 < 0 || x0 >= (int)vWidth() || y0 < 0 || y0 >= (int)vHeight()) continue; // drawing off-screen
if (((bits>>(j+(8-w))) & 0x01)) { // bit set if (((bits>>(j+(8-w))) & 0x01)) { // bit set
setPixelColorXY(x0, y0, c); setPixelColorXY(x0, y0, c.color32);
} }
} }
_colorScaled = false; _colorScaled = false;

View File

@ -270,7 +270,7 @@ void Segment::startTransition(uint16_t dur) {
_t->_briT = on ? opacity : 0; _t->_briT = on ? opacity : 0;
_t->_cctT = cct; _t->_cctT = cct;
#ifndef WLED_DISABLE_MODE_BLEND #ifndef WLED_DISABLE_MODE_BLEND
swapSegenv(_t->_segT); swapSegenv(_t->_segT); // copy runtime data to temporary
_t->_modeT = mode; _t->_modeT = mode;
_t->_segT._dataLenT = 0; _t->_segT._dataLenT = 0;
_t->_segT._dataT = nullptr; _t->_segT._dataT = nullptr;
@ -282,6 +282,13 @@ void Segment::startTransition(uint16_t dur) {
_t->_segT._dataLenT = _dataLen; _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 #else
for (size_t i=0; i<NUM_COLORS; i++) _t->_colorT[i] = colors[i]; for (size_t i=0; i<NUM_COLORS; i++) _t->_colorT[i] = colors[i];
#endif #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 #ifndef WLED_DISABLE_2D
if (Segment::maxHeight>1) boundsUnchanged &= (startY == i1Y && stopY == i2Y); // 2D if (Segment::maxHeight>1) boundsUnchanged &= (startY == i1Y && stopY == i2Y); // 2D
#endif #endif
if (stop && (spc > 0 || m12 != map1D2D)) clear();
/*
if (boundsUnchanged if (boundsUnchanged
&& (!grp || (grouping == grp && spacing == spc)) && (!grp || (grouping == grp && spacing == spc))
&& (ofs == UINT16_MAX || ofs == offset) && (ofs == UINT16_MAX || ofs == offset)
&& (m12 == map1D2D) && (m12 == map1D2D)
) return; ) return;
*/
stateChanged = true; // send UDP/WS broadcast 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 if (grp) { // prevent assignment of 0
grouping = grp; grouping = grp;
spacing = spc; 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; if (ofs < UINT16_MAX) offset = ofs;
map1D2D = constrain(m12, 0, 7); map1D2D = constrain(m12, 0, 7);
DEBUG_PRINT(F("setUp segment: ")); DEBUG_PRINT(i1); DEBUG_PRINTF_P(PSTR("Segment geometry: %d,%d -> %d,%d\n"), (int)i1, (int)i2, (int)i1Y, (int)i2Y);
DEBUG_PRINT(','); DEBUG_PRINT(i2);
DEBUG_PRINT(F(" -> ")); DEBUG_PRINT(i1Y);
DEBUG_PRINT(','); DEBUG_PRINTLN(i2Y);
markForReset(); markForReset();
if (boundsUnchanged) return; if (boundsUnchanged) return;
@ -1138,12 +1138,9 @@ void Segment::refreshLightCapabilities() {
} }
for (unsigned b = 0; b < BusManager::getNumBusses(); b++) { for (unsigned b = 0; b < BusManager::getNumBusses(); b++) {
Bus *bus = BusManager::getBus(b); const Bus *bus = BusManager::getBus(b);
if (bus == nullptr || bus->getLength()==0) break; if (!bus || !bus->isOk()) break;
if (!bus->isOk()) continue; if (bus->getStart() >= segStopIdx || bus->getStart() + bus->getLength() <= segStartIdx) continue;
if (bus->getStart() >= segStopIdx) continue;
if (bus->getStart() + bus->getLength() <= segStartIdx) continue;
if (bus->hasRGB() || (strip.cctFromRgb && bus->hasCCT())) capabilities |= SEG_CAPABILITY_RGB; if (bus->hasRGB() || (strip.cctFromRgb && bus->hasCCT())) capabilities |= SEG_CAPABILITY_RGB;
if (!strip.cctFromRgb && bus->hasCCT()) capabilities |= SEG_CAPABILITY_CCT; 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) 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; _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 * Fills segment with color
*/ */
@ -1178,42 +1195,45 @@ void Segment::fill(uint32_t c) {
/* /*
* fade out function, higher rate = quicker fade * 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) { void Segment::fade_out(uint8_t rate) {
if (!isActive()) return; // not active if (!isActive()) return; // not active
const int cols = is2D() ? vWidth() : vLength(); const int cols = is2D() ? vWidth() : vLength();
const int rows = vHeight(); // will be 1 for 1D const int rows = vHeight(); // will be 1 for 1D
rate = (255-rate) >> 1; rate = (256-rate) >> 1;
float mappedRate = 1.0f / (float(rate) + 1.1f); const int mappedRate = 256 / (rate + 1);
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);
for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { 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 if (color == colors[1]) continue; // already at target color
int w1 = W(color); for (int i = 0; i < 32; i += 8) {
int r1 = R(color); uint8_t c2 = (colors[1]>>i); // get background channel
int g1 = G(color); uint8_t c1 = (color>>i); // get foreground channel
int b1 = B(color); // 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; // fades all pixels to secondary color
int rdelta = (r2 - r1) * mappedRate; void Segment::fadeToSecondaryBy(uint8_t fadeBy) {
int gdelta = (g2 - g1) * mappedRate; if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply
int bdelta = (b2 - b1) * mappedRate; 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) for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) {
wdelta += (w2 == w1) ? 0 : (w2 > w1) ? 1 : -1; if (is2D()) setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), colors[1], fadeBy));
rdelta += (r2 == r1) ? 0 : (r2 > r1) ? 1 : -1; else setPixelColor(x, color_blend(getPixelColor(x), colors[1], fadeBy));
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);
} }
} }
@ -1291,12 +1311,12 @@ uint32_t Segment::color_wheel(uint8_t pos) const {
* Gets a single color from the currently selected palette. * 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 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 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 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) * @param pbri Value to scale the brightness of the returned color by. Default is 255. (no scaling)
* @returns Single color from palette * @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); uint32_t color = getCurrentColor(mcol < NUM_COLORS ? mcol : 0);
// default palette or no RGB support on segment // default palette or no RGB support on segment
if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) { 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(); const int vL = vLength();
unsigned paletteIndex = i; unsigned paletteIndex = i;
if (mapping && vL > 1) paletteIndex = (i*255)/(vL -1); if (mapping && vL > 1) paletteIndex = (i*255)/(vL -1);
// paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined/no interpolation of palette entries)
if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" // ColorFromPalette interpolations are: NOBLEND, LINEARBLEND, LINEARBLEND_NOWRAP
CRGBW palcol = ColorFromPaletteWLED(_currentPalette, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global 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); palcol.w = W(color);
return palcol.color32; return palcol.color32;
@ -1449,8 +1475,7 @@ void WS2812FX::finalizeInit() {
_length = 0; _length = 0;
for (int i=0; i<BusManager::getNumBusses(); i++) { for (int i=0; i<BusManager::getNumBusses(); i++) {
Bus *bus = BusManager::getBus(i); Bus *bus = BusManager::getBus(i);
if (bus == nullptr) continue; if (!bus || !bus->isOk() || bus->getStart() + bus->getLength() > MAX_LEDS) break;
if (bus->getStart() + bus->getLength() > MAX_LEDS) break;
//RGBW mode is enabled if at least one of the strips is RGBW //RGBW mode is enabled if at least one of the strips is RGBW
_hasWhiteChannel |= bus->hasWhite(); _hasWhiteChannel |= bus->hasWhite();
//refresh is required to remain off if at least one of the strips requires the refresh. //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 // This must be done after all buses have been created, as some kinds (parallel I2S) interact
bus->begin(); bus->begin();
bus->setBrightness(bri);
} }
DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), ESP.getFreeHeap()); DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), ESP.getFreeHeap());
@ -1490,7 +1516,7 @@ void WS2812FX::service() {
_segment_index = 0; _segment_index = 0;
for (segment &seg : _segments) { 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) // process transition (mode changes in the middle of transition)
seg.handleTransition(); seg.handleTransition();
@ -1521,6 +1547,11 @@ void WS2812FX::service() {
#ifndef WLED_DISABLE_MODE_BLEND #ifndef WLED_DISABLE_MODE_BLEND
Segment::setClippingRect(0, 0); // disable clipping (just in case) Segment::setClippingRect(0, 0); // disable clipping (just in case)
if (seg.isInTransition()) { 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 // set clipping rectangle
// new mode is run inside clipping area and old mode outside clipping area // new mode is run inside clipping area and old mode outside clipping area
unsigned p = seg.progress(); unsigned p = seg.progress();
@ -1529,7 +1560,20 @@ void WS2812FX::service() {
unsigned dw = p * w / 0xFFFFU + 1; unsigned dw = p * w / 0xFFFFU + 1;
unsigned dh = p * h / 0xFFFFU + 1; unsigned dh = p * h / 0xFFFFU + 1;
unsigned orgBS = blendingStyle; 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) { switch (blendingStyle) {
case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped())
Segment::setClippingRect(0, w, 0, h); Segment::setClippingRect(0, w, 0, h);
@ -1575,7 +1619,7 @@ void WS2812FX::service() {
Segment::setClippingRect(0, dw, h - dh, h); Segment::setClippingRect(0, dw, h - dh, h);
break; break;
} }
frameDelay = (*_mode[seg.currentMode()])(); // run new/current mode frameDelay = (*_mode[m])(); // run new/current mode
// now run old/previous mode // now run old/previous mode
Segment::tmpsegd_t _tmpSegData; Segment::tmpsegd_t _tmpSegData;
Segment::modeBlend(true); // set semaphore Segment::modeBlend(true); // set semaphore
@ -1611,8 +1655,8 @@ void WS2812FX::service() {
if (doShow) { if (doShow) {
yield(); yield();
Segment::handleRandomPalette(); // slowly transition random palette; move it into for loop when each segment has individual random palette 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 _lastServiceShow = nowUp; // update timestamp, for precise FPS control
if (!_suspend) show();
} }
#ifdef WLED_DEBUG #ifdef WLED_DEBUG
if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); 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 //not influenced by auto-white mode, also true if white slider does not affect output white channel
bool WS2812FX::hasRGBWBus() const { bool WS2812FX::hasRGBWBus() const {
for (size_t b = 0; b < BusManager::getNumBusses(); b++) { for (size_t b = 0; b < BusManager::getNumBusses(); b++) {
Bus *bus = BusManager::getBus(b); const Bus *bus = BusManager::getBus(b);
if (bus == nullptr || bus->getLength()==0) break; if (!bus || !bus->isOk()) break;
if (bus->hasRGB() && bus->hasWhite()) return true; if (bus->hasRGB() && bus->hasWhite()) return true;
} }
return false; return false;
@ -1757,8 +1801,8 @@ bool WS2812FX::hasRGBWBus() const {
bool WS2812FX::hasCCTBus() const { bool WS2812FX::hasCCTBus() const {
if (cctFromRgb && !correctWB) return false; if (cctFromRgb && !correctWB) return false;
for (size_t b = 0; b < BusManager::getNumBusses(); b++) { for (size_t b = 0; b < BusManager::getNumBusses(); b++) {
Bus *bus = BusManager::getBus(b); const Bus *bus = BusManager::getBus(b);
if (bus == nullptr || bus->getLength()==0) break; if (!bus || !bus->isOk()) break;
if (bus->hasCCT()) return true; if (bus->hasCCT()) return true;
} }
return false; return false;
@ -1811,10 +1855,11 @@ void WS2812FX::makeAutoSegments(bool forceReset) {
#endif #endif
for (size_t i = s; i < BusManager::getNumBusses(); i++) { 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(); segStarts[s] = bus->getStart();
segStops[s] = segStarts[s] + b->getLength(); segStops[s] = segStarts[s] + bus->getLength();
#ifndef WLED_DISABLE_2D #ifndef WLED_DISABLE_2D
if (isMatrix && segStops[s] <= Segment::maxWidth*Segment::maxHeight) continue; // ignore buses comprising matrix if (isMatrix && segStops[s] <= Segment::maxWidth*Segment::maxHeight) continue; // ignore buses comprising matrix
@ -1904,7 +1949,8 @@ bool WS2812FX::checkSegmentAlignment() const {
bool aligned = false; bool aligned = false;
for (const segment &seg : _segments) { for (const segment &seg : _segments) {
for (unsigned b = 0; b<BusManager::getNumBusses(); b++) { 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 == bus->getStart() && seg.stop == bus->getStart() + bus->getLength()) aligned = true;
} }
if (seg.start == 0 && seg.stop == _length) 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)) #define W(c) (byte((c) >> 24))
static ColorOrderMap _colorOrderMap = {};
bool ColorOrderMap::add(uint16_t start, uint16_t len, uint8_t colorOrder) { 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 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}); _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 { uint8_t IRAM_ATTR ColorOrderMap::getPixelColorOrder(uint16_t pix, uint8_t defaultColorOrder) const {
// upper nibble contains W swap information // 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 // 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++) { for (const auto& map : _mappings) {
if (pix >= _mappings[i].start && pix < (_mappings[i].start + _mappings[i].len)) { if (pix >= map.start && pix < (map.start + map.len)) return map.colorOrder | ((map.colorOrder >> 4) ? 0 : (defaultColorOrder & 0xF0));
return _mappings[i].colorOrder | ((_mappings[i].colorOrder >> 4) ? 0 : (defaultColorOrder & 0xF0));
}
} }
return defaultColorOrder; return defaultColorOrder;
} }
@ -99,23 +99,14 @@ uint32_t Bus::autoWhiteCalc(uint32_t c) const {
return RGBW32(r, g, b, w); 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() { BusDigital::BusDigital(const BusConfig &bc, uint8_t nr)
if (_data) free(_data);
_data = nullptr;
}
BusDigital::BusDigital(const BusConfig &bc, uint8_t nr, const ColorOrderMap &com)
: Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, (bc.refreshReq || bc.type == TYPE_TM1814)) : Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, (bc.refreshReq || bc.type == TYPE_TM1814))
, _skip(bc.skipAmount) //sacrificial pixels , _skip(bc.skipAmount) //sacrificial pixels
, _colorOrder(bc.colorOrder) , _colorOrder(bc.colorOrder)
, _milliAmpsPerLed(bc.milliAmpsPerLed) , _milliAmpsPerLed(bc.milliAmpsPerLed)
, _milliAmpsMax(bc.milliAmpsMax) , _milliAmpsMax(bc.milliAmpsMax)
, _colorOrderMap(com) , _data(nullptr)
{ {
DEBUGBUS_PRINTLN(F("Bus: Creating digital bus.")); DEBUGBUS_PRINTLN(F("Bus: Creating digital bus."));
if (!isDigital(bc.type) || !bc.count) { DEBUGBUS_PRINTLN(F("Not digial or empty bus!")); return; } 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); _hasRgb = hasRGB(bc.type);
_hasWhite = hasWhite(bc.type); _hasWhite = hasWhite(bc.type);
_hasCCT = hasCCT(bc.type); _hasCCT = hasCCT(bc.type);
if (bc.doubleBuffer && !allocateData(bc.count * Bus::getNumberOfChannels(bc.type))) { DEBUGBUS_PRINTLN(F("Buffer allocation failed!")); return; } if (bc.doubleBuffer) {
//_buffering = bc.doubleBuffer; _data = (uint8_t*)calloc(_len, Bus::getNumberOfChannels(_type));
if (!_data) DEBUGBUS_PRINTLN(F("Bus: Buffer allocation failed!"));
}
uint16_t lenToCreate = bc.count; 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 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); _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"), 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", _valid?"S":"Uns",
(int)nr, (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 // 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 // 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) // or async show has a separate buffer (ESP32 RMT and I2S are ok)
@ -421,11 +414,11 @@ void BusDigital::begin() {
void BusDigital::cleanup() { void BusDigital::cleanup() {
DEBUGBUS_PRINTLN(F("Digital Cleanup.")); DEBUGBUS_PRINTLN(F("Digital Cleanup."));
PolyBus::cleanup(_busPtr, _iType); PolyBus::cleanup(_busPtr, _iType);
free(_data);
_data = nullptr;
_iType = I_NONE; _iType = I_NONE;
_valid = false; _valid = false;
_busPtr = nullptr; _busPtr = nullptr;
freeData();
//PinManager::deallocateMultiplePins(_pins, 2, PinOwner::BusDigital);
PinManager::deallocatePin(_pins[1], PinOwner::BusDigital); PinManager::deallocatePin(_pins[1], PinOwner::BusDigital);
PinManager::deallocatePin(_pins[0], PinOwner::BusDigital); PinManager::deallocatePin(_pins[0], PinOwner::BusDigital);
} }
@ -497,7 +490,6 @@ BusPwm::BusPwm(const BusConfig &bc)
_hasRgb = hasRGB(bc.type); _hasRgb = hasRGB(bc.type);
_hasWhite = hasWhite(bc.type); _hasWhite = hasWhite(bc.type);
_hasCCT = hasCCT(bc.type); _hasCCT = hasCCT(bc.type);
_data = _pwmdata; // avoid malloc() and use already allocated memory
_valid = true; _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]); 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() { void BusPwm::show() {
if (!_valid) return; if (!_valid) return;
const unsigned numPins = getPins(); const size_t numPins = getPins();
#ifdef ESP8266 #ifdef ESP8266
const unsigned analogPeriod = F_CPU / _frequency; const unsigned analogPeriod = F_CPU / _frequency;
const unsigned maxBri = analogPeriod; // compute to clock cycle accuracy const unsigned maxBri = analogPeriod; // compute to clock cycle accuracy
@ -648,7 +640,7 @@ std::vector<LEDType> BusPwm::getLEDTypes() {
} }
void BusPwm::deallocatePins() { void BusPwm::deallocatePins() {
unsigned numPins = getPins(); size_t numPins = getPins();
for (unsigned i = 0; i < numPins; i++) { for (unsigned i = 0; i < numPins; i++) {
PinManager::deallocatePin(_pins[i], PinOwner::BusPwm); PinManager::deallocatePin(_pins[i], PinOwner::BusPwm);
if (!PinManager::isPinOk(_pins[i])) continue; if (!PinManager::isPinOk(_pins[i])) continue;
@ -666,7 +658,7 @@ void BusPwm::deallocatePins() {
BusOnOff::BusOnOff(const BusConfig &bc) BusOnOff::BusOnOff(const BusConfig &bc)
: Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed) : Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed)
, _onoffdata(0) , _data(0)
{ {
if (!Bus::isOnOff(bc.type)) return; if (!Bus::isOnOff(bc.type)) return;
@ -679,7 +671,6 @@ BusOnOff::BusOnOff(const BusConfig &bc)
_hasRgb = false; _hasRgb = false;
_hasWhite = false; _hasWhite = false;
_hasCCT = false; _hasCCT = false;
_data = &_onoffdata; // avoid malloc() and use stack
_valid = true; _valid = true;
DEBUGBUS_PRINTF_P(PSTR("%successfully inited On/Off strip with pin %u\n"), _valid?"S":"Uns", _pin); 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 g = G(c);
uint8_t b = B(c); uint8_t b = B(c);
uint8_t w = W(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 { uint32_t BusOnOff::getPixelColor(unsigned pix) const {
if (!_valid) return 0; if (!_valid) return 0;
return RGBW32(_data[0], _data[0], _data[0], _data[0]); return RGBW32(_data, _data, _data, _data);
} }
void BusOnOff::show() { void BusOnOff::show() {
if (!_valid) return; 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 { unsigned BusOnOff::getPins(uint8_t* pinArray) const {
@ -740,7 +731,8 @@ BusNetwork::BusNetwork(const BusConfig &bc)
_hasCCT = false; _hasCCT = false;
_UDPchannels = _hasWhite + 3; _UDPchannels = _hasWhite + 3;
_client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[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]); 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() { void BusNetwork::cleanup() {
DEBUGBUS_PRINTLN(F("Virtual Cleanup.")); DEBUGBUS_PRINTLN(F("Virtual Cleanup."));
free(_data);
_data = nullptr;
_type = I_NONE; _type = I_NONE;
_valid = false; _valid = false;
freeData();
} }
@ -844,17 +837,17 @@ int BusManager::add(const BusConfig &bc) {
unsigned numDigital = 0; unsigned numDigital = 0;
for (const auto &bus : busses) if (bus->isDigital() && !bus->is2Pin()) numDigital++; for (const auto &bus : busses) if (bus->isDigital() && !bus->is2Pin()) numDigital++;
if (Bus::isVirtual(bc.type)) { if (Bus::isVirtual(bc.type)) {
//busses.push_back(std::make_unique<BusNetwork>(bc)); // when C++ >11 busses.push_back(make_unique<BusNetwork>(bc));
busses.push_back(new BusNetwork(bc)); //busses.push_back(new BusNetwork(bc));
} else if (Bus::isDigital(bc.type)) { } else if (Bus::isDigital(bc.type)) {
//busses.push_back(std::make_unique<BusDigital>(bc, numDigital, colorOrderMap)); busses.push_back(make_unique<BusDigital>(bc, numDigital));
busses.push_back(new BusDigital(bc, numDigital, colorOrderMap)); //busses.push_back(new BusDigital(bc, numDigital));
} else if (Bus::isOnOff(bc.type)) { } else if (Bus::isOnOff(bc.type)) {
//busses.push_back(std::make_unique<BusOnOff>(bc)); busses.push_back(make_unique<BusOnOff>(bc));
busses.push_back(new BusOnOff(bc)); //busses.push_back(new BusOnOff(bc));
} else { } else {
//busses.push_back(std::make_unique<BusPwm>(bc)); busses.push_back(make_unique<BusPwm>(bc));
busses.push_back(new BusPwm(bc)); //busses.push_back(new BusPwm(bc));
} }
return busses.size(); return busses.size();
} }
@ -898,7 +891,7 @@ void BusManager::removeAll() {
DEBUGBUS_PRINTLN(F("Removing all.")); DEBUGBUS_PRINTLN(F("Removing all."));
//prevents crashes due to deleting busses while in use. //prevents crashes due to deleting busses while in use.
while (!canAllShow()) yield(); 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(); busses.clear();
PolyBus::setParallelI2S1Output(false); PolyBus::setParallelI2S1Output(false);
} }
@ -949,8 +942,8 @@ void BusManager::on() {
uint8_t pins[2] = {255,255}; uint8_t pins[2] = {255,255};
if (bus->isDigital() && bus->getPins(pins)) { if (bus->isDigital() && bus->getPins(pins)) {
if (pins[0] == LED_BUILTIN || pins[1] == LED_BUILTIN) { if (pins[0] == LED_BUILTIN || pins[1] == LED_BUILTIN) {
BusDigital *b = static_cast<BusDigital*>(bus); BusDigital &b = static_cast<BusDigital&>(*bus);
b->begin(); b.begin();
break; break;
} }
} }
@ -978,17 +971,13 @@ void BusManager::off() {
} }
void BusManager::show() { void BusManager::show() {
_milliAmpsUsed = 0; _gMilliAmpsUsed = 0;
for (auto &bus : busses) { for (auto &bus : busses) {
bus->show(); 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) { void IRAM_ATTR BusManager::setPixelColor(unsigned pix, uint32_t c) {
for (auto &bus : busses) { for (auto &bus : busses) {
unsigned bstart = bus->getStart(); 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) { void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) {
if (cct > 255) cct = 255; if (cct > 255) cct = 255;
if (cct >= 0) { if (cct >= 0) {
@ -1024,17 +1009,8 @@ bool BusManager::canAllShow() {
return true; return true;
} }
Bus* BusManager::getBus(uint8_t busNr) { ColorOrderMap& BusManager::getColorOrderMap() { return _colorOrderMap; }
if (busNr >= busses.size()) return nullptr;
return busses[busNr];
}
//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; bool PolyBus::_useParallelI2S = false;
@ -1045,8 +1021,7 @@ uint8_t Bus::_gAWM = 255;
uint16_t BusDigital::_milliAmpsTotal = 0; uint16_t BusDigital::_milliAmpsTotal = 0;
//std::vector<std::unique_ptr<Bus>> BusManager::busses; std::vector<std::unique_ptr<Bus>> BusManager::busses;
std::vector<Bus*> BusManager::busses; //std::vector<Bus*> BusManager::busses;
ColorOrderMap BusManager::colorOrderMap = {}; uint16_t BusManager::_gMilliAmpsUsed = 0;
uint16_t BusManager::_milliAmpsUsed = 0; uint16_t BusManager::_gMilliAmpsMax = ABL_MILLIAMPS_DEFAULT;
uint16_t BusManager::_milliAmpsMax = ABL_MILLIAMPS_DEFAULT;

View File

@ -11,6 +11,18 @@
#include <vector> #include <vector>
#include <memory> #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 // enable additional debug output
#if defined(WLED_DEBUG_HOST) #if defined(WLED_DEBUG_HOST)
#include "net_debug.h" #include "net_debug.h"
@ -94,11 +106,10 @@ class Bus {
: _type(type) : _type(type)
, _bri(255) , _bri(255)
, _start(start) , _start(start)
, _len(len) , _len(std::max(len,(uint16_t)1))
, _reversed(reversed) , _reversed(reversed)
, _valid(false) , _valid(false)
, _needsRefresh(refresh) , _needsRefresh(refresh)
, _data(nullptr) // keep data access consistent across all types of buses
{ {
_autoWhiteMode = Bus::hasWhite(type) ? aw : RGBW_MODE_MANUAL_ONLY; _autoWhiteMode = Bus::hasWhite(type) ? aw : RGBW_MODE_MANUAL_ONLY;
}; };
@ -202,7 +213,6 @@ class Bus {
bool _hasCCT;// : 1; bool _hasCCT;// : 1;
//} __attribute__ ((packed)); //} __attribute__ ((packed));
uint8_t _autoWhiteMode; uint8_t _autoWhiteMode;
uint8_t *_data;
// global Auto White Calculation override // global Auto White Calculation override
static uint8_t _gAWM; static uint8_t _gAWM;
// _cct has the following menaings (see calculateCCT() & BusManager::setSegmentCCT()): // _cct has the following menaings (see calculateCCT() & BusManager::setSegmentCCT()):
@ -217,14 +227,12 @@ class Bus {
static uint8_t _cctBlend; static uint8_t _cctBlend;
uint32_t autoWhiteCalc(uint32_t c) const; uint32_t autoWhiteCalc(uint32_t c) const;
uint8_t *allocateData(size_t size = 1);
void freeData();
}; };
class BusDigital : public Bus { class BusDigital : public Bus {
public: public:
BusDigital(const BusConfig &bc, uint8_t nr, const ColorOrderMap &com); BusDigital(const BusConfig &bc, uint8_t nr);
~BusDigital() { cleanup(); } ~BusDigital() { cleanup(); }
void show() override; void show() override;
@ -248,15 +256,15 @@ class BusDigital : public Bus {
static std::vector<LEDType> getLEDTypes(); static std::vector<LEDType> getLEDTypes();
private: private:
uint8_t _skip; uint8_t _skip;
uint8_t _colorOrder; uint8_t _colorOrder;
uint8_t _pins[2]; uint8_t _pins[2];
uint8_t _iType; uint8_t _iType;
uint16_t _frequencykHz; uint16_t _frequencykHz;
uint8_t _milliAmpsPerLed; uint8_t _milliAmpsPerLed;
uint16_t _milliAmpsMax; uint16_t _milliAmpsMax;
void * _busPtr; uint8_t *_data;
const ColorOrderMap &_colorOrderMap; void *_busPtr;
static uint16_t _milliAmpsTotal; // is overwitten/recalculated on each show() 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; } uint16_t getFrequency() const override { return _frequency; }
unsigned getBusSize() const override { return sizeof(BusPwm); } unsigned getBusSize() const override { return sizeof(BusPwm); }
void show() override; void show() override;
inline void cleanup() { deallocatePins(); _data = nullptr; } inline void cleanup() { deallocatePins(); }
static std::vector<LEDType> getLEDTypes(); static std::vector<LEDType> getLEDTypes();
private: private:
uint8_t _pins[OUTPUT_MAX_PINS]; uint8_t _pins[OUTPUT_MAX_PINS];
uint8_t _pwmdata[OUTPUT_MAX_PINS]; uint8_t _data[OUTPUT_MAX_PINS];
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
uint8_t _ledcStart; uint8_t _ledcStart;
#endif #endif
@ -313,13 +321,13 @@ class BusOnOff : public Bus {
unsigned getPins(uint8_t* pinArray) const override; unsigned getPins(uint8_t* pinArray) const override;
unsigned getBusSize() const override { return sizeof(BusOnOff); } unsigned getBusSize() const override { return sizeof(BusOnOff); }
void show() override; 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(); static std::vector<LEDType> getLEDTypes();
private: private:
uint8_t _pin; uint8_t _pin;
uint8_t _onoffdata; uint8_t _data;
}; };
@ -343,6 +351,7 @@ class BusNetwork : public Bus {
uint8_t _UDPtype; uint8_t _UDPtype;
uint8_t _UDPchannels; uint8_t _UDPchannels;
bool _broadcastLock; bool _broadcastLock;
uint8_t *_data;
}; };
@ -363,7 +372,7 @@ struct BusConfig {
uint16_t milliAmpsMax; 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) 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) , start(pstart)
, colorOrder(pcolorOrder) , colorOrder(pcolorOrder)
, reversed(rev) , reversed(rev)
@ -416,59 +425,58 @@ struct BusConfig {
#endif #endif
#endif #endif
class BusManager { namespace BusManager {
public:
BusManager() {};
static unsigned memUsage(); extern std::vector<std::unique_ptr<Bus>> busses;
static uint16_t currentMilliamps() { return _milliAmpsUsed + MA_FOR_ESP; } //extern std::vector<Bus*> busses;
static uint16_t ablMilliampsMax() { return _milliAmpsMax; } extern uint16_t _gMilliAmpsUsed;
extern uint16_t _gMilliAmpsMax;
static int add(const BusConfig &bc); #ifdef ESP32_DATA_IDLE_HIGH
static void useParallelOutput(); // workaround for inaccessible PolyBus void esp32RMTInvertIdle() ;
static bool hasParallelOutput(); // workaround for inaccessible PolyBus #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) size_t memUsage();
static void removeAll(); 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(); void useParallelOutput(); // workaround for inaccessible PolyBus
static void off(); bool hasParallelOutput(); // workaround for inaccessible PolyBus
static void show(); //do not call this method from system context (network callback)
static bool canAllShow(); void removeAll();
static void setStatusPixel(uint32_t c); int add(const BusConfig &bc);
[[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(); }
static Bus* getBus(uint8_t busNr); void on();
void off();
//semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit()) [[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c);
static uint16_t getTotalLength(); [[gnu::hot]] uint32_t getPixelColor(unsigned pix);
static inline uint8_t getNumBusses() { return busses.size(); } void show();
static String getLEDTypesJSONString(); 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; } //semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit())
inline uint16_t getTotalLength(bool onlyPhysical = false) {
private: unsigned len = 0;
//static std::vector<std::unique_ptr<Bus>> busses; // we'd need C++ >11 for (const auto &bus : busses) if (!(bus->isVirtual() && onlyPhysical)) len += bus->getLength();
static std::vector<Bus*> busses; return len;
static ColorOrderMap colorOrderMap; }
static uint16_t _milliAmpsUsed; String getLEDTypesJSONString();
static uint16_t _milliAmpsMax; ColorOrderMap& getColorOrderMap();
#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;
}
}; };
#endif #endif

View File

@ -40,7 +40,7 @@ void longPressAction(uint8_t b)
{ {
if (!macroLongPress[b]) { if (!macroLongPress[b]) {
switch (b) { switch (b) {
case 0: setRandomColor(col); colorUpdated(CALL_MODE_BUTTON); break; case 0: setRandomColor(colPri); colorUpdated(CALL_MODE_BUTTON); break;
case 1: case 1:
if(buttonBriDirection) { if(buttonBriDirection) {
if (bri == 255) break; // avoid unnecessary updates to brightness 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 effectPalette = constrain(effectPalette, 0, strip.getPaletteCount()-1); // map is allowed to "overshoot", so we need to contrain the result
} else if (macroDoublePress[b] == 200) { } else if (macroDoublePress[b] == 200) {
// primary color, hue, full saturation // primary color, hue, full saturation
colorHStoRGB(aRead*256,255,col); colorHStoRGB(aRead*256,255,colPri);
} else { } else {
// otherwise use "double press" for segment selection // otherwise use "double press" for segment selection
Segment& seg = strip.getSegment(macroDoublePress[b]); 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 if (fromFS) BusManager::removeAll(); // can't safely manipulate busses directly in network callback
for (JsonObject elm : ins) { 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}; uint8_t pins[5] = {255, 255, 255, 255, 255};
JsonArray pinArr = elm["pin"]; JsonArray pinArr = elm["pin"];
if (pinArr.size() == 0) continue; 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 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() 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 // read color order map configuration
JsonArray hw_com = hw[F("com")]; JsonArray hw_com = hw[F("com")];
@ -824,8 +825,8 @@ void serializeConfig() {
for (size_t s = 0; s < BusManager::getNumBusses(); s++) { for (size_t s = 0; s < BusManager::getNumBusses(); s++) {
DEBUG_PRINTF_P(PSTR("Cfg: Saving bus #%u\n"), s); DEBUG_PRINTF_P(PSTR("Cfg: Saving bus #%u\n"), s);
Bus *bus = BusManager::getBus(s); const Bus *bus = BusManager::getBus(s);
if (!bus || bus->getLength()==0) break; 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"), 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->getStart(), (int)(bus->getStart()+bus->getLength()),
(int)(bus->getType() & 0x7F), (int)(bus->getType() & 0x7F),
@ -838,28 +839,27 @@ void serializeConfig() {
); );
JsonObject ins = hw_led_ins.createNestedObject(); JsonObject ins = hw_led_ins.createNestedObject();
ins["start"] = bus->getStart(); ins["start"] = bus->getStart();
ins["len"] = bus->getLength(); ins["len"] = bus->getLength();
JsonArray ins_pin = ins.createNestedArray("pin"); JsonArray ins_pin = ins.createNestedArray("pin");
uint8_t pins[5]; uint8_t pins[5];
uint8_t nPins = bus->getPins(pins); uint8_t nPins = bus->getPins(pins);
for (int i = 0; i < nPins; i++) ins_pin.add(pins[i]); for (int i = 0; i < nPins; i++) ins_pin.add(pins[i]);
ins[F("order")] = bus->getColorOrder(); ins[F("order")] = bus->getColorOrder();
ins["rev"] = bus->isReversed(); ins["rev"] = bus->isReversed();
ins[F("skip")] = bus->skippedLeds(); ins[F("skip")] = bus->skippedLeds();
ins["type"] = bus->getType() & 0x7F; ins["type"] = bus->getType() & 0x7F;
ins["ref"] = bus->isOffRefreshRequired(); ins["ref"] = bus->isOffRefreshRequired();
ins[F("rgbwm")] = bus->getAutoWhiteMode(); ins[F("rgbwm")] = bus->getAutoWhiteMode();
ins[F("freq")] = bus->getFrequency(); ins[F("freq")] = bus->getFrequency();
ins[F("maxpwr")] = bus->getMaxCurrent(); ins[F("maxpwr")] = bus->getMaxCurrent();
ins[F("ledma")] = bus->getLEDCurrent(); ins[F("ledma")] = bus->getLEDCurrent();
} }
JsonArray hw_com = hw.createNestedArray(F("com")); JsonArray hw_com = hw.createNestedArray(F("com"));
const ColorOrderMap& com = BusManager::getColorOrderMap(); const ColorOrderMap& com = BusManager::getColorOrderMap();
for (size_t s = 0; s < com.count(); s++) { for (size_t s = 0; s < com.count(); s++) {
const ColorOrderMapEntry *entry = com.get(s); const ColorOrderMapEntry *entry = com.get(s);
if (!entry) break; if (!entry || !entry->len) break;
JsonObject co = hw_com.createNestedObject(); JsonObject co = hw_com.createNestedObject();
co["start"] = entry->start; co["start"] = entry->start;
co["len"] = entry->len; co["len"] = entry->len;

View File

@ -10,12 +10,13 @@
*/ */
uint32_t color_blend(uint32_t color1, uint32_t color2, uint8_t blend) { 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 // min / max blend checking is omitted: calls with 0 or 255 are rare, checking lowers overall performance
uint32_t rb1 = color1 & 0x00FF00FF; 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 wg1 = (color1>>8) & 0x00FF00FF; uint32_t rb1 = color1 & TWO_CHANNEL_MASK; // extract R & B channels from color1
uint32_t rb2 = color2 & 0x00FF00FF; uint32_t wg1 = (color1 >> 8) & TWO_CHANNEL_MASK; // extract W & G channels from color1 (shifted for multiplication later)
uint32_t wg2 = (color2>>8) & 0x00FF00FF; uint32_t rb2 = color2 & TWO_CHANNEL_MASK; // extract R & B channels from color2
uint32_t rb3 = ((((rb1 << 8) | rb2) + (rb2 * blend) - (rb1 * blend)) >> 8) & 0x00FF00FF; uint32_t wg2 = (color2 >> 8) & TWO_CHANNEL_MASK; // extract W & G channels from color2 (shifted for multiplication later)
uint32_t wg3 = ((((wg1 << 8) | wg2) + (wg2 * blend) - (wg1 * blend))) & 0xFF00FF00; 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; 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 (c1 == BLACK) return c2;
if (c2 == BLACK) return c1; if (c2 == BLACK) return c1;
uint32_t rb = (c1 & 0x00FF00FF) + (c2 & 0x00FF00FF); // mask and add two colors at once const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; // mask for R and B channels or W and G if negated
uint32_t wg = ((c1>>8) & 0x00FF00FF) + ((c2>>8) & 0x00FF00FF); 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 r = rb >> 16; // extract single color values
uint32_t b = rb & 0xFFFF; uint32_t b = rb & 0xFFFF;
uint32_t w = wg >> 16; 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 = b > max ? b : max;
//max = w > max ? w : max; //max = w > max ? w : max;
if (max > 255) { 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 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) & 0x00FF00FF; // rb = ((rb * scale) >> 8) & TWO_CHANNEL_MASK;
wg = (wg * scale) & 0xFF00FF00; wg = (wg * scale) & ~TWO_CHANNEL_MASK;
} else wg = wg << 8; //shift white and green back to correct position } else wg <<= 8; //shift white and green back to correct position
return rb | wg; return rb | wg;
} else { } else {
r = r > 255 ? 255 : r; 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 |= B(c1) ? 0x00000001 : 0;
addRemains |= W(c1) ? 0x01000000 : 0; addRemains |= W(c1) ? 0x01000000 : 0;
} }
uint32_t rb = (((c1 & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; // scale red and blue const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF;
uint32_t wg = (((c1 & 0xFF00FF00) >> 8) * scale) & 0xFF00FF00; // scale white and green 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; scaledcolor = (rb | wg) + addRemains;
return scaledcolor; return scaledcolor;
} }
@ -95,7 +98,7 @@ uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t
unsigned red1 = entry->r; unsigned red1 = entry->r;
unsigned green1 = entry->g; unsigned green1 = entry->g;
unsigned blue1 = entry->b; unsigned blue1 = entry->b;
if(lo4 && blendType != NOBLEND) { if (lo4 && blendType != NOBLEND) {
if (hi4 == 15) entry = &(pal[0]); if (hi4 == 15) entry = &(pal[0]);
else ++entry; else ++entry;
unsigned f2 = (lo4 << 4); 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; 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 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) uint32_t scale = brightness + 1; // adjust for rounding (bitshift)
red1 = (red1 * scale) >> 8; // note: using color_fade() is 30% slower red1 = (red1 * scale) >> 8; // note: using color_fade() is 30% slower
green1 = (green1 * scale) >> 8; green1 = (green1 * scale) >> 8;

View File

@ -49,31 +49,31 @@
#define WLED_MAX_DIGITAL_CHANNELS 3 #define WLED_MAX_DIGITAL_CHANNELS 3
#define WLED_MAX_ANALOG_CHANNELS 5 #define WLED_MAX_ANALOG_CHANNELS 5
#define WLED_MAX_BUSSES 4 // will allow 3 digital & 1 analog RGB #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 #else
#define WLED_MAX_ANALOG_CHANNELS (LEDC_CHANNEL_MAX*LEDC_SPEED_MODE_MAX) #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 #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_BUSSES 6 // will allow 2 digital & 2 analog RGB or 6 PWM white
#define WLED_MAX_DIGITAL_CHANNELS 2 #define WLED_MAX_DIGITAL_CHANNELS 2
//#define WLED_MAX_ANALOG_CHANNELS 6 //#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 #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) // 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_BUSSES 7 // will allow 5 digital & 2 analog RGB
#define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT + x1/x8 I2S0 #define WLED_MAX_DIGITAL_CHANNELS 5
//#define WLED_MAX_ANALOG_CHANNELS 8 //#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 #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_BUSSES 14 // will allow 12 digital & 2 analog RGB
#define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT + x8 I2S-LCD #define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT + x8 I2S-LCD
//#define WLED_MAX_ANALOG_CHANNELS 8 //#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 #else
// the last digital bus (I2S0) will prevent Audioreactive usermod from functioning // 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_BUSSES 19 // will allow 16 digital & 3 analog RGB
#define WLED_MAX_DIGITAL_CHANNELS 16 // x1/x8 I2S1 + x8 RMT #define WLED_MAX_DIGITAL_CHANNELS 16 // x1/x8 I2S1 + x8 RMT
//#define WLED_MAX_ANALOG_CHANNELS 16 //#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
#endif #endif
#else #else
@ -87,7 +87,7 @@
#ifndef WLED_MAX_DIGITAL_CHANNELS #ifndef WLED_MAX_DIGITAL_CHANNELS
#error You must also define WLED_MAX_DIGITAL_CHANNELS. #error You must also define WLED_MAX_DIGITAL_CHANNELS.
#endif #endif
#define WLED_MIN_VIRTUAL_BUSSES (5-WLED_MAX_BUSSES) #define WLED_MIN_VIRTUAL_BUSSES 3
#else #else
#if WLED_MAX_BUSSES > 20 #if WLED_MAX_BUSSES > 20
#error Maximum number of buses is 20. #error Maximum number of buses is 20.
@ -98,7 +98,11 @@
#ifndef WLED_MAX_DIGITAL_CHANNELS #ifndef WLED_MAX_DIGITAL_CHANNELS
#error You must also define WLED_MAX_DIGITAL_CHANNELS. #error You must also define WLED_MAX_DIGITAL_CHANNELS.
#endif #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
#endif #endif

View File

@ -6,6 +6,7 @@
<meta name="theme-color" content="#222222"> <meta name="theme-color" content="#222222">
<meta content="yes" name="apple-mobile-web-app-capable"> <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="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> <title>WLED</title>
<link rel="stylesheet" href="index.css"> <link rel="stylesheet" href="index.css">
</head> </head>

View File

@ -409,7 +409,9 @@ function pName(i)
function isPlaylist(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) function papiVal(i)
@ -775,8 +777,8 @@ function populateSegments(s)
} }
let segp = `<div id="segp${i}" class="sbs">`+ 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>`+ `<i class="icons slider-icon pwr ${inst.on ? "act":""}" id="seg${i}pwr" title="Power" onclick="setSegPwr(${i})">&#xe08f;</i>`+
`<div class="sliderwrap il">`+ `<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}" />`+ `<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 class="sliderdisplay"></div>`+
`</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}">`+ cn += `<div class="seg lstI ${i==s.mainseg && !simplifiedUI ? 'selected' : ''} ${exp ? "expanded":""}" id="seg${i}" data-set="${inst.set}">`+
`<label class="check schkl ${smpl}">`+ `<label class="check schkl ${smpl}">`+
`<input type="checkbox" id="seg${i}sel" onchange="selSeg(${i})" ${inst.sel ? "checked":""}>`+ `<input type="checkbox" id="seg${i}sel" onchange="selSeg(${i})" ${inst.sel ? "checked":""}>`+
`<span class="checkmark"></span>`+ `<span class="checkmark" title="Select"></span>`+
`</label>`+ `</label>`+
`<div class="segname ${smpl}" onclick="selSegEx(${i})">`+ `<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>`+ `<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); paOnOff[0] = paOnOff[0].substring(0,dPos);
} }
if (paOnOff.length>0 && paOnOff[0] != "!") text = paOnOff[0]; if (paOnOff.length>0 && paOnOff[0] != "!") text = paOnOff[0];
gId("adPal").classList.remove("hide");
if (lastinfo.cpalcount>0) gId("rmPal").classList.remove("hide");
} else { } else {
// disable palette list // disable palette list
text += ' not used'; text += ' not used';
palw.style.display = "none"; palw.style.display = "none";
gId("adPal").classList.add("hide");
gId("rmPal").classList.add("hide");
// Close palette dialog if not available // Close palette dialog if not available
if (gId("palw").lastElementChild.tagName == "DIALOG") { if (palw.lastElementChild.tagName == "DIALOG") {
gId("palw").lastElementChild.close(); palw.lastElementChild.close();
} }
} }
pall.innerHTML = icon + text; pall.innerHTML = icon + text;
@ -1887,7 +1893,7 @@ function makeSeg()
function resetUtil(off=false) function resetUtil(off=false)
{ {
gId('segutil').innerHTML = `<div class="seg btn btn-s${off?' off':''}" style="padding:0;margin-bottom:12px;">` 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="segname" ${off?'':'onclick="makeSeg()"'}><i class="icons btn-icon">&#xe18a;</i>Add segment</div>`
+ '<div class="pop hide" onclick="event.stopPropagation();">' + '<div class="pop hide" onclick="event.stopPropagation();">'
+ `<i class="icons g-icon" title="Select group" onclick="this.nextElementSibling.classList.toggle('hide');">&#xE34B;</i>` + `<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");}); if (lSeg>2) d.querySelectorAll("#Segments .pop").forEach((e)=>{e.classList.remove("hide");});
} }
function makePlSel(el, incPl=false) function makePlSel(p, el)
{ {
var plSelContent = ""; var plSelContent = "";
delete pJson["0"]; // remove filler preset delete pJson["0"]; // remove filler preset
Object.entries(pJson).sort(cmpP).forEach((a)=>{ Object.entries(pJson).sort(cmpP).forEach((a)=>{
var n = a[1].n ? a[1].n : "Preset " + a[0]; 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 (cfg.comp.idsort) n = a[0] + ' ' + n;
if (!(!incPl && a[1].playlist && a[1].playlist.ps)) // skip playlists, sub-playlists not yet supported // skip endless playlists and itself
plSelContent += `<option value="${a[0]}" ${a[0]==el?"selected":""}>${n}</option>`; 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; 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) function addPl(p,i)
{ {
plJson[p].ps.splice(i+1,0,0); const pl = plJson[p];
plJson[p].dur.splice(i+1,0,plJson[p].dur[i]); pl.ps.splice(i+1,0,1);
plJson[p].transition.splice(i+1,0,plJson[p].transition[i]); pl.dur.splice(i+1,0,pl.dur[i]);
pl.transition.splice(i+1,0,pl.transition[i]);
refreshPlE(p); refreshPlE(p);
} }
function delPl(p,i) function delPl(p,i)
{ {
if (plJson[p].ps.length < 2) return; const pl = plJson[p];
plJson[p].ps.splice(i,1); if (pl.ps.length < 2) return;
plJson[p].dur.splice(i,1); pl.ps.splice(i,1);
plJson[p].transition.splice(i,1); pl.dur.splice(i,1);
pl.transition.splice(i,1);
refreshPlE(p); refreshPlE(p);
} }
@ -1965,6 +1974,13 @@ function pleDur(p,i,field)
function pleTr(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) if (field.validity.valid)
plJson[p].transition[i] = Math.floor(field.value*10); 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) function makeP(i,pl)
{ {
var content = ""; var content = "";
@ -1997,12 +2024,17 @@ function makeP(i,pl)
r: false, r: false,
end: 0 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 = content =
`<div id="ple${i}" style="margin-top:10px;"></div><label class="check revchkl">Shuffle `<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":""}> <input type="checkbox" id="pl${i}rtgl" onchange="plR(${i})" ${plJson[i].r||rep<0?"checked":""}>
<span class="checkmark"></span> <span class="checkmark"></span>
</label> </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 <label class="check revchkl">Repeat indefinitely
<input type="checkbox" id="pl${i}rptgl" onchange="plR(${i})" ${rep>0?"":"checked"}> <input type="checkbox" id="pl${i}rptgl" onchange="plR(${i})" ${rep>0?"":"checked"}>
<span class="checkmark"></span> <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}> <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="0">None</option>
<option value="255" ${plJson[i].end && plJson[i].end==255?"selected":""}>Restore preset</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> </select></div></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>`; <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) function makePlEntry(p,i)
{ {
const man = gId(`pl${p}manual`).checked;
return `<div class="plentry"> return `<div class="plentry">
<div class="hrz"></div> <div class="hrz"></div>
<table> <table>
<tr> <tr>
<td width="80%" colspan=2> <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}"> <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> </select></div>
</td> </td>
<td class="c"><button class="btn btn-pl-add" onclick="addPl(${p},${i})"><i class="icons btn-icon">&#xe18a;</i></button></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>
<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">Transition</td>
<td class="c">#${i+1}</td> <td class="c">#${i+1}</td>
</tr> </tr>
<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="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)" value="${plJson[p].transition[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)" 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> <td class="c"><button class="btn btn-pl-del" onclick="delPl(${p},${i})"><i class="icons btn-icon">&#xe037;</i></button></div></td>
</tr> </tr>
</table> </table>
@ -2657,28 +2690,28 @@ function fromRgb()
var g = gId('sliderG').value; var g = gId('sliderG').value;
var b = gId('sliderB').value; var b = gId('sliderB').value;
setPicker(`rgb(${r},${g},${b})`); setPicker(`rgb(${r},${g},${b})`);
let cd = gId('csl').children; // color slots let cd = gId('csl').children[csel]; // color slots
cd[csel].dataset.r = r; cd.dataset.r = r;
cd[csel].dataset.g = g; cd.dataset.g = g;
cd[csel].dataset.b = b; cd.dataset.b = b;
setCSL(cd[csel]); setCSL(cd);
} }
function fromW() function fromW()
{ {
let w = gId('sliderW'); let w = gId('sliderW');
let cd = gId('csl').children; // color slots let cd = gId('csl').children[csel]; // color slots
cd[csel].dataset.w = w.value; cd.dataset.w = w.value;
setCSL(cd[csel]); setCSL(cd);
updateTrail(w); updateTrail(w);
} }
// sr 0: from RGB sliders, 1: from picker, 2: from hex // sr 0: from RGB sliders, 1: from picker, 2: from hex
function setColor(sr) function setColor(sr)
{ {
var cd = gId('csl').children; // color slots var cd = gId('csl').children[csel]; // color slots
let cdd = cd[csel].dataset; let cdd = cd.dataset;
let w = 0, r,g,b; 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 == 1 && isRgbBlack(cdd)) cpick.color.setChannel('hsv', 'v', 100);
if (sr != 2 && hasWhite) w = parseInt(gId('sliderW').value); if (sr != 2 && hasWhite) w = parseInt(gId('sliderW').value);
var col = cpick.color.rgb; var col = cpick.color.rgb;
@ -2686,7 +2719,7 @@ function setColor(sr)
cdd.g = g = hasRGB ? col.g : w; cdd.g = g = hasRGB ? col.g : w;
cdd.b = b = hasRGB ? col.b : w; cdd.b = b = hasRGB ? col.b : w;
cdd.w = w; cdd.w = w;
setCSL(cd[csel]); setCSL(cd);
var obj = {"seg": {"col": [[],[],[]]}}; var obj = {"seg": {"col": [[],[],[]]}};
obj.seg.col[csel] = [r, g, b, w]; obj.seg.col[csel] = [r, g, b, w];
requestJson(obj); requestJson(obj);

View File

@ -24,6 +24,7 @@
function is16b(t) { return !!(gT(t).c & 0x10); } // is digital 16 bit type 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 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 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() { function S() {
getLoc(); getLoc();
loadJS(getURL('/settings/s.js?p=2'), false, ()=>{ loadJS(getURL('/settings/s.js?p=2'), false, ()=>{
@ -42,10 +43,10 @@
if (loc) d.Sf.action = getURL('/settings/leds'); if (loc) d.Sf.action = getURL('/settings/leds');
} }
function bLimits(b,v,p,m,l,o=5,d=2,a=6) { 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 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): 16 - ESP32, 12 - S3/S2, 2 - C3, 3 - 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 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 maxPB = p; // maxPB - max LEDs per bus
maxM = m; // maxM - max LED memory maxM = m; // maxM - max LED memory
maxL = l; // maxL - max LEDs (will serve to determine ESP >1664 == ESP32) maxL = l; // maxL - max LEDs (will serve to determine ESP >1664 == ESP32)
@ -138,7 +139,7 @@
gId("ppldis").style.display = ppl ? 'inline' : 'none'; gId("ppldis").style.display = ppl ? 'inline' : 'none';
// set PPL minimum value and clear actual PPL limit if ABL is disabled // set PPL minimum value and clear actual PPL limit if ABL is disabled
d.Sf.querySelectorAll("#mLC input[name^=MA]").forEach((i,x)=>{ 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"; gId("PSU"+n).style.display = ppl ? "inline" : "none";
const t = parseInt(d.Sf["LT"+n].value); // LED type SELECT const t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
const c = parseInt(d.Sf["LC"+n].value); //get LED count const c = parseInt(d.Sf["LC"+n].value); //get LED count
@ -169,7 +170,7 @@
// select appropriate LED current // select appropriate LED current
d.Sf.querySelectorAll("#mLC select[name^=LAsel]").forEach((sel,x)=>{ d.Sf.querySelectorAll("#mLC select[name^=LAsel]").forEach((sel,x)=>{
sel.value = 0; // set custom sel.value = 0; // set custom
var n = String.fromCharCode((x<10?48:55)+x); var n = chrID(x);
if (en) if (en)
switch (parseInt(d.Sf["LA"+n].value)) { switch (parseInt(d.Sf["LA"+n].value)) {
case 0: break; // disable ABL case 0: break; // disable ABL
@ -359,8 +360,9 @@
gId("prl").classList.add("hide"); gId("prl").classList.add("hide");
} else } else
gId("prl").classList.remove("hide"); 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 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 // distribute ABL current if not using PPL
enPPL(sDI); enPPL(sDI);
@ -399,7 +401,7 @@
} }
function lastEnd(i) { function lastEnd(i) {
if (i-- < 1) return 0; 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); v = parseInt(d.getElementsByName("LS"+s)[0].value) + parseInt(d.getElementsByName("LC"+s)[0].value);
var t = parseInt(d.getElementsByName("LT"+s)[0].value); var t = parseInt(d.getElementsByName("LT"+s)[0].value);
if (isPWM(t)) v = 1; //PWM busses if (isPWM(t)) v = 1; //PWM busses
@ -421,8 +423,8 @@
if (isVir(t)) virtB++; if (isVir(t)) virtB++;
}); });
if ((n==1 && i>=maxB+maxV) || (n==-1 && i==0)) return; 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 = String.fromCharCode((i<10?48:55)+i); var s = chrID(i);
if (n==1) { if (n==1) {
// npm run build has trouble minimizing spaces inside string // 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; 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"; gId("-").style.display = (i>0) ? "inline":"none";
if (!init) { 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) { function addCOM(start=0,len=1,co=0) {
var i = gEBCN("com_entry").length; var i = gEBCN("com_entry").length;
if (i >= maxCO) return; if (i >= maxCO) return;
var s = String.fromCharCode((i<10?48:55)+i); var s = chrID(i);
var b = `<div class="com_entry"> var b = `<div class="com_entry">
<hr class="sml"> <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; ${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) { function addBtn(i,p,t) {
var c = gId("btns").innerHTML; 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 += `Button ${i} GPIO: <input type="number" name="BT${s}" onchange="UI()" class="xs" value="${p}">`;
c += `&nbsp;<select name="BE${s}">` c += `&nbsp;<select name="BE${s}">`
c += `<option value="0" ${t==0?"selected":""}>Disabled</option>`; 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 function checkSi() { //on load, checks whether there are custom start fields
var cs = false; var cs = false;
for (var i=1; i < gEBCN("iST").length; i++) { for (var i=1; i < gEBCN("iST").length; i++) {
var v = parseInt(gId("ls"+(i-1)).value) + parseInt(gN("LC"+(i-1)).value); var s = chrID(i);
if (v != parseInt(gId("ls"+i).value)) {cs = true; startsDirty[i] = true;} 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;} if (gId("ls0") && parseInt(gId("ls0").value) != 0) {cs = true; startsDirty[0] = true;}
gId("si").checked = cs; gId("si").checked = cs;
@ -614,22 +618,32 @@ Swap: <select id="xw${s}" name="XW${s}">
function receivedText(e) { function receivedText(e) {
let lines = e.target.result; let lines = e.target.result;
var c = JSON.parse(lines); let c = JSON.parse(lines);
if (c.hw) { if (c.hw) {
if (c.hw.led) { if (c.hw.led) {
for (var i=0; i<10; i++) addLEDs(-1); // remove all existing outputs
var l = c.hw.led; 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)=>{ l.ins.forEach((v,i,a)=>{
addLEDs(1); addLEDs(1);
for (var j=0; j<v.pin.length; j++) d.getElementsByName(`L${j}${i}`)[0].value = v.pin[j]; 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("LT"+i)[0].value = v.type;
d.getElementsByName("LS"+i)[0].value = v.start; d.getElementsByName("LS"+i)[0].value = v.start;
d.getElementsByName("LC"+i)[0].value = v.len; d.getElementsByName("LC"+i)[0].value = v.len;
d.getElementsByName("CO"+i)[0].value = v.order; d.getElementsByName("CO"+i)[0].value = v.order & 0x0F;
d.getElementsByName("SL"+i)[0].value = v.skip; d.getElementsByName("SL"+i)[0].value = v.skip;
d.getElementsByName("RF"+i)[0].checked = v.ref; d.getElementsByName("RF"+i)[0].checked = v.ref;
d.getElementsByName("CV"+i)[0].checked = v.rev; 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) { if(c.hw.com) {
resetCOM(); resetCOM();
@ -637,22 +651,28 @@ Swap: <select id="xw${s}" name="XW${s}">
addCOM(e.start, e.len, e.order); addCOM(e.start, e.len, e.order);
}); });
} }
if (c.hw.btn) { let b = c.hw.btn;
var b = c.hw.btn; if (b) {
if (Array.isArray(b.ins)) gId("btns").innerHTML = ""; if (Array.isArray(b.ins)) gId("btns").innerHTML = "";
b.ins.forEach((v,i,a)=>{ b.ins.forEach((v,i,a)=>{
addBtn(i,v.pin[0],v.type); addBtn(i,v.pin[0],v.type);
}); });
d.getElementsByName("TT")[0].value = b.tt; d.getElementsByName("TT")[0].value = b.tt;
} }
if (c.hw.ir) { let ir = c.hw.ir;
d.getElementsByName("IR")[0].value = c.hw.ir.pin; if (ir) {
d.getElementsByName("IT")[0].value = c.hw.ir.type; d.getElementsByName("IR")[0].value = ir.pin;
d.getElementsByName("IT")[0].value = ir.type;
} }
if (c.hw.relay) { let rl = c.hw.relay;
d.getElementsByName("RL")[0].value = c.hw.relay.pin; if (rl) {
d.getElementsByName("RM")[0].checked = c.hw.relay.rev; d.getElementsByName("RL")[0].value = rl.pin;
d.getElementsByName("RO")[0].checked = c.hw.relay.odrain; 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(); UI();
} }

View File

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

View File

@ -327,6 +327,7 @@ void serializePlaylist(JsonObject obj);
//presets.cpp //presets.cpp
const char *getPresetsFileName(bool persistent = true); const char *getPresetsFileName(bool persistent = true);
bool presetNeedsSaving();
void initPresetsFile(); void initPresetsFile();
void handlePresets(); void handlePresets();
bool applyPreset(byte index, byte callMode = CALL_MODE_DIRECT_CHANGE); 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) switch(hueColormode)
{ {
case 1: if (hueX != hueXLast || hueY != hueYLast) colorXYtoRGB(hueX,hueY,col); hueXLast = hueX; hueYLast = hueY; 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,col); hueHueLast = hueHue; hueSatLast = hueSat; break; case 2: if (hueHue != hueHueLast || hueSat != hueSatLast) colorHStoRGB(hueHue,hueSat,colPri); hueHueLast = hueHue; hueSatLast = hueSat; break;
case 3: if (hueCt != hueCtLast) colorCTtoRGB(hueCt,col); hueCtLast = hueCt; break; case 3: if (hueCt != hueCtLast) colorCTtoRGB(hueCt,colPri); hueCtLast = hueCt; break;
} }
} }
hueReceived = true; 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 grp = elem["grp"] | seg.grouping;
uint16_t spc = elem[F("spc")] | seg.spacing; uint16_t spc = elem[F("spc")] | seg.spacing;
uint16_t of = seg.offset; uint16_t of = seg.offset;
uint8_t soundSim = elem["si"] | seg.soundSim; uint8_t soundSim = elem["si"] | seg.soundSim;
uint8_t map1D2D = elem["m12"] | seg.map1D2D; uint8_t map1D2D = elem["m12"] | seg.map1D2D;
uint8_t set = elem[F("set")] | seg.set; uint8_t set = elem[F("set")] | seg.set;
seg.set = constrain(set, 0, 3); bool selected = getBoolVal(elem["sel"], seg.selected);
seg.soundSim = constrain(soundSim, 0, 3); 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 len = (stop > start) ? stop - start : 1;
int offset = elem[F("of")] | INT32_MAX; int offset = elem[F("of")] | INT32_MAX;
@ -200,20 +206,16 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
} }
#endif #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 #ifndef WLED_DISABLE_2D
bool reverse = seg.reverse; seg.reverse_y = reverse_y;
bool mirror = seg.mirror; seg.mirror_y = mirror_y;
#endif seg.transpose = transpose;
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)
#endif #endif
byte fx = seg.mode; byte fx = seg.mode;
@ -392,35 +394,38 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
int it = 0; int it = 0;
JsonVariant segVar = root["seg"]; JsonVariant segVar = root["seg"];
if (!segVar.isNull()) strip.suspend(); if (!segVar.isNull()) {
if (segVar.is<JsonObject>()) // we may be called during strip.service() so we must not modify segments while effects are executing
{ strip.suspend();
int id = segVar["id"] | -1; const unsigned long start = millis();
//if "seg" is not an array and ID not specified, apply to all selected/checked segments while (strip.isServicing() && millis() - start < strip.getFrameTime()) yield(); // wait until frame is over
if (id < 0) { #ifdef WLED_DEBUG
//apply all selected segments if (millis() - start > 0) DEBUG_PRINTLN(F("JSON: Waited for strip to finish servicing."));
//bool didSet = false; #endif
for (size_t s = 0; s < strip.getSegmentsNum(); s++) { if (segVar.is<JsonObject>()) {
Segment &sg = strip.getSegment(s); int id = segVar["id"] | -1;
if (sg.isActive() && sg.isSelected()) { //if "seg" is not an array and ID not specified, apply to all selected/checked segments
deserializeSegment(segVar, s, presetId); if (id < 0) {
//didSet = true; //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 { } 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 { strip.resume();
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();
UsermodManager::readFromJsonState(root); UsermodManager::readFromJsonState(root);

View File

@ -9,10 +9,10 @@ void setValuesFromFirstSelectedSeg() { setValuesFromSegment(strip.getFirstSelect
void setValuesFromSegment(uint8_t s) void setValuesFromSegment(uint8_t s)
{ {
Segment& seg = strip.getSegment(s); Segment& seg = strip.getSegment(s);
col[0] = R(seg.colors[0]); colPri[0] = R(seg.colors[0]);
col[1] = G(seg.colors[0]); colPri[1] = G(seg.colors[0]);
col[2] = B(seg.colors[0]); colPri[2] = B(seg.colors[0]);
col[3] = W(seg.colors[0]); colPri[3] = W(seg.colors[0]);
colSec[0] = R(seg.colors[1]); colSec[0] = R(seg.colors[1]);
colSec[1] = G(seg.colors[1]); colSec[1] = G(seg.colors[1]);
colSec[2] = B(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 (effectIntensity != selsegPrev.intensity) {seg.intensity = effectIntensity; stateChanged = true;}
if (effectPalette != selsegPrev.palette) {seg.setPalette(effectPalette);} if (effectPalette != selsegPrev.palette) {seg.setPalette(effectPalette);}
if (effectCurrent != selsegPrev.mode) {seg.setMode(effectCurrent);} 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]); uint32_t col1 = RGBW32(colSec[0], colSec[1], colSec[2], colSec[3]);
if (col0 != selsegPrev.colors[0]) {seg.setColor(0, col0);} if (col0 != selsegPrev.colors[0]) {seg.setColor(0, col0);}
if (col1 != selsegPrev.colors[1]) {seg.setColor(1, col1);} 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)) { if (callMode != CALL_MODE_NO_NOTIFY && nightlightActive && (nightlightMode == NL_MODE_FADE || nightlightMode == NL_MODE_COLORFADE)) {
briNlT = bri; briNlT = bri;
nightlightDelayMs -= (millis() - nightlightStartTime); nightlightDelayMs -= (now - nightlightStartTime);
nightlightStartTime = millis(); nightlightStartTime = now;
} }
if (briT == 0) { if (briT == 0) {
if (callMode != CALL_MODE_NOTIFICATION) strip.resetTimebase(); //effect start from beginning if (callMode != CALL_MODE_NOTIFICATION) strip.resetTimebase(); //effect start from beginning
@ -141,7 +142,7 @@ void stateUpdated(byte callMode) {
} else } else
strip.setTransitionMode(true); // force all segments to transition mode strip.setTransitionMode(true); // force all segments to transition mode
transitionActive = true; transitionActive = true;
transitionStartTime = millis(); transitionStartTime = now;
} }
@ -150,14 +151,14 @@ void updateInterfaces(uint8_t callMode) {
sendDataWs(); sendDataWs();
lastInterfaceUpdate = millis(); lastInterfaceUpdate = millis();
interfaceUpdateCallMode = 0; //disable further updates interfaceUpdateCallMode = CALL_MODE_INIT; //disable further updates
if (callMode == CALL_MODE_WS_SEND) return; if (callMode == CALL_MODE_WS_SEND) return;
#ifndef WLED_DISABLE_ALEXA #ifndef WLED_DISABLE_ALEXA
if (espalexaDevice != nullptr && callMode != CALL_MODE_ALEXA) { if (espalexaDevice != nullptr && callMode != CALL_MODE_ALEXA) {
espalexaDevice->setValue(bri); espalexaDevice->setValue(bri);
espalexaDevice->setColor(col[0], col[1], col[2]); espalexaDevice->setColor(colPri[0], colPri[1], colPri[2]);
} }
#endif #endif
#ifndef WLED_DISABLE_MQTT #ifndef WLED_DISABLE_MQTT
@ -211,7 +212,7 @@ void handleNightlight() {
nightlightDelayMs = (unsigned)(nightlightDelayMins*60000); nightlightDelayMs = (unsigned)(nightlightDelayMins*60000);
nightlightActiveOld = true; nightlightActiveOld = true;
briNlT = bri; 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) if (nightlightMode == NL_MODE_SUN)
{ {
//save current //save current
@ -236,7 +237,7 @@ void handleNightlight() {
bri = briNlT + ((nightlightTargetBri - briNlT)*nper); bri = briNlT + ((nightlightTargetBri - briNlT)*nper);
if (nightlightMode == NL_MODE_COLORFADE) // color fading only is enabled with "NF=2" 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); 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 //Prefix is stripped from the topic at this point
if (strcmp_P(topic, PSTR("/col")) == 0) { if (strcmp_P(topic, PSTR("/col")) == 0) {
colorFromDecOrHexString(col, payloadStr); colorFromDecOrHexString(colPri, payloadStr);
colorUpdated(CALL_MODE_DIRECT_CHANGE); colorUpdated(CALL_MODE_DIRECT_CHANGE);
} else if (strcmp_P(topic, PSTR("/api")) == 0) { } else if (strcmp_P(topic, PSTR("/api")) == 0) {
if (requestJSONBufferLock(15)) { if (requestJSONBufferLock(15)) {
@ -169,7 +169,7 @@ void publishMqtt()
strcat_P(subuf, PSTR("/g")); strcat_P(subuf, PSTR("/g"));
mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) 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); strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/c")); strcat_P(subuf, PSTR("/c"));
mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) 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) uint16_t tr; //Duration of the transition TO this entry (in tenths of seconds)
} ple; } ple;
byte playlistRepeat = 1; //how many times to repeat the playlist (0 = infinitely) static 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) static 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 playlistOptions = 0; //bit 0: shuffle playlist after each iteration. bits 1-7 TBD
PlaylistEntry *playlistEntries = nullptr; static PlaylistEntry *playlistEntries = nullptr;
byte playlistLen; //number of playlist entries static byte playlistLen; //number of playlist entries
int8_t playlistIndex = -1; static int8_t playlistIndex = -1;
uint16_t playlistEntryDur = 0; //duration of the current entry in tenths of seconds 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 //values we need to keep about the parent playlist while inside sub-playlist
//int8_t parentPlaylistIndex = -1; static int16_t parentPlaylistIndex = -1;
//byte parentPlaylistRepeat = 0; static byte parentPlaylistRepeat = 0;
//byte parentPlaylistPresetId = 0; //for re-loading static byte parentPlaylistPresetId = 0; //for re-loading
void shufflePlaylist() { void shufflePlaylist() {
@ -54,6 +54,12 @@ void unloadPlaylist() {
int16_t loadPlaylist(JsonObject playlistObj, byte presetId) { 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(); unloadPlaylist();
JsonArray presets = playlistObj["ps"]; JsonArray presets = playlistObj["ps"];
@ -79,7 +85,7 @@ int16_t loadPlaylist(JsonObject playlistObj, byte presetId) {
} else { } else {
for (int dur : durations) { for (int dur : durations) {
if (it >= playlistLen) break; if (it >= playlistLen) break;
playlistEntries[it].dur = (dur > 1) ? dur : 100; playlistEntries[it].dur = constrain(dur, 0, 65530);
it++; it++;
} }
} }
@ -117,6 +123,19 @@ int16_t loadPlaylist(JsonObject playlistObj, byte presetId) {
shuffle = shuffle || playlistObj["r"]; shuffle = shuffle || playlistObj["r"];
if (shuffle) playlistOptions |= PL_OPTION_SHUFFLE; 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; currentPlaylist = presetId;
DEBUG_PRINTLN(F("Playlist loaded.")); DEBUG_PRINTLN(F("Playlist loaded."));
return currentPlaylist; return currentPlaylist;
@ -127,7 +146,7 @@ void handlePlaylist() {
static unsigned long presetCycledTime = 0; static unsigned long presetCycledTime = 0;
if (currentPlaylist < 0 || playlistEntries == nullptr) return; if (currentPlaylist < 0 || playlistEntries == nullptr) return;
if (millis() - presetCycledTime > (100 * playlistEntryDur) || doAdvancePlaylist) { if ((playlistEntryDur < UINT16_MAX && millis() - presetCycledTime > 100 * playlistEntryDur) || doAdvancePlaylist) {
presetCycledTime = millis(); presetCycledTime = millis();
if (bri == 0 || nightlightActive) return; if (bri == 0 || nightlightActive) return;
@ -137,7 +156,10 @@ if (millis() - presetCycledTime > (100 * playlistEntryDur) || doAdvancePlaylist)
if (!playlistIndex) { if (!playlistIndex) {
if (playlistRepeat == 1) { //stop if all repetitions are done if (playlistRepeat == 1) { //stop if all repetitions are done
unloadPlaylist(); 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; return;
} }
if (playlistRepeat > 1) playlistRepeat--; // decrease repeat count on each index reset if not an endless playlist 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; jsonTransitionOnce = true;
strip.setTransition(playlistEntries[playlistIndex].tr * 100); strip.setTransition(playlistEntries[playlistIndex].tr * 100);
playlistEntryDur = playlistEntries[playlistIndex].dur; playlistEntryDur = playlistEntries[playlistIndex].dur > 0 ? playlistEntries[playlistIndex].dur : UINT16_MAX;
applyPresetFromPlaylist(playlistEntries[playlistIndex].preset); applyPresetFromPlaylist(playlistEntries[playlistIndex].preset);
doAdvancePlaylist = false; doAdvancePlaylist = false;
} }

View File

@ -22,6 +22,10 @@ const char *getPresetsFileName(bool persistent) {
return persistent ? presets_json : tmp_json; return persistent ? presets_json : tmp_json;
} }
bool presetNeedsSaving() {
return presetToSave;
}
static void doSaveState() { static void doSaveState() {
bool persist = (presetToSave < 251); bool persist = (presetToSave < 251);
@ -269,7 +273,7 @@ void savePreset(byte index, const char* pname, JsonObject sObj)
quickLoad = nullptr; quickLoad = nullptr;
} else { } else {
// store playlist // 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(); includeBri = true; // !sObj["on"].isNull();
playlistSave = true; playlistSave = true;
} }

View File

@ -145,8 +145,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
#endif #endif
bool busesChanged = false; bool busesChanged = false;
for (int s = 0; s < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; s++) { for (int s = 0; s < 36; s++) { // theoretical limit is 36 : "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
int offset = s < 10 ? 48 : 55; int offset = s < 10 ? '0' : 'A';
char lp[4] = "L0"; lp[2] = offset+s; lp[3] = 0; //ascii 0-9 //strip data pin char 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 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 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 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 char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max mA
if (!request->hasArg(lp)) { 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; break;
} }
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
lp[1] = offset+i; lp[1] = '0'+i;
if (!request->hasArg(lp)) break; if (!request->hasArg(lp)) break;
pins[i] = (request->arg(lp).length() > 0) ? request->arg(lp).toInt() : 255; 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 type |= request->hasArg(rf) << 7; // off refresh override
// actual finalization is done in WLED::loop() (removing old busses and adding new) // 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 // 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; busesChanged = true;
} }
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed //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 // we will not bother with pre-allocating ColorOrderMappings vector
BusManager::getColorOrderMap().reset(); BusManager::getColorOrderMap().reset();
for (int s = 0; s < WLED_MAX_COLOR_ORDER_MAPPINGS; s++) { 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 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 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 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")); disablePullUp = (bool)request->hasArg(F("IP"));
touchThreshold = request->arg(F("TT")).toInt(); touchThreshold = request->arg(F("TT")).toInt();
for (int i = 0; i < WLED_MAX_BUTTONS; i++) { 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 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) 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(); 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 // 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 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 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(); if (WLED_CONNECTED && aOtaEnabled && !otaLock && correctPIN) ArduinoOTA.handle();
#endif #endif
handleNightlight(); handleNightlight();
handlePlaylist();
yield(); yield();
#ifndef WLED_DISABLE_HUESYNC #ifndef WLED_DISABLE_HUESYNC
@ -117,6 +116,10 @@ void WLED::loop()
yield(); yield();
#endif #endif
if (!presetNeedsSaving()) {
handlePlaylist();
yield();
}
handlePresets(); handlePresets();
yield(); yield();
@ -461,7 +464,7 @@ void WLED::setup()
#endif #endif
// fill in unique mdns default // 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 #ifndef WLED_DISABLE_MQTT
if (mqttDeviceTopic[0] == 0) sprintf_P(mqttDeviceTopic, PSTR("wled/%*s"), 6, escapedMac.c_str() + 6); 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); 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); Segment &seg = strip.getSegment(i);
if (seg.isActive()) seg.colors[0] = BLACK; 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; briLast = briS; bri = 0;
strip.fill(BLACK); strip.fill(BLACK);
strip.show(); strip.show();
} }
colorUpdated(CALL_MODE_INIT); // will not send notification but will initiate transition
if (bootPreset > 0) { if (bootPreset > 0) {
applyPreset(bootPreset, CALL_MODE_INIT); applyPreset(bootPreset, CALL_MODE_INIT);
} }
colorUpdated(CALL_MODE_INIT); // will not send notification
// init relay pin // init relay pin
if (rlyPin >= 0) { if (rlyPin >= 0) {

View File

@ -217,6 +217,10 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
#define WLED_AP_PASS DEFAULT_AP_PASS #define WLED_AP_PASS DEFAULT_AP_PASS
#endif #endif
#ifndef WLED_PIN
#define WLED_PIN ""
#endif
#ifndef SPIFFS_EDITOR_AIRCOOOKIE #ifndef SPIFFS_EDITOR_AIRCOOOKIE
#error You are not using the Aircoookie fork of the ESPAsyncWebserver library.\ #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.\ 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!) // AP and OTA default passwords (for maximum security change them!)
WLED_GLOBAL char apPass[65] _INIT(WLED_AP_PASS); 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); WLED_GLOBAL char otaPass[33] _INIT(DEFAULT_OTA_PASS);
#endif
// Hardware and pin config // Hardware and pin config
#ifndef BTNPIN #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 bool gammaCorrectBri _INIT(false); // use gamma correction on brightness
WLED_GLOBAL float gammaCorrectVal _INIT(2.8f); // gamma correction value 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 colSec[] _INIT_N(({ 0, 0, 0, 0 })); // current RGB(W) secondary color
WLED_GLOBAL byte nightlightTargetBri _INIT(0); // brightness after nightlight is over 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}); WLED_GLOBAL byte macroDoublePress[WLED_MAX_BUTTONS] _INIT({0});
// Security CONFIG // Security CONFIG
WLED_GLOBAL bool otaLock _INIT(false); // prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks #ifdef WLED_OTA_PASS
WLED_GLOBAL bool wifiLock _INIT(false); // prevents access to WiFi settings when OTA lock is enabled WLED_GLOBAL bool otaLock _INIT(true); // prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks
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 #else
WLED_GLOBAL char settingsPIN[5] _INIT(""); // PIN for settings pages WLED_GLOBAL bool otaLock _INIT(false); // prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks
WLED_GLOBAL bool correctPIN _INIT(true); #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 unsigned long lastEditTime _INIT(0);
WLED_GLOBAL uint16_t userVar0 _INIT(0), userVar1 _INIT(0); //available for use in usermod 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); WLED_GLOBAL bool e131NewData _INIT(false);
// led fx library object // led fx library object
WLED_GLOBAL BusManager busses _INIT(BusManager()); WLED_GLOBAL WS2812FX strip _INIT(WS2812FX());
WLED_GLOBAL WS2812FX strip _INIT(WS2812FX()); WLED_GLOBAL std::vector<BusConfig> busConfigs; //temporary, to remember values from network callback until after
WLED_GLOBAL std::vector<BusConfig> busConfigs; //temporary, to remember values from network callback until after WLED_GLOBAL bool doInitBusses _INIT(false);
WLED_GLOBAL bool doInitBusses _INIT(false); WLED_GLOBAL int8_t loadLedmap _INIT(-1);
WLED_GLOBAL int8_t loadLedmap _INIT(-1); WLED_GLOBAL uint8_t currentLedmap _INIT(0);
WLED_GLOBAL uint8_t currentLedmap _INIT(0);
#ifndef ESP8266 #ifndef ESP8266
WLED_GLOBAL char *ledmapNames[WLED_MAX_LEDMAPS-1] _INIT_N(({nullptr})); WLED_GLOBAL char *ledmapNames[WLED_MAX_LEDMAPS-1] _INIT_N(({nullptr}));
#endif #endif

View File

@ -567,13 +567,14 @@ void serveSettings(AsyncWebServerRequest* request, bool post) {
//else if (url.indexOf("/edit") >= 0) subPage = 10; //else if (url.indexOf("/edit") >= 0) subPage = 10;
else subPage = SUBPAGE_WELCOME; 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; originalSubPage = subPage;
subPage = SUBPAGE_PINREQ; // require PIN subPage = SUBPAGE_PINREQ; // require PIN
} }
// if OTA locked or too frequent PIN entry requests fail hard // 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; 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); if (!s2[0]) strcpy_P(s2, s_redirecting);
bool redirectAfter9s = (subPage == SUBPAGE_WIFI || ((subPage == SUBPAGE_SEC || subPage == SUBPAGE_UM) && doReboot)); 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; 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); 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++) 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++) 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>"), 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, notifyDirect, receiveGroups!=0, nightlightActive, nightlightMode > NL_MODE_SET, nightlightDelayMins,
nightlightTargetBri, effectCurrent, effectSpeed, effectIntensity, effectPalette, 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)") : "", serverDescription, realtimeMode ? PSTR(" (live)") : "",
strip.getFirstSelectedSegId() strip.getFirstSelectedSegId()
); );
@ -275,7 +275,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
// set limits // set limits
settingsScript.printf_P(PSTR("bLimits(%d,%d,%d,%d,%d,%d,%d,%d);"), settingsScript.printf_P(PSTR("bLimits(%d,%d,%d,%d,%d,%d,%d,%d);"),
WLED_MAX_BUSSES, 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_LEDS_PER_BUS,
MAX_LED_MEMORY, MAX_LED_MEMORY,
MAX_LEDS, MAX_LEDS,
@ -296,9 +296,9 @@ void getSettingsJS(byte subPage, Print& settingsScript)
unsigned sumMa = 0; unsigned sumMa = 0;
for (int s = 0; s < BusManager::getNumBusses(); s++) { for (int s = 0; s < BusManager::getNumBusses(); s++) {
Bus* bus = BusManager::getBus(s); const Bus* bus = BusManager::getBus(s);
if (bus == nullptr) continue; if (!bus || !bus->isOk()) break; // should not happen but for safety
int offset = s < 10 ? 48 : 55; int offset = s < 10 ? '0' : 'A';
char lp[4] = "L0"; lp[2] = offset+s; lp[3] = 0; //ascii 0-9 //strip data pin char 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 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 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]; uint8_t pins[5];
int nPins = bus->getPins(pins); int nPins = bus->getPins(pins);
for (int i = 0; i < nPins; i++) { 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]); if (PinManager::isPinOk(pins[i]) || bus->isVirtual()) printSetFormValue(settingsScript,lp,pins[i]);
} }
printSetFormValue(settingsScript,lc,bus->getLength()); printSetFormValue(settingsScript,lc,bus->getLength());
@ -361,7 +361,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
const ColorOrderMap& com = BusManager::getColorOrderMap(); const ColorOrderMap& com = BusManager::getColorOrderMap();
for (int s = 0; s < com.count(); s++) { for (int s = 0; s < com.count(); s++) {
const ColorOrderMapEntry* entry = com.get(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); settingsScript.printf_P(PSTR("addCOM(%d,%d,%d);"), entry->start, entry->len, entry->colorOrder);
} }