Segment layering & effect blending improvements

- Photoshop-style segment/layer blending
- return of strip ABL
- remove global LED buffer in favour of segment-local buffer
- new effect blending modes/transitions
- custom palettes moved out of WS2812FX class
- increased limits (matrix size, LED RAM)
- added "rainbow"-mode colorwheel
- replaced palettes with gamma unmodified ones
- move gamma adjustment to last step before sending to LEDs
- Segment & WS2812FX class reorganisation (mutable members, reordered members, protected members)
This commit is contained in:
Blaž Kristan 2025-04-22 22:37:18 +02:00
parent 353868414a
commit ee9ac947a1
32 changed files with 2723 additions and 2931 deletions

View File

@ -102,9 +102,9 @@ private:
void secondsEffectSineFade(int16_t secondLed, Toki::Time const& time) { void secondsEffectSineFade(int16_t secondLed, Toki::Time const& time) {
uint32_t ms = time.ms % 1000; uint32_t ms = time.ms % 1000;
uint8_t b0 = (cos8_t(ms * 64 / 1000) - 128) * 2; uint8_t b0 = (cos8_t(ms * 64 / 1000) - 128) * 2;
setPixelColor(secondLed, gamma32(scale32(secondColor, b0))); setPixelColor(secondLed, scale32(secondColor, b0));
uint8_t b1 = (sin8_t(ms * 64 / 1000) - 128) * 2; uint8_t b1 = (sin8_t(ms * 64 / 1000) - 128) * 2;
setPixelColor(inc(secondLed, 1, secondsSegment), gamma32(scale32(secondColor, b1))); setPixelColor(inc(secondLed, 1, secondsSegment), scale32(secondColor, b1));
} }
static inline uint32_t qadd32(uint32_t c1, uint32_t c2) { static inline uint32_t qadd32(uint32_t c1, uint32_t c2) {
@ -191,7 +191,7 @@ public:
// for (uint16_t i = 1; i < secondsTrail + 1; ++i) { // for (uint16_t i = 1; i < secondsTrail + 1; ++i) {
// uint16_t trailLed = dec(secondLed, i, secondsSegment); // uint16_t trailLed = dec(secondLed, i, secondsSegment);
// uint8_t trailBright = 255 / (secondsTrail + 1) * (secondsTrail - i + 1); // uint8_t trailBright = 255 / (secondsTrail + 1) * (secondsTrail - i + 1);
// setPixelColor(trailLed, gamma32(scale32(secondColor, trailBright))); // setPixelColor(trailLed, scale32(secondColor, trailBright));
// } // }
} }

View File

@ -247,7 +247,7 @@ class UsermodCronixie : public Usermod {
if (backlight && _digitOut[i] <11) if (backlight && _digitOut[i] <11)
{ {
uint32_t col = gamma32(strip.getSegment(0).colors[1]); uint32_t col = strip.getSegment(0).colors[1];
for (uint16_t j=o; j< o+10; j++) { for (uint16_t j=o; j< o+10; j++) {
if (j != excl) strip.setPixelColor(j, col); if (j != excl) strip.setPixelColor(j, col);
} }

View File

@ -5053,7 +5053,11 @@ uint16_t mode_2Dfirenoise(void) { // firenoise2d. By Andrew Tuline
unsigned yscale = SEGMENT.speed*8; unsigned yscale = SEGMENT.speed*8;
unsigned indexx = 0; unsigned indexx = 0;
CRGBPalette16 pal = SEGMENT.check1 ? SEGPALETTE : SEGMENT.loadPalette(pal, 35); //CRGBPalette16 pal = SEGMENT.check1 ? SEGPALETTE : SEGMENT.loadPalette(pal, 35);
CRGBPalette16 pal = SEGMENT.check1 ? SEGPALETTE : CRGBPalette16(CRGB::Black, CRGB::Black, CRGB::Black, CRGB::Black,
CRGB::Red, CRGB::Red, CRGB::Red, CRGB::DarkOrange,
CRGB::DarkOrange,CRGB::DarkOrange, CRGB::Orange, CRGB::Orange,
CRGB::Yellow, CRGB::Orange, CRGB::Yellow, CRGB::Yellow);
for (int j=0; j < cols; j++) { for (int j=0; j < cols; j++) {
for (int i=0; i < rows; i++) { for (int i=0; i < rows; i++) {
indexx = perlin8(j*yscale*rows/255, i*xscale+strip.now/4); // We're moving along our Perlin map. indexx = perlin8(j*yscale*rows/255, i*xscale+strip.now/4); // We're moving along our Perlin map.
@ -6088,7 +6092,8 @@ uint16_t mode_2Dscrollingtext(void) {
case 5: letterWidth = 5; letterHeight = 12; break; case 5: letterWidth = 5; letterHeight = 12; break;
} }
// letters are rotated // letters are rotated
if (((SEGMENT.custom3+1)>>3) % 2) { const int8_t rotate = map(SEGMENT.custom3, 0, 31, -2, 2);
if (rotate == 1 || rotate == -1) {
rotLH = letterWidth; rotLH = letterWidth;
rotLW = letterHeight; rotLW = letterHeight;
} else { } else {
@ -6126,6 +6131,7 @@ uint16_t mode_2Dscrollingtext(void) {
else if (!strncmp_P(text,PSTR("#DD"),3)) sprintf (text, zero? ("%02d") : ("%d"), day(localTime)); else if (!strncmp_P(text,PSTR("#DD"),3)) sprintf (text, zero? ("%02d") : ("%d"), day(localTime));
else if (!strncmp_P(text,PSTR("#DAY"),4)) sprintf (text, ("%s") , dayShortStr(day(localTime))); else if (!strncmp_P(text,PSTR("#DAY"),4)) sprintf (text, ("%s") , dayShortStr(day(localTime)));
else if (!strncmp_P(text,PSTR("#DDDD"),5)) sprintf (text, ("%s") , dayStr(day(localTime))); else if (!strncmp_P(text,PSTR("#DDDD"),5)) sprintf (text, ("%s") , dayStr(day(localTime)));
else if (!strncmp_P(text,PSTR("#DAYL"),5)) sprintf (text, ("%s") , dayStr(day(localTime)));
else if (!strncmp_P(text,PSTR("#MO"),3)) sprintf (text, zero? ("%02d") : ("%d"), month(localTime)); else if (!strncmp_P(text,PSTR("#MO"),3)) sprintf (text, zero? ("%02d") : ("%d"), month(localTime));
else if (!strncmp_P(text,PSTR("#MON"),4)) sprintf (text, ("%s") , monthShortStr(month(localTime))); else if (!strncmp_P(text,PSTR("#MON"),4)) sprintf (text, ("%s") , monthShortStr(month(localTime)));
else if (!strncmp_P(text,PSTR("#MMMM"),5)) sprintf (text, ("%s") , monthStr(month(localTime))); else if (!strncmp_P(text,PSTR("#MMMM"),5)) sprintf (text, ("%s") , monthStr(month(localTime)));
@ -6159,27 +6165,28 @@ uint16_t mode_2Dscrollingtext(void) {
SEGENV.step = strip.now + map(SEGMENT.speed, 0, 255, 250, 50); // shift letters every ~250ms to ~50ms SEGENV.step = strip.now + map(SEGMENT.speed, 0, 255, 250, 50); // shift letters every ~250ms to ~50ms
} }
if (!SEGMENT.check2) SEGMENT.fade_out(255 - (SEGMENT.custom1>>4)); // trail SEGMENT.fade_out(255 - (SEGMENT.custom1>>4)); // trail
bool usePaletteGradient = false;
uint32_t col1 = SEGMENT.color_from_palette(SEGENV.aux1, false, PALETTE_SOLID_WRAP, 0); uint32_t col1 = SEGMENT.color_from_palette(SEGENV.aux1, false, PALETTE_SOLID_WRAP, 0);
uint32_t col2 = BLACK; uint32_t col2 = BLACK;
// if gradient is selected and palette is default (0) drawCharacter() uses gradient from SEGCOLOR(0) to SEGCOLOR(2)
// otherwise col2 == BLACK means use currently selected palette for gradient
// if gradient is not selected set both colors the same
if (SEGMENT.check1) { // use gradient if (SEGMENT.check1) { // use gradient
if(SEGMENT.palette == 0) { // use colors for gradient if (SEGMENT.palette == 0) { // use colors for gradient
col1 = SEGCOLOR(0); col1 = SEGCOLOR(0);
col2 = SEGCOLOR(2); col2 = SEGCOLOR(2);
} }
else usePaletteGradient = true; } else col2 = col1; // force characters to use single color (from palette)
}
for (int i = 0; i < numberOfLetters; i++) { for (int i = 0; i < numberOfLetters; i++) {
int xoffset = int(cols) - int(SEGENV.aux0) + rotLW*i; int xoffset = int(cols) - int(SEGENV.aux0) + rotLW*i;
if (xoffset + rotLW < 0) continue; // don't draw characters off-screen if (xoffset + rotLW < 0) continue; // don't draw characters off-screen
SEGMENT.drawCharacter(text[i], xoffset, yoffset, letterWidth, letterHeight, col1, col2, map(SEGMENT.custom3, 0, 31, -2, 2), usePaletteGradient); SEGMENT.drawCharacter(text[i], xoffset, yoffset, letterWidth, letterHeight, col1, col2, rotate);
} }
return FRAMETIME; return FRAMETIME;
} }
static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Offset,Trail,Font size,Rotate,Gradient,Overlay,Reverse;!,!,Gradient;!;2;ix=128,c1=0,rev=0,mi=0,rY=0,mY=0"; static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Offset,Trail,Font size,Rotate,Gradient,,Reverse;!,!,Gradient;!;2;ix=128,c1=0,rev=0,mi=0,rY=0,mY=0";
//////////////////////////// ////////////////////////////

712
wled00/FX.h Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,6 @@
Parts of the code adapted from WLED Sound Reactive Parts of the code adapted from WLED Sound Reactive
*/ */
#include "wled.h" #include "wled.h"
#include "FX.h"
#include "palettes.h" #include "palettes.h"
// setUpMatrix() - constructs ledmap array from matrix of panels with WxH pixels // setUpMatrix() - constructs ledmap array from matrix of panels with WxH pixels
@ -26,8 +25,7 @@ void WS2812FX::setUpMatrix() {
// calculate width dynamically because it may have gaps // calculate width dynamically because it may have gaps
Segment::maxWidth = 1; Segment::maxWidth = 1;
Segment::maxHeight = 1; Segment::maxHeight = 1;
for (size_t i = 0; i < panel.size(); i++) { for (const Panel &p : panel) {
Panel &p = panel[i];
if (p.xOffset + p.width > Segment::maxWidth) { if (p.xOffset + p.width > Segment::maxWidth) {
Segment::maxWidth = p.xOffset + p.width; Segment::maxWidth = p.xOffset + p.width;
} }
@ -37,21 +35,24 @@ void WS2812FX::setUpMatrix() {
} }
// safety check // safety check
if (Segment::maxWidth * Segment::maxHeight > MAX_LEDS || Segment::maxWidth <= 1 || Segment::maxHeight <= 1) { if (Segment::maxWidth * Segment::maxHeight > MAX_LEDS || Segment::maxWidth > 255 || Segment::maxHeight > 255 || Segment::maxWidth <= 1 || Segment::maxHeight <= 1) {
DEBUG_PRINTLN(F("2D Bounds error.")); DEBUG_PRINTLN(F("2D Bounds error."));
isMatrix = false; isMatrix = false;
Segment::maxWidth = _length; Segment::maxWidth = _length;
Segment::maxHeight = 1; Segment::maxHeight = 1;
panels = 0;
panel.clear(); // release memory allocated by panels panel.clear(); // release memory allocated by panels
panel.shrink_to_fit(); // release memory if allocated
resetSegments(); resetSegments();
return; return;
} }
suspend();
waitForIt();
customMappingSize = 0; // prevent use of mapping if anything goes wrong customMappingSize = 0; // prevent use of mapping if anything goes wrong
if (customMappingTable) free(customMappingTable); d_free(customMappingTable);
customMappingTable = static_cast<uint16_t*>(malloc(sizeof(uint16_t)*getLengthTotal())); customMappingTable = static_cast<uint16_t*>(d_malloc(sizeof(uint16_t)*getLengthTotal())); // prefer to not use SPI RAM
if (customMappingTable) { if (customMappingTable) {
customMappingSize = getLengthTotal(); customMappingSize = getLengthTotal();
@ -85,7 +86,7 @@ void WS2812FX::setUpMatrix() {
JsonArray map = pDoc->as<JsonArray>(); JsonArray map = pDoc->as<JsonArray>();
gapSize = map.size(); gapSize = map.size();
if (!map.isNull() && gapSize >= matrixSize) { // not an empty map if (!map.isNull() && gapSize >= matrixSize) { // not an empty map
gapTable = static_cast<int8_t*>(malloc(gapSize)); gapTable = static_cast<int8_t*>(w_malloc(gapSize));
if (gapTable) for (size_t i = 0; i < gapSize; i++) { if (gapTable) for (size_t i = 0; i < gapSize; i++) {
gapTable[i] = constrain(map[i], -1, 1); gapTable[i] = constrain(map[i], -1, 1);
} }
@ -96,8 +97,7 @@ void WS2812FX::setUpMatrix() {
} }
unsigned x, y, pix=0; //pixel unsigned x, y, pix=0; //pixel
for (size_t pan = 0; pan < panel.size(); pan++) { for (const Panel &p : panel) {
Panel &p = panel[pan];
unsigned h = p.vertical ? p.height : p.width; unsigned h = p.vertical ? p.height : p.width;
unsigned v = p.vertical ? p.width : p.height; unsigned v = p.vertical ? p.width : p.height;
for (size_t j = 0; j < v; j++){ for (size_t j = 0; j < v; j++){
@ -113,7 +113,8 @@ void WS2812FX::setUpMatrix() {
} }
// delete gap array as we no longer need it // delete gap array as we no longer need it
if (gapTable) free(gapTable); w_free(gapTable);
resume();
#ifdef WLED_DEBUG #ifdef WLED_DEBUG
DEBUG_PRINT(F("Matrix ledmap:")); DEBUG_PRINT(F("Matrix ledmap:"));
@ -126,7 +127,6 @@ void WS2812FX::setUpMatrix() {
} else { // memory allocation error } else { // memory allocation error
DEBUG_PRINTLN(F("ERROR 2D LED map allocation error.")); DEBUG_PRINTLN(F("ERROR 2D LED map allocation error."));
isMatrix = false; isMatrix = false;
panels = 0;
panel.clear(); panel.clear();
Segment::maxWidth = _length; Segment::maxWidth = _length;
Segment::maxHeight = 1; Segment::maxHeight = 1;
@ -144,103 +144,50 @@ void WS2812FX::setUpMatrix() {
/////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////
#ifndef WLED_DISABLE_2D #ifndef WLED_DISABLE_2D
// pixel is clipped if it falls outside clipping range
// raw setColor function without checks (checks are done in setPixelColorXY())
void IRAM_ATTR_YN Segment::_setPixelColorXY_raw(const int& x, const int& y, uint32_t& col) const
{
const int baseX = start + x;
const int baseY = startY + y;
#ifndef WLED_DISABLE_MODE_BLEND
// if blending modes, blend with underlying pixel
if (_modeBlend && blendingStyle == BLEND_STYLE_FADE) col = color_blend16(strip.getPixelColorXY(baseX, baseY), col, 0xFFFFU - progress());
#endif
strip.setPixelColorXY(baseX, baseY, col);
// Apply mirroring
if (mirror || mirror_y) {
const int mirrorX = start + width() - x - 1;
const int mirrorY = startY + height() - y - 1;
if (mirror) strip.setPixelColorXY(transpose ? baseX : mirrorX, transpose ? mirrorY : baseY, col);
if (mirror_y) strip.setPixelColorXY(transpose ? mirrorX : baseX, transpose ? baseY : mirrorY, col);
if (mirror && mirror_y) strip.setPixelColorXY(mirrorX, mirrorY, col);
}
}
// pixel is clipped if it falls outside clipping range (_modeBlend==true) or is inside clipping range (_modeBlend==false)
// if clipping start > stop the clipping range is inverted // if clipping start > stop the clipping range is inverted
// _modeBlend==true -> old effect during transition
// _modeBlend==false -> new effect during transition
bool IRAM_ATTR_YN Segment::isPixelXYClipped(int x, int y) const { bool IRAM_ATTR_YN Segment::isPixelXYClipped(int x, int y) const {
#ifndef WLED_DISABLE_MODE_BLEND if (blendingStyle != BLEND_STYLE_FADE && isInTransition() && _clipStart != _clipStop) {
if (_clipStart != _clipStop && blendingStyle != BLEND_STYLE_FADE) { const bool invertX = _clipStart > _clipStop;
const bool invertX = _clipStart > _clipStop;
const bool invertY = _clipStartY > _clipStopY; const bool invertY = _clipStartY > _clipStopY;
const int startX = invertX ? _clipStop : _clipStart; const int cStartX = invertX ? _clipStop : _clipStart;
const int stopX = invertX ? _clipStart : _clipStop; const int cStopX = invertX ? _clipStart : _clipStop;
const int startY = invertY ? _clipStopY : _clipStartY; const int cStartY = invertY ? _clipStopY : _clipStartY;
const int stopY = invertY ? _clipStartY : _clipStopY; const int cStopY = invertY ? _clipStartY : _clipStopY;
if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { if (blendingStyle == BLEND_STYLE_FAIRY_DUST) {
const unsigned width = stopX - startX; // assumes full segment width (faster than virtualWidth()) const unsigned width = cStopX - cStartX; // assumes full segment width (faster than virtualWidth())
const unsigned len = width * (stopY - startY); // assumes full segment height (faster than virtualHeight()) const unsigned len = width * (cStopY - cStartY); // assumes full segment height (faster than virtualHeight())
if (len < 2) return false; if (len < 2) return false;
const unsigned shuffled = hashInt(x + y * width) % len; const unsigned shuffled = hashInt(x + y * width) % len;
const unsigned pos = (shuffled * 0xFFFFU) / len; const unsigned pos = (shuffled * 0xFFFFU) / len;
return progress() > pos; return progress() <= pos;
} }
bool xInside = (x >= startX && x < stopX); if (invertX) xInside = !xInside; if (blendingStyle == BLEND_STYLE_CIRCULAR_IN || blendingStyle == BLEND_STYLE_CIRCULAR_OUT) {
bool yInside = (y >= startY && y < stopY); if (invertY) yInside = !yInside; const int cx = (cStopX-cStartX+1) / 2;
const bool clip = (invertX && invertY) ? !_modeBlend : _modeBlend; const int cy = (cStopY-cStartY+1) / 2;
if (xInside && yInside) return clip; // covers window & corners (inverted) const bool out = (blendingStyle == BLEND_STYLE_CIRCULAR_OUT);
const unsigned prog = out ? progress() : 0xFFFFU - progress();
int radius2 = max(cx, cy) * prog / 0xFFFF;
radius2 = 2 * radius2 * radius2;
if (radius2 == 0) return out;
const int dx = x - cx;
const int dy = y - cy;
const bool outside = dx * dx + dy * dy > radius2;
return out ? outside : !outside;
}
bool xInside = (x >= cStartX && x < cStopX); if (invertX) xInside = !xInside;
bool yInside = (y >= cStartY && y < cStopY); if (invertY) yInside = !yInside;
const bool clip = blendingStyle == BLEND_STYLE_OUTSIDE_IN ? xInside || yInside : xInside && yInside;
return !clip; return !clip;
} }
#endif
return false; return false;
} }
void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) const void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) const
{ {
if (!isActive()) return; // not active if (!isActive()) return; // not active
if (x >= (int)vWidth() || y >= (int)vHeight() || x < 0 || y < 0) return; // if pixel would fall out of virtual segment just exit
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) setPixelColorXYRaw(x, y, col);
const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
#ifndef WLED_DISABLE_MODE_BLEND
unsigned prog = 0xFFFF - progress();
if (!prog && !_modeBlend && (blendingStyle & BLEND_STYLE_PUSH_MASK)) {
unsigned dX = (blendingStyle == BLEND_STYLE_PUSH_UP || blendingStyle == BLEND_STYLE_PUSH_DOWN) ? 0 : prog * vW / 0xFFFF;
unsigned dY = (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_RIGHT) ? 0 : prog * vH / 0xFFFF;
if (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_BL) x += dX;
else x -= dX;
if (blendingStyle == BLEND_STYLE_PUSH_DOWN || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_TR) y -= dY;
else y += dY;
}
#endif
if (x >= vW || y >= vH || x < 0 || y < 0 || isPixelXYClipped(x,y)) return; // if pixel would fall out of virtual segment just exit
// if color is unscaled
if (!_colorScaled) col = color_fade(col, _segBri);
if (reverse ) x = vW - x - 1;
if (reverse_y) y = vH - y - 1;
if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed
unsigned groupLen = groupLength();
if (groupLen > 1) {
int W = width();
int H = height();
x *= groupLen; // expand to physical pixels
y *= groupLen; // expand to physical pixels
const int maxY = std::min(y + grouping, H);
const int maxX = std::min(x + grouping, W);
for (int yY = y; yY < maxY; yY++) {
for (int xX = x; xX < maxX; xX++) {
_setPixelColorXY_raw(xX, yY, col);
}
}
} else {
_setPixelColorXY_raw(x, y, col);
}
} }
#ifdef WLED_USE_AA_PIXELS #ifdef WLED_USE_AA_PIXELS
@ -289,84 +236,62 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) const
// returns RGBW values of pixel // returns RGBW values of pixel
uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const { uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const {
if (!isActive()) return 0; // not active if (!isActive()) return 0; // not active
if (x >= (int)vWidth() || y >= (int)vHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit
const int vW = vWidth(); return getPixelColorXYRaw(x,y);
const int vH = vHeight();
#ifndef WLED_DISABLE_MODE_BLEND
unsigned prog = 0xFFFF - progress();
if (!prog && !_modeBlend && (blendingStyle & BLEND_STYLE_PUSH_MASK)) {
unsigned dX = (blendingStyle == BLEND_STYLE_PUSH_UP || blendingStyle == BLEND_STYLE_PUSH_DOWN) ? 0 : prog * vW / 0xFFFF;
unsigned dY = (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_RIGHT) ? 0 : prog * vH / 0xFFFF;
if (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_BL) x -= dX;
else x += dX;
if (blendingStyle == BLEND_STYLE_PUSH_DOWN || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_TR) y -= dY;
else y += dY;
}
#endif
if (x >= vW || y >= vH || x<0 || y<0 || isPixelXYClipped(x,y)) return 0; // if pixel would fall out of virtual segment just exit
if (reverse ) x = vW - x - 1;
if (reverse_y) y = vH - y - 1;
if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed
x *= groupLength(); // expand to physical pixels
y *= groupLength(); // expand to physical pixels
if (x >= width() || y >= height()) return 0;
return strip.getPixelColorXY(start + x, startY + y);
} }
// 2D blurring, can be asymmetrical // 2D blurring, can be asymmetrical
void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) { void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) const {
if (!isActive()) return; // not active if (!isActive()) return; // not active
const unsigned cols = vWidth(); const unsigned cols = vWidth();
const unsigned rows = vHeight(); const unsigned rows = vHeight();
uint32_t lastnew; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration const auto XY = [&](unsigned x, unsigned y){ return x + y*cols; };
uint32_t lastnew;
uint32_t last; uint32_t last;
if (blur_x) { if (blur_x) {
const uint8_t keepx = smear ? 255 : 255 - blur_x; const uint8_t keepx = smear ? 255 : 255 - blur_x;
const uint8_t seepx = blur_x >> 1; const uint8_t seepx = blur_x >> (1 + smear);
for (unsigned row = 0; row < rows; row++) { // blur rows (x direction) for (unsigned row = 0; row < rows; row++) { // blur rows (x direction)
uint32_t carryover = BLACK; uint32_t carryover = BLACK;
uint32_t curnew = BLACK; uint32_t curnew = BLACK;
for (unsigned x = 0; x < cols; x++) { for (unsigned x = 0; x < cols; x++) {
uint32_t cur = getPixelColorXY(x, row); uint32_t cur = getPixelColorRaw(XY(x, row));
uint32_t part = color_fade(cur, seepx); uint32_t part = color_fade(cur, seepx);
curnew = color_fade(cur, keepx); curnew = color_fade(cur, keepx);
if (x > 0) { if (x > 0) {
if (carryover) curnew = color_add(curnew, carryover); if (carryover) curnew = color_add(curnew, carryover);
uint32_t prev = color_add(lastnew, part); uint32_t prev = color_add(lastnew, part);
// optimization: only set pixel if color has changed // optimization: only set pixel if color has changed
if (last != prev) setPixelColorXY(x - 1, row, prev); if (last != prev) setPixelColorRaw(XY(x - 1, row), prev);
} else setPixelColorXY(x, row, curnew); // first pixel } else setPixelColorRaw(XY(x, row), curnew); // first pixel
lastnew = curnew; lastnew = curnew;
last = cur; // save original value for comparison on next iteration last = cur; // save original value for comparison on next iteration
carryover = part; carryover = part;
} }
setPixelColorXY(cols-1, row, curnew); // set last pixel setPixelColorRaw(XY(cols-1, row), curnew); // set last pixel
} }
} }
if (blur_y) { if (blur_y) {
const uint8_t keepy = smear ? 255 : 255 - blur_y; const uint8_t keepy = smear ? 255 : 255 - blur_y;
const uint8_t seepy = blur_y >> 1; const uint8_t seepy = blur_y >> (1 + smear);
for (unsigned col = 0; col < cols; col++) { for (unsigned col = 0; col < cols; col++) {
uint32_t carryover = BLACK; uint32_t carryover = BLACK;
uint32_t curnew = BLACK; uint32_t curnew = BLACK;
for (unsigned y = 0; y < rows; y++) { for (unsigned y = 0; y < rows; y++) {
uint32_t cur = getPixelColorXY(col, y); uint32_t cur = getPixelColorRaw(XY(col, y));
uint32_t part = color_fade(cur, seepy); uint32_t part = color_fade(cur, seepy);
curnew = color_fade(cur, keepy); curnew = color_fade(cur, keepy);
if (y > 0) { if (y > 0) {
if (carryover) curnew = color_add(curnew, carryover); if (carryover) curnew = color_add(curnew, carryover);
uint32_t prev = color_add(lastnew, part); uint32_t prev = color_add(lastnew, part);
// optimization: only set pixel if color has changed // optimization: only set pixel if color has changed
if (last != prev) setPixelColorXY(col, y - 1, prev); if (last != prev) setPixelColorRaw(XY(col, y - 1), prev);
} else setPixelColorXY(col, y, curnew); // first pixel } else setPixelColorRaw(XY(col, y), curnew); // first pixel
lastnew = curnew; lastnew = curnew;
last = cur; //save original value for comparison on next iteration last = cur; //save original value for comparison on next iteration
carryover = part; carryover = part;
} }
setPixelColorXY(col, rows - 1, curnew); setPixelColorRaw(XY(col, rows - 1), curnew);
} }
} }
} }
@ -445,10 +370,11 @@ void Segment::box_blur(unsigned radius, bool smear) {
delete[] tmpWSum; delete[] tmpWSum;
} }
*/ */
void Segment::moveX(int delta, bool wrap) { void Segment::moveX(int delta, bool wrap) const {
if (!isActive() || !delta) return; // not active if (!isActive() || !delta) return; // not active
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int vH = vHeight(); // segment height in logical pixels (is always >= 1) const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
const auto XY = [&](unsigned x, unsigned y){ return x + y*vW; };
int absDelta = abs(delta); int absDelta = abs(delta);
if (absDelta >= vW) return; if (absDelta >= vW) return;
uint32_t newPxCol[vW]; uint32_t newPxCol[vW];
@ -465,16 +391,17 @@ void Segment::moveX(int delta, bool wrap) {
for (int x = 0; x < stop; x++) { for (int x = 0; x < stop; x++) {
int srcX = x + newDelta; int srcX = x + newDelta;
if (wrap) srcX %= vW; // Wrap using modulo when `wrap` is true if (wrap) srcX %= vW; // Wrap using modulo when `wrap` is true
newPxCol[x] = getPixelColorXY(srcX, y); newPxCol[x] = getPixelColorRaw(XY(srcX, y));
} }
for (int x = 0; x < stop; x++) setPixelColorXY(x + start, y, newPxCol[x]); for (int x = 0; x < stop; x++) setPixelColorRaw(XY(x + start, y), newPxCol[x]);
} }
} }
void Segment::moveY(int delta, bool wrap) { void Segment::moveY(int delta, bool wrap) const {
if (!isActive() || !delta) return; // not active if (!isActive() || !delta) return; // not active
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int vH = vHeight(); // segment height in logical pixels (is always >= 1) const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
const auto XY = [&](unsigned x, unsigned y){ return x + y*vW; };
int absDelta = abs(delta); int absDelta = abs(delta);
if (absDelta >= vH) return; if (absDelta >= vH) return;
uint32_t newPxCol[vH]; uint32_t newPxCol[vH];
@ -491,9 +418,9 @@ void Segment::moveY(int delta, bool wrap) {
for (int y = 0; y < stop; y++) { for (int y = 0; y < stop; y++) {
int srcY = y + newDelta; int srcY = y + newDelta;
if (wrap) srcY %= vH; // Wrap using modulo when `wrap` is true if (wrap) srcY %= vH; // Wrap using modulo when `wrap` is true
newPxCol[y] = getPixelColorXY(x, srcY); newPxCol[y] = getPixelColorRaw(XY(x, srcY));
} }
for (int y = 0; y < stop; y++) setPixelColorXY(x, y + start, newPxCol[y]); for (int y = 0; y < stop; y++) setPixelColorRaw(XY(x, y + start), newPxCol[y]);
} }
} }
@ -501,7 +428,7 @@ void Segment::moveY(int delta, bool wrap) {
// @param dir direction: 0=left, 1=left-up, 2=up, 3=right-up, 4=right, 5=right-down, 6=down, 7=left-down // @param dir direction: 0=left, 1=left-up, 2=up, 3=right-up, 4=right, 5=right-down, 6=down, 7=left-down
// @param delta number of pixels to move // @param delta number of pixels to move
// @param wrap around // @param wrap around
void Segment::move(unsigned dir, unsigned delta, bool wrap) { void Segment::move(unsigned dir, unsigned delta, bool wrap) const {
if (delta==0) return; if (delta==0) return;
switch (dir) { switch (dir) {
case 0: moveX( delta, wrap); break; case 0: moveX( delta, wrap); break;
@ -515,7 +442,7 @@ void Segment::move(unsigned dir, unsigned delta, bool wrap) {
} }
} }
void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) { void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) const {
if (!isActive() || radius == 0) return; // not active if (!isActive() || radius == 0) return; // not active
if (soft) { if (soft) {
// Xiaolin Wus algorithm // Xiaolin Wus algorithm
@ -549,9 +476,6 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col,
x++; x++;
} }
} else { } else {
// pre-scale color for all pixels
col = color_fade(col, _segBri);
_colorScaled = true;
// Bresenhams Algorithm // Bresenhams Algorithm
int d = 3 - (2*radius); int d = 3 - (2*radius);
int y = radius, x = 0; int y = radius, x = 0;
@ -570,20 +494,16 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col,
d += 4 * x + 6; d += 4 * x + 6;
} }
} }
_colorScaled = false;
} }
} }
// by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs // by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs
void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) { void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) const {
if (!isActive() || radius == 0) return; // not active if (!isActive() || radius == 0) return; // not active
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int vH = vHeight(); // segment height in logical pixels (is always >= 1) const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
// draw soft bounding circle // draw soft bounding circle
if (soft) drawCircle(cx, cy, radius, col, soft); if (soft) drawCircle(cx, cy, radius, col, soft);
// pre-scale color for all pixels
col = color_fade(col, _segBri);
_colorScaled = true;
// fill it // fill it
for (int y = -radius; y <= radius; y++) { for (int y = -radius; y <= radius; y++) {
for (int x = -radius; x <= radius; x++) { for (int x = -radius; x <= radius; x++) {
@ -593,11 +513,10 @@ void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col,
setPixelColorXY(cx + x, cy + y, col); setPixelColorXY(cx + x, cy + y, col);
} }
} }
_colorScaled = false;
} }
//line function //line function
void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft) { void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft) const {
if (!isActive()) return; // not active if (!isActive()) return; // not active
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int vH = vHeight(); // segment height in logical pixels (is always >= 1) const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
@ -633,15 +552,12 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3
int y = int(intersectY); int y = int(intersectY);
if (steep) std::swap(x,y); // temporaryly swap if steep if (steep) std::swap(x,y); // temporaryly swap if steep
// pixel coverage is determined by fractional part of y co-ordinate // pixel coverage is determined by fractional part of y co-ordinate
setPixelColorXY(x, y, color_blend(c, getPixelColorXY(x, y), keep)); blendPixelColorXY(x, y, c, seep);
setPixelColorXY(x+int(steep), y+int(!steep), color_blend(c, getPixelColorXY(x+int(steep), y+int(!steep)), seep)); blendPixelColorXY(x+int(steep), y+int(!steep), c, keep);
intersectY += gradient; intersectY += gradient;
if (steep) std::swap(x,y); // restore if steep if (steep) std::swap(x,y); // restore if steep
} }
} else { } else {
// pre-scale color for all pixels
c = color_fade(c, _segBri);
_colorScaled = true;
// Bresenham's algorithm // Bresenham's algorithm
int err = (dx>dy ? dx : -dy)/2; // error direction int err = (dx>dy ? dx : -dy)/2; // error direction
for (;;) { for (;;) {
@ -651,7 +567,6 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3
if (e2 >-dx) { err -= dy; x0 += sx; } if (e2 >-dx) { err -= dy; x0 += sx; }
if (e2 < dy) { err += dx; y0 += sy; } if (e2 < dy) { err += dx; y0 += sy; }
} }
_colorScaled = false;
} }
} }
@ -663,29 +578,25 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3
// draws a raster font character on canvas // draws a raster font character on canvas
// only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48 and 7x9=63 fonts ATM // only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48 and 7x9=63 fonts ATM
void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2, int8_t rotate, bool usePalGrad) { void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2, int8_t rotate) const {
if (!isActive()) return; // not active if (!isActive()) return; // not active
if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported
chr -= 32; // align with font table entries chr -= 32; // align with font table entries
const int font = w*h; const int font = w*h;
CRGB col = CRGB(color); CRGBPalette16 grad = col2 ? CRGBPalette16(CRGB(color), CRGB(col2)) : SEGPALETTE; // selected palette as gradient
CRGBPalette16 grad = CRGBPalette16(col, col2 ? CRGB(col2) : col);
if(usePalGrad) grad = SEGPALETTE; // selected palette as gradient
//if (w<5 || w>6 || h!=8) return;
for (int i = 0; i<h; i++) { // character height for (int i = 0; i<h; i++) { // character height
uint8_t bits = 0; uint8_t bits = 0;
switch (font) { switch (font) {
case 24: bits = pgm_read_byte_near(&console_font_4x6[(chr * h) + i]); break; // 5x8 font case 24: bits = pgm_read_byte_near(&console_font_4x6[(chr * h) + i]); break; // 4x6 font
case 40: bits = pgm_read_byte_near(&console_font_5x8[(chr * h) + i]); break; // 5x8 font case 40: bits = pgm_read_byte_near(&console_font_5x8[(chr * h) + i]); break; // 5x8 font
case 48: bits = pgm_read_byte_near(&console_font_6x8[(chr * h) + i]); break; // 6x8 font case 48: bits = pgm_read_byte_near(&console_font_6x8[(chr * h) + i]); break; // 6x8 font
case 63: bits = pgm_read_byte_near(&console_font_7x9[(chr * h) + i]); break; // 7x9 font case 63: bits = pgm_read_byte_near(&console_font_7x9[(chr * h) + i]); break; // 7x9 font
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;
} }
CRGBW c = ColorFromPalette(grad, (i+1)*255/h, _segBri, LINEARBLEND_NOWRAP); CRGBW c = ColorFromPalette(grad, (i+1)*255/h, 255, LINEARBLEND_NOWRAP); // NOBLEND is faster
_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;
switch (rotate) { switch (rotate) {
@ -697,15 +608,14 @@ 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.color32); setPixelColorXYRaw(x0, y0, c.color32);
} }
} }
_colorScaled = false;
} }
} }
#define WU_WEIGHT(a,b) ((uint8_t) (((a)*(b)+(a)+(b))>>8)) #define WU_WEIGHT(a,b) ((uint8_t) (((a)*(b)+(a)+(b))>>8))
void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) { //awesome wu_pixel procedure by reddit u/sutaburosu void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) const { //awesome wu_pixel procedure by reddit u/sutaburosu
if (!isActive()) return; // not active if (!isActive()) return; // not active
// extract the fractional parts and derive their inverses // extract the fractional parts and derive their inverses
unsigned xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy; unsigned xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy;

1848
wled00/FX_fcn.cpp Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@ -32,8 +32,29 @@ extern bool useParallelI2S;
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
//udp.cpp //udp.cpp
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const uint8_t* buffer, uint8_t bri=255, bool isRGBW=false); uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const byte *buffer, uint8_t bri=255, bool isRGBW=false);
//util.cpp
// PSRAM allocation wrappers
#ifndef ESP8266
void *w_malloc(size_t); // prefer PSRAM over DRAM
void *w_calloc(size_t, size_t); // prefer PSRAM over DRAM
void *w_realloc(void *, size_t); // prefer PSRAM over DRAM
inline void w_free(void *ptr) { heap_caps_free(ptr); }
void *d_malloc(size_t); // prefer DRAM over PSRAM
void *d_calloc(size_t, size_t); // prefer DRAM over PSRAM
void *d_realloc(void *, size_t); // prefer DRAM over PSRAM
inline void d_free(void *ptr) { heap_caps_free(ptr); }
#else
#define w_malloc malloc
#define w_calloc calloc
#define w_realloc realloc
#define w_free free
#define d_malloc malloc
#define d_calloc calloc
#define d_realloc realloc
#define d_free free
#endif
//color mangling macros //color mangling macros
#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b)))) #define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
@ -72,7 +93,7 @@ void Bus::calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw) {
} else { } else {
cct = (approximateKelvinFromRGB(c) - 1900) >> 5; // convert K (from RGB value) to relative format cct = (approximateKelvinFromRGB(c) - 1900) >> 5; // convert K (from RGB value) to relative format
} }
//0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold) //0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold)
if (cct < _cctBlend) ww = 255; if (cct < _cctBlend) ww = 255;
else ww = ((255-cct) * 255) / (255 - _cctBlend); else ww = ((255-cct) * 255) / (255 - _cctBlend);
@ -106,7 +127,6 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr)
, _colorOrder(bc.colorOrder) , _colorOrder(bc.colorOrder)
, _milliAmpsPerLed(bc.milliAmpsPerLed) , _milliAmpsPerLed(bc.milliAmpsPerLed)
, _milliAmpsMax(bc.milliAmpsMax) , _milliAmpsMax(bc.milliAmpsMax)
, _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; }
@ -127,10 +147,6 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr)
_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) {
_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);
@ -213,43 +229,6 @@ void BusDigital::show() {
uint8_t cctWW = 0, cctCW = 0; uint8_t cctWW = 0, cctCW = 0;
unsigned newBri = estimateCurrentAndLimitBri(); // will fill _milliAmpsTotal (TODO: could use PolyBus::CalcTotalMilliAmpere()) unsigned newBri = estimateCurrentAndLimitBri(); // will fill _milliAmpsTotal (TODO: could use PolyBus::CalcTotalMilliAmpere())
if (newBri < _bri) PolyBus::setBrightness(_busPtr, _iType, newBri); // limit brightness to stay within current limits if (newBri < _bri) PolyBus::setBrightness(_busPtr, _iType, newBri); // limit brightness to stay within current limits
if (_data) {
size_t channels = getNumberOfChannels();
int16_t oldCCT = Bus::_cct; // temporarily save bus CCT
for (size_t i=0; i<_len; i++) {
size_t offset = i * channels;
unsigned co = _colorOrderMap.getPixelColorOrder(i+_start, _colorOrder);
uint32_t c;
if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs (_len is always a multiple of 3)
switch (i%3) {
case 0: c = RGBW32(_data[offset] , _data[offset+1], _data[offset+2], 0); break;
case 1: c = RGBW32(_data[offset-1], _data[offset] , _data[offset+1], 0); break;
case 2: c = RGBW32(_data[offset-2], _data[offset-1], _data[offset] , 0); break;
}
} else {
if (hasRGB()) c = RGBW32(_data[offset], _data[offset+1], _data[offset+2], hasWhite() ? _data[offset+3] : 0);
else c = RGBW32(0, 0, 0, _data[offset]);
}
if (hasCCT()) {
// unfortunately as a segment may span multiple buses or a bus may contain multiple segments and each segment may have different CCT
// we need to extract and appy CCT value for each pixel individually even though all buses share the same _cct variable
// TODO: there is an issue if CCT is calculated from RGB value (_cct==-1), we cannot do that with double buffer
Bus::_cct = _data[offset+channels-1];
Bus::calculateCCT(c, cctWW, cctCW);
if (_type == TYPE_WS2812_WWA) c = RGBW32(cctWW, cctCW, 0, W(c)); // may need swapping
}
unsigned pix = i;
if (_reversed) pix = _len - pix -1;
pix += _skip;
PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, (cctCW<<8) | cctWW);
}
#if !defined(STATUSLED) || STATUSLED>=0
if (_skip) PolyBus::setPixelColor(_busPtr, _iType, 0, 0, _colorOrderMap.getPixelColorOrder(_start, _colorOrder)); // paint skipped pixels black
#endif
for (int i=1; i<_skip; i++) PolyBus::setPixelColor(_busPtr, _iType, i, 0, _colorOrderMap.getPixelColorOrder(_start, _colorOrder)); // paint skipped pixels black
Bus::_cct = oldCCT;
} else {
if (newBri < _bri) { if (newBri < _bri) {
unsigned hwLen = _len; unsigned hwLen = _len;
if (_type == TYPE_WS2812_1CH_X3) hwLen = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus if (_type == TYPE_WS2812_1CH_X3) hwLen = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus
@ -260,8 +239,7 @@ void BusDigital::show() {
PolyBus::setPixelColor(_busPtr, _iType, i, c, 0, (cctCW<<8) | cctWW); // repaint all pixels with new brightness PolyBus::setPixelColor(_busPtr, _iType, i, c, 0, (cctCW<<8) | cctWW); // repaint all pixels with new brightness
} }
} }
} PolyBus::show(_busPtr, _iType, false); // faster if buffer consistency is not important
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)
@ -292,86 +270,61 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) {
if (!_valid) return; if (!_valid) return;
if (hasWhite()) c = autoWhiteCalc(c); if (hasWhite()) c = autoWhiteCalc(c);
if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
if (_data) { if (_reversed) pix = _len - pix -1;
size_t offset = pix * getNumberOfChannels(); pix += _skip;
uint8_t* dataptr = _data + offset; unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
if (hasRGB()) { if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs
*dataptr++ = R(c); unsigned pOld = pix;
*dataptr++ = G(c); pix = IC_INDEX_WS2812_1CH_3X(pix);
*dataptr++ = B(c); uint32_t cOld = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, pix, co),_bri);
switch (pOld % 3) { // change only the single channel (TODO: this can cause loss because of get/set)
case 0: c = RGBW32(R(cOld), W(c) , B(cOld), 0); break;
case 1: c = RGBW32(W(c) , G(cOld), B(cOld), 0); break;
case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break;
} }
if (hasWhite()) *dataptr++ = W(c);
// unfortunately as a segment may span multiple buses or a bus may contain multiple segments and each segment may have different CCT
// we need to store CCT value for each pixel (if there is a color correction in play, convert K in CCT ratio)
if (hasCCT()) *dataptr = Bus::_cct >= 1900 ? (Bus::_cct - 1900) >> 5 : (Bus::_cct < 0 ? 127 : Bus::_cct); // TODO: if _cct == -1 we simply ignore it
} else {
if (_reversed) pix = _len - pix -1;
pix += _skip;
unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs
unsigned pOld = pix;
pix = IC_INDEX_WS2812_1CH_3X(pix);
uint32_t cOld = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, pix, co),_bri);
switch (pOld % 3) { // change only the single channel (TODO: this can cause loss because of get/set)
case 0: c = RGBW32(R(cOld), W(c) , B(cOld), 0); break;
case 1: c = RGBW32(W(c) , G(cOld), B(cOld), 0); break;
case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break;
}
}
uint16_t wwcw = 0;
if (hasCCT()) {
uint8_t cctWW = 0, cctCW = 0;
Bus::calculateCCT(c, cctWW, cctCW);
wwcw = (cctCW<<8) | cctWW;
if (_type == TYPE_WS2812_WWA) c = RGBW32(cctWW, cctCW, 0, W(c)); // may need swapping
}
PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, wwcw);
} }
uint16_t wwcw = 0;
if (hasCCT()) {
uint8_t cctWW = 0, cctCW = 0;
Bus::calculateCCT(c, cctWW, cctCW);
wwcw = (cctCW<<8) | cctWW;
if (_type == TYPE_WS2812_WWA) c = RGBW32(cctWW, cctCW, 0, W(c));
}
PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, wwcw);
} }
// returns original color if global buffering is enabled, else returns lossly restored color from bus // returns original color if global buffering is enabled, else returns lossly restored color from bus
uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const { uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const {
if (!_valid) return 0; if (!_valid) return 0;
if (_data) { if (_reversed) pix = _len - pix -1;
const size_t offset = pix * getNumberOfChannels(); pix += _skip;
uint32_t c; const unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
if (!hasRGB()) { uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, (_type==TYPE_WS2812_1CH_X3) ? IC_INDEX_WS2812_1CH_3X(pix) : pix, co),_bri);
c = RGBW32(_data[offset], _data[offset], _data[offset], _data[offset]); if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs
} else { unsigned r = R(c);
c = RGBW32(_data[offset], _data[offset+1], _data[offset+2], hasWhite() ? _data[offset+3] : 0); unsigned g = _reversed ? B(c) : G(c); // should G and B be switched if _reversed?
unsigned b = _reversed ? G(c) : B(c);
switch (pix % 3) { // get only the single channel
case 0: c = RGBW32(g, g, g, g); break;
case 1: c = RGBW32(r, r, r, r); break;
case 2: c = RGBW32(b, b, b, b); break;
} }
return c;
} else {
if (_reversed) pix = _len - pix -1;
pix += _skip;
const unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, (_type==TYPE_WS2812_1CH_X3) ? IC_INDEX_WS2812_1CH_3X(pix) : pix, co),_bri);
if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs
unsigned r = R(c);
unsigned g = _reversed ? B(c) : G(c); // should G and B be switched if _reversed?
unsigned b = _reversed ? G(c) : B(c);
switch (pix % 3) { // get only the single channel
case 0: c = RGBW32(g, g, g, g); break;
case 1: c = RGBW32(r, r, r, r); break;
case 2: c = RGBW32(b, b, b, b); break;
}
}
if (_type == TYPE_WS2812_WWA) {
uint8_t w = R(c) | G(c);
c = RGBW32(w, w, 0, w);
}
return c;
} }
if (_type == TYPE_WS2812_WWA) {
uint8_t w = R(c) | G(c);
c = RGBW32(w, w, 0, w);
}
return c;
} }
unsigned BusDigital::getPins(uint8_t* pinArray) const { size_t BusDigital::getPins(uint8_t* pinArray) const {
unsigned numPins = is2Pin(_type) + 1; unsigned numPins = is2Pin(_type) + 1;
if (pinArray) for (unsigned i = 0; i < numPins; i++) pinArray[i] = _pins[i]; if (pinArray) for (unsigned i = 0; i < numPins; i++) pinArray[i] = _pins[i];
return numPins; return numPins;
} }
unsigned BusDigital::getBusSize() const { size_t BusDigital::getBusSize() const {
return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) + (_data ? _len * getNumberOfChannels() : 0) : 0); return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) /*+ (_data ? _len * getNumberOfChannels() : 0)*/ : 0);
} }
void BusDigital::setColorOrder(uint8_t colorOrder) { void BusDigital::setColorOrder(uint8_t colorOrder) {
@ -380,7 +333,7 @@ void BusDigital::setColorOrder(uint8_t colorOrder) {
_colorOrder = colorOrder; _colorOrder = colorOrder;
} }
// credit @willmmiles & @netmindz https://github.com/wled-dev/WLED/pull/4056 // credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056
std::vector<LEDType> BusDigital::getLEDTypes() { std::vector<LEDType> BusDigital::getLEDTypes() {
return { return {
{TYPE_WS2812_RGB, "D", PSTR("WS281x")}, {TYPE_WS2812_RGB, "D", PSTR("WS281x")},
@ -414,8 +367,6 @@ 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;
@ -453,7 +404,7 @@ BusPwm::BusPwm(const BusConfig &bc)
: Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed, bc.refreshReq) // hijack Off refresh flag to indicate usage of dithering : Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed, bc.refreshReq) // hijack Off refresh flag to indicate usage of dithering
{ {
if (!isPWM(bc.type)) return; if (!isPWM(bc.type)) return;
unsigned numPins = numPWMPins(bc.type); const unsigned numPins = numPWMPins(bc.type);
[[maybe_unused]] const bool dithering = _needsRefresh; [[maybe_unused]] const bool dithering = _needsRefresh;
_frequency = bc.frequency ? bc.frequency : WLED_PWM_FREQ; _frequency = bc.frequency ? bc.frequency : WLED_PWM_FREQ;
// duty cycle resolution (_depth) can be extracted from this formula: CLOCK_FREQUENCY > _frequency * 2^_depth // duty cycle resolution (_depth) can be extracted from this formula: CLOCK_FREQUENCY > _frequency * 2^_depth
@ -461,36 +412,40 @@ BusPwm::BusPwm(const BusConfig &bc)
managed_pin_type pins[numPins]; managed_pin_type pins[numPins];
for (unsigned i = 0; i < numPins; i++) pins[i] = {(int8_t)bc.pins[i], true}; for (unsigned i = 0; i < numPins; i++) pins[i] = {(int8_t)bc.pins[i], true};
if (!PinManager::allocateMultiplePins(pins, numPins, PinOwner::BusPwm)) return; if (PinManager::allocateMultiplePins(pins, numPins, PinOwner::BusPwm)) {
#ifdef ARDUINO_ARCH_ESP32
// for 2 pin PWM CCT strip pinManager will make sure both LEDC channels are in the same speed group and sharing the same timer
_ledcStart = PinManager::allocateLedc(numPins);
if (_ledcStart == 255) { //no more free LEDC channels
PinManager::deallocateMultiplePins(pins, numPins, PinOwner::BusPwm);
return;
}
// if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor)
if (dithering) _depth = 12; // fixed 8 bit depth PWM with 4 bit dithering (ESP8266 has no hardware to support dithering)
#endif
for (unsigned i = 0; i < numPins; i++) {
_pins[i] = bc.pins[i]; // store only after allocateMultiplePins() succeeded
#ifdef ESP8266 #ifdef ESP8266
pinMode(_pins[i], OUTPUT); analogWriteRange((1<<_depth)-1);
analogWriteFreq(_frequency);
#else #else
unsigned channel = _ledcStart + i; // for 2 pin PWM CCT strip pinManager will make sure both LEDC channels are in the same speed group and sharing the same timer
ledcSetup(channel, _frequency, _depth - (dithering*4)); // with dithering _frequency doesn't really matter as resolution is 8 bit _ledcStart = PinManager::allocateLedc(numPins);
ledcAttachPin(_pins[i], channel); if (_ledcStart == 255) { //no more free LEDC channels
// LEDC timer reset credit @dedehai PinManager::deallocateMultiplePins(pins, numPins, PinOwner::BusPwm);
uint8_t group = (channel / 8), timer = ((channel / 2) % 4); // same fromula as in ledcSetup() DEBUGBUS_PRINTLN(F("No more free LEDC channels!"));
ledc_timer_rst((ledc_mode_t)group, (ledc_timer_t)timer); // reset timer so all timers are almost in sync (for phase shift) return;
}
// if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor)
if (dithering) _depth = 12; // fixed 8 bit depth PWM with 4 bit dithering (ESP8266 has no hardware to support dithering)
#endif #endif
for (unsigned i = 0; i < numPins; i++) {
_pins[i] = bc.pins[i]; // store only after allocateMultiplePins() succeeded
#ifdef ESP8266
pinMode(_pins[i], OUTPUT);
#else
unsigned channel = _ledcStart + i;
ledcSetup(channel, _frequency, _depth - (dithering*4)); // with dithering _frequency doesn't really matter as resolution is 8 bit
ledcAttachPin(_pins[i], channel);
// LEDC timer reset credit @dedehai
uint8_t group = (channel / 8), timer = ((channel / 2) % 4); // same fromula as in ledcSetup()
ledc_timer_rst((ledc_mode_t)group, (ledc_timer_t)timer); // reset timer so all timers are almost in sync (for phase shift)
#endif
}
_hasRgb = hasRGB(bc.type);
_hasWhite = hasWhite(bc.type);
_hasCCT = hasCCT(bc.type);
_valid = true;
} }
_hasRgb = hasRGB(bc.type);
_hasWhite = hasWhite(bc.type);
_hasCCT = hasCCT(bc.type);
_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 +516,7 @@ void BusPwm::show() {
constexpr unsigned bitShift = 8; // 256 clocks for dead time, ~3us at 80MHz constexpr unsigned bitShift = 8; // 256 clocks for dead time, ~3us at 80MHz
#else #else
// if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor) // if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor)
// https://github.com/wled-dev/WLED/pull/4115 and https://github.com/zalatnaicsongor/WLED/pull/1) // https://github.com/wled/WLED/pull/4115 and https://github.com/zalatnaicsongor/WLED/pull/1)
const bool dithering = _needsRefresh; // avoid working with bitfield const bool dithering = _needsRefresh; // avoid working with bitfield
const unsigned maxBri = (1<<_depth); // possible values: 16384 (14), 8192 (13), 4096 (12), 2048 (11), 1024 (10), 512 (9) and 256 (8) const unsigned maxBri = (1<<_depth); // possible values: 16384 (14), 8192 (13), 4096 (12), 2048 (11), 1024 (10), 512 (9) and 256 (8)
const unsigned bitShift = dithering * 4; // if dithering, _depth is 12 bit but LEDC channel is set to 8 bit (using 4 fractional bits) const unsigned bitShift = dithering * 4; // if dithering, _depth is 12 bit but LEDC channel is set to 8 bit (using 4 fractional bits)
@ -620,14 +575,14 @@ void BusPwm::show() {
} }
} }
unsigned BusPwm::getPins(uint8_t* pinArray) const { size_t BusPwm::getPins(uint8_t* pinArray) const {
if (!_valid) return 0; if (!_valid) return 0;
unsigned numPins = numPWMPins(_type); unsigned numPins = numPWMPins(_type);
if (pinArray) for (unsigned i = 0; i < numPins; i++) pinArray[i] = _pins[i]; if (pinArray) for (unsigned i = 0; i < numPins; i++) pinArray[i] = _pins[i];
return numPins; return numPins;
} }
// credit @willmmiles & @netmindz https://github.com/wled-dev/WLED/pull/4056 // credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056
std::vector<LEDType> BusPwm::getLEDTypes() { std::vector<LEDType> BusPwm::getLEDTypes() {
return { return {
{TYPE_ANALOG_1CH, "A", PSTR("PWM White")}, {TYPE_ANALOG_1CH, "A", PSTR("PWM White")},
@ -695,13 +650,13 @@ void BusOnOff::show() {
digitalWrite(_pin, _reversed ? !(bool)_data : (bool)_data); digitalWrite(_pin, _reversed ? !(bool)_data : (bool)_data);
} }
unsigned BusOnOff::getPins(uint8_t* pinArray) const { size_t BusOnOff::getPins(uint8_t* pinArray) const {
if (!_valid) return 0; if (!_valid) return 0;
if (pinArray) pinArray[0] = _pin; if (pinArray) pinArray[0] = _pin;
return 1; return 1;
} }
// credit @willmmiles & @netmindz https://github.com/wled-dev/WLED/pull/4056 // credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056
std::vector<LEDType> BusOnOff::getLEDTypes() { std::vector<LEDType> BusOnOff::getLEDTypes() {
return { return {
{TYPE_ONOFF, "", PSTR("On/Off")}, {TYPE_ONOFF, "", PSTR("On/Off")},
@ -731,7 +686,7 @@ 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]);
_data = (uint8_t*)calloc(_len, _UDPchannels); _data = (uint8_t*)d_calloc(_len, _UDPchannels);
_valid = (_data != nullptr); _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]);
} }
@ -760,12 +715,12 @@ void BusNetwork::show() {
_broadcastLock = false; _broadcastLock = false;
} }
unsigned BusNetwork::getPins(uint8_t* pinArray) const { size_t BusNetwork::getPins(uint8_t* pinArray) const {
if (pinArray) for (unsigned i = 0; i < 4; i++) pinArray[i] = _client[i]; if (pinArray) for (unsigned i = 0; i < 4; i++) pinArray[i] = _client[i];
return 4; return 4;
} }
// credit @willmmiles & @netmindz https://github.com/wled-dev/WLED/pull/4056 // credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056
std::vector<LEDType> BusNetwork::getLEDTypes() { std::vector<LEDType> BusNetwork::getLEDTypes() {
return { return {
{TYPE_NET_DDP_RGB, "N", PSTR("DDP RGB (network)")}, // should be "NNNN" to determine 4 "pin" fields {TYPE_NET_DDP_RGB, "N", PSTR("DDP RGB (network)")}, // should be "NNNN" to determine 4 "pin" fields
@ -776,13 +731,13 @@ std::vector<LEDType> BusNetwork::getLEDTypes() {
//{TYPE_VIRTUAL_I2C_W, "V", PSTR("I2C White (virtual)")}, // allows setting I2C address in _pin[0] //{TYPE_VIRTUAL_I2C_W, "V", PSTR("I2C White (virtual)")}, // allows setting I2C address in _pin[0]
//{TYPE_VIRTUAL_I2C_CCT, "V", PSTR("I2C CCT (virtual)")}, // allows setting I2C address in _pin[0] //{TYPE_VIRTUAL_I2C_CCT, "V", PSTR("I2C CCT (virtual)")}, // allows setting I2C address in _pin[0]
//{TYPE_VIRTUAL_I2C_RGB, "VVV", PSTR("I2C RGB (virtual)")}, // allows setting I2C address in _pin[0] and 2 additional values in _pin[1] & _pin[2] //{TYPE_VIRTUAL_I2C_RGB, "VVV", PSTR("I2C RGB (virtual)")}, // allows setting I2C address in _pin[0] and 2 additional values in _pin[1] & _pin[2]
//{TYPE_USERMOD, "VVVVV", PSTR("Usermod (virtual)")}, // 5 data fields (see https://github.com/wled-dev/WLED/pull/4123) //{TYPE_USERMOD, "VVVVV", PSTR("Usermod (virtual)")}, // 5 data fields (see https://github.com/wled/WLED/pull/4123)
}; };
} }
void BusNetwork::cleanup() { void BusNetwork::cleanup() {
DEBUGBUS_PRINTLN(F("Virtual Cleanup.")); DEBUGBUS_PRINTLN(F("Virtual Cleanup."));
free(_data); d_free(_data);
_data = nullptr; _data = nullptr;
_type = I_NONE; _type = I_NONE;
_valid = false; _valid = false;
@ -790,11 +745,11 @@ void BusNetwork::cleanup() {
//utility to get the approx. memory usage of a given BusConfig //utility to get the approx. memory usage of a given BusConfig
unsigned BusConfig::memUsage(unsigned nr) const { size_t BusConfig::memUsage(unsigned nr) const {
if (Bus::isVirtual(type)) { if (Bus::isVirtual(type)) {
return sizeof(BusNetwork) + (count * Bus::getNumberOfChannels(type)); return sizeof(BusNetwork) + (count * Bus::getNumberOfChannels(type));
} else if (Bus::isDigital(type)) { } else if (Bus::isDigital(type)) {
return sizeof(BusDigital) + PolyBus::memUsage(count + skipAmount, PolyBus::getI(type, pins, nr)) + doubleBuffer * (count + skipAmount) * Bus::getNumberOfChannels(type); return sizeof(BusDigital) + PolyBus::memUsage(count + skipAmount, PolyBus::getI(type, pins, nr)) /*+ doubleBuffer * (count + skipAmount) * Bus::getNumberOfChannels(type)*/;
} else if (Bus::isOnOff(type)) { } else if (Bus::isOnOff(type)) {
return sizeof(BusOnOff); return sizeof(BusOnOff);
} else { } else {
@ -803,7 +758,7 @@ unsigned BusConfig::memUsage(unsigned nr) const {
} }
unsigned BusManager::memUsage() { size_t BusManager::memUsage() {
// when ESP32, S2 & S3 use parallel I2S only the largest bus determines the total memory requirements for back buffers // when ESP32, S2 & S3 use parallel I2S only the largest bus determines the total memory requirements for back buffers
// front buffers are always allocated per bus // front buffers are always allocated per bus
unsigned size = 0; unsigned size = 0;
@ -832,22 +787,24 @@ unsigned BusManager::memUsage() {
} }
int BusManager::add(const BusConfig &bc) { int BusManager::add(const BusConfig &bc) {
DEBUGBUS_PRINTF_P(PSTR("Bus: Adding bus (%d - %d >= %d)\n"), getNumBusses(), getNumVirtualBusses(), WLED_MAX_BUSSES); DEBUGBUS_PRINTF_P(PSTR("Bus: Adding bus (p:%d v:%d)\n"), getNumBusses(), getNumVirtualBusses());
if (getNumBusses() - getNumVirtualBusses() >= WLED_MAX_BUSSES) return -1; unsigned digital = 0;
unsigned numDigital = 0; unsigned analog = 0;
for (const auto &bus : busses) if (bus->isDigital() && !bus->is2Pin()) numDigital++; unsigned twoPin = 0;
for (const auto &bus : busses) {
if (bus->isPWM()) analog += bus->getPins(); // number of analog channels used
if (bus->isDigital() && !bus->is2Pin()) digital++;
if (bus->is2Pin()) twoPin++;
}
if (digital > WLED_MAX_DIGITAL_CHANNELS || analog > WLED_MAX_ANALOG_CHANNELS) return -1;
if (Bus::isVirtual(bc.type)) { if (Bus::isVirtual(bc.type)) {
busses.push_back(make_unique<BusNetwork>(bc)); busses.push_back(make_unique<BusNetwork>(bc));
//busses.push_back(new BusNetwork(bc));
} else if (Bus::isDigital(bc.type)) { } else if (Bus::isDigital(bc.type)) {
busses.push_back(make_unique<BusDigital>(bc, numDigital)); busses.push_back(make_unique<BusDigital>(bc, Bus::is2Pin(bc.type) ? twoPin : digital));
//busses.push_back(new BusDigital(bc, numDigital));
} else if (Bus::isOnOff(bc.type)) { } else if (Bus::isOnOff(bc.type)) {
busses.push_back(make_unique<BusOnOff>(bc)); busses.push_back(make_unique<BusOnOff>(bc));
//busses.push_back(new BusOnOff(bc));
} else { } else {
busses.push_back(make_unique<BusPwm>(bc)); busses.push_back(make_unique<BusPwm>(bc));
//busses.push_back(new BusPwm(bc));
} }
return busses.size(); return busses.size();
} }
@ -865,7 +822,7 @@ static String LEDTypesToJson(const std::vector<LEDType>& types) {
return json; return json;
} }
// credit @willmmiles & @netmindz https://github.com/wled-dev/WLED/pull/4056 // credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056
String BusManager::getLEDTypesJSONString() { String BusManager::getLEDTypesJSONString() {
String json = "["; String json = "[";
json += LEDTypesToJson(BusDigital::getLEDTypes()); json += LEDTypesToJson(BusDigital::getLEDTypes());
@ -891,7 +848,6 @@ 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
busses.clear(); busses.clear();
PolyBus::setParallelI2S1Output(false); PolyBus::setParallelI2S1Output(false);
} }
@ -980,9 +936,8 @@ void BusManager::show() {
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(); if (!bus->containsPixel(pix)) continue;
if (pix < bstart || pix >= bstart + bus->getLength()) continue; bus->setPixelColor(pix - bus->getStart(), c);
bus->setPixelColor(pix - bstart, c);
} }
} }
@ -997,9 +952,8 @@ void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) {
uint32_t BusManager::getPixelColor(unsigned pix) { uint32_t BusManager::getPixelColor(unsigned pix) {
for (auto &bus : busses) { for (auto &bus : busses) {
unsigned bstart = bus->getStart();
if (!bus->containsPixel(pix)) continue; if (!bus->containsPixel(pix)) continue;
return bus->getPixelColor(pix - bstart); return bus->getPixelColor(pix - bus->getStart());
} }
return 0; return 0;
} }
@ -1022,6 +976,5 @@ 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;
uint16_t BusManager::_gMilliAmpsUsed = 0; uint16_t BusManager::_gMilliAmpsUsed = 0;
uint16_t BusManager::_gMilliAmpsMax = ABL_MILLIAMPS_DEFAULT; uint16_t BusManager::_gMilliAmpsMax = ABL_MILLIAMPS_DEFAULT;

View File

@ -114,17 +114,17 @@ class Bus {
_autoWhiteMode = Bus::hasWhite(type) ? aw : RGBW_MODE_MANUAL_ONLY; _autoWhiteMode = Bus::hasWhite(type) ? aw : RGBW_MODE_MANUAL_ONLY;
}; };
virtual ~Bus() {} //throw the bus under the bus (derived class needs to freeData()) virtual ~Bus() {} //throw the bus under the bus
virtual void begin() {}; virtual void begin() {};
virtual void show() = 0; virtual void show() = 0;
virtual bool canShow() const { return true; } virtual bool canShow() const { return true; }
virtual void setStatusPixel(uint32_t c) {} virtual void setStatusPixel(uint32_t c) {}
virtual void setPixelColor(unsigned pix, uint32_t c) = 0; virtual void setPixelColor(unsigned pix, uint32_t c) = 0;
virtual void setBrightness(uint8_t b) { _bri = b; }; virtual void setBrightness(uint8_t b) { _bri = b; };
virtual void setColorOrder(uint8_t co) {} virtual void setColorOrder(uint8_t co) {}
virtual uint32_t getPixelColor(unsigned pix) const { return 0; } virtual uint32_t getPixelColor(unsigned pix) const { return 0; }
virtual unsigned getPins(uint8_t* pinArray = nullptr) const { return 0; } virtual size_t getPins(uint8_t* pinArray = nullptr) const { return 0; }
virtual uint16_t getLength() const { return isOk() ? _len : 0; } virtual uint16_t getLength() const { return isOk() ? _len : 0; }
virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; } virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; }
virtual unsigned skippedLeds() const { return 0; } virtual unsigned skippedLeds() const { return 0; }
@ -132,7 +132,7 @@ class Bus {
virtual uint16_t getLEDCurrent() const { return 0; } virtual uint16_t getLEDCurrent() const { return 0; }
virtual uint16_t getUsedCurrent() const { return 0; } virtual uint16_t getUsedCurrent() const { return 0; }
virtual uint16_t getMaxCurrent() const { return 0; } virtual uint16_t getMaxCurrent() const { return 0; }
virtual unsigned getBusSize() const { return sizeof(Bus); } virtual size_t getBusSize() const { return sizeof(Bus); }
inline bool hasRGB() const { return _hasRgb; } inline bool hasRGB() const { return _hasRgb; }
inline bool hasWhite() const { return _hasWhite; } inline bool hasWhite() const { return _hasWhite; }
@ -148,7 +148,7 @@ class Bus {
inline void setStart(uint16_t start) { _start = start; } inline void setStart(uint16_t start) { _start = start; }
inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; } inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; }
inline uint8_t getAutoWhiteMode() const { return _autoWhiteMode; } inline uint8_t getAutoWhiteMode() const { return _autoWhiteMode; }
inline unsigned getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); } inline size_t getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); }
inline uint16_t getStart() const { return _start; } inline uint16_t getStart() const { return _start; }
inline uint8_t getType() const { return _type; } inline uint8_t getType() const { return _type; }
inline bool isOk() const { return _valid; } inline bool isOk() const { return _valid; }
@ -157,8 +157,8 @@ class Bus {
inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; } inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; }
static inline std::vector<LEDType> getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes static inline std::vector<LEDType> getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes
static constexpr unsigned getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK static constexpr size_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK
static constexpr unsigned getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); } static constexpr size_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); }
static constexpr bool hasRGB(uint8_t type) { static constexpr bool hasRGB(uint8_t type) {
return !((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_ANALOG_1CH || type == TYPE_ANALOG_2CH || type == TYPE_ONOFF); return !((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_ANALOG_1CH || type == TYPE_ANALOG_2CH || type == TYPE_ONOFF);
} }
@ -243,13 +243,13 @@ class BusDigital : public Bus {
void setColorOrder(uint8_t colorOrder) override; void setColorOrder(uint8_t colorOrder) override;
[[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override; [[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override;
uint8_t getColorOrder() const override { return _colorOrder; } uint8_t getColorOrder() const override { return _colorOrder; }
unsigned getPins(uint8_t* pinArray = nullptr) const override; size_t getPins(uint8_t* pinArray = nullptr) const override;
unsigned skippedLeds() const override { return _skip; } unsigned skippedLeds() const override { return _skip; }
uint16_t getFrequency() const override { return _frequencykHz; } uint16_t getFrequency() const override { return _frequencykHz; }
uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; } uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; }
uint16_t getUsedCurrent() const override { return _milliAmpsTotal; } uint16_t getUsedCurrent() const override { return _milliAmpsTotal; }
uint16_t getMaxCurrent() const override { return _milliAmpsMax; } uint16_t getMaxCurrent() const override { return _milliAmpsMax; }
unsigned getBusSize() const override; size_t getBusSize() const override;
void begin() override; void begin() override;
void cleanup(); void cleanup();
@ -263,7 +263,6 @@ class BusDigital : public Bus {
uint16_t _frequencykHz; uint16_t _frequencykHz;
uint8_t _milliAmpsPerLed; uint8_t _milliAmpsPerLed;
uint16_t _milliAmpsMax; uint16_t _milliAmpsMax;
uint8_t *_data;
void *_busPtr; void *_busPtr;
static uint16_t _milliAmpsTotal; // is overwitten/recalculated on each show() static uint16_t _milliAmpsTotal; // is overwitten/recalculated on each show()
@ -290,9 +289,9 @@ class BusPwm : public Bus {
void setPixelColor(unsigned pix, uint32_t c) override; void setPixelColor(unsigned pix, uint32_t c) override;
uint32_t getPixelColor(unsigned pix) const override; //does no index check uint32_t getPixelColor(unsigned pix) const override; //does no index check
unsigned getPins(uint8_t* pinArray = nullptr) const override; size_t getPins(uint8_t* pinArray = nullptr) const override;
uint16_t getFrequency() const override { return _frequency; } uint16_t getFrequency() const override { return _frequency; }
unsigned getBusSize() const override { return sizeof(BusPwm); } size_t getBusSize() const override { return sizeof(BusPwm); }
void show() override; void show() override;
inline void cleanup() { deallocatePins(); } inline void cleanup() { deallocatePins(); }
@ -318,8 +317,8 @@ class BusOnOff : public Bus {
void setPixelColor(unsigned pix, uint32_t c) override; void setPixelColor(unsigned pix, uint32_t c) override;
uint32_t getPixelColor(unsigned pix) const override; uint32_t getPixelColor(unsigned pix) const override;
unsigned getPins(uint8_t* pinArray) const override; size_t getPins(uint8_t* pinArray) const override;
unsigned getBusSize() const override { return sizeof(BusOnOff); } size_t getBusSize() const override { return sizeof(BusOnOff); }
void show() override; void show() override;
inline void cleanup() { PinManager::deallocatePin(_pin, PinOwner::BusOnOff); } inline void cleanup() { PinManager::deallocatePin(_pin, PinOwner::BusOnOff); }
@ -339,10 +338,10 @@ class BusNetwork : public Bus {
bool canShow() const override { return !_broadcastLock; } // this should be a return value from UDP routine if it is still sending data out bool canShow() const override { return !_broadcastLock; } // this should be a return value from UDP routine if it is still sending data out
[[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override; [[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override;
[[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override; [[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override;
unsigned getPins(uint8_t* pinArray = nullptr) const override; size_t getPins(uint8_t* pinArray = nullptr) const override;
unsigned getBusSize() const override { return sizeof(BusNetwork) + (isOk() ? _len * _UDPchannels : 0); } size_t getBusSize() const override { return sizeof(BusNetwork) + (isOk() ? _len * _UDPchannels : 0); }
void show() override; void show() override;
void cleanup(); void cleanup();
static std::vector<LEDType> getLEDTypes(); static std::vector<LEDType> getLEDTypes();
@ -367,11 +366,10 @@ struct BusConfig {
uint8_t autoWhite; uint8_t autoWhite;
uint8_t pins[5] = {255, 255, 255, 255, 255}; uint8_t pins[5] = {255, 255, 255, 255, 255};
uint16_t frequency; uint16_t frequency;
bool doubleBuffer;
uint8_t milliAmpsPerLed; uint8_t milliAmpsPerLed;
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, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT)
: count(std::max(len,(uint16_t)1)) : count(std::max(len,(uint16_t)1))
, start(pstart) , start(pstart)
, colorOrder(pcolorOrder) , colorOrder(pcolorOrder)
@ -379,7 +377,6 @@ struct BusConfig {
, skipAmount(skip) , skipAmount(skip)
, autoWhite(aw) , autoWhite(aw)
, frequency(clock_kHz) , frequency(clock_kHz)
, doubleBuffer(dblBfr)
, milliAmpsPerLed(maPerLed) , milliAmpsPerLed(maPerLed)
, milliAmpsMax(maMax) , milliAmpsMax(maMax)
{ {
@ -411,7 +408,7 @@ struct BusConfig {
return true; return true;
} }
unsigned memUsage(unsigned nr = 0) const; size_t memUsage(unsigned nr = 0) const;
}; };

View File

@ -6,6 +6,35 @@
* The structure of the JSON is not to be considered an official API and may change without notice. * The structure of the JSON is not to be considered an official API and may change without notice.
*/ */
#ifndef PIXEL_COUNTS
#define PIXEL_COUNTS DEFAULT_LED_COUNT
#endif
#ifndef DATA_PINS
#define DATA_PINS DEFAULT_LED_PIN
#endif
#ifndef LED_TYPES
#define LED_TYPES DEFAULT_LED_TYPE
#endif
#ifndef DEFAULT_LED_COLOR_ORDER
#define DEFAULT_LED_COLOR_ORDER COL_ORDER_GRB //default to GRB
#endif
static constexpr unsigned sumPinsRequired(const unsigned* current, size_t count) {
return (count > 0) ? (Bus::getNumberOfPins(*current) + sumPinsRequired(current+1,count-1)) : 0;
}
static constexpr bool validatePinsAndTypes(const unsigned* types, unsigned numTypes, unsigned numPins ) {
// Pins provided < pins required -> always invalid
// Pins provided = pins required -> always valid
// Pins provided > pins required -> valid if excess pins are a product of last type pins since it will be repeated
return (sumPinsRequired(types, numTypes) > numPins) ? false :
(numPins - sumPinsRequired(types, numTypes)) % Bus::getNumberOfPins(types[numTypes-1]) == 0;
}
//simple macro for ArduinoJSON's or syntax //simple macro for ArduinoJSON's or syntax
#define CJSON(a,b) a = b | a #define CJSON(a,b) a = b | a
@ -20,7 +49,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
//long vid = doc[F("vid")]; // 2010020 //long vid = doc[F("vid")]; // 2010020
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) #ifdef WLED_USE_ETHERNET
JsonObject ethernet = doc[F("eth")]; JsonObject ethernet = doc[F("eth")];
CJSON(ethernetType, ethernet["type"]); CJSON(ethernetType, ethernet["type"]);
// NOTE: Ethernet configuration takes priority over other use of pins // NOTE: Ethernet configuration takes priority over other use of pins
@ -120,7 +149,6 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
uint8_t cctBlending = hw_led[F("cb")] | Bus::getCCTBlend(); uint8_t cctBlending = hw_led[F("cb")] | Bus::getCCTBlend();
Bus::setCCTBlend(cctBlending); Bus::setCCTBlend(cctBlending);
strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS
CJSON(useGlobalLedBuffer, hw_led[F("ld")]);
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
CJSON(useParallelI2S, hw_led[F("prl")]); CJSON(useParallelI2S, hw_led[F("prl")]);
#endif #endif
@ -130,12 +158,13 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject matrix = hw_led[F("matrix")]; JsonObject matrix = hw_led[F("matrix")];
if (!matrix.isNull()) { if (!matrix.isNull()) {
strip.isMatrix = true; strip.isMatrix = true;
CJSON(strip.panels, matrix[F("mpc")]); unsigned numPanels = matrix[F("mpc")] | 1;
numPanels = constrain(numPanels, 1, WLED_MAX_PANELS);
strip.panel.clear(); strip.panel.clear();
JsonArray panels = matrix[F("panels")]; JsonArray panels = matrix[F("panels")];
int s = 0; unsigned s = 0;
if (!panels.isNull()) { if (!panels.isNull()) {
strip.panel.reserve(max(1U,min((size_t)strip.panels,(size_t)WLED_MAX_PANELS))); // pre-allocate memory for panels strip.panel.reserve(numPanels); // pre-allocate default 8x8 panels
for (JsonObject pnl : panels) { for (JsonObject pnl : panels) {
WS2812FX::Panel p; WS2812FX::Panel p;
CJSON(p.bottomStart, pnl["b"]); CJSON(p.bottomStart, pnl["b"]);
@ -147,30 +176,21 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(p.height, pnl["h"]); CJSON(p.height, pnl["h"]);
CJSON(p.width, pnl["w"]); CJSON(p.width, pnl["w"]);
strip.panel.push_back(p); strip.panel.push_back(p);
if (++s >= WLED_MAX_PANELS || s >= strip.panels) break; // max panels reached if (++s >= numPanels) break; // max panels reached
} }
} else {
// fallback
WS2812FX::Panel p;
strip.panels = 1;
p.height = p.width = 8;
p.xOffset = p.yOffset = 0;
p.options = 0;
strip.panel.push_back(p);
} }
// cannot call strip.setUpMatrix() here due to already locked JSON buffer strip.panel.shrink_to_fit(); // release unused memory (just in case)
// cannot call strip.deserializeLedmap()/strip.setUpMatrix() here due to already locked JSON buffer
//if (!fromFS) doInit2D = true; // if called at boot (fromFS==true), WLED::beginStrip() will take care of setting up matrix
} }
#endif #endif
DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), ESP.getFreeHeap());
JsonArray ins = hw_led["ins"]; JsonArray ins = hw_led["ins"];
if (!ins.isNull()) {
if (fromFS || !ins.isNull()) {
DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), ESP.getFreeHeap());
int s = 0; // bus iterator int s = 0; // bus iterator
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) break; if (s >= WLED_MAX_BUSSES) break; // only counts physical buses
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,11 +219,101 @@ 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.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, 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()
if (!Bus::isVirtual(ledType)) s++; // have as many virtual buses as you want if (!Bus::isVirtual(ledType)) s++; // have as many virtual buses as you want
} }
} else if (fromFS) {
//if busses failed to load, add default (fresh install, FS issue, ...)
BusManager::removeAll();
busConfigs.clear();
DEBUG_PRINTLN(F("No busses, init default"));
constexpr unsigned defDataTypes[] = {LED_TYPES};
constexpr unsigned defDataPins[] = {DATA_PINS};
constexpr unsigned defCounts[] = {PIXEL_COUNTS};
constexpr unsigned defNumTypes = (sizeof(defDataTypes) / sizeof(defDataTypes[0]));
constexpr unsigned defNumPins = (sizeof(defDataPins) / sizeof(defDataPins[0]));
constexpr unsigned defNumCounts = (sizeof(defCounts) / sizeof(defCounts[0]));
static_assert(validatePinsAndTypes(defDataTypes, defNumTypes, defNumPins),
"The default pin list defined in DATA_PINS does not match the pin requirements for the default buses defined in LED_TYPES");
unsigned mem = 0;
unsigned pinsIndex = 0;
unsigned digitalCount = 0;
for (unsigned i = 0; i < WLED_MAX_BUSSES; i++) {
uint8_t defPin[OUTPUT_MAX_PINS];
// if we have less types than requested outputs and they do not align, use last known type to set current type
unsigned dataType = defDataTypes[(i < defNumTypes) ? i : defNumTypes -1];
unsigned busPins = Bus::getNumberOfPins(dataType);
// if we need more pins than available all outputs have been configured
if (pinsIndex + busPins > defNumPins) break;
// Assign all pins first so we can check for conflicts on this bus
for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) defPin[j] = defDataPins[pinsIndex + j];
for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) {
bool validPin = true;
// When booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware
// i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), read/only pins, etc.
// Pin should not be already allocated, read/only or defined for current bus
while (PinManager::isPinAllocated(defPin[j]) || !PinManager::isPinOk(defPin[j],true)) {
if (validPin) {
DEBUG_PRINTLN(F("Some of the provided pins cannot be used to configure this LED output."));
defPin[j] = 1; // start with GPIO1 and work upwards
validPin = false;
} else if (defPin[j] < WLED_NUM_PINS) {
defPin[j]++;
} else {
DEBUG_PRINTLN(F("No available pins left! Can't configure output."));
break;
}
// is the newly assigned pin already defined or used previously?
// try next in line until there are no clashes or we run out of pins
bool clash;
do {
clash = false;
// check for conflicts on current bus
for (const auto &pin : defPin) {
if (&pin != &defPin[j] && pin == defPin[j]) {
clash = true;
break;
}
}
// We already have a clash on current bus, no point checking next buses
if (!clash) {
// check for conflicts in defined pins
for (const auto &pin : defDataPins) {
if (pin == defPin[j]) {
clash = true;
break;
}
}
}
if (clash) defPin[j]++;
if (defPin[j] >= WLED_NUM_PINS) break;
} while (clash);
}
}
pinsIndex += busPins;
// if we have less counts than pins and they do not align, use last known count to set current count
unsigned count = defCounts[(i < defNumCounts) ? i : defNumCounts -1];
unsigned start = 0;
// analog always has length 1
if (Bus::isPWM(dataType) || Bus::isOnOff(dataType)) count = 1;
BusConfig defCfg = BusConfig(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0);
mem += defCfg.memUsage(Bus::isDigital(dataType) && !Bus::is2Pin(dataType) ? digitalCount++ : 0);
if (mem > MAX_LED_MEMORY) {
DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)dataType, (int)count, digitalCount);
break;
}
busConfigs.push_back(defCfg); // use push_back for simplification as we needed defCfg to calculate memory usage
doInitBusses = true; // finalization done in beginStrip()
}
DEBUG_PRINTF_P(PSTR("LED buffer size: %uB/%uB\n"), mem, BusManager::memUsage());
} }
if (hw_led["rev"] && BusManager::getNumBusses()) 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
@ -292,30 +402,28 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
macroLongPress[s] = 0; macroLongPress[s] = 0;
macroDoublePress[s] = 0; macroDoublePress[s] = 0;
} }
} else { } else if (fromFS) {
// new install/missing configuration (button 0 has defaults) // new install/missing configuration (button 0 has defaults)
if (fromFS) { // relies upon only being called once with fromFS == true, which is currently true.
// relies upon only being called once with fromFS == true, which is currently true. for (size_t s = 0; s < WLED_MAX_BUTTONS; s++) {
for (size_t s = 0; s < WLED_MAX_BUTTONS; s++) { if (buttonType[s] == BTN_TYPE_NONE || btnPin[s] < 0 || !PinManager::allocatePin(btnPin[s], false, PinOwner::Button)) {
if (buttonType[s] == BTN_TYPE_NONE || btnPin[s] < 0 || !PinManager::allocatePin(btnPin[s], false, PinOwner::Button)) { btnPin[s] = -1;
btnPin[s] = -1; buttonType[s] = BTN_TYPE_NONE;
buttonType[s] = BTN_TYPE_NONE;
}
if (btnPin[s] >= 0) {
if (disablePullUp) {
pinMode(btnPin[s], INPUT);
} else {
#ifdef ESP32
pinMode(btnPin[s], buttonType[s]==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP);
#else
pinMode(btnPin[s], INPUT_PULLUP);
#endif
}
}
macroButton[s] = 0;
macroLongPress[s] = 0;
macroDoublePress[s] = 0;
} }
if (btnPin[s] >= 0) {
if (disablePullUp) {
pinMode(btnPin[s], INPUT);
} else {
#ifdef ESP32
pinMode(btnPin[s], buttonType[s]==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP);
#else
pinMode(btnPin[s], INPUT_PULLUP);
#endif
}
}
macroButton[s] = 0;
macroLongPress[s] = 0;
macroDoublePress[s] = 0;
} }
} }
@ -392,8 +500,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject light = doc[F("light")]; JsonObject light = doc[F("light")];
CJSON(briMultiplier, light[F("scale-bri")]); CJSON(briMultiplier, light[F("scale-bri")]);
CJSON(strip.paletteBlend, light[F("pal-mode")]); CJSON(paletteBlend, light[F("pal-mode")]);
CJSON(strip.autoSegments, light[F("aseg")]); CJSON(strip.autoSegments, light[F("aseg")]);
CJSON(useRainbowWheel, light[F("rw")]);
CJSON(gammaCorrectVal, light["gc"]["val"]); // default 2.8 CJSON(gammaCorrectVal, light["gc"]["val"]); // default 2.8
float light_gc_bri = light["gc"]["bri"]; float light_gc_bri = light["gc"]["bri"];
@ -648,11 +757,10 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
static const char s_cfg_json[] PROGMEM = "/cfg.json"; static const char s_cfg_json[] PROGMEM = "/cfg.json";
void deserializeConfigFromFS() { void deserializeConfigFromFS() {
bool success = deserializeConfigSec(); [[maybe_unused]] bool success = deserializeConfigSec();
#ifdef WLED_ADD_EEPROM_SUPPORT #ifdef WLED_ADD_EEPROM_SUPPORT
if (!success) { //if file does not exist, try reading from EEPROM if (!success) { //if file does not exist, try reading from EEPROM
deEEPSettings(); deEEPSettings();
return;
} }
#endif #endif
@ -661,23 +769,6 @@ void deserializeConfigFromFS() {
DEBUG_PRINTLN(F("Reading settings from /cfg.json...")); DEBUG_PRINTLN(F("Reading settings from /cfg.json..."));
success = readObjectFromFile(s_cfg_json, nullptr, pDoc); success = readObjectFromFile(s_cfg_json, nullptr, pDoc);
if (!success) { // if file does not exist, optionally try reading from EEPROM and then save defaults to FS
releaseJSONBufferLock();
#ifdef WLED_ADD_EEPROM_SUPPORT
deEEPSettings();
#endif
// save default values to /cfg.json
// call readFromConfig() with an empty object so that usermods can initialize to defaults prior to saving
JsonObject empty = JsonObject();
UsermodManager::readFromConfig(empty);
serializeConfigToFS();
// init Ethernet (in case default type is set at compile time)
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
initEthernet();
#endif
return;
}
// NOTE: This routine deserializes *and* applies the configuration // NOTE: This routine deserializes *and* applies the configuration
// Therefore, must also initialize ethernet from this function // Therefore, must also initialize ethernet from this function
@ -800,14 +891,13 @@ void serializeConfig(JsonObject root) {
JsonObject hw_led = hw.createNestedObject("led"); JsonObject hw_led = hw.createNestedObject("led");
hw_led[F("total")] = strip.getLengthTotal(); //provided for compatibility on downgrade and per-output ABL hw_led[F("total")] = strip.getLengthTotal(); //provided for compatibility on downgrade and per-output ABL
hw_led[F("maxpwr")] = BusManager::ablMilliampsMax(); hw_led[F("maxpwr")] = BusManager::ablMilliampsMax();
hw_led[F("ledma")] = 0; // no longer used // hw_led[F("ledma")] = 0; // no longer used
hw_led["cct"] = strip.correctWB; hw_led["cct"] = strip.correctWB;
hw_led[F("cr")] = strip.cctFromRgb; hw_led[F("cr")] = strip.cctFromRgb;
hw_led[F("ic")] = cctICused; hw_led[F("ic")] = cctICused;
hw_led[F("cb")] = Bus::getCCTBlend(); hw_led[F("cb")] = Bus::getCCTBlend();
hw_led["fps"] = strip.getTargetFps(); hw_led["fps"] = strip.getTargetFps();
hw_led[F("rgbwm")] = Bus::getGlobalAWMode(); // global auto white mode override hw_led[F("rgbwm")] = Bus::getGlobalAWMode(); // global auto white mode override
hw_led[F("ld")] = useGlobalLedBuffer;
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
hw_led[F("prl")] = BusManager::hasParallelOutput(); hw_led[F("prl")] = BusManager::hasParallelOutput();
#endif #endif
@ -816,7 +906,7 @@ void serializeConfig(JsonObject root) {
// 2D Matrix Settings // 2D Matrix Settings
if (strip.isMatrix) { if (strip.isMatrix) {
JsonObject matrix = hw_led.createNestedObject(F("matrix")); JsonObject matrix = hw_led.createNestedObject(F("matrix"));
matrix[F("mpc")] = strip.panels; matrix[F("mpc")] = strip.panel.size();
JsonArray panels = matrix.createNestedArray(F("panels")); JsonArray panels = matrix.createNestedArray(F("panels"));
for (size_t i = 0; i < strip.panel.size(); i++) { for (size_t i = 0; i < strip.panel.size(); i++) {
JsonObject pnl = panels.createNestedObject(); JsonObject pnl = panels.createNestedObject();
@ -926,8 +1016,9 @@ void serializeConfig(JsonObject root) {
JsonObject light = root.createNestedObject(F("light")); JsonObject light = root.createNestedObject(F("light"));
light[F("scale-bri")] = briMultiplier; light[F("scale-bri")] = briMultiplier;
light[F("pal-mode")] = strip.paletteBlend; light[F("pal-mode")] = paletteBlend;
light[F("aseg")] = strip.autoSegments; light[F("aseg")] = strip.autoSegments;
light[F("rw")] = useRainbowWheel;
JsonObject light_gc = light.createNestedObject("gc"); JsonObject light_gc = light.createNestedObject("gc");
light_gc["bri"] = (gammaCorrectBri) ? gammaCorrectVal : 1.0f; // keep compatibility light_gc["bri"] = (gammaCorrectBri) ? gammaCorrectVal : 1.0f; // keep compatibility

View File

@ -208,14 +208,14 @@ CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette)
makepastelpalette = true; makepastelpalette = true;
} }
// apply saturation & gamma correction // apply saturation
CRGB RGBpalettecolors[4]; CRGB RGBpalettecolors[4];
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (makepastelpalette && palettecolors[i].saturation > 180) { if (makepastelpalette && palettecolors[i].saturation > 180) {
palettecolors[i].saturation -= 160; //desaturate all four colors palettecolors[i].saturation -= 160; //desaturate all four colors
} }
RGBpalettecolors[i] = (CRGB)palettecolors[i]; //convert to RGB RGBpalettecolors[i] = (CRGB)palettecolors[i]; //convert to RGB
RGBpalettecolors[i] = gamma32(((uint32_t)RGBpalettecolors[i]) & 0x00FFFFFFU); //strip alpha from CRGB RGBpalettecolors[i] = ((uint32_t)RGBpalettecolors[i]) & 0x00FFFFFFU; //strip alpha from CRGB
} }
return CRGBPalette16(RGBpalettecolors[0], return CRGBPalette16(RGBpalettecolors[0],
@ -232,6 +232,54 @@ CRGBPalette16 generateRandomPalette() // generate fully random palette
CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255))); CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)));
} }
void loadCustomPalettes() {
byte tcp[72]; //support gradient palettes with up to 18 entries
CRGBPalette16 targetPalette;
customPalettes.clear(); // start fresh
for (int index = 0; index<10; index++) {
char fileName[32];
sprintf_P(fileName, PSTR("/palette%d.json"), index);
StaticJsonDocument<1536> pDoc; // barely enough to fit 72 numbers
if (WLED_FS.exists(fileName)) {
DEBUGFX_PRINTF_P(PSTR("Reading palette from %s\n"), fileName);
if (readObjectFromFile(fileName, nullptr, &pDoc)) {
JsonArray pal = pDoc[F("palette")];
if (!pal.isNull() && pal.size()>3) { // not an empty palette (at least 2 entries)
memset(tcp, 255, sizeof(tcp));
if (pal[0].is<int>() && pal[1].is<const char *>()) {
// we have an array of index & hex strings
size_t palSize = MIN(pal.size(), 36);
palSize -= palSize % 2; // make sure size is multiple of 2
for (size_t i=0, j=0; i<palSize && pal[i].as<int>()<256; i+=2) {
uint8_t rgbw[] = {0,0,0,0};
if (colorFromHexString(rgbw, pal[i+1].as<const char *>())) { // will catch non-string entires
tcp[ j ] = (uint8_t) pal[ i ].as<int>(); // index
for (size_t c=0; c<3; c++) tcp[j+1+c] = rgbw[c]; // only use RGB component
DEBUGFX_PRINTF_P(PSTR("%2u -> %3d [%3d,%3d,%3d]\n"), i, int(tcp[j]), int(tcp[j+1]), int(tcp[j+2]), int(tcp[j+3]));
j += 4;
}
}
} else {
size_t palSize = MIN(pal.size(), 72);
palSize -= palSize % 4; // make sure size is multiple of 4
for (size_t i=0; i<palSize && pal[i].as<int>()<256; i+=4) {
tcp[ i ] = (uint8_t) pal[ i ].as<int>(); // index
for (size_t c=0; c<3; c++) tcp[i+1+c] = (uint8_t) pal[i+1+c].as<int>();
DEBUGFX_PRINTF_P(PSTR("%2u -> %3d [%3d,%3d,%3d]\n"), i, int(tcp[i]), int(tcp[i+1]), int(tcp[i+2]), int(tcp[i+3]));
}
}
customPalettes.push_back(targetPalette.loadDynamicGradientPalette(tcp));
} else {
DEBUGFX_PRINTLN(F("Wrong palette format."));
}
}
} else {
break;
}
}
}
void hsv2rgb(const CHSV32& hsv, uint32_t& rgb) // convert HSV (16bit hue) to RGB (32bit with white = 0) void hsv2rgb(const CHSV32& hsv, uint32_t& rgb) // convert HSV (16bit hue) to RGB (32bit with white = 0)
{ {
unsigned int remainder, region, p, q, t; unsigned int remainder, region, p, q, t;

View File

@ -44,67 +44,48 @@
#endif #endif
#endif #endif
#ifndef WLED_MAX_BUSSES #ifdef ESP8266
#ifdef ESP8266 #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_MIN_VIRTUAL_BUSSES 3 // no longer used for bus creation but used to distinguish S2/S3 in UI
#define WLED_MAX_BUSSES 4 // will allow 3 digital & 1 analog RGB
#define WLED_MIN_VIRTUAL_BUSSES 3 // no longer used for bus creation but used to distinguish S2/S3 in UI
#else
#define WLED_MAX_ANALOG_CHANNELS (LEDC_CHANNEL_MAX*LEDC_SPEED_MODE_MAX)
#if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, 6 LEDC, only has 1 I2S but NPB does not support it ATM
#define WLED_MAX_BUSSES 6 // will allow 2 digital & 2 analog RGB or 6 PWM white
#define WLED_MAX_DIGITAL_CHANNELS 2
//#define WLED_MAX_ANALOG_CHANNELS 6
#define WLED_MIN_VIRTUAL_BUSSES 4 // no longer used for bus creation but used to distinguish S2/S3 in UI
#elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, 8 LEDC, only has 1 I2S bus, supported in NPB
// the 5th bus (I2S) will prevent Audioreactive usermod from functioning (it is last used though)
#define WLED_MAX_BUSSES 7 // will allow 5 digital & 2 analog RGB
#define WLED_MAX_DIGITAL_CHANNELS 5
//#define WLED_MAX_ANALOG_CHANNELS 8
#define WLED_MIN_VIRTUAL_BUSSES 4 // no longer used for bus creation but used to distinguish S2/S3 in UI
#elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, 8 LEDC, has 2 I2S but NPB supports parallel x8 LCD on I2S1
#define WLED_MAX_BUSSES 14 // will allow 12 digital & 2 analog RGB
#define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT + x8 I2S-LCD
//#define WLED_MAX_ANALOG_CHANNELS 8
#define WLED_MIN_VIRTUAL_BUSSES 6 // no longer used for bus creation but used to distinguish S2/S3 in UI
#else
// the last digital bus (I2S0) will prevent Audioreactive usermod from functioning
#define WLED_MAX_BUSSES 19 // will allow 16 digital & 3 analog RGB
#define WLED_MAX_DIGITAL_CHANNELS 16 // x1/x8 I2S1 + x8 RMT
//#define WLED_MAX_ANALOG_CHANNELS 16
#define WLED_MIN_VIRTUAL_BUSSES 6 // no longer used for bus creation but used to distinguish S2/S3 in UI
#endif
#endif
#else #else
#ifdef ESP8266 #define WLED_MAX_ANALOG_CHANNELS (LEDC_CHANNEL_MAX*LEDC_SPEED_MODE_MAX)
#if WLED_MAX_BUSSES > 5 #if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, 6 LEDC, only has 1 I2S but NPB does not support it ATM
#error Maximum number of buses is 5. #define WLED_MAX_DIGITAL_CHANNELS 2
#endif //#define WLED_MAX_ANALOG_CHANNELS 6
#ifndef WLED_MAX_ANALOG_CHANNELS #define WLED_MIN_VIRTUAL_BUSSES 4 // no longer used for bus creation but used to distinguish S2/S3 in UI
#error You must also define WLED_MAX_ANALOG_CHANNELS. #elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, 8 LEDC, only has 1 I2S bus, supported in NPB
#endif // the 5th bus (I2S) will prevent Audioreactive usermod from functioning (it is last used though)
#ifndef WLED_MAX_DIGITAL_CHANNELS #define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT + x1/x8 I2S0
#error You must also define WLED_MAX_DIGITAL_CHANNELS. //#define WLED_MAX_ANALOG_CHANNELS 8
#endif #define WLED_MIN_VIRTUAL_BUSSES 4 // no longer used for bus creation but used to distinguish S2/S3 in UI
#define WLED_MIN_VIRTUAL_BUSSES 3 #elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, 8 LEDC, has 2 I2S but NPB supports parallel x8 LCD on I2S1
#define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT + x8 I2S-LCD
//#define WLED_MAX_ANALOG_CHANNELS 8
#define WLED_MIN_VIRTUAL_BUSSES 6 // no longer used for bus creation but used to distinguish S2/S3 in UI
#else #else
#if WLED_MAX_BUSSES > 20 // the last digital bus (I2S0) will prevent Audioreactive usermod from functioning
#error Maximum number of buses is 20. #define WLED_MAX_DIGITAL_CHANNELS 16 // x1/x8 I2S1 + x8 RMT
#endif //#define WLED_MAX_ANALOG_CHANNELS 16
#ifndef WLED_MAX_ANALOG_CHANNELS #define WLED_MIN_VIRTUAL_BUSSES 6 // no longer used for bus creation but used to distinguish S2/S3 in UI
#error You must also define WLED_MAX_ANALOG_CHANNELS.
#endif
#ifndef WLED_MAX_DIGITAL_CHANNELS
#error You must also define WLED_MAX_DIGITAL_CHANNELS.
#endif
#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
// WLED_MAX_BUSSES was used to define the size of busses[] array which is no longer needed
// instead it will help determine max number of buses that can be defined at compile time
#ifdef WLED_MAX_BUSSES
#undef WLED_MAX_BUSSES
#endif
#define WLED_MAX_BUSSES (WLED_MAX_DIGITAL_CHANNELS+WLED_MAX_ANALOG_CHANNELS)
// Maximum number of pins per output. 5 for RGBCCT analog LEDs.
#define OUTPUT_MAX_PINS 5
// for pin manager
#ifdef ESP8266
#define WLED_NUM_PINS (GPIO_PIN_COUNT+1) // somehow they forgot GPIO 16 (0-16==17)
#else
#define WLED_NUM_PINS (GPIO_PIN_COUNT)
#endif
#ifndef WLED_MAX_BUTTONS #ifndef WLED_MAX_BUTTONS
#ifdef ESP8266 #ifdef ESP8266
@ -151,6 +132,8 @@
#endif #endif
#endif #endif
#define WLED_MAX_PANELS 18 // must not be more than 32
//Usermod IDs //Usermod IDs
#define USERMOD_ID_RESERVED 0 //Unused. Might indicate no usermod present #define USERMOD_ID_RESERVED 0 //Unused. Might indicate no usermod present
#define USERMOD_ID_UNSPECIFIED 1 //Default value for a general user mod that does not specify a custom ID #define USERMOD_ID_UNSPECIFIED 1 //Default value for a general user mod that does not specify a custom ID
@ -336,18 +319,6 @@
#define TYPE_NET_ARTNET_RGBW 89 //network ArtNet RGB bus (master broadcast bus, unused) #define TYPE_NET_ARTNET_RGBW 89 //network ArtNet RGB bus (master broadcast bus, unused)
#define TYPE_VIRTUAL_MAX 95 #define TYPE_VIRTUAL_MAX 95
/*
// old macros that have been moved to Bus class
#define IS_TYPE_VALID(t) ((t) > 15 && (t) < 128)
#define IS_DIGITAL(t) (((t) > 15 && (t) < 40) || ((t) > 47 && (t) < 64)) //digital are 16-39 and 48-63
#define IS_2PIN(t) ((t) > 47 && (t) < 64)
#define IS_16BIT(t) ((t) == TYPE_UCS8903 || (t) == TYPE_UCS8904)
#define IS_ONOFF(t) ((t) == 40)
#define IS_PWM(t) ((t) > 40 && (t) < 46) //does not include on/Off type
#define NUM_PWM_PINS(t) ((t) - 40) //for analog PWM 41-45 only
#define IS_VIRTUAL(t) ((t) >= 80 && (t) < 96) //this was a poor choice a better would be 96-111
*/
//Color orders //Color orders
#define COL_ORDER_GRB 0 //GRB(w),defaut #define COL_ORDER_GRB 0 //GRB(w),defaut
#define COL_ORDER_RGB 1 //common for WS2811 #define COL_ORDER_RGB 1 //common for WS2811
@ -435,6 +406,7 @@
#define ERR_CONCURRENCY 2 // Conurrency (client active) #define ERR_CONCURRENCY 2 // Conurrency (client active)
#define ERR_NOBUF 3 // JSON buffer was not released in time, request cannot be handled at this time #define ERR_NOBUF 3 // JSON buffer was not released in time, request cannot be handled at this time
#define ERR_NOT_IMPL 4 // Not implemented #define ERR_NOT_IMPL 4 // Not implemented
#define ERR_NORAM_PX 7 // not enough RAM for pixels
#define ERR_NORAM 8 // effect RAM depleted #define ERR_NORAM 8 // effect RAM depleted
#define ERR_JSON 9 // JSON parsing failed (input too large?) #define ERR_JSON 9 // JSON parsing failed (input too large?)
#define ERR_FS_BEGIN 10 // Could not init filesystem (no partition?) #define ERR_FS_BEGIN 10 // Could not init filesystem (no partition?)
@ -474,30 +446,29 @@
#define NTP_PACKET_SIZE 48 // size of NTP receive buffer #define NTP_PACKET_SIZE 48 // size of NTP receive buffer
#define NTP_MIN_PACKET_SIZE 48 // min expected size - NTP v4 allows for "extended information" appended to the standard fields #define NTP_MIN_PACKET_SIZE 48 // min expected size - NTP v4 allows for "extended information" appended to the standard fields
// Maximum number of pins per output. 5 for RGBCCT analog LEDs.
#define OUTPUT_MAX_PINS 5
//maximum number of rendered LEDs - this does not have to match max. physical LEDs, e.g. if there are virtual busses //maximum number of rendered LEDs - this does not have to match max. physical LEDs, e.g. if there are virtual busses
#ifndef MAX_LEDS #ifndef MAX_LEDS
#ifdef ESP8266 #ifdef ESP8266
#define MAX_LEDS 1664 //can't rely on memory limit to limit this to 1600 LEDs #define MAX_LEDS 1536 //can't rely on memory limit to limit this to 1536 LEDs
#elif defined(CONFIG_IDF_TARGET_ESP32S2) #elif defined(CONFIG_IDF_TARGET_ESP32S2)
#define MAX_LEDS 2048 //due to memory constraints #define MAX_LEDS 2048 //due to memory constraints S2
#else #elif defined(CONFIG_IDF_TARGET_ESP32C3)
#define MAX_LEDS 8192 #define MAX_LEDS 4096
#endif #else
#define MAX_LEDS 16384
#endif
#endif #endif
#ifndef MAX_LED_MEMORY #ifndef MAX_LED_MEMORY
#ifdef ESP8266 #ifdef ESP8266
#define MAX_LED_MEMORY 4000 #define MAX_LED_MEMORY 4096
#else #else
#if defined(ARDUINO_ARCH_ESP32S2) #if defined(ARDUINO_ARCH_ESP32S2)
#define MAX_LED_MEMORY 16000 #define MAX_LED_MEMORY 16384
#elif defined(ARDUINO_ARCH_ESP32C3) #elif defined(ARDUINO_ARCH_ESP32C3)
#define MAX_LED_MEMORY 32000 #define MAX_LED_MEMORY 32768
#else #else
#define MAX_LED_MEMORY 64000 #define MAX_LED_MEMORY 65536
#endif #endif
#endif #endif
#endif #endif

View File

@ -353,12 +353,12 @@ button {
padding: 4px 0 0; padding: 4px 0 0;
} }
#segutil, #segutil2, #segcont, #putil, #pcont, #pql, #fx, #palw, #segutil, #segutil2, #segcont, #putil, #pcont, #pql, #fx, #palw, #bsp,
.fnd { .fnd {
max-width: 280px; max-width: 280px;
} }
#putil, #segutil, #segutil2 { #putil, #segutil, #segutil2, #bsp {
min-height: 42px; min-height: 42px;
margin: 0 auto; margin: 0 auto;
} }

View File

@ -268,28 +268,28 @@
<button class="btn btn-s" id="rsbtn" onclick="rSegs()">Reset segments</button> <button class="btn btn-s" id="rsbtn" onclick="rSegs()">Reset segments</button>
</div> </div>
<p>Transition: <input id="tt" type="number" min="0" max="65.5" step="0.1" value="0.7" onchange="parseFloat(this.value)===0?gId('bsp').classList.add('hide'):gId('bsp').classList.remove('hide');">&nbsp;s</p> <p>Transition: <input id="tt" type="number" min="0" max="65.5" step="0.1" value="0.7" onchange="parseFloat(this.value)===0?gId('bsp').classList.add('hide'):gId('bsp').classList.remove('hide');">&nbsp;s</p>
<p id="bsp">Blend: <div id="bsp" class="sel-p"><select id="bs" class="sel-ple" onchange="requestJson({'bs':parseInt(this.value)})">
<select id="bs" class="sel-sg" onchange="requestJson({'bs':parseInt(this.value)})"> <option value="0">Fade</option>
<option value="0">Fade</option> <option value="1">Fairy Dust</option>
<option value="1">Fairy Dust</option> <option value="2">Swipe right</option>
<option value="2">Swipe right</option> <option value="3">Swipe left</option>
<option value="3">Swipe left</option> <option value="16">Push right</option>
<option value="16">Push right</option> <option value="17">Push left</option>
<option value="17">Push left</option> <option value="4">Outside-in</option>
<option value="4">Pinch-out</option> <option value="5">Inside-out</option>
<option value="5">Inside-out</option> <option value="6" data-type="2D">Swipe up</option>
<option value="6" data-type="2D">Swipe up</option> <option value="7" data-type="2D">Swipe down</option>
<option value="7" data-type="2D">Swipe down</option> <option value="8" data-type="2D">Open H</option>
<option value="8" data-type="2D">Open H</option> <option value="9" data-type="2D">Open V</option>
<option value="9" data-type="2D">Open V</option> <option value="18" data-type="2D">Push up</option>
<option value="18" data-type="2D">Push up</option> <option value="19" data-type="2D">Push down</option>
<option value="19" data-type="2D">Push down</option> <option value="10" data-type="2D">Swipe TL</option>
<option value="20" data-type="2D">Push TL</option> <option value="11" data-type="2D">Swipe TR</option>
<option value="21" data-type="2D">Push TR</option> <option value="12" data-type="2D">Swipe BR</option>
<option value="22" data-type="2D">Push BR</option> <option value="13" data-type="2D">Swipe BL</option>
<option value="23" data-type="2D">Push BL</option> <option value="14" data-type="2D">Circular Out</option>
</select> <option value="15" data-type="2D">Circular In</option>
</p> </select></div>
<p id="ledmap" class="hide"></p> <p id="ledmap" class="hide"></p>
</div> </div>
@ -363,7 +363,7 @@
<!-- <!--
If you want to load iro.js and rangetouch.js as consecutive requests, you can do it like it was done in 0.14.0: If you want to load iro.js and rangetouch.js as consecutive requests, you can do it like it was done in 0.14.0:
https://github.com/wled-dev/WLED/blob/v0.14.0/wled00/data/index.htm https://github.com/wled/WLED/blob/v0.14.0/wled00/data/index.htm
--> -->
<script src="iro.js"></script> <script src="iro.js"></script>
<script src="rangetouch.js"></script> <script src="rangetouch.js"></script>

View File

@ -35,9 +35,10 @@ var cfg = {
// [year, month (0 -> January, 11 -> December), day, duration in days, image url] // [year, month (0 -> January, 11 -> December), day, duration in days, image url]
var hol = [ var hol = [
[0, 11, 24, 4, "https://aircoookie.github.io/xmas.png"], // christmas [0, 11, 24, 4, "https://aircoookie.github.io/xmas.png"], // christmas
[0, 2, 17, 1, "https://images.alphacoders.com/491/491123.jpg"], // st. Patrick's day [0, 2, 17, 1, "https://images.alphacoders.com/491/491123.jpg"], // st. Patrick's day
[2025, 3, 20, 2, "https://aircoookie.github.io/easter.png"], // easter 2025 [2026, 3, 5, 2, "https://aircoookie.github.io/easter.png"], // easter 2026
[2024, 2, 31, 2, "https://aircoookie.github.io/easter.png"], // easter 2024 [2027, 2, 28, 2, "https://aircoookie.github.io/easter.png"], // easter 2027
//[2028, 3, 16, 2, "https://aircoookie.github.io/easter.png"], // easter 2028
[0, 6, 4, 1, "https://images.alphacoders.com/516/516792.jpg"], // 4th of July [0, 6, 4, 1, "https://images.alphacoders.com/516/516792.jpg"], // 4th of July
[0, 0, 1, 1, "https://images.alphacoders.com/119/1198800.jpg"] // new year [0, 0, 1, 1, "https://images.alphacoders.com/119/1198800.jpg"] // new year
]; ];
@ -57,7 +58,7 @@ function handleVisibilityChange() {if (!d.hidden && new Date () - lastUpdate > 3
function sCol(na, col) {d.documentElement.style.setProperty(na, col);} function sCol(na, col) {d.documentElement.style.setProperty(na, col);}
function gId(c) {return d.getElementById(c);} function gId(c) {return d.getElementById(c);}
function gEBCN(c) {return d.getElementsByClassName(c);} function gEBCN(c) {return d.getElementsByClassName(c);}
function isEmpty(o) {return Object.keys(o).length === 0;} function isEmpty(o) {for (const i in o) return false; return true;}
function isObj(i) {return (i && typeof i === 'object' && !Array.isArray(i));} function isObj(i) {return (i && typeof i === 'object' && !Array.isArray(i));}
function isNumeric(n) {return !isNaN(parseFloat(n)) && isFinite(n);} function isNumeric(n) {return !isNaN(parseFloat(n)) && isFinite(n);}
@ -805,6 +806,26 @@ function populateSegments(s)
`<option value="4" ${inst.m12==4?' selected':''}>Pinwheel</option>`+ `<option value="4" ${inst.m12==4?' selected':''}>Pinwheel</option>`+
`</select></div>`+ `</select></div>`+
`</div>`; `</div>`;
let blend = `<div class="lbl-l">Blend mode<br>`+
`<div class="sel-p"><select class="sel-ple" id="seg${i}bm" onchange="setBm(${i})">`+
`<option value="0" ${inst.bm==0?' selected':''}>Top/Default</option>`+
`<option value="1" ${inst.bm==1?' selected':''}>Bottom/None</option>`+
`<option value="2" ${inst.bm==2?' selected':''}>Add</option>`+
`<option value="3" ${inst.bm==3?' selected':''}>Subtract</option>`+
`<option value="4" ${inst.bm==4?' selected':''}>Difference</option>`+
`<option value="5" ${inst.bm==5?' selected':''}>Average</option>`+
`<option value="6" ${inst.bm==6?' selected':''}>Multiply</option>`+
`<option value="7" ${inst.bm==7?' selected':''}>Divide</option>`+
`<option value="8" ${inst.bm==8?' selected':''}>Lighten</option>`+
`<option value="9" ${inst.bm==9?' selected':''}>Darken</option>`+
`<option value="10" ${inst.bm==10?' selected':''}>Screen</option>`+
`<option value="11" ${inst.bm==11?' selected':''}>Overlay</option>`+
`<option value="12" ${inst.bm==12?' selected':''}>Hard Light</option>`+
`<option value="13" ${inst.bm==13?' selected':''}>Soft Light</option>`+
`<option value="14" ${inst.bm==14?' selected':''}>Dodge</option>`+
`<option value="15" ${inst.bm==15?' selected':''}>Burn</option>`+
`</select></div>`+
`</div>`;
let sndSim = `<div data-snd="si" class="lbl-s hide">Sound sim<br>`+ let sndSim = `<div data-snd="si" class="lbl-s hide">Sound sim<br>`+
`<div class="sel-p"><select class="sel-p" id="seg${i}si" onchange="setSi(${i})">`+ `<div class="sel-p"><select class="sel-p" id="seg${i}si" onchange="setSi(${i})">`+
`<option value="0" ${inst.si==0?' selected':''}>BeatSin</option>`+ `<option value="0" ${inst.si==0?' selected':''}>BeatSin</option>`+
@ -860,6 +881,7 @@ function populateSegments(s)
`</tr>`+ `</tr>`+
`</table>`+ `</table>`+
`<div class="h bp" id="seg${i}len"></div>`+ `<div class="h bp" id="seg${i}len"></div>`+
blend +
(!isMSeg ? rvXck : '') + (!isMSeg ? rvXck : '') +
(isMSeg&&stoY-staY>1&&stoX-staX>1 ? map2D : '') + (isMSeg&&stoY-staY>1&&stoX-staX>1 ? map2D : '') +
(s.AudioReactive && s.AudioReactive.on ? "" : sndSim) + (s.AudioReactive && s.AudioReactive.on ? "" : sndSim) +
@ -1420,7 +1442,7 @@ function makeWS() {
ws = null; ws = null;
} }
ws.onopen = (e)=>{ ws.onopen = (e)=>{
//ws.send("{'v':true}"); // unnecessary (https://github.com/wled-dev/WLED/blob/main/wled00/ws.cpp#L18) //ws.send("{'v':true}"); // unnecessary (https://github.com/wled/WLED/blob/master/wled00/ws.cpp#L18)
wsRpt = 0; wsRpt = 0;
reqsLegal = true; reqsLegal = true;
} }
@ -1448,41 +1470,32 @@ function readState(s,command=false)
else gId('bsp').classList.remove('hide') else gId('bsp').classList.remove('hide')
populateSegments(s); populateSegments(s);
var selc=0;
var sellvl=0; // 0: selc is invalid, 1: selc is mainseg, 2: selc is first selected
hasRGB = hasWhite = hasCCT = has2D = false; hasRGB = hasWhite = hasCCT = has2D = false;
segLmax = 0; let i = {};
for (let i = 0; i < (s.seg||[]).length; i++) // determine light capabilities from selected segments
{ for (let seg of (s.seg||[])) {
if (sellvl == 0 && s.seg[i].id == s.mainseg) { let w = (seg.stop - seg.start);
selc = i; let h = seg.stopY ? (seg.stopY - seg.startY) : 1;
sellvl = 1; let lc = seg.lc;
} if (w*h > segLmax) segLmax = w*h;
if (s.seg[i].sel) { if (seg.sel) {
if (sellvl < 2) selc = i; // get first selected segment if (isEmpty(i) || (i.id == s.mainseg && !i.sel)) i = seg; // get first selected segment (and replace mainseg if it is not selected)
sellvl = 2;
let w = (s.seg[i].stop - s.seg[i].start);
let h = s.seg[i].stopY ? (s.seg[i].stopY - s.seg[i].startY) : 1;
let lc = lastinfo.leds.seglc[i];
hasRGB |= !!(lc & 0x01); hasRGB |= !!(lc & 0x01);
hasWhite |= !!(lc & 0x02); hasWhite |= !!(lc & 0x02);
hasCCT |= !!(lc & 0x04); hasCCT |= !!(lc & 0x04);
has2D |= w > 1 && h > 1; has2D |= w > 1 && h > 1;
if (w*h > segLmax) segLmax = w*h; } else if (isEmpty(i) && seg.id == s.mainseg) i = seg; // assign mainseg if no segments are selected
}
} }
var i=s.seg[selc]; if (isEmpty(i)) {
if (sellvl == 1) { showToast('No segments!', true);
let lc = lastinfo.leds.seglc[selc];
hasRGB = !!(lc & 0x01);
hasWhite = !!(lc & 0x02);
hasCCT = !!(lc & 0x04);
has2D = (i.stop - i.start) > 1 && (i.stopY ? (i.stopY - i.startY) : 1) > 1;
}
if (!i) {
showToast('No Segments!', true);
updateUI(); updateUI();
return true; return true;
} else if (i.id == s.mainseg) {
// fallback if no segments are selected but we have mainseg
hasRGB |= !!(i.lc & 0x01);
hasWhite |= !!(i.lc & 0x02);
hasCCT |= !!(i.lc & 0x04);
has2D |= (i.stop - i.start) > 1 && (i.stopY ? (i.stopY - i.startY) : 1) > 1;
} }
var cd = gId('csl').querySelectorAll("button"); var cd = gId('csl').querySelectorAll("button");
@ -2339,6 +2352,13 @@ function setSi(s)
requestJson(obj); requestJson(obj);
} }
function setBm(s)
{
var value = gId(`seg${s}bm`).selectedIndex;
var obj = {"seg": {"id": s, "bm": value}};
requestJson(obj);
}
function setTp(s) function setTp(s)
{ {
var tp = gId(`seg${s}tp`).checked; var tp = gId(`seg${s}tp`).checked;
@ -2762,7 +2782,7 @@ setInterval(()=>{
gId('heart').style.color = `hsl(${hc}, 100%, 50%)`; gId('heart').style.color = `hsl(${hc}, 100%, 50%)`;
}, 910); }, 910);
function openGH() { window.open("https://github.com/wled-dev/WLED/wiki"); } function openGH() { window.open("https://github.com/wled/WLED/wiki"); }
var cnfr = false; var cnfr = false;
function cnfReset() function cnfReset()
@ -3155,7 +3175,8 @@ function mergeDeep(target, ...sources)
return mergeDeep(target, ...sources); return mergeDeep(target, ...sources);
} }
function tooltip(cont=null) { function tooltip(cont=null)
{
d.querySelectorAll((cont?cont+" ":"")+"[title]").forEach((element)=>{ d.querySelectorAll((cont?cont+" ":"")+"[title]").forEach((element)=>{
element.addEventListener("pointerover", ()=>{ element.addEventListener("pointerover", ()=>{
// save title // save title

View File

@ -202,7 +202,6 @@
if (maxM >= 10000) { //ESP32 RMT uses double buffer? if (maxM >= 10000) { //ESP32 RMT uses double buffer?
mul = 2; mul = 2;
} }
if (d.Sf.LD.checked) dbl = len * ch; // double buffering
} }
return len * ch * mul + dbl; return len * ch * mul + dbl;
} }
@ -326,7 +325,7 @@
LC.style.color="#fff"; LC.style.color="#fff";
return; // do not check conflicts return; // do not check conflicts
} else { } else {
LC.max = d.max_gpio; LC.max = d.max_gpio-1;
LC.min = -1; LC.min = -1;
} }
} }
@ -641,7 +640,6 @@ Swap: <select id="xw${s}" name="XW${s}">
d.getElementsByName("MA"+i)[0].value = v.maxpwr; d.getElementsByName("MA"+i)[0].value = v.maxpwr;
}); });
d.getElementsByName("PR")[0].checked = l.prl | 0; d.getElementsByName("PR")[0].checked = l.prl | 0;
d.getElementsByName("LD")[0].checked = l.ld;
d.getElementsByName("MA")[0].value = l.maxpwr; d.getElementsByName("MA")[0].value = l.maxpwr;
d.getElementsByName("ABL")[0].checked = l.maxpwr > 0; d.getElementsByName("ABL")[0].checked = l.maxpwr > 0;
} }
@ -823,7 +821,6 @@ Swap: <select id="xw${s}" name="XW${s}">
<div id="prl" class="hide">Use parallel I2S: <input type="checkbox" name="PR"><br></div> <div id="prl" class="hide">Use parallel I2S: <input type="checkbox" name="PR"><br></div>
Make a segment for each output: <input type="checkbox" name="MS"><br> Make a segment for each output: <input type="checkbox" name="MS"><br>
Custom bus start indices: <input type="checkbox" onchange="tglSi(this.checked)" id="si"><br> Custom bus start indices: <input type="checkbox" onchange="tglSi(this.checked)" id="si"><br>
Use global LED buffer: <input type="checkbox" name="LD" onchange="UI()"><br>
<hr class="sml"> <hr class="sml">
<div id="color_order_mapping"> <div id="color_order_mapping">
Color Order Override: Color Order Override:
@ -866,7 +863,6 @@ Swap: <select id="xw${s}" name="XW${s}">
<h3>Transitions</h3> <h3>Transitions</h3>
Default transition time: <input name="TD" type="number" class="xl" min="0" max="65500"> ms<br> Default transition time: <input name="TD" type="number" class="xl" min="0" max="65500"> ms<br>
<i>Random Cycle</i> Palette Time: <input name="TP" type="number" class="m" min="1" max="255"> s<br> <i>Random Cycle</i> Palette Time: <input name="TP" type="number" class="m" min="1" max="255"> s<br>
Use harmonic <i>Random Cycle</i> Palette: <input type="checkbox" name="TH"><br>
<h3>Timed light</h3> <h3>Timed light</h3>
Default duration: <input name="TL" type="number" class="m" min="1" max="255" required> min<br> Default duration: <input name="TL" type="number" class="m" min="1" max="255" required> min<br>
Default target brightness: <input name="TB" type="number" class="m" min="0" max="255" required><br> Default target brightness: <input name="TB" type="number" class="m" min="0" max="255" required><br>
@ -903,8 +899,10 @@ Swap: <select id="xw${s}" name="XW${s}">
<option value="2">Linear (never wrap)</option> <option value="2">Linear (never wrap)</option>
<option value="3">None (not recommended)</option> <option value="3">None (not recommended)</option>
</select><br> </select><br>
Use harmonic <i>Random Cycle</i> palette: <input type="checkbox" name="TH"><br>
Use &quot;rainbow&quot; color wheel: <input type="checkbox" name="RW"><br>
Target refresh rate: <input type="number" class="s" min="0" max="250" name="FR" oninput="UI()" required> FPS Target refresh rate: <input type="number" class="s" min="0" max="250" name="FR" oninput="UI()" required> FPS
<div id="fpsNone" class="warn" style="display: none;">&#9888; Unlimited FPS Mode is experimental &#9888;<br></div> <div id="fpsNone" class="warn" style="display: none;">&#9888; Unlimited FPS Mode is experimental &#9888;<br></div>
<div id="fpsHigh" class="warn" style="display: none;">&#9888; High FPS Mode is experimental.<br></div> <div id="fpsHigh" class="warn" style="display: none;">&#9888; High FPS Mode is experimental.<br></div>
<div id="fpsWarn" class="warn" style="display: none;">Please <a class="lnk" href="sec#backup">backup</a> WLED configuration and presets first!<br></div> <div id="fpsWarn" class="warn" style="display: none;">Please <a class="lnk" href="sec#backup">backup</a> WLED configuration and presets first!<br></div>
<hr class="sml"> <hr class="sml">

View File

@ -38,8 +38,7 @@ void handleDDPPacket(e131_packet_t* p) {
if (realtimeMode != REALTIME_MODE_DDP) ddpSeenPush = false; // just starting, no push yet if (realtimeMode != REALTIME_MODE_DDP) ddpSeenPush = false; // just starting, no push yet
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP); realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP);
if (!realtimeOverride || (realtimeMode && useMainSegmentOnly)) { if (!realtimeOverride) {
if (useMainSegmentOnly) strip.getMainSegment().beginDraw();
for (unsigned i = start; i < stop; i++, c += ddpChannelsPerLed) { for (unsigned i = start; i < stop; i++, c += ddpChannelsPerLed) {
setRealtimePixel(i, data[c], data[c+1], data[c+2], ddpChannelsPerLed >3 ? data[c+3] : 0); setRealtimePixel(i, data[c], data[c+1], data[c+2], ddpChannelsPerLed >3 ? data[c+3] : 0);
} }
@ -150,10 +149,9 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8
realtimeLock(realtimeTimeoutMs, mde); realtimeLock(realtimeTimeoutMs, mde);
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; if (realtimeOverride) return;
wChannel = (availDMXLen > 3) ? e131_data[dataOffset+3] : 0; wChannel = (availDMXLen > 3) ? e131_data[dataOffset+3] : 0;
if (useMainSegmentOnly) strip.getMainSegment().beginDraw();
for (unsigned i = 0; i < totalLen; i++) for (unsigned i = 0; i < totalLen; i++)
setRealtimePixel(i, e131_data[dataOffset+0], e131_data[dataOffset+1], e131_data[dataOffset+2], wChannel); setRealtimePixel(i, e131_data[dataOffset+0], e131_data[dataOffset+1], e131_data[dataOffset+2], wChannel);
break; break;
@ -163,7 +161,7 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8
if (availDMXLen < 4) return; if (availDMXLen < 4) return;
realtimeLock(realtimeTimeoutMs, mde); realtimeLock(realtimeTimeoutMs, mde);
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; if (realtimeOverride) return;
wChannel = (availDMXLen > 4) ? e131_data[dataOffset+4] : 0; wChannel = (availDMXLen > 4) ? e131_data[dataOffset+4] : 0;
if (bri != e131_data[dataOffset+0]) { if (bri != e131_data[dataOffset+0]) {
@ -171,7 +169,6 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8
strip.setBrightness(bri, true); strip.setBrightness(bri, true);
} }
if (useMainSegmentOnly) strip.getMainSegment().beginDraw();
for (unsigned i = 0; i < totalLen; i++) for (unsigned i = 0; i < totalLen; i++)
setRealtimePixel(i, e131_data[dataOffset+1], e131_data[dataOffset+2], e131_data[dataOffset+3], wChannel); setRealtimePixel(i, e131_data[dataOffset+1], e131_data[dataOffset+2], e131_data[dataOffset+3], wChannel);
break; break;
@ -228,16 +225,16 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8
if (e131_data[dataOffset+3] != seg.intensity) seg.intensity = e131_data[dataOffset+3]; if (e131_data[dataOffset+3] != seg.intensity) seg.intensity = e131_data[dataOffset+3];
if (e131_data[dataOffset+4] != seg.palette) seg.setPalette(e131_data[dataOffset+4]); if (e131_data[dataOffset+4] != seg.palette) seg.setPalette(e131_data[dataOffset+4]);
if ((e131_data[dataOffset+5] & 0b00000010) != seg.reverse_y) { seg.setOption(SEG_OPTION_REVERSED_Y, e131_data[dataOffset+5] & 0b00000010); } if (bool(e131_data[dataOffset+5] & 0b00000010) != seg.reverse_y) { seg.reverse_y = bool(e131_data[dataOffset+5] & 0b00000010); }
if ((e131_data[dataOffset+5] & 0b00000100) != seg.mirror_y) { seg.setOption(SEG_OPTION_MIRROR_Y, e131_data[dataOffset+5] & 0b00000100); } if (bool(e131_data[dataOffset+5] & 0b00000100) != seg.mirror_y) { seg.mirror_y = bool(e131_data[dataOffset+5] & 0b00000100); }
if ((e131_data[dataOffset+5] & 0b00001000) != seg.transpose) { seg.setOption(SEG_OPTION_TRANSPOSED, e131_data[dataOffset+5] & 0b00001000); } if (bool(e131_data[dataOffset+5] & 0b00001000) != seg.transpose) { seg.transpose = bool(e131_data[dataOffset+5] & 0b00001000); }
if ((e131_data[dataOffset+5] & 0b00110000) / 8 != seg.map1D2D) { if ((e131_data[dataOffset+5] & 0b00110000) >> 4 != seg.map1D2D) {
seg.map1D2D = (e131_data[dataOffset+5] & 0b00110000) / 8; seg.map1D2D = (e131_data[dataOffset+5] & 0b00110000) >> 4;
} }
// To maintain backwards compatibility with prior e1.31 values, reverse is fixed to mask 0x01000000 // To maintain backwards compatibility with prior e1.31 values, reverse is fixed to mask 0x01000000
if ((e131_data[dataOffset+5] & 0b01000000) != seg.reverse) { seg.setOption(SEG_OPTION_REVERSED, e131_data[dataOffset+5] & 0b01000000); } if ((e131_data[dataOffset+5] & 0b01000000) != seg.reverse) { seg.reverse = bool(e131_data[dataOffset+5] & 0b01000000); }
// To maintain backwards compatibility with prior e1.31 values, mirror is fixed to mask 0x10000000 // To maintain backwards compatibility with prior e1.31 values, mirror is fixed to mask 0x10000000
if ((e131_data[dataOffset+5] & 0b10000000) != seg.mirror) { seg.setOption(SEG_OPTION_MIRROR, e131_data[dataOffset+5] & 0b10000000); } if ((e131_data[dataOffset+5] & 0b10000000) != seg.mirror) { seg.mirror = bool(e131_data[dataOffset+5] & 0b10000000); }
uint32_t colors[3]; uint32_t colors[3];
byte whites[3] = {0,0,0}; byte whites[3] = {0,0,0};
@ -271,7 +268,7 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8
case DMX_MODE_MULTIPLE_RGB: case DMX_MODE_MULTIPLE_RGB:
case DMX_MODE_MULTIPLE_RGBW: case DMX_MODE_MULTIPLE_RGBW:
{ {
bool is4Chan = (DMXMode == DMX_MODE_MULTIPLE_RGBW); const bool is4Chan = (DMXMode == DMX_MODE_MULTIPLE_RGBW);
const unsigned dmxChannelsPerLed = is4Chan ? 4 : 3; const unsigned dmxChannelsPerLed = is4Chan ? 4 : 3;
const unsigned ledsPerUniverse = is4Chan ? MAX_4_CH_LEDS_PER_UNIVERSE : MAX_3_CH_LEDS_PER_UNIVERSE; const unsigned ledsPerUniverse = is4Chan ? MAX_4_CH_LEDS_PER_UNIVERSE : MAX_3_CH_LEDS_PER_UNIVERSE;
uint8_t stripBrightness = bri; uint8_t stripBrightness = bri;
@ -303,7 +300,7 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8
} }
realtimeLock(realtimeTimeoutMs, mde); realtimeLock(realtimeTimeoutMs, mde);
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; if (realtimeOverride) return;
if (ledsTotal > totalLen) { if (ledsTotal > totalLen) {
ledsTotal = totalLen; ledsTotal = totalLen;
@ -316,17 +313,9 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8
} }
} }
if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); for (unsigned i = previousLeds; i < ledsTotal; i++) {
if (!is4Chan) { setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], is4Chan ? e131_data[dmxOffset+3] : 0);
for (unsigned i = previousLeds; i < ledsTotal; i++) { dmxOffset += dmxChannelsPerLed;
setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], 0);
dmxOffset+=3;
}
} else {
for (unsigned i = previousLeds; i < ledsTotal; i++) {
setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], e131_data[dmxOffset+3]);
dmxOffset+=4;
}
} }
break; break;
} }
@ -529,7 +518,7 @@ void sendArtnetPollReply(ArtPollReply *reply, IPAddress ipAddress, uint16_t port
reply->reply_sub_sw = (uint8_t)((portAddress >> 4) & 0x000F); reply->reply_sub_sw = (uint8_t)((portAddress >> 4) & 0x000F);
reply->reply_sw_out[0] = (uint8_t)(portAddress & 0x000F); reply->reply_sw_out[0] = (uint8_t)(portAddress & 0x000F);
snprintf_P((char *)reply->reply_node_report, sizeof(reply->reply_node_report)-1, PSTR("#0001 [%04u] OK - WLED v" TOSTRING(WLED_VERSION)), pollReplyCount); snprintf_P((char *)reply->reply_node_report, sizeof(reply->reply_node_report)-1, PSTR("#0001 [%04u] OK - WLED v%s"), pollReplyCount, versionString);
if (pollReplyCount < 9999) { if (pollReplyCount < 9999) {
pollReplyCount++; pollReplyCount++;

View File

@ -172,6 +172,8 @@ inline uint32_t color_blend16(uint32_t c1, uint32_t c2, uint16_t b) { return col
[[gnu::hot, gnu::pure]] uint32_t ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); [[gnu::hot, gnu::pure]] uint32_t ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND);
CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette); CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette);
CRGBPalette16 generateRandomPalette(); CRGBPalette16 generateRandomPalette();
void loadCustomPalettes();
#define getPaletteCount() (13 + GRADIENT_PALETTE_COUNT + customPalettes.size())
inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); }
void hsv2rgb(const CHSV32& hsv, uint32_t& rgb); void hsv2rgb(const CHSV32& hsv, uint32_t& rgb);
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); void colorHStoRGB(uint16_t hue, byte sat, byte* rgb);
@ -490,11 +492,11 @@ void userLoop();
#define inoise8 perlin8 // fastled legacy alias #define inoise8 perlin8 // fastled legacy alias
#define inoise16 perlin16 // fastled legacy alias #define inoise16 perlin16 // fastled legacy alias
#define hex2int(a) (((a)>='0' && (a)<='9') ? (a)-'0' : ((a)>='A' && (a)<='F') ? (a)-'A'+10 : ((a)>='a' && (a)<='f') ? (a)-'a'+10 : 0) #define hex2int(a) (((a)>='0' && (a)<='9') ? (a)-'0' : ((a)>='A' && (a)<='F') ? (a)-'A'+10 : ((a)>='a' && (a)<='f') ? (a)-'a'+10 : 0)
[[gnu::pure]] int getNumVal(const String* req, uint16_t pos); [[gnu::pure]] int getNumVal(const String &req, uint16_t pos);
void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255); void parseNumber(const char* str, byte &val, byte minv=0, byte maxv=255);
bool getVal(JsonVariant elem, byte* val, byte vmin=0, byte vmax=255); // getVal supports inc/decrementing and random ("X~Y(r|[w]~[-][Z])" form) bool getVal(JsonVariant elem, byte &val, byte vmin=0, byte vmax=255); // getVal supports inc/decrementing and random ("X~Y(r|[w]~[-][Z])" form)
[[gnu::pure]] bool getBoolVal(const JsonVariant &elem, bool dflt); [[gnu::pure]] bool getBoolVal(const JsonVariant &elem, bool dflt);
bool updateVal(const char* req, const char* key, byte* val, byte minv=0, byte maxv=255); bool updateVal(const char* req, const char* key, byte &val, byte minv=0, byte maxv=255);
size_t printSetFormCheckbox(Print& settingsScript, const char* key, int val); size_t printSetFormCheckbox(Print& settingsScript, const char* key, int val);
size_t printSetFormValue(Print& settingsScript, const char* key, int val); size_t printSetFormValue(Print& settingsScript, const char* key, int val);
size_t printSetFormValue(Print& settingsScript, const char* key, const char* val); size_t printSetFormValue(Print& settingsScript, const char* key, const char* val);
@ -544,6 +546,27 @@ inline uint8_t hw_random8() { return HW_RND_REGISTER; };
inline uint8_t hw_random8(uint32_t upperlimit) { return (hw_random8() * upperlimit) >> 8; }; // input range 0-255 inline uint8_t hw_random8(uint32_t upperlimit) { return (hw_random8() * upperlimit) >> 8; }; // input range 0-255
inline uint8_t hw_random8(uint32_t lowerlimit, uint32_t upperlimit) { uint32_t range = upperlimit - lowerlimit; return lowerlimit + hw_random8(range); }; // input range 0-255 inline uint8_t hw_random8(uint32_t lowerlimit, uint32_t upperlimit) { uint32_t range = upperlimit - lowerlimit; return lowerlimit + hw_random8(range); }; // input range 0-255
// PSRAM allocation wrappers
#ifndef ESP8266
void *w_malloc(size_t); // prefer PSRAM over DRAM
void *w_calloc(size_t, size_t); // prefer PSRAM over DRAM
void *w_realloc(void *, size_t); // prefer PSRAM over DRAM
inline void w_free(void *ptr) { heap_caps_free(ptr); }
void *d_malloc(size_t); // prefer DRAM over PSRAM
void *d_calloc(size_t, size_t); // prefer DRAM over PSRAM
void *d_realloc(void *, size_t); // prefer DRAM over PSRAM
inline void d_free(void *ptr) { heap_caps_free(ptr); }
#else
#define w_malloc malloc
#define w_calloc calloc
#define w_realloc realloc
#define w_free free
#define d_malloc malloc
#define d_calloc calloc
#define d_realloc realloc
#define d_free free
#endif
// RAII guard class for the JSON Buffer lock // RAII guard class for the JSON Buffer lock
// Modeled after std::lock_guard // Modeled after std::lock_guard
class JSONBufferGuard { class JSONBufferGuard {

View File

@ -39,7 +39,7 @@ void closeFile() {
uint32_t s = millis(); uint32_t s = millis();
#endif #endif
f.close(); f.close();
DEBUGFS_PRINTF("took %d ms\n", millis() - s); DEBUGFS_PRINTF("took %lu ms\n", millis() - s);
doCloseFile = false; doCloseFile = false;
} }
@ -69,14 +69,14 @@ static bool bufferedFind(const char *target, bool fromStart = true) {
if(buf[count] == target[index]) { if(buf[count] == target[index]) {
if(++index >= targetLen) { // return true if all chars in the target match if(++index >= targetLen) { // return true if all chars in the target match
f.seek((f.position() - bufsize) + count +1); f.seek((f.position() - bufsize) + count +1);
DEBUGFS_PRINTF("Found at pos %d, took %d ms", f.position(), millis() - s); DEBUGFS_PRINTF("Found at pos %d, took %lu ms", f.position(), millis() - s);
return true; return true;
} }
} }
count++; count++;
} }
} }
DEBUGFS_PRINTF("No match, took %d ms\n", millis() - s); DEBUGFS_PRINTF("No match, took %lu ms\n", millis() - s);
return false; return false;
} }
@ -111,7 +111,7 @@ static bool bufferedFindSpace(size_t targetLen, bool fromStart = true) {
f.seek((f.position() - bufsize) + count +1 - targetLen); f.seek((f.position() - bufsize) + count +1 - targetLen);
knownLargestSpace = MAX_SPACE; //there may be larger spaces after, so we don't know knownLargestSpace = MAX_SPACE; //there may be larger spaces after, so we don't know
} }
DEBUGFS_PRINTF("Found at pos %d, took %d ms", f.position(), millis() - s); DEBUGFS_PRINTF("Found at pos %d, took %lu ms", f.position(), millis() - s);
return true; return true;
} }
} else { } else {
@ -125,7 +125,7 @@ static bool bufferedFindSpace(size_t targetLen, bool fromStart = true) {
count++; count++;
} }
} }
DEBUGFS_PRINTF("No match, took %d ms\n", millis() - s); DEBUGFS_PRINTF("No match, took %lu ms\n", millis() - s);
return false; return false;
} }
@ -151,13 +151,13 @@ static bool bufferedFindObjectEnd() {
if (buf[count] == '}') objDepth--; if (buf[count] == '}') objDepth--;
if (objDepth == 0) { if (objDepth == 0) {
f.seek((f.position() - bufsize) + count +1); f.seek((f.position() - bufsize) + count +1);
DEBUGFS_PRINTF("} at pos %d, took %d ms", f.position(), millis() - s); DEBUGFS_PRINTF("} at pos %d, took %lu ms", f.position(), millis() - s);
return true; return true;
} }
count++; count++;
} }
} }
DEBUGFS_PRINTF("No match, took %d ms\n", millis() - s); DEBUGFS_PRINTF("No match, took %lu ms\n", millis() - s);
return false; return false;
} }
@ -203,7 +203,7 @@ static bool appendObjectToFile(const char* key, const JsonDocument* content, uin
if (f.position() > 2) f.write(','); //add comma if not first object if (f.position() > 2) f.write(','); //add comma if not first object
f.print(key); f.print(key);
serializeJson(*content, f); serializeJson(*content, f);
DEBUGFS_PRINTF("Inserted, took %d ms (total %d)", millis() - s1, millis() - s); DEBUGFS_PRINTF("Inserted, took %lu ms (total %lu)", millis() - s1, millis() - s);
doCloseFile = true; doCloseFile = true;
return true; return true;
} }
@ -251,7 +251,7 @@ static bool appendObjectToFile(const char* key, const JsonDocument* content, uin
f.write('}'); f.write('}');
doCloseFile = true; doCloseFile = true;
DEBUGFS_PRINTF("Appended, took %d ms (total %d)", millis() - s1, millis() - s); DEBUGFS_PRINTF("Appended, took %lu ms (total %lu)", millis() - s1, millis() - s);
return true; return true;
} }
@ -321,7 +321,7 @@ bool writeObjectToFile(const char* file, const char* key, const JsonDocument* co
} }
doCloseFile = true; doCloseFile = true;
DEBUGFS_PRINTF("Replaced/deleted, took %d ms\n", millis() - s); DEBUGFS_PRINTF("Replaced/deleted, took %lu ms\n", millis() - s);
return true; return true;
} }
@ -356,7 +356,7 @@ bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest, c
else deserializeJson(*dest, f); else deserializeJson(*dest, f);
f.close(); f.close();
DEBUGFS_PRINTF("Read, took %d ms\n", millis() - s); DEBUGFS_PRINTF("Read, took %lu ms\n", millis() - s);
return true; return true;
} }
@ -392,7 +392,7 @@ static const uint8_t *getPresetCache(size_t &size) {
if ((presetsModifiedTime != presetsCachedTime) || (presetsCachedValidate != cacheInvalidate)) { if ((presetsModifiedTime != presetsCachedTime) || (presetsCachedValidate != cacheInvalidate)) {
if (presetsCached) { if (presetsCached) {
free(presetsCached); w_free(presetsCached);
presetsCached = nullptr; presetsCached = nullptr;
} }
} }
@ -403,7 +403,7 @@ static const uint8_t *getPresetCache(size_t &size) {
presetsCachedTime = presetsModifiedTime; presetsCachedTime = presetsModifiedTime;
presetsCachedValidate = cacheInvalidate; presetsCachedValidate = cacheInvalidate;
presetsCachedSize = 0; presetsCachedSize = 0;
presetsCached = (uint8_t*)ps_malloc(file.size() + 1); presetsCached = (uint8_t*)w_malloc(file.size() + 1);
if (presetsCached) { if (presetsCached) {
presetsCachedSize = file.size(); presetsCachedSize = file.size();
file.read(presetsCached, presetsCachedSize); file.read(presetsCached, presetsCachedSize);
@ -419,7 +419,7 @@ static const uint8_t *getPresetCache(size_t &size) {
#endif #endif
bool handleFileRead(AsyncWebServerRequest* request, String path){ bool handleFileRead(AsyncWebServerRequest* request, String path){
DEBUG_PRINT(F("WS FileRead: ")); DEBUG_PRINTLN(path); DEBUGFS_PRINT(F("WS FileRead: ")); DEBUGFS_PRINTLN(path);
if(path.endsWith("/")) path += "index.htm"; if(path.endsWith("/")) path += "index.htm";
if(path.indexOf(F("sec")) > -1) return false; if(path.indexOf(F("sec")) > -1) return false;
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32

View File

@ -425,8 +425,8 @@ static void decodeIR44(uint32_t code)
case IR44_COLDWHITE2 : changeColor(COLOR_COLDWHITE2, 255); changeEffect(FX_MODE_STATIC); break; case IR44_COLDWHITE2 : changeColor(COLOR_COLDWHITE2, 255); changeEffect(FX_MODE_STATIC); break;
case IR44_REDPLUS : changeEffect(relativeChange(effectCurrent, 1, 0, strip.getModeCount() -1)); break; case IR44_REDPLUS : changeEffect(relativeChange(effectCurrent, 1, 0, strip.getModeCount() -1)); break;
case IR44_REDMINUS : changeEffect(relativeChange(effectCurrent, -1, 0, strip.getModeCount() -1)); break; case IR44_REDMINUS : changeEffect(relativeChange(effectCurrent, -1, 0, strip.getModeCount() -1)); break;
case IR44_GREENPLUS : changePalette(relativeChange(effectPalette, 1, 0, strip.getPaletteCount() -1)); break; case IR44_GREENPLUS : changePalette(relativeChange(effectPalette, 1, 0, getPaletteCount() -1)); break;
case IR44_GREENMINUS : changePalette(relativeChange(effectPalette, -1, 0, strip.getPaletteCount() -1)); break; case IR44_GREENMINUS : changePalette(relativeChange(effectPalette, -1, 0, getPaletteCount() -1)); break;
case IR44_BLUEPLUS : changeEffectIntensity( 16); break; case IR44_BLUEPLUS : changeEffectIntensity( 16); break;
case IR44_BLUEMINUS : changeEffectIntensity(-16); break; case IR44_BLUEMINUS : changeEffectIntensity(-16); break;
case IR44_QUICK : changeEffectSpeed( 16); break; case IR44_QUICK : changeEffectSpeed( 16); break;
@ -435,7 +435,7 @@ static void decodeIR44(uint32_t code)
case IR44_DIY2 : presetFallback(2, FX_MODE_BREATH, 0); break; case IR44_DIY2 : presetFallback(2, FX_MODE_BREATH, 0); break;
case IR44_DIY3 : presetFallback(3, FX_MODE_FIRE_FLICKER, 0); break; case IR44_DIY3 : presetFallback(3, FX_MODE_FIRE_FLICKER, 0); break;
case IR44_DIY4 : presetFallback(4, FX_MODE_RAINBOW, 0); break; case IR44_DIY4 : presetFallback(4, FX_MODE_RAINBOW, 0); break;
case IR44_DIY5 : presetFallback(5, FX_MODE_METEOR, 0); break; case IR44_DIY5 : presetFallback(5, FX_MODE_METEOR, 0); break;
case IR44_DIY6 : presetFallback(6, FX_MODE_RAIN, 0); break; case IR44_DIY6 : presetFallback(6, FX_MODE_RAIN, 0); break;
case IR44_AUTO : changeEffect(FX_MODE_STATIC); break; case IR44_AUTO : changeEffect(FX_MODE_STATIC); break;
case IR44_FLASH : changeEffect(FX_MODE_PALETTE); break; case IR44_FLASH : changeEffect(FX_MODE_PALETTE); break;
@ -484,7 +484,7 @@ static void decodeIR6(uint32_t code)
case IR6_CHANNEL_UP: incBrightness(); break; case IR6_CHANNEL_UP: incBrightness(); break;
case IR6_CHANNEL_DOWN: decBrightness(); break; case IR6_CHANNEL_DOWN: decBrightness(); break;
case IR6_VOLUME_UP: changeEffect(relativeChange(effectCurrent, 1, 0, strip.getModeCount() -1)); break; case IR6_VOLUME_UP: changeEffect(relativeChange(effectCurrent, 1, 0, strip.getModeCount() -1)); break;
case IR6_VOLUME_DOWN: changePalette(relativeChange(effectPalette, 1, 0, strip.getPaletteCount() -1)); case IR6_VOLUME_DOWN: changePalette(relativeChange(effectPalette, 1, 0, getPaletteCount() -1));
switch(lastIR6ColourIdx) { switch(lastIR6ColourIdx) {
case 0: changeColor(COLOR_RED); break; case 0: changeColor(COLOR_RED); break;
case 1: changeColor(COLOR_REDDISH); break; case 1: changeColor(COLOR_REDDISH); break;
@ -530,7 +530,7 @@ static void decodeIR9(uint32_t code)
/* /*
This allows users to customize IR actions without the need to edit C code and compile. This allows users to customize IR actions without the need to edit C code and compile.
From the https://github.com/wled-dev/WLED/wiki/Infrared-Control page, download the starter From the https://github.com/wled/WLED/wiki/Infrared-Control page, download the starter
ir.json file that corresponds to the number of buttons on your remote. ir.json file that corresponds to the number of buttons on your remote.
Many of the remotes with the same number of buttons emit the same codes, but will have Many of the remotes with the same number of buttons emit the same codes, but will have
different labels or colors. Once you edit the ir.json file, upload it to your controller different labels or colors. Once you edit the ir.json file, upload it to your controller

View File

@ -2,14 +2,74 @@
#include "palettes.h" #include "palettes.h"
#define JSON_PATH_STATE 1
#define JSON_PATH_INFO 2
#define JSON_PATH_STATE_INFO 3
#define JSON_PATH_NODES 4
#define JSON_PATH_PALETTES 5
#define JSON_PATH_FXDATA 6
#define JSON_PATH_NETWORKS 7
#define JSON_PATH_EFFECTS 8
/* /*
* JSON API (De)serialization * JSON API (De)serialization
*/ */
namespace {
typedef struct {
uint32_t colors[NUM_COLORS];
uint16_t start;
uint16_t stop;
uint16_t offset;
uint16_t grouping;
uint16_t spacing;
uint16_t startY;
uint16_t stopY;
uint16_t options;
uint8_t mode;
uint8_t palette;
uint8_t opacity;
uint8_t speed;
uint8_t intensity;
uint8_t custom1;
uint8_t custom2;
uint8_t custom3;
bool check1;
bool check2;
bool check3;
} SegmentCopy;
bool deserializeSegment(JsonObject elem, byte it, byte presetId) uint8_t differs(const Segment& b, const SegmentCopy& a) {
uint8_t d = 0;
if (a.start != b.start) d |= SEG_DIFFERS_BOUNDS;
if (a.stop != b.stop) d |= SEG_DIFFERS_BOUNDS;
if (a.offset != b.offset) d |= SEG_DIFFERS_GSO;
if (a.grouping != b.grouping) d |= SEG_DIFFERS_GSO;
if (a.spacing != b.spacing) d |= SEG_DIFFERS_GSO;
if (a.opacity != b.opacity) d |= SEG_DIFFERS_BRI;
if (a.mode != b.mode) d |= SEG_DIFFERS_FX;
if (a.speed != b.speed) d |= SEG_DIFFERS_FX;
if (a.intensity != b.intensity) d |= SEG_DIFFERS_FX;
if (a.palette != b.palette) d |= SEG_DIFFERS_FX;
if (a.custom1 != b.custom1) d |= SEG_DIFFERS_FX;
if (a.custom2 != b.custom2) d |= SEG_DIFFERS_FX;
if (a.custom3 != b.custom3) d |= SEG_DIFFERS_FX;
if (a.startY != b.startY) d |= SEG_DIFFERS_BOUNDS;
if (a.stopY != b.stopY) d |= SEG_DIFFERS_BOUNDS;
//bit pattern: (msb first)
// set:2, sound:2, mapping:3, transposed, mirrorY, reverseY, [reset,] paused, mirrored, on, reverse, [selected]
if ((a.options & 0b1111111111011110U) != (b.options & 0b1111111111011110U)) d |= SEG_DIFFERS_OPT;
if ((a.options & 0x0001U) != (b.options & 0x0001U)) d |= SEG_DIFFERS_SEL;
for (unsigned i = 0; i < NUM_COLORS; i++) if (a.colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL;
return d;
}
}
static bool deserializeSegment(JsonObject elem, byte it, byte presetId)
{ {
byte id = elem["id"] | it; byte id = elem["id"] | it;
if (id >= strip.getMaxSegments()) return false; if (id >= WS2812FX::getMaxSegments()) return false;
bool newSeg = false; bool newSeg = false;
int stop = elem["stop"] | -1; int stop = elem["stop"] | -1;
@ -17,16 +77,37 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
// append segment // append segment
if (id >= strip.getSegmentsNum()) { if (id >= strip.getSegmentsNum()) {
if (stop <= 0) return false; // ignore empty/inactive segments if (stop <= 0) return false; // ignore empty/inactive segments
strip.appendSegment(Segment(0, strip.getLengthTotal())); strip.appendSegment(0, strip.getLengthTotal());
id = strip.getSegmentsNum()-1; // segments are added at the end of list id = strip.getSegmentsNum()-1; // segments are added at the end of list
newSeg = true; newSeg = true;
} }
//DEBUG_PRINTLN(F("-- JSON deserialize segment.")); //DEBUG_PRINTLN(F("-- JSON deserialize segment."));
Segment& seg = strip.getSegment(id); Segment& seg = strip.getSegment(id);
//DEBUG_PRINTF_P(PSTR("-- Original segment: %p (%p)\n"), &seg, seg.data); // we do not want to make segment copy as it may use a lot of RAM (effect data and pixel buffer)
const Segment prev = seg; //make a backup so we can tell if something changed (calling copy constructor) // so we will create a copy of segment options and compare it with original segment when done processing
//DEBUG_PRINTF_P(PSTR("-- Duplicate segment: %p (%p)\n"), &prev, prev.data); SegmentCopy prev = {
{seg.colors[0], seg.colors[1], seg.colors[2]},
seg.start,
seg.stop,
seg.offset,
seg.grouping,
seg.spacing,
seg.startY,
seg.stopY,
seg.options,
seg.mode,
seg.palette,
seg.opacity,
seg.speed,
seg.intensity,
seg.custom1,
seg.custom2,
seg.custom3,
seg.check1,
seg.check2,
seg.check3
};
int start = elem["start"] | seg.start; int start = elem["start"] | seg.start;
if (stop < 0) { if (stop < 0) {
@ -44,7 +125,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
elem.remove("rpt"); // remove for recursive call elem.remove("rpt"); // remove for recursive call
elem.remove("n"); // remove for recursive call elem.remove("n"); // remove for recursive call
unsigned len = stop - start; unsigned len = stop - start;
for (size_t i=id+1; i<strip.getMaxSegments(); i++) { for (size_t i=id+1; i<WS2812FX::getMaxSegments(); i++) {
start = start + len; start = start + len;
if (start >= strip.getLengthTotal()) break; if (start >= strip.getLengthTotal()) break;
//TODO: add support for 2D //TODO: add support for 2D
@ -58,28 +139,11 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
if (elem["n"]) { if (elem["n"]) {
// name field exists // name field exists
if (seg.name) { //clear old name
free(seg.name);
seg.name = nullptr;
}
const char * name = elem["n"].as<const char*>(); const char * name = elem["n"].as<const char*>();
size_t len = 0; seg.setName(name); // will resolve empty and null correctly
if (name != nullptr) len = strlen(name);
if (len > 0) {
if (len > WLED_MAX_SEGNAME_LEN) len = WLED_MAX_SEGNAME_LEN;
seg.name = static_cast<char*>(malloc(len+1));
if (seg.name) strlcpy(seg.name, name, WLED_MAX_SEGNAME_LEN+1);
} else {
// but is empty (already deleted above)
elem.remove("n");
}
} else if (start != seg.start || stop != seg.stop) { } else if (start != seg.start || stop != seg.stop) {
// clearing or setting segment without name field // clearing or setting segment without name field
if (seg.name) { seg.clearName();
free(seg.name);
seg.name = nullptr;
}
} }
uint16_t grp = elem["grp"] | seg.grouping; uint16_t grp = elem["grp"] | seg.grouping;
@ -97,6 +161,12 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
bool transpose = getBoolVal(elem[F("tp")], seg.transpose); bool transpose = getBoolVal(elem[F("tp")], seg.transpose);
#endif #endif
// if segment's virtual dimensions change we need to restart effect (segment blending and PS rely on dimensions)
if (seg.mirror != mirror) seg.markForReset();
#ifndef WLED_DISABLE_2D
if (seg.mirror_y != mirror_y || seg.transpose != transpose) seg.markForReset();
#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;
if (offset != INT32_MAX) { if (offset != INT32_MAX) {
@ -118,8 +188,8 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
} }
byte segbri = seg.opacity; byte segbri = seg.opacity;
if (getVal(elem["bri"], &segbri)) { if (getVal(elem["bri"], segbri)) {
if (segbri > 0) seg.setOpacity(segbri); if (segbri > 0) seg.setOpacity(segbri); // use transition
seg.setOption(SEG_OPTION_ON, segbri); // use transition seg.setOption(SEG_OPTION_ON, segbri); // use transition
} }
@ -175,13 +245,13 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
if (!colValid) continue; if (!colValid) continue;
seg.setColor(i, RGBW32(rgbw[0],rgbw[1],rgbw[2],rgbw[3])); seg.setColor(i, RGBW32(rgbw[0],rgbw[1],rgbw[2],rgbw[3])); // use transition
if (seg.mode == FX_MODE_STATIC) strip.trigger(); //instant refresh if (seg.mode == FX_MODE_STATIC) strip.trigger(); //instant refresh
} }
} else { } else {
// non RGB & non White segment (usually On/Off bus) // non RGB & non White segment (usually On/Off bus)
seg.setColor(0, ULTRAWHITE); seg.setColor(0, ULTRAWHITE); // use transition
seg.setColor(1, BLACK); seg.setColor(1, BLACK); // use transition
} }
} }
@ -197,7 +267,6 @@ 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.set = constrain(set, 0, 3);
seg.soundSim = constrain(soundSim, 0, 3); seg.soundSim = constrain(soundSim, 0, 3);
seg.selected = selected; seg.selected = selected;
@ -210,57 +279,58 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
#endif #endif
byte fx = seg.mode; byte fx = seg.mode;
if (getVal(elem["fx"], &fx, 0, strip.getModeCount())) { if (getVal(elem["fx"], fx, 0, strip.getModeCount())) {
if (!presetId && currentPlaylist>=0) unloadPlaylist(); if (!presetId && currentPlaylist>=0) unloadPlaylist();
if (fx != seg.mode) seg.setMode(fx, elem[F("fxdef")]); if (fx != seg.mode) seg.setMode(fx, elem[F("fxdef")]); // use transition (WARNING: may change map1D2D causing geometry change)
} }
getVal(elem["sx"], &seg.speed); getVal(elem["sx"], seg.speed);
getVal(elem["ix"], &seg.intensity); getVal(elem["ix"], seg.intensity);
uint8_t pal = seg.palette; uint8_t pal = seg.palette;
if (seg.getLightCapabilities() & 1) { // ignore palette for White and On/Off segments if (seg.getLightCapabilities() & 1) { // ignore palette for White and On/Off segments
if (getVal(elem["pal"], &pal, 0, strip.getPaletteCount())) seg.setPalette(pal); if (getVal(elem["pal"], pal, 0, getPaletteCount())) seg.setPalette(pal);
} }
getVal(elem["c1"], &seg.custom1); getVal(elem["c1"], seg.custom1);
getVal(elem["c2"], &seg.custom2); getVal(elem["c2"], seg.custom2);
uint8_t cust3 = seg.custom3; uint8_t cust3 = seg.custom3;
getVal(elem["c3"], &cust3, 0, 31); // we can't pass reference to bitfield getVal(elem["c3"], cust3, 0, 31); // we can't pass reference to bitfield
seg.custom3 = constrain(cust3, 0, 31); seg.custom3 = constrain(cust3, 0, 31);
seg.check1 = getBoolVal(elem["o1"], seg.check1); seg.check1 = getBoolVal(elem["o1"], seg.check1);
seg.check2 = getBoolVal(elem["o2"], seg.check2); seg.check2 = getBoolVal(elem["o2"], seg.check2);
seg.check3 = getBoolVal(elem["o3"], seg.check3); seg.check3 = getBoolVal(elem["o3"], seg.check3);
uint8_t blend = seg.blendMode;
getVal(elem["bm"], blend, 0, 15); // we can't pass reference to bitfield
seg.blendMode = constrain(blend, 0, 15);
JsonArray iarr = elem[F("i")]; //set individual LEDs JsonArray iarr = elem[F("i")]; //set individual LEDs
if (!iarr.isNull()) { if (!iarr.isNull()) {
uint8_t oldMap1D2D = seg.map1D2D;
seg.map1D2D = M12_Pixels; // no mapping
// set brightness immediately and disable transition // set brightness immediately and disable transition
jsonTransitionOnce = true; jsonTransitionOnce = true;
seg.stopTransition(); if (seg.isInTransition()) seg.startTransition(0); // setting transition time to 0 will stop transition in next frame
strip.setTransition(0); strip.setTransition(0);
strip.setBrightness(scaledBri(bri), true); strip.setBrightness(scaledBri(bri), true);
// freeze and init to black // freeze and init to black
if (!seg.freeze) { if (!seg.freeze) {
seg.freeze = true; seg.freeze = true;
seg.fill(BLACK); seg.clear();
} }
start = 0, stop = 0; unsigned iStart = 0, iStop = 0;
set = 0; //0 nothing set, 1 start set, 2 range set unsigned iSet = 0; //0 nothing set, 1 start set, 2 range set
for (size_t i = 0; i < iarr.size(); i++) { for (size_t i = 0; i < iarr.size(); i++) {
if(iarr[i].is<JsonInteger>()) { if (iarr[i].is<JsonInteger>()) {
if (!set) { if (!iSet) {
start = abs(iarr[i].as<int>()); iStart = abs(iarr[i].as<int>());
set++; iSet++;
} else { } else {
stop = abs(iarr[i].as<int>()); iStop = abs(iarr[i].as<int>());
set++; iSet++;
} }
} else { //color } else { //color
uint8_t rgbw[] = {0,0,0,0}; uint8_t rgbw[] = {0,0,0,0};
@ -276,17 +346,16 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
} }
} }
if (set < 2 || stop <= start) stop = start + 1; if (iSet < 2 || iStop <= iStart) iStop = iStart + 1;
uint32_t c = gamma32(RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3])); uint32_t c = RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3]);
while (start < stop) seg.setPixelColor(start++, c); while (iStart < iStop) seg.setRawPixelColor(iStart++, c); // sets pixel color without 1D->2D expansion, grouping or spacing
set = 0; iSet = 0;
} }
} }
seg.map1D2D = oldMap1D2D; // restore mapping
strip.trigger(); // force segment update strip.trigger(); // force segment update
} }
// send UDP/WS if segment options changed (except selection; will also deselect current preset) // send UDP/WS if segment options changed (except selection; will also deselect current preset)
if (seg.differs(prev) & 0x7F) stateChanged = true; if (differs(seg, prev) & ~SEG_DIFFERS_SEL) stateChanged = true;
return true; return true;
} }
@ -302,7 +371,8 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
#endif #endif
bool onBefore = bri; bool onBefore = bri;
getVal(root["bri"], &bri); getVal(root["bri"], bri);
if (bri != briOld) stateChanged = true;
bool on = root["on"] | (bri > 0); bool on = root["on"] | (bri > 0);
if (!on != !bri) toggleOnOff(); if (!on != !bri) toggleOnOff();
@ -329,10 +399,8 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
} }
} }
#ifndef WLED_DISABLE_MODE_BLEND
blendingStyle = root[F("bs")] | blendingStyle; blendingStyle = root[F("bs")] | blendingStyle;
blendingStyle &= 0x1F; blendingStyle &= 0x1F;
#endif
// temporary transition (applies only once) // temporary transition (applies only once)
tr = root[F("tt")] | -1; tr = root[F("tt")] | -1;
@ -345,6 +413,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
if (tr >= 0) strip.timebase = (unsigned long)tr - millis(); if (tr >= 0) strip.timebase = (unsigned long)tr - millis();
JsonObject nl = root["nl"]; JsonObject nl = root["nl"];
if (!nl.isNull()) stateChanged = true;
nightlightActive = getBoolVal(nl["on"], nightlightActive); nightlightActive = getBoolVal(nl["on"], nightlightActive);
nightlightDelayMins = nl["dur"] | nightlightDelayMins; nightlightDelayMins = nl["dur"] | nightlightDelayMins;
nightlightMode = nl["mode"] | nightlightMode; nightlightMode = nl["mode"] | nightlightMode;
@ -371,6 +440,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS; if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS;
if (realtimeMode && useMainSegmentOnly) { if (realtimeMode && useMainSegmentOnly) {
strip.getMainSegment().freeze = !realtimeOverride; strip.getMainSegment().freeze = !realtimeOverride;
realtimeOverride = REALTIME_OVERRIDE_NONE; // ignore request for override if using main segment only
} }
if (root.containsKey("live")) { if (root.containsKey("live")) {
@ -388,18 +458,14 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
if (!segVar.isNull()) { if (!segVar.isNull()) {
// we may be called during strip.service() so we must not modify segments while effects are executing // we may be called during strip.service() so we must not modify segments while effects are executing
strip.suspend(); strip.suspend();
const unsigned long start = millis(); strip.waitForIt();
while (strip.isServicing() && millis() - start < strip.getFrameTime()) yield(); // wait until frame is over
#ifdef WLED_DEBUG
if (millis() - start > 0) DEBUG_PRINTLN(F("JSON: Waited for strip to finish servicing."));
#endif
if (segVar.is<JsonObject>()) { if (segVar.is<JsonObject>()) {
int id = segVar["id"] | -1; int id = segVar["id"] | -1;
//if "seg" is not an array and ID not specified, apply to all selected/checked segments //if "seg" is not an array and ID not specified, apply to all selected/checked segments
if (id < 0) { if (id < 0) {
//apply all selected segments //apply all selected segments
for (size_t s = 0; s < strip.getSegmentsNum(); s++) { for (size_t s = 0; s < strip.getSegmentsNum(); s++) {
Segment &sg = strip.getSegment(s); const Segment &sg = strip.getSegment(s);
if (sg.isActive() && sg.isSelected()) { if (sg.isActive() && sg.isSelected()) {
deserializeSegment(segVar, s, presetId); deserializeSegment(segVar, s, presetId);
} }
@ -449,7 +515,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
DEBUG_PRINTF_P(PSTR("Preset direct: %d\n"), currentPreset); DEBUG_PRINTF_P(PSTR("Preset direct: %d\n"), currentPreset);
} else if (!root["ps"].isNull()) { } else if (!root["ps"].isNull()) {
// we have "ps" call (i.e. from button or external API call) or "pd" that includes "ps" (i.e. from UI call) // we have "ps" call (i.e. from button or external API call) or "pd" that includes "ps" (i.e. from UI call)
if (root["win"].isNull() && getVal(root["ps"], &presetCycCurr, 1, 250) && presetCycCurr > 0 && presetCycCurr < 251 && presetCycCurr != currentPreset) { if (root["win"].isNull() && getVal(root["ps"], presetCycCurr, 1, 250) && presetCycCurr > 0 && presetCycCurr < 251 && presetCycCurr != currentPreset) {
DEBUG_PRINTF_P(PSTR("Preset select: %d\n"), presetCycCurr); DEBUG_PRINTF_P(PSTR("Preset select: %d\n"), presetCycCurr);
// b) preset ID only or preset that does not change state (use embedded cycling limits if they exist in getVal()) // b) preset ID only or preset that does not change state (use embedded cycling limits if they exist in getVal())
applyPreset(presetCycCurr, callMode); // async load from file system (only preset ID was specified) applyPreset(presetCycCurr, callMode); // async load from file system (only preset ID was specified)
@ -465,11 +531,11 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
} }
if (root.containsKey(F("rmcpal")) && root[F("rmcpal")].as<bool>()) { if (root.containsKey(F("rmcpal")) && root[F("rmcpal")].as<bool>()) {
if (strip.customPalettes.size()) { if (customPalettes.size()) {
char fileName[32]; char fileName[32];
sprintf_P(fileName, PSTR("/palette%d.json"), strip.customPalettes.size()-1); sprintf_P(fileName, PSTR("/palette%d.json"), customPalettes.size()-1);
if (WLED_FS.exists(fileName)) WLED_FS.remove(fileName); if (WLED_FS.exists(fileName)) WLED_FS.remove(fileName);
strip.loadCustomPalettes(); loadCustomPalettes();
} }
} }
@ -488,13 +554,13 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
//if (restart) forceReconnect = true; //if (restart) forceReconnect = true;
} }
stateUpdated(callMode); if (stateChanged) stateUpdated(callMode);
if (presetToRestore) currentPreset = presetToRestore; if (presetToRestore) currentPreset = presetToRestore;
return stateResponse; return stateResponse;
} }
void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool forPreset, bool segmentBounds) static void serializeSegment(JsonObject& root, const Segment& seg, byte id, bool forPreset, bool segmentBounds)
{ {
root["id"] = id; root["id"] = id;
if (segmentBounds) { if (segmentBounds) {
@ -517,6 +583,7 @@ void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool
root["bri"] = (segbri) ? segbri : 255; root["bri"] = (segbri) ? segbri : 255;
root["cct"] = seg.cct; root["cct"] = seg.cct;
root[F("set")] = seg.set; root[F("set")] = seg.set;
root["lc"] = seg.getLightCapabilities();
if (seg.name != nullptr) root["n"] = reinterpret_cast<const char *>(seg.name); //not good practice, but decreases required JSON buffer if (seg.name != nullptr) root["n"] = reinterpret_cast<const char *>(seg.name); //not good practice, but decreases required JSON buffer
else if (forPreset) root["n"] = ""; else if (forPreset) root["n"] = "";
@ -561,6 +628,7 @@ void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool
root["o3"] = seg.check3; root["o3"] = seg.check3;
root["si"] = seg.soundSim; root["si"] = seg.soundSim;
root["m12"] = seg.map1D2D; root["m12"] = seg.map1D2D;
root["bm"] = seg.blendMode;
} }
void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segmentBounds, bool selectedSegmentsOnly) void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segmentBounds, bool selectedSegmentsOnly)
@ -569,9 +637,7 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme
root["on"] = (bri > 0); root["on"] = (bri > 0);
root["bri"] = briLast; root["bri"] = briLast;
root[F("transition")] = transitionDelay/100; //in 100ms root[F("transition")] = transitionDelay/100; //in 100ms
#ifndef WLED_DISABLE_MODE_BLEND
root[F("bs")] = blendingStyle; root[F("bs")] = blendingStyle;
#endif
} }
if (!forPreset) { if (!forPreset) {
@ -602,7 +668,7 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme
root[F("mainseg")] = strip.getMainSegmentId(); root[F("mainseg")] = strip.getMainSegmentId();
JsonArray seg = root.createNestedArray("seg"); JsonArray seg = root.createNestedArray("seg");
for (size_t s = 0; s < strip.getMaxSegments(); s++) { for (size_t s = 0; s < WS2812FX::getMaxSegments(); s++) {
if (s >= strip.getSegmentsNum()) { if (s >= strip.getSegmentsNum()) {
if (forPreset && segmentBounds && !selectedSegmentsOnly) { //disable segments not part of preset if (forPreset && segmentBounds && !selectedSegmentsOnly) { //disable segments not part of preset
JsonObject seg0 = seg.createNestedObject(); JsonObject seg0 = seg.createNestedObject();
@ -611,7 +677,7 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme
} else } else
break; break;
} }
Segment &sg = strip.getSegment(s); const Segment &sg = strip.getSegment(s);
if (forPreset && selectedSegmentsOnly && !sg.isSelected()) continue; if (forPreset && selectedSegmentsOnly && !sg.isSelected()) continue;
if (sg.isActive()) { if (sg.isActive()) {
JsonObject seg0 = seg.createNestedObject(); JsonObject seg0 = seg.createNestedObject();
@ -635,7 +701,7 @@ void serializeInfo(JsonObject root)
leds[F("pwr")] = BusManager::currentMilliamps(); leds[F("pwr")] = BusManager::currentMilliamps();
leds["fps"] = strip.getFps(); leds["fps"] = strip.getFps();
leds[F("maxpwr")] = BusManager::currentMilliamps()>0 ? BusManager::ablMilliampsMax() : 0; leds[F("maxpwr")] = BusManager::currentMilliamps()>0 ? BusManager::ablMilliampsMax() : 0;
leds[F("maxseg")] = strip.getMaxSegments(); leds[F("maxseg")] = WS2812FX::getMaxSegments();
//leds[F("actseg")] = strip.getActiveSegmentsNum(); //leds[F("actseg")] = strip.getActiveSegmentsNum();
//leds[F("seglock")] = false; //might be used in the future to prevent modifications to segment config //leds[F("seglock")] = false; //might be used in the future to prevent modifications to segment config
leds[F("bootps")] = bootPreset; leds[F("bootps")] = bootPreset;
@ -649,13 +715,13 @@ void serializeInfo(JsonObject root)
#endif #endif
unsigned totalLC = 0; unsigned totalLC = 0;
JsonArray lcarr = leds.createNestedArray(F("seglc")); JsonArray lcarr = leds.createNestedArray(F("seglc")); // deprecated, use state.seg[].lc
size_t nSegs = strip.getSegmentsNum(); size_t nSegs = strip.getSegmentsNum();
for (size_t s = 0; s < nSegs; s++) { for (size_t s = 0; s < nSegs; s++) {
if (!strip.getSegment(s).isActive()) continue; if (!strip.getSegment(s).isActive()) continue;
unsigned lc = strip.getSegment(s).getLightCapabilities(); unsigned lc = strip.getSegment(s).getLightCapabilities();
totalLC |= lc; totalLC |= lc;
lcarr.add(lc); lcarr.add(lc); // deprecated, use state.seg[].lc
} }
leds["lc"] = totalLC; leds["lc"] = totalLC;
@ -703,8 +769,8 @@ void serializeInfo(JsonObject root)
#endif #endif
root[F("fxcount")] = strip.getModeCount(); root[F("fxcount")] = strip.getModeCount();
root[F("palcount")] = strip.getPaletteCount(); root[F("palcount")] = getPaletteCount();
root[F("cpalcount")] = strip.customPalettes.size(); //number of custom palettes root[F("cpalcount")] = customPalettes.size(); //number of custom palettes
JsonArray ledmaps = root.createNestedArray(F("maps")); JsonArray ledmaps = root.createNestedArray(F("maps"));
for (size_t i=0; i<WLED_MAX_LEDMAPS; i++) { for (size_t i=0; i<WLED_MAX_LEDMAPS; i++) {
@ -867,15 +933,15 @@ void serializePalettes(JsonObject root, int page)
int itemPerPage = 8; int itemPerPage = 8;
#endif #endif
int customPalettes = strip.customPalettes.size(); int customPalettesCount = customPalettes.size();
int palettesCount = strip.getPaletteCount() - customPalettes; int palettesCount = getPaletteCount() - customPalettesCount;
int maxPage = (palettesCount + customPalettes -1) / itemPerPage; int maxPage = (palettesCount + customPalettesCount -1) / itemPerPage;
if (page > maxPage) page = maxPage; if (page > maxPage) page = maxPage;
int start = itemPerPage * page; int start = itemPerPage * page;
int end = start + itemPerPage; int end = start + itemPerPage;
if (end > palettesCount + customPalettes) end = palettesCount + customPalettes; if (end > palettesCount + customPalettesCount) end = palettesCount + customPalettesCount;
root[F("m")] = maxPage; // inform caller how many pages there are root[F("m")] = maxPage; // inform caller how many pages there are
JsonObject palettes = root.createNestedObject("p"); JsonObject palettes = root.createNestedObject("p");
@ -911,7 +977,7 @@ void serializePalettes(JsonObject root, int page)
break; break;
default: default:
if (i >= palettesCount) if (i >= palettesCount)
setPaletteColors(curPalette, strip.customPalettes[i - palettesCount]); setPaletteColors(curPalette, customPalettes[i - palettesCount]);
else if (i < 13) // palette 6 - 12, fastled palettes else if (i < 13) // palette 6 - 12, fastled palettes
setPaletteColors(curPalette, *fastledPalettes[i-6]); setPaletteColors(curPalette, *fastledPalettes[i-6]);
else { else {

View File

@ -4,11 +4,9 @@
* LED methods * LED methods
*/ */
void setValuesFromMainSeg() { setValuesFromSegment(strip.getMainSegmentId()); } // applies chosen setment properties to legacy values
void setValuesFromFirstSelectedSeg() { setValuesFromSegment(strip.getFirstSelectedSegId()); } void setValuesFromSegment(uint8_t s) {
void setValuesFromSegment(uint8_t s) const Segment& seg = strip.getSegment(s);
{
Segment& seg = strip.getSegment(s);
colPri[0] = R(seg.colors[0]); colPri[0] = R(seg.colors[0]);
colPri[1] = G(seg.colors[0]); colPri[1] = G(seg.colors[0]);
colPri[2] = B(seg.colors[0]); colPri[2] = B(seg.colors[0]);
@ -24,25 +22,19 @@ void setValuesFromSegment(uint8_t s)
} }
// applies global legacy values (col, colSec, effectCurrent...) // applies global legacy values (colPri, colSec, effectCurrent...) to each selected segment
// problem: if the first selected segment already has the value to be set, other selected segments are not updated void applyValuesToSelectedSegs() {
void applyValuesToSelectedSegs()
{
// copy of first selected segment to tell if value was updated
unsigned firstSel = strip.getFirstSelectedSegId();
Segment selsegPrev = strip.getSegment(firstSel);
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 (i != firstSel && (!seg.isActive() || !seg.isSelected())) continue; if (!(seg.isActive() && seg.isSelected())) continue;
if (effectSpeed != seg.speed) {seg.speed = effectSpeed; stateChanged = true;}
if (effectSpeed != selsegPrev.speed) {seg.speed = effectSpeed; stateChanged = true;} if (effectIntensity != seg.intensity) {seg.intensity = effectIntensity; stateChanged = true;}
if (effectIntensity != selsegPrev.intensity) {seg.intensity = effectIntensity; stateChanged = true;} if (effectPalette != seg.palette) {seg.setPalette(effectPalette);}
if (effectPalette != selsegPrev.palette) {seg.setPalette(effectPalette);} if (effectCurrent != seg.mode) {seg.setMode(effectCurrent);}
if (effectCurrent != selsegPrev.mode) {seg.setMode(effectCurrent);}
uint32_t col0 = RGBW32(colPri[0], colPri[1], colPri[2], colPri[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 != seg.colors[0]) {seg.setColor(0, col0);}
if (col1 != selsegPrev.colors[1]) {seg.setColor(1, col1);} if (col1 != seg.colors[1]) {seg.setColor(1, col1);}
} }
} }
@ -73,7 +65,8 @@ byte scaledBri(byte in)
//applies global temporary brightness (briT) to strip //applies global temporary brightness (briT) to strip
void applyBri() { void applyBri() {
if (!(realtimeMode && arlsForceMaxBri)) { if (realtimeOverride || !(realtimeMode && arlsForceMaxBri))
{
//DEBUG_PRINTF_P(PSTR("Applying strip brightness: %d (%d,%d)\n"), (int)briT, (int)bri, (int)briOld); //DEBUG_PRINTF_P(PSTR("Applying strip brightness: %d (%d,%d)\n"), (int)briT, (int)bri, (int)briOld);
strip.setBrightness(scaledBri(briT)); strip.setBrightness(scaledBri(briT));
} }
@ -94,7 +87,7 @@ void applyFinalBri() {
void stateUpdated(byte callMode) { void stateUpdated(byte callMode) {
//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)
// 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa 11: ws send only 12: button preset // 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa 11: ws send only 12: button preset
setValuesFromFirstSelectedSeg(); setValuesFromFirstSelectedSeg(); // a much better approach would be to use main segment: setValuesFromMainSeg()
if (bri != briOld || stateChanged) { if (bri != briOld || stateChanged) {
if (stateChanged) currentPreset = 0; //something changed, so we are no longer in the preset if (stateChanged) currentPreset = 0; //something changed, so we are no longer in the preset
@ -104,7 +97,6 @@ void stateUpdated(byte callMode) {
//set flag to update ws and mqtt //set flag to update ws and mqtt
interfaceUpdateCallMode = callMode; interfaceUpdateCallMode = callMode;
stateChanged = false;
} else { } else {
if (nightlightActive && !nightlightActiveOld && callMode != CALL_MODE_NOTIFICATION && callMode != CALL_MODE_NO_NOTIFY) { if (nightlightActive && !nightlightActiveOld && callMode != CALL_MODE_NOTIFICATION && callMode != CALL_MODE_NO_NOTIFY) {
notify(CALL_MODE_NIGHTLIGHT); notify(CALL_MODE_NIGHTLIGHT);
@ -134,15 +126,16 @@ void stateUpdated(byte callMode) {
jsonTransitionOnce = false; jsonTransitionOnce = false;
transitionActive = false; transitionActive = false;
applyFinalBri(); applyFinalBri();
return; strip.trigger();
} else {
if (transitionActive) {
briOld = briT;
} else if (bri != briOld || stateChanged)
strip.setTransitionMode(true); // force all segments to transition mode
transitionActive = true;
transitionStartTime = now;
} }
stateChanged = false;
if (transitionActive) {
briOld = briT;
} else
strip.setTransitionMode(true); // force all segments to transition mode
transitionActive = true;
transitionStartTime = now;
} }

View File

@ -68,8 +68,8 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp
} }
if (index == 0) { // start (1st partial packet or the only packet) if (index == 0) { // start (1st partial packet or the only packet)
if (payloadStr) free(payloadStr); // fail-safe: release buffer w_free(payloadStr); // release buffer if it exists
payloadStr = static_cast<char*>(malloc(total+1)); // allocate new buffer payloadStr = static_cast<char*>(w_malloc(total+1)); // allocate new buffer
} }
if (payloadStr == nullptr) return; // buffer not allocated if (payloadStr == nullptr) return; // buffer not allocated
@ -94,7 +94,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp
} else { } else {
// Non-Wled Topic used here. Probably a usermod subscribed to this topic. // Non-Wled Topic used here. Probably a usermod subscribed to this topic.
UsermodManager::onMqttMessage(topic, payloadStr); UsermodManager::onMqttMessage(topic, payloadStr);
free(payloadStr); w_free(payloadStr);
payloadStr = nullptr; payloadStr = nullptr;
return; return;
} }
@ -124,7 +124,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp
// topmost topic (just wled/MAC) // topmost topic (just wled/MAC)
parseMQTTBriPayload(payloadStr); parseMQTTBriPayload(payloadStr);
} }
free(payloadStr); w_free(payloadStr);
payloadStr = nullptr; payloadStr = nullptr;
} }
@ -196,7 +196,8 @@ bool initMqtt()
if (!mqttEnabled || mqttServer[0] == 0 || !WLED_CONNECTED) return false; if (!mqttEnabled || mqttServer[0] == 0 || !WLED_CONNECTED) return false;
if (mqtt == nullptr) { if (mqtt == nullptr) {
mqtt = new AsyncMqttClient(); void *ptr = w_malloc(sizeof(AsyncMqttClient));
mqtt = new (ptr) AsyncMqttClient(); // use placement new (into PSRAM), client will never be deleted
if (!mqtt) return false; if (!mqtt) return false;
mqtt->onMessage(onMqttMessage); mqtt->onMessage(onMqttMessage);
mqtt->onConnect(onMqttConnect); mqtt->onConnect(onMqttConnect);

View File

@ -90,9 +90,8 @@ void _overlayAnalogCountdown()
void handleOverlayDraw() { void handleOverlayDraw() {
UsermodManager::handleOverlayDraw(); UsermodManager::handleOverlayDraw();
if (analogClockSolidBlack) { if (analogClockSolidBlack) {
const Segment* segments = strip.getSegments();
for (unsigned i = 0; i < strip.getSegmentsNum(); i++) { for (unsigned i = 0; i < strip.getSegmentsNum(); i++) {
const Segment& segment = segments[i]; const Segment& segment = strip.getSegment(i);
if (!segment.isActive()) continue; if (!segment.isActive()) continue;
if (segment.mode > 0 || segment.colors[0] > 0) { if (segment.mode > 0 || segment.colors[0] > 0) {
return; return;

1117
wled00/palettes.h Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@ -29,8 +29,9 @@ bool presetNeedsSaving() {
static void doSaveState() { static void doSaveState() {
bool persist = (presetToSave < 251); bool persist = (presetToSave < 251);
unsigned long start = millis(); unsigned long maxWait = millis() + strip.getFrameTime();
while (strip.isUpdating() && millis()-start < (2*FRAMETIME_FIXED)+1) yield(); // wait 2 frames while (strip.isUpdating() && millis() < maxWait) delay(1); // wait for strip to finish updating, accessing FS during sendout causes glitches
if (!requestJSONBufferLock(10)) return; if (!requestJSONBufferLock(10)) return;
initPresetsFile(); // just in case if someone deleted presets.json using /edit initPresetsFile(); // just in case if someone deleted presets.json using /edit
@ -56,14 +57,10 @@ static void doSaveState() {
*/ */
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
if (!persist) { if (!persist) {
if (tmpRAMbuffer!=nullptr) free(tmpRAMbuffer); w_free(tmpRAMbuffer);
size_t len = measureJson(*pDoc) + 1; size_t len = measureJson(*pDoc) + 1;
DEBUG_PRINTLN(len);
// if possible use SPI RAM on ESP32 // if possible use SPI RAM on ESP32
if (psramSafe && psramFound()) tmpRAMbuffer = (char*)w_malloc(len);
tmpRAMbuffer = (char*) ps_malloc(len);
else
tmpRAMbuffer = (char*) malloc(len);
if (tmpRAMbuffer!=nullptr) { if (tmpRAMbuffer!=nullptr) {
serializeJson(*pDoc, tmpRAMbuffer, len); serializeJson(*pDoc, tmpRAMbuffer, len);
} else { } else {
@ -80,8 +77,8 @@ static void doSaveState() {
// clean up // clean up
saveLedmap = -1; saveLedmap = -1;
presetToSave = 0; presetToSave = 0;
free(saveName); w_free(saveName);
free(quickLoad); w_free(quickLoad);
saveName = nullptr; saveName = nullptr;
quickLoad = nullptr; quickLoad = nullptr;
playlistSave = false; playlistSave = false;
@ -168,9 +165,9 @@ void handlePresets()
DEBUG_PRINTF_P(PSTR("Applying preset: %u\n"), (unsigned)tmpPreset); DEBUG_PRINTF_P(PSTR("Applying preset: %u\n"), (unsigned)tmpPreset);
#if defined(ARDUINO_ARCH_ESP32S3) || defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32C3) #if defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32C3)
unsigned long start = millis(); unsigned long maxWait = millis() + strip.getFrameTime();
while (strip.isUpdating() && millis() - start < FRAMETIME_FIXED) yield(); // wait for strip to finish updating, accessing FS during sendout causes glitches while (strip.isUpdating() && millis() < maxWait) delay(1); // wait for strip to finish updating, accessing FS during sendout causes glitches
#endif #endif
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
@ -206,7 +203,7 @@ void handlePresets()
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
//Aircoookie recommended not to delete buffer //Aircoookie recommended not to delete buffer
if (tmpPreset==255 && tmpRAMbuffer!=nullptr) { if (tmpPreset==255 && tmpRAMbuffer!=nullptr) {
free(tmpRAMbuffer); w_free(tmpRAMbuffer);
tmpRAMbuffer = nullptr; tmpRAMbuffer = nullptr;
} }
#endif #endif
@ -220,8 +217,8 @@ void handlePresets()
//called from handleSet(PS=) [network callback (sObj is empty), IR (irrational), deserializeState, UDP] and deserializeState() [network callback (filedoc!=nullptr)] //called from handleSet(PS=) [network callback (sObj is empty), IR (irrational), deserializeState, UDP] and deserializeState() [network callback (filedoc!=nullptr)]
void savePreset(byte index, const char* pname, JsonObject sObj) void savePreset(byte index, const char* pname, JsonObject sObj)
{ {
if (!saveName) saveName = static_cast<char*>(malloc(33)); if (!saveName) saveName = static_cast<char*>(w_malloc(33));
if (!quickLoad) quickLoad = static_cast<char*>(malloc(9)); if (!quickLoad) quickLoad = static_cast<char*>(w_malloc(9));
if (!saveName || !quickLoad) return; if (!saveName || !quickLoad) return;
if (index == 0 || (index > 250 && index < 255)) return; if (index == 0 || (index > 250 && index < 255)) return;
@ -267,8 +264,8 @@ void savePreset(byte index, const char* pname, JsonObject sObj)
presetsModifiedTime = toki.second(); //unix time presetsModifiedTime = toki.second(); //unix time
updateFSInfo(); updateFSInfo();
} }
free(saveName); w_free(saveName);
free(quickLoad); w_free(quickLoad);
saveName = nullptr; saveName = nullptr;
quickLoad = nullptr; quickLoad = nullptr;
} else { } else {

View File

@ -28,7 +28,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
char gw[5] = "GW"; gw[2] = 48+n; gw[4] = 0; //GW address char gw[5] = "GW"; gw[2] = 48+n; gw[4] = 0; //GW address
char sn[5] = "SN"; sn[2] = 48+n; sn[4] = 0; //subnet mask char sn[5] = "SN"; sn[2] = 48+n; sn[4] = 0; //subnet mask
if (request->hasArg(cs)) { if (request->hasArg(cs)) {
if (n >= multiWiFi.size()) multiWiFi.push_back(WiFiConfig()); // expand vector by one if (n >= multiWiFi.size()) multiWiFi.emplace_back(); // expand vector by one
char oldSSID[33]; strcpy(oldSSID, multiWiFi[n].clientSSID); char oldSSID[33]; strcpy(oldSSID, multiWiFi[n].clientSSID);
char oldPass[65]; strcpy(oldPass, multiWiFi[n].clientPass); char oldPass[65]; strcpy(oldPass, multiWiFi[n].clientPass);
@ -129,6 +129,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
unsigned length, start, maMax; unsigned length, start, maMax;
uint8_t pins[5] = {255, 255, 255, 255, 255}; uint8_t pins[5] = {255, 255, 255, 255, 255};
// this will set global ABL max current used when per-port ABL is not used
unsigned ablMilliampsMax = request->arg(F("MA")).toInt(); unsigned ablMilliampsMax = request->arg(F("MA")).toInt();
BusManager::setMilliampsMax(ablMilliampsMax); BusManager::setMilliampsMax(ablMilliampsMax);
@ -136,10 +137,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
strip.correctWB = request->hasArg(F("CCT")); strip.correctWB = request->hasArg(F("CCT"));
strip.cctFromRgb = request->hasArg(F("CR")); strip.cctFromRgb = request->hasArg(F("CR"));
cctICused = request->hasArg(F("IC")); cctICused = request->hasArg(F("IC"));
Bus::setCCTBlend(request->arg(F("CB")).toInt()); uint8_t cctBlending = request->arg(F("CB")).toInt();
Bus::setCCTBlend(cctBlending);
Bus::setGlobalAWMode(request->arg(F("AW")).toInt()); Bus::setGlobalAWMode(request->arg(F("AW")).toInt());
strip.setTargetFps(request->arg(F("FR")).toInt()); strip.setTargetFps(request->arg(F("FR")).toInt());
useGlobalLedBuffer = request->hasArg(F("LD"));
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
useParallelI2S = request->hasArg(F("PR")); useParallelI2S = request->hasArg(F("PR"));
#endif #endif
@ -207,12 +208,12 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
maMax = 0; maMax = 0;
} else { } else {
maPerLed = request->arg(la).toInt(); maPerLed = request->arg(la).toInt();
maMax = request->arg(ma).toInt(); // if ABL is disabled this will be 0 maMax = request->arg(ma).toInt() * request->hasArg(F("PPL")); // if PP-ABL is disabled maMax (per bus) must be 0
} }
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.emplace_back(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, 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
@ -334,6 +335,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
t = request->arg(F("TP")).toInt(); t = request->arg(F("TP")).toInt();
randomPaletteChangeTime = MIN(255,MAX(1,t)); randomPaletteChangeTime = MIN(255,MAX(1,t));
useHarmonicRandomPalette = request->hasArg(F("TH")); useHarmonicRandomPalette = request->hasArg(F("TH"));
useRainbowWheel = request->hasArg(F("RW"));
nightlightTargetBri = request->arg(F("TB")).toInt(); nightlightTargetBri = request->arg(F("TB")).toInt();
t = request->arg(F("TL")).toInt(); t = request->arg(F("TL")).toInt();
@ -342,7 +344,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
nightlightMode = request->arg(F("TW")).toInt(); nightlightMode = request->arg(F("TW")).toInt();
t = request->arg(F("PB")).toInt(); t = request->arg(F("PB")).toInt();
if (t >= 0 && t < 4) strip.paletteBlend = t; if (t >= 0 && t < 4) paletteBlend = t;
t = request->arg(F("BF")).toInt(); t = request->arg(F("BF")).toInt();
if (t > 0) briMultiplier = t; if (t > 0) briMultiplier = t;
@ -358,7 +360,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
DEBUG_PRINTLN(F("Enumerating ledmaps")); DEBUG_PRINTLN(F("Enumerating ledmaps"));
enumerateLedmaps(); enumerateLedmaps();
DEBUG_PRINTLN(F("Loading custom palettes")); DEBUG_PRINTLN(F("Loading custom palettes"));
strip.loadCustomPalettes(); // (re)load all custom palettes loadCustomPalettes(); // (re)load all custom palettes
} }
//SYNC //SYNC
@ -771,14 +773,14 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
if (subPage == SUBPAGE_2D) if (subPage == SUBPAGE_2D)
{ {
strip.isMatrix = request->arg(F("SOMP")).toInt(); strip.isMatrix = request->arg(F("SOMP")).toInt();
strip.panel.clear(); // release memory if allocated strip.panel.clear();
if (strip.isMatrix) { if (strip.isMatrix) {
strip.panels = MAX(1,MIN(WLED_MAX_PANELS,request->arg(F("MPC")).toInt())); unsigned panels = constrain(request->arg(F("MPC")).toInt(), 1, WLED_MAX_PANELS);
strip.panel.reserve(strip.panels); // pre-allocate memory strip.panel.reserve(panels); // pre-allocate memory
for (unsigned i=0; i<strip.panels; i++) { for (unsigned i=0; i<panels; i++) {
WS2812FX::Panel p; WS2812FX::Panel p;
char pO[8] = { '\0' }; char pO[8] = { '\0' };
snprintf_P(pO, 7, PSTR("P%d"), i); // MAX_PANELS is 64 so pO will always only be 4 characters or less snprintf_P(pO, 7, PSTR("P%d"), i); // WLED_MAX_PANELS is less than 100 so pO will always only be 4 characters or less
pO[7] = '\0'; pO[7] = '\0';
unsigned l = strlen(pO); unsigned l = strlen(pO);
// create P0B, P1B, ..., P63B, etc for other PxxX // create P0B, P1B, ..., P63B, etc for other PxxX
@ -793,13 +795,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
pO[l] = 'H'; p.height = request->arg(pO).toInt(); pO[l] = 'H'; p.height = request->arg(pO).toInt();
strip.panel.push_back(p); strip.panel.push_back(p);
} }
strip.setUpMatrix(); // will check limits
strip.makeAutoSegments(true);
strip.deserializeMap();
} else {
Segment::maxWidth = strip.getLengthTotal();
Segment::maxHeight = 1;
} }
strip.panel.shrink_to_fit(); // release unused memory
strip.deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist)
strip.makeAutoSegments(true); // force re-creation of segments
} }
#endif #endif
@ -824,7 +823,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
//segment select (sets main segment) //segment select (sets main segment)
pos = req.indexOf(F("SM=")); pos = req.indexOf(F("SM="));
if (pos > 0 && !realtimeMode) { if (pos > 0 && !realtimeMode) {
strip.setMainSegmentId(getNumVal(&req, pos)); strip.setMainSegmentId(getNumVal(req, pos));
} }
byte selectedSeg = strip.getFirstSelectedSegId(); byte selectedSeg = strip.getFirstSelectedSegId();
@ -833,7 +832,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("SS=")); pos = req.indexOf(F("SS="));
if (pos > 0) { if (pos > 0) {
unsigned t = getNumVal(&req, pos); unsigned t = getNumVal(req, pos);
if (t < strip.getSegmentsNum()) { if (t < strip.getSegmentsNum()) {
selectedSeg = t; selectedSeg = t;
singleSegment = true; singleSegment = true;
@ -843,7 +842,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
Segment& selseg = strip.getSegment(selectedSeg); Segment& selseg = strip.getSegment(selectedSeg);
pos = req.indexOf(F("SV=")); //segment selected pos = req.indexOf(F("SV=")); //segment selected
if (pos > 0) { if (pos > 0) {
unsigned t = getNumVal(&req, pos); unsigned t = getNumVal(req, pos);
if (t == 2) for (unsigned i = 0; i < strip.getSegmentsNum(); i++) strip.getSegment(i).selected = false; // unselect other segments if (t == 2) for (unsigned i = 0; i < strip.getSegmentsNum(); i++) strip.getSegment(i).selected = false; // unselect other segments
selseg.selected = t; selseg.selected = t;
} }
@ -872,19 +871,19 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
uint16_t spcI = selseg.spacing; uint16_t spcI = selseg.spacing;
pos = req.indexOf(F("&S=")); //segment start pos = req.indexOf(F("&S=")); //segment start
if (pos > 0) { if (pos > 0) {
startI = std::abs(getNumVal(&req, pos)); startI = std::abs(getNumVal(req, pos));
} }
pos = req.indexOf(F("S2=")); //segment stop pos = req.indexOf(F("S2=")); //segment stop
if (pos > 0) { if (pos > 0) {
stopI = std::abs(getNumVal(&req, pos)); stopI = std::abs(getNumVal(req, pos));
} }
pos = req.indexOf(F("GP=")); //segment grouping pos = req.indexOf(F("GP=")); //segment grouping
if (pos > 0) { if (pos > 0) {
grpI = std::max(1,getNumVal(&req, pos)); grpI = std::max(1,getNumVal(req, pos));
} }
pos = req.indexOf(F("SP=")); //segment spacing pos = req.indexOf(F("SP=")); //segment spacing
if (pos > 0) { if (pos > 0) {
spcI = std::max(0,getNumVal(&req, pos)); spcI = std::max(0,getNumVal(req, pos));
} }
strip.suspend(); // must suspend strip operations before changing geometry strip.suspend(); // must suspend strip operations before changing geometry
selseg.setGeometry(startI, stopI, grpI, spcI, UINT16_MAX, startY, stopY, selseg.map1D2D); selseg.setGeometry(startI, stopI, grpI, spcI, UINT16_MAX, startY, stopY, selseg.map1D2D);
@ -898,7 +897,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("SB=")); //Segment brightness/opacity pos = req.indexOf(F("SB=")); //Segment brightness/opacity
if (pos > 0) { if (pos > 0) {
byte segbri = getNumVal(&req, pos); byte segbri = getNumVal(req, pos);
selseg.setOption(SEG_OPTION_ON, segbri); // use transition selseg.setOption(SEG_OPTION_ON, segbri); // use transition
if (segbri) { if (segbri) {
selseg.setOpacity(segbri); selseg.setOpacity(segbri);
@ -907,7 +906,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("SW=")); //segment power pos = req.indexOf(F("SW=")); //segment power
if (pos > 0) { if (pos > 0) {
switch (getNumVal(&req, pos)) { switch (getNumVal(req, pos)) {
case 0: selseg.setOption(SEG_OPTION_ON, false); break; // use transition case 0: selseg.setOption(SEG_OPTION_ON, false); break; // use transition
case 1: selseg.setOption(SEG_OPTION_ON, true); break; // use transition case 1: selseg.setOption(SEG_OPTION_ON, true); break; // use transition
default: selseg.setOption(SEG_OPTION_ON, !selseg.on); break; // use transition default: selseg.setOption(SEG_OPTION_ON, !selseg.on); break; // use transition
@ -915,16 +914,16 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
} }
pos = req.indexOf(F("PS=")); //saves current in preset pos = req.indexOf(F("PS=")); //saves current in preset
if (pos > 0) savePreset(getNumVal(&req, pos)); if (pos > 0) savePreset(getNumVal(req, pos));
pos = req.indexOf(F("P1=")); //sets first preset for cycle pos = req.indexOf(F("P1=")); //sets first preset for cycle
if (pos > 0) presetCycMin = getNumVal(&req, pos); if (pos > 0) presetCycMin = getNumVal(req, pos);
pos = req.indexOf(F("P2=")); //sets last preset for cycle pos = req.indexOf(F("P2=")); //sets last preset for cycle
if (pos > 0) presetCycMax = getNumVal(&req, pos); if (pos > 0) presetCycMax = getNumVal(req, pos);
//apply preset //apply preset
if (updateVal(req.c_str(), "PL=", &presetCycCurr, presetCycMin, presetCycMax)) { if (updateVal(req.c_str(), "PL=", presetCycCurr, presetCycMin, presetCycMax)) {
applyPreset(presetCycCurr); applyPreset(presetCycCurr);
} }
@ -932,25 +931,25 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
if (pos > 0) doAdvancePlaylist = true; if (pos > 0) doAdvancePlaylist = true;
//set brightness //set brightness
updateVal(req.c_str(), "&A=", &bri); updateVal(req.c_str(), "&A=", bri);
bool col0Changed = false, col1Changed = false, col2Changed = false; bool col0Changed = false, col1Changed = false, col2Changed = false;
//set colors //set colors
col0Changed |= updateVal(req.c_str(), "&R=", &colIn[0]); col0Changed |= updateVal(req.c_str(), "&R=", colIn[0]);
col0Changed |= updateVal(req.c_str(), "&G=", &colIn[1]); col0Changed |= updateVal(req.c_str(), "&G=", colIn[1]);
col0Changed |= updateVal(req.c_str(), "&B=", &colIn[2]); col0Changed |= updateVal(req.c_str(), "&B=", colIn[2]);
col0Changed |= updateVal(req.c_str(), "&W=", &colIn[3]); col0Changed |= updateVal(req.c_str(), "&W=", colIn[3]);
col1Changed |= updateVal(req.c_str(), "R2=", &colInSec[0]); col1Changed |= updateVal(req.c_str(), "R2=", colInSec[0]);
col1Changed |= updateVal(req.c_str(), "G2=", &colInSec[1]); col1Changed |= updateVal(req.c_str(), "G2=", colInSec[1]);
col1Changed |= updateVal(req.c_str(), "B2=", &colInSec[2]); col1Changed |= updateVal(req.c_str(), "B2=", colInSec[2]);
col1Changed |= updateVal(req.c_str(), "W2=", &colInSec[3]); col1Changed |= updateVal(req.c_str(), "W2=", colInSec[3]);
#ifdef WLED_ENABLE_LOXONE #ifdef WLED_ENABLE_LOXONE
//lox parser //lox parser
pos = req.indexOf(F("LX=")); // Lox primary color pos = req.indexOf(F("LX=")); // Lox primary color
if (pos > 0) { if (pos > 0) {
int lxValue = getNumVal(&req, pos); int lxValue = getNumVal(req, pos);
if (parseLx(lxValue, colIn)) { if (parseLx(lxValue, colIn)) {
bri = 255; bri = 255;
nightlightActive = false; //always disable nightlight when toggling nightlightActive = false; //always disable nightlight when toggling
@ -959,7 +958,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
} }
pos = req.indexOf(F("LY=")); // Lox secondary color pos = req.indexOf(F("LY=")); // Lox secondary color
if (pos > 0) { if (pos > 0) {
int lxValue = getNumVal(&req, pos); int lxValue = getNumVal(req, pos);
if(parseLx(lxValue, colInSec)) { if(parseLx(lxValue, colInSec)) {
bri = 255; bri = 255;
nightlightActive = false; //always disable nightlight when toggling nightlightActive = false; //always disable nightlight when toggling
@ -971,11 +970,11 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
//set hue //set hue
pos = req.indexOf(F("HU=")); pos = req.indexOf(F("HU="));
if (pos > 0) { if (pos > 0) {
uint16_t temphue = getNumVal(&req, pos); uint16_t temphue = getNumVal(req, pos);
byte tempsat = 255; byte tempsat = 255;
pos = req.indexOf(F("SA=")); pos = req.indexOf(F("SA="));
if (pos > 0) { if (pos > 0) {
tempsat = getNumVal(&req, pos); tempsat = getNumVal(req, pos);
} }
byte sec = req.indexOf(F("H2")); byte sec = req.indexOf(F("H2"));
colorHStoRGB(temphue, tempsat, (sec>0) ? colInSec : colIn); colorHStoRGB(temphue, tempsat, (sec>0) ? colInSec : colIn);
@ -986,25 +985,25 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("&K=")); pos = req.indexOf(F("&K="));
if (pos > 0) { if (pos > 0) {
byte sec = req.indexOf(F("K2")); byte sec = req.indexOf(F("K2"));
colorKtoRGB(getNumVal(&req, pos), (sec>0) ? colInSec : colIn); colorKtoRGB(getNumVal(req, pos), (sec>0) ? colInSec : colIn);
col0Changed |= (!sec); col1Changed |= sec; col0Changed |= (!sec); col1Changed |= sec;
} }
//set color from HEX or 32bit DEC //set color from HEX or 32bit DEC
pos = req.indexOf(F("CL=")); pos = req.indexOf(F("CL="));
if (pos > 0) { if (pos > 0) {
colorFromDecOrHexString(colIn, req.substring(pos + 3).c_str()); colorFromDecOrHexString(colIn, (char*)req.substring(pos + 3).c_str());
col0Changed = true; col0Changed = true;
} }
pos = req.indexOf(F("C2=")); pos = req.indexOf(F("C2="));
if (pos > 0) { if (pos > 0) {
colorFromDecOrHexString(colInSec, req.substring(pos + 3).c_str()); colorFromDecOrHexString(colInSec, (char*)req.substring(pos + 3).c_str());
col1Changed = true; col1Changed = true;
} }
pos = req.indexOf(F("C3=")); pos = req.indexOf(F("C3="));
if (pos > 0) { if (pos > 0) {
byte tmpCol[4]; byte tmpCol[4];
colorFromDecOrHexString(tmpCol, req.substring(pos + 3).c_str()); colorFromDecOrHexString(tmpCol, (char*)req.substring(pos + 3).c_str());
col2 = RGBW32(tmpCol[0], tmpCol[1], tmpCol[2], tmpCol[3]); col2 = RGBW32(tmpCol[0], tmpCol[1], tmpCol[2], tmpCol[3]);
selseg.setColor(2, col2); // defined above (SS= or main) selseg.setColor(2, col2); // defined above (SS= or main)
col2Changed = true; col2Changed = true;
@ -1013,7 +1012,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
//set to random hue SR=0->1st SR=1->2nd //set to random hue SR=0->1st SR=1->2nd
pos = req.indexOf(F("SR")); pos = req.indexOf(F("SR"));
if (pos > 0) { if (pos > 0) {
byte sec = getNumVal(&req, pos); byte sec = getNumVal(req, pos);
setRandomColor(sec? colInSec : colIn); setRandomColor(sec? colInSec : colIn);
col0Changed |= (!sec); col1Changed |= sec; col0Changed |= (!sec); col1Changed |= sec;
} }
@ -1039,19 +1038,19 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
bool fxModeChanged = false, speedChanged = false, intensityChanged = false, paletteChanged = false; bool fxModeChanged = false, speedChanged = false, intensityChanged = false, paletteChanged = false;
bool custom1Changed = false, custom2Changed = false, custom3Changed = false, check1Changed = false, check2Changed = false, check3Changed = false; bool custom1Changed = false, custom2Changed = false, custom3Changed = false, check1Changed = false, check2Changed = false, check3Changed = false;
// set effect parameters // set effect parameters
if (updateVal(req.c_str(), "FX=", &effectIn, 0, strip.getModeCount()-1)) { if (updateVal(req.c_str(), "FX=", effectIn, 0, strip.getModeCount()-1)) {
if (request != nullptr) unloadPlaylist(); // unload playlist if changing FX using web request if (request != nullptr) unloadPlaylist(); // unload playlist if changing FX using web request
fxModeChanged = true; fxModeChanged = true;
} }
speedChanged = updateVal(req.c_str(), "SX=", &speedIn); speedChanged = updateVal(req.c_str(), "SX=", speedIn);
intensityChanged = updateVal(req.c_str(), "IX=", &intensityIn); intensityChanged = updateVal(req.c_str(), "IX=", intensityIn);
paletteChanged = updateVal(req.c_str(), "FP=", &paletteIn, 0, strip.getPaletteCount()-1); paletteChanged = updateVal(req.c_str(), "FP=", paletteIn, 0, getPaletteCount()-1);
custom1Changed = updateVal(req.c_str(), "X1=", &custom1In); custom1Changed = updateVal(req.c_str(), "X1=", custom1In);
custom2Changed = updateVal(req.c_str(), "X2=", &custom2In); custom2Changed = updateVal(req.c_str(), "X2=", custom2In);
custom3Changed = updateVal(req.c_str(), "X3=", &custom3In); custom3Changed = updateVal(req.c_str(), "X3=", custom3In);
check1Changed = updateVal(req.c_str(), "M1=", &check1In); check1Changed = updateVal(req.c_str(), "M1=", check1In);
check2Changed = updateVal(req.c_str(), "M2=", &check2In); check2Changed = updateVal(req.c_str(), "M2=", check2In);
check3Changed = updateVal(req.c_str(), "M3=", &check3In); check3Changed = updateVal(req.c_str(), "M3=", check3In);
stateChanged |= (fxModeChanged || speedChanged || intensityChanged || paletteChanged || custom1Changed || custom2Changed || custom3Changed || check1Changed || check2Changed || check3Changed); stateChanged |= (fxModeChanged || speedChanged || intensityChanged || paletteChanged || custom1Changed || custom2Changed || custom3Changed || check1Changed || check2Changed || check3Changed);
@ -1077,13 +1076,13 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
//set advanced overlay //set advanced overlay
pos = req.indexOf(F("OL=")); pos = req.indexOf(F("OL="));
if (pos > 0) { if (pos > 0) {
overlayCurrent = getNumVal(&req, pos); overlayCurrent = getNumVal(req, pos);
} }
//apply macro (deprecated, added for compatibility with pre-0.11 automations) //apply macro (deprecated, added for compatibility with pre-0.11 automations)
pos = req.indexOf(F("&M=")); pos = req.indexOf(F("&M="));
if (pos > 0) { if (pos > 0) {
applyPreset(getNumVal(&req, pos) + 16); applyPreset(getNumVal(req, pos) + 16);
} }
//toggle send UDP direct notifications //toggle send UDP direct notifications
@ -1102,7 +1101,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("&T=")); pos = req.indexOf(F("&T="));
if (pos > 0) { if (pos > 0) {
nightlightActive = false; //always disable nightlight when toggling nightlightActive = false; //always disable nightlight when toggling
switch (getNumVal(&req, pos)) switch (getNumVal(req, pos))
{ {
case 0: if (bri != 0){briLast = bri; bri = 0;} break; //off, only if it was previously on case 0: if (bri != 0){briLast = bri; bri = 0;} break; //off, only if it was previously on
case 1: if (bri == 0) bri = briLast; break; //on, only if it was previously off case 1: if (bri == 0) bri = briLast; break; //on, only if it was previously off
@ -1121,7 +1120,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
nightlightActive = false; nightlightActive = false;
} else { } else {
nightlightActive = true; nightlightActive = true;
if (!aNlDef) nightlightDelayMins = getNumVal(&req, pos); if (!aNlDef) nightlightDelayMins = getNumVal(req, pos);
else nightlightDelayMins = nightlightDelayMinsDefault; else nightlightDelayMins = nightlightDelayMinsDefault;
nightlightStartTime = millis(); nightlightStartTime = millis();
} }
@ -1135,7 +1134,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
//set nightlight target brightness //set nightlight target brightness
pos = req.indexOf(F("NT=")); pos = req.indexOf(F("NT="));
if (pos > 0) { if (pos > 0) {
nightlightTargetBri = getNumVal(&req, pos); nightlightTargetBri = getNumVal(req, pos);
nightlightActiveOld = false; //re-init nightlightActiveOld = false; //re-init
} }
@ -1143,35 +1142,36 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("NF=")); pos = req.indexOf(F("NF="));
if (pos > 0) if (pos > 0)
{ {
nightlightMode = getNumVal(&req, pos); nightlightMode = getNumVal(req, pos);
nightlightActiveOld = false; //re-init nightlightActiveOld = false; //re-init
} }
if (nightlightMode > NL_MODE_SUN) nightlightMode = NL_MODE_SUN; if (nightlightMode > NL_MODE_SUN) nightlightMode = NL_MODE_SUN;
pos = req.indexOf(F("TT=")); pos = req.indexOf(F("TT="));
if (pos > 0) transitionDelay = getNumVal(&req, pos); if (pos > 0) transitionDelay = getNumVal(req, pos);
strip.setTransition(transitionDelay); strip.setTransition(transitionDelay);
//set time (unix timestamp) //set time (unix timestamp)
pos = req.indexOf(F("ST=")); pos = req.indexOf(F("ST="));
if (pos > 0) { if (pos > 0) {
setTimeFromAPI(getNumVal(&req, pos)); setTimeFromAPI(getNumVal(req, pos));
} }
//set countdown goal (unix timestamp) //set countdown goal (unix timestamp)
pos = req.indexOf(F("CT=")); pos = req.indexOf(F("CT="));
if (pos > 0) { if (pos > 0) {
countdownTime = getNumVal(&req, pos); countdownTime = getNumVal(req, pos);
if (countdownTime - toki.second() > 0) countdownOverTriggered = false; if (countdownTime - toki.second() > 0) countdownOverTriggered = false;
} }
pos = req.indexOf(F("LO=")); pos = req.indexOf(F("LO="));
if (pos > 0) { if (pos > 0) {
realtimeOverride = getNumVal(&req, pos); realtimeOverride = getNumVal(req, pos);
if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS; if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS;
if (realtimeMode && useMainSegmentOnly) { if (realtimeMode && useMainSegmentOnly) {
strip.getMainSegment().freeze = !realtimeOverride; strip.getMainSegment().freeze = !realtimeOverride;
realtimeOverride = REALTIME_OVERRIDE_NONE; // ignore request for override if using main segment only
} }
} }
@ -1184,12 +1184,12 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("U0=")); //user var 0 pos = req.indexOf(F("U0=")); //user var 0
if (pos > 0) { if (pos > 0) {
userVar0 = getNumVal(&req, pos); userVar0 = getNumVal(req, pos);
} }
pos = req.indexOf(F("U1=")); //user var 1 pos = req.indexOf(F("U1=")); //user var 1
if (pos > 0) { if (pos > 0) {
userVar1 = getNumVal(&req, pos); userVar1 = getNumVal(req, pos);
} }
// you can add more if you need // you can add more if you need

View File

@ -6,7 +6,7 @@
#define UDP_SEG_SIZE 36 #define UDP_SEG_SIZE 36
#define SEG_OFFSET (41) #define SEG_OFFSET (41)
#define WLEDPACKETSIZE (41+(MAX_NUM_SEGMENTS*UDP_SEG_SIZE)+0) #define WLEDPACKETSIZE (41+(WS2812FX::getMaxSegments()*UDP_SEG_SIZE)+0)
#define UDP_IN_MAXSIZE 1472 #define UDP_IN_MAXSIZE 1472
#define PRESUMED_NETWORK_DELAY 3 //how many ms could it take on avg to reach the receiver? This will be added to transmitted times #define PRESUMED_NETWORK_DELAY 3 //how many ms could it take on avg to reach the receiver? This will be added to transmitted times
@ -55,7 +55,7 @@ void notify(byte callMode, bool followUp)
//0: old 1: supports white 2: supports secondary color //0: old 1: supports white 2: supports secondary color
//3: supports FX intensity, 24 byte packet 4: supports transitionDelay 5: sup palette //3: supports FX intensity, 24 byte packet 4: supports transitionDelay 5: sup palette
//6: supports timebase syncing, 29 byte packet 7: supports tertiary color 8: supports sys time sync, 36 byte packet //6: supports timebase syncing, 29 byte packet 7: supports tertiary color 8: supports sys time sync, 36 byte packet
//9: supports sync groups, 37 byte packet 10: supports CCT, 39 byte packet 11: per segment options, variable packet length (40+MAX_NUM_SEGMENTS*3) //9: supports sync groups, 37 byte packet 10: supports CCT, 39 byte packet 11: per segment options, variable packet length (40+WS2812FX::getMaxSegments()*3)
//12: enhanced effect sliders, 2D & mapping options //12: enhanced effect sliders, 2D & mapping options
udpOut[11] = 12; udpOut[11] = 12;
col = mainseg.colors[1]; col = mainseg.colors[1];
@ -104,7 +104,7 @@ void notify(byte callMode, bool followUp)
udpOut[40] = UDP_SEG_SIZE; //size of each loop iteration (one segment) udpOut[40] = UDP_SEG_SIZE; //size of each loop iteration (one segment)
size_t s = 0, nsegs = strip.getSegmentsNum(); size_t s = 0, nsegs = strip.getSegmentsNum();
for (size_t i = 0; i < nsegs; i++) { for (size_t i = 0; i < nsegs; i++) {
Segment &selseg = strip.getSegment(i); const Segment &selseg = strip.getSegment(i);
if (!selseg.isActive()) continue; if (!selseg.isActive()) continue;
unsigned ofs = 41 + s*UDP_SEG_SIZE; //start of segment offset byte unsigned ofs = 41 + s*UDP_SEG_SIZE; //start of segment offset byte
udpOut[0 +ofs] = s; udpOut[0 +ofs] = s;
@ -177,7 +177,7 @@ void notify(byte callMode, bool followUp)
memcpy(buffer.data + packetSize, &udpOut[41+i*UDP_SEG_SIZE], UDP_SEG_SIZE); memcpy(buffer.data + packetSize, &udpOut[41+i*UDP_SEG_SIZE], UDP_SEG_SIZE);
packetSize += UDP_SEG_SIZE; packetSize += UDP_SEG_SIZE;
if (packetSize + UDP_SEG_SIZE < bufferSize) continue; if (packetSize + UDP_SEG_SIZE < bufferSize) continue;
DEBUG_PRINTF_P(PSTR("ESP-NOW sending packet: %d (%d)\n"), (int)buffer.packet, packetSize+3); DEBUG_PRINTF_P(PSTR("ESP-NOW sending packet: %d (%u)\n"), (int)buffer.packet, packetSize+3);
err = quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast<const uint8_t*>(&buffer), packetSize+3); err = quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast<const uint8_t*>(&buffer), packetSize+3);
buffer.packet++; buffer.packet++;
packetSize = 0; packetSize = 0;
@ -266,13 +266,13 @@ static void parseNotifyPacket(const uint8_t *udpIn) {
strip.resume(); strip.resume();
} }
size_t inactiveSegs = 0; size_t inactiveSegs = 0;
for (size_t i = 0; i < numSrcSegs && i < strip.getMaxSegments(); i++) { for (size_t i = 0; i < numSrcSegs && i < WS2812FX::getMaxSegments(); i++) {
unsigned ofs = 41 + i*udpIn[40]; //start of segment offset byte unsigned ofs = 41 + i*udpIn[40]; //start of segment offset byte
unsigned id = udpIn[0 +ofs]; unsigned id = udpIn[0 +ofs];
DEBUG_PRINTF_P(PSTR("UDP segment received: %u\n"), id); DEBUG_PRINTF_P(PSTR("UDP segment received: %u\n"), id);
if (id > strip.getSegmentsNum()) break; if (id > strip.getSegmentsNum()) break;
else if (id == strip.getSegmentsNum()) { else if (id == strip.getSegmentsNum()) {
if (receiveSegmentBounds && id < strip.getMaxSegments()) strip.appendSegment(); if (receiveSegmentBounds && id < WS2812FX::getMaxSegments()) strip.appendSegment();
else break; else break;
} }
DEBUG_PRINTF_P(PSTR("UDP segment check: %u\n"), id); DEBUG_PRINTF_P(PSTR("UDP segment check: %u\n"), id);
@ -327,7 +327,7 @@ static void parseNotifyPacket(const uint8_t *udpIn) {
// freeze, reset should never be synced // freeze, reset should never be synced
// LSB to MSB: select, reverse, on, mirror, freeze, reset, reverse_y, mirror_y, transpose, map1d2d (3), ssim (2), set (2) // LSB to MSB: select, reverse, on, mirror, freeze, reset, reverse_y, mirror_y, transpose, map1d2d (3), ssim (2), set (2)
DEBUG_PRINTF_P(PSTR("Apply options: %u\n"), id); DEBUG_PRINTF_P(PSTR("Apply options: %u\n"), id);
selseg.options = (selseg.options & 0b0000000000110001U) | (udpIn[28+ofs]<<8) | (udpIn[9 +ofs] & 0b11001110U); // ignore selected, freeze, reset selseg.options = (selseg.options & 0b0000000000110001U) | ((uint16_t)udpIn[28+ofs]<<8) | (udpIn[9 +ofs] & 0b11001110U); // ignore selected, freeze, reset
if (applyEffects) { if (applyEffects) {
DEBUG_PRINTF_P(PSTR("Apply sliders: %u\n"), id); DEBUG_PRINTF_P(PSTR("Apply sliders: %u\n"), id);
selseg.custom1 = udpIn[29+ofs]; selseg.custom1 = udpIn[29+ofs];
@ -406,31 +406,26 @@ static void parseNotifyPacket(const uint8_t *udpIn) {
stateUpdated(CALL_MODE_NOTIFICATION); stateUpdated(CALL_MODE_NOTIFICATION);
} }
// realtimeLock() is called from UDP notifications, JSON API or serial Ada
void realtimeLock(uint32_t timeoutMs, byte md) void realtimeLock(uint32_t timeoutMs, byte md)
{ {
if (!realtimeMode && !realtimeOverride) { if (!realtimeMode && !realtimeOverride) {
unsigned stop, start;
if (useMainSegmentOnly) { if (useMainSegmentOnly) {
Segment& mainseg = strip.getMainSegment(); Segment& mainseg = strip.getMainSegment();
start = mainseg.start; mainseg.clear(); // clear entire segment (in case sender transmits less pixels)
stop = mainseg.stop;
mainseg.freeze = true; mainseg.freeze = true;
// if WLED was off and using main segment only, freeze non-main segments so they stay off // if WLED was off and using main segment only, freeze non-main segments so they stay off
if (bri == 0) { if (bri == 0) {
for (size_t s = 0; s < strip.getSegmentsNum(); s++) { for (size_t s = 0; s < strip.getSegmentsNum(); s++) strip.getSegment(s).freeze = true;
strip.getSegment(s).freeze = true;
}
} }
} else { } else {
start = 0; // clear entire strip
stop = strip.getLengthTotal(); strip.fill(BLACK);
}
// if strip is off (bri==0) and not already in RTM
if (briT == 0) {
strip.setBrightness(scaledBri(briLast), true);
} }
// clear strip/segment
for (size_t i = start; i < stop; i++) strip.setPixelColor(i,BLACK);
}
// if strip is off (bri==0) and not already in RTM
if (briT == 0 && !realtimeMode && !realtimeOverride) {
strip.setBrightness(scaledBri(briLast), true);
} }
if (realtimeTimeout != UINT32_MAX) { if (realtimeTimeout != UINT32_MAX) {
@ -452,6 +447,7 @@ void exitRealtime() {
realtimeIP[0] = 0; realtimeIP[0] = 0;
if (useMainSegmentOnly) { // unfreeze live segment again if (useMainSegmentOnly) { // unfreeze live segment again
strip.getMainSegment().freeze = false; strip.getMainSegment().freeze = false;
strip.trigger();
} else { } else {
strip.show(); // possible fix for #3589 strip.show(); // possible fix for #3589
} }
@ -481,7 +477,8 @@ void handleNotifications()
if (e131NewData && millis() - strip.getLastShow() > 15) if (e131NewData && millis() - strip.getLastShow() > 15)
{ {
e131NewData = false; e131NewData = false;
strip.show(); if (useMainSegmentOnly) strip.trigger();
else strip.show();
} }
//unlock strip when realtime UDP times out //unlock strip when realtime UDP times out
@ -508,13 +505,13 @@ void handleNotifications()
uint8_t lbuf[packetSize]; uint8_t lbuf[packetSize];
rgbUdp.read(lbuf, packetSize); rgbUdp.read(lbuf, packetSize);
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_HYPERION); realtimeLock(realtimeTimeoutMs, REALTIME_MODE_HYPERION);
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; if (realtimeOverride) return;
unsigned totalLen = strip.getLengthTotal(); unsigned totalLen = strip.getLengthTotal();
if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); // set up parameters for get/setPixelColor()
for (size_t i = 0, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++) { for (size_t i = 0, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++) {
setRealtimePixel(id, lbuf[i], lbuf[i+1], lbuf[i+2], 0); setRealtimePixel(id, lbuf[i], lbuf[i+1], lbuf[i+2], 0);
} }
if (!(realtimeMode && useMainSegmentOnly)) strip.show(); if (useMainSegmentOnly) strip.trigger();
else strip.show();
return; return;
} }
} }
@ -583,7 +580,7 @@ void handleNotifications()
realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP(); realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP();
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_TPM2NET); realtimeLock(realtimeTimeoutMs, REALTIME_MODE_TPM2NET);
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; if (realtimeOverride) return;
tpmPacketCount++; //increment the packet count tpmPacketCount++; //increment the packet count
if (tpmPacketCount == 1) tpmPayloadFrameSize = (udpIn[2] << 8) + udpIn[3]; //save frame size for the whole payload if this is the first packet if (tpmPacketCount == 1) tpmPayloadFrameSize = (udpIn[2] << 8) + udpIn[3]; //save frame size for the whole payload if this is the first packet
@ -592,13 +589,13 @@ void handleNotifications()
unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED
unsigned totalLen = strip.getLengthTotal(); unsigned totalLen = strip.getLengthTotal();
if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); // set up parameters for get/setPixelColor()
for (size_t i = 6; i < tpmPayloadFrameSize + 4U && id < totalLen; i += 3, id++) { for (size_t i = 6; i < tpmPayloadFrameSize + 4U && id < totalLen; i += 3, id++) {
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
} }
if (tpmPacketCount == numPackets) { //reset packet count and show if all packets were received if (tpmPacketCount == numPackets) { //reset packet count and show if all packets were received
tpmPacketCount = 0; tpmPacketCount = 0;
strip.show(); if (useMainSegmentOnly) strip.trigger();
else strip.show();
} }
return; return;
} }
@ -610,17 +607,15 @@ void handleNotifications()
DEBUG_PRINTLN(realtimeIP); DEBUG_PRINTLN(realtimeIP);
if (packetSize < 2) return; if (packetSize < 2) return;
if (udpIn[1] == 0) if (udpIn[1] == 0) {
{ realtimeTimeout = 0; // cancel realtime mode immediately
realtimeTimeout = 0;
return; return;
} else { } else {
realtimeLock(udpIn[1]*1000 +1, REALTIME_MODE_UDP); realtimeLock(udpIn[1]*1000 +1, REALTIME_MODE_UDP);
} }
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; if (realtimeOverride) return;
unsigned totalLen = strip.getLengthTotal(); unsigned totalLen = strip.getLengthTotal();
if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); // set up parameters for get/setPixelColor()
if (udpIn[0] == 1 && packetSize > 5) //warls if (udpIn[0] == 1 && packetSize > 5) //warls
{ {
for (size_t i = 2; i < packetSize -3; i += 4) for (size_t i = 2; i < packetSize -3; i += 4)
@ -654,7 +649,8 @@ void handleNotifications()
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]); setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
} }
} }
strip.show(); if (useMainSegmentOnly) strip.trigger();
else strip.show();
return; return;
} }
@ -679,20 +675,7 @@ void handleNotifications()
void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w) void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w)
{ {
unsigned pix = i + arlsOffset; unsigned pix = i + arlsOffset;
if (pix < strip.getLengthTotal()) { strip.setRealtimePixelColor(pix, RGBW32(r,g,b,w));
if (!arlsDisableGammaCorrection && gammaCorrectCol) {
r = gamma8(r);
g = gamma8(g);
b = gamma8(b);
w = gamma8(w);
}
uint32_t col = RGBW32(r,g,b,w);
if (useMainSegmentOnly) {
strip.getMainSegment().setPixelColor(pix, col); // this expects that strip.getMainSegment().beginDraw() has been called in handleNotification()
} else {
strip.setPixelColor(pix, col);
}
}
} }
/*********************************************************************************************\ /*********************************************************************************************\
@ -808,7 +791,7 @@ static size_t sequenceNumber = 0; // this needs to be shared across all ou
static const size_t ART_NET_HEADER_SIZE = 12; static const size_t ART_NET_HEADER_SIZE = 12;
static const byte ART_NET_HEADER[] PROGMEM = {0x41,0x72,0x74,0x2d,0x4e,0x65,0x74,0x00,0x00,0x50,0x00,0x0e}; static const byte ART_NET_HEADER[] PROGMEM = {0x41,0x72,0x74,0x2d,0x4e,0x65,0x74,0x00,0x00,0x50,0x00,0x0e};
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const uint8_t* buffer, uint8_t bri, bool isRGBW) { uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const uint8_t *buffer, uint8_t bri, bool isRGBW) {
if (!(apActive || interfacesInited) || !client[0] || !length) return 1; // network not initialised or dummy/unset IP address 031522 ajn added check for ap if (!(apActive || interfacesInited) || !client[0] || !length) return 1; // network not initialised or dummy/unset IP address 031522 ajn added check for ap
WiFiUDP ddpUdp; WiFiUDP ddpUdp;

View File

@ -4,17 +4,17 @@
//helper to get int value at a position in string //helper to get int value at a position in string
int getNumVal(const String* req, uint16_t pos) int getNumVal(const String &req, uint16_t pos)
{ {
return req->substring(pos+3).toInt(); return req.substring(pos+3).toInt();
} }
//helper to get int value with in/decrementing support via ~ syntax //helper to get int value with in/decrementing support via ~ syntax
void parseNumber(const char* str, byte* val, byte minv, byte maxv) void parseNumber(const char* str, byte &val, byte minv, byte maxv)
{ {
if (str == nullptr || str[0] == '\0') return; if (str == nullptr || str[0] == '\0') return;
if (str[0] == 'r') {*val = hw_random8(minv,maxv?maxv:255); return;} // maxv for random cannot be 0 if (str[0] == 'r') {val = hw_random8(minv,maxv?maxv:255); return;} // maxv for random cannot be 0
bool wrap = false; bool wrap = false;
if (str[0] == 'w' && strlen(str) > 1) {str++; wrap = true;} if (str[0] == 'w' && strlen(str) > 1) {str++; wrap = true;}
if (str[0] == '~') { if (str[0] == '~') {
@ -22,19 +22,19 @@ void parseNumber(const char* str, byte* val, byte minv, byte maxv)
if (out == 0) { if (out == 0) {
if (str[1] == '0') return; if (str[1] == '0') return;
if (str[1] == '-') { if (str[1] == '-') {
*val = (int)(*val -1) < (int)minv ? maxv : min((int)maxv,(*val -1)); //-1, wrap around val = (int)(val -1) < (int)minv ? maxv : min((int)maxv,(val -1)); //-1, wrap around
} else { } else {
*val = (int)(*val +1) > (int)maxv ? minv : max((int)minv,(*val +1)); //+1, wrap around val = (int)(val +1) > (int)maxv ? minv : max((int)minv,(val +1)); //+1, wrap around
} }
} else { } else {
if (wrap && *val == maxv && out > 0) out = minv; if (wrap && val == maxv && out > 0) out = minv;
else if (wrap && *val == minv && out < 0) out = maxv; else if (wrap && val == minv && out < 0) out = maxv;
else { else {
out += *val; out += val;
if (out > maxv) out = maxv; if (out > maxv) out = maxv;
if (out < minv) out = minv; if (out < minv) out = minv;
} }
*val = out; val = out;
} }
return; return;
} else if (minv == maxv && minv == 0) { // limits "unset" i.e. both 0 } else if (minv == maxv && minv == 0) { // limits "unset" i.e. both 0
@ -49,14 +49,14 @@ void parseNumber(const char* str, byte* val, byte minv, byte maxv)
} }
} }
} }
*val = atoi(str); val = atoi(str);
} }
//getVal supports inc/decrementing and random ("X~Y(r|~[w][-][Z])" form) //getVal supports inc/decrementing and random ("X~Y(r|~[w][-][Z])" form)
bool getVal(JsonVariant elem, byte* val, byte vmin, byte vmax) { bool getVal(JsonVariant elem, byte &val, byte vmin, byte vmax) {
if (elem.is<int>()) { if (elem.is<int>()) {
if (elem < 0) return false; //ignore e.g. {"ps":-1} if (elem < 0) return false; //ignore e.g. {"ps":-1}
*val = elem; val = elem;
return true; return true;
} else if (elem.is<const char*>()) { } else if (elem.is<const char*>()) {
const char* str = elem; const char* str = elem;
@ -82,7 +82,7 @@ bool getBoolVal(const JsonVariant &elem, bool dflt) {
} }
bool updateVal(const char* req, const char* key, byte* val, byte minv, byte maxv) bool updateVal(const char* req, const char* key, byte &val, byte minv, byte maxv)
{ {
const char *v = strstr(req, key); const char *v = strstr(req, key);
if (v) v += strlen(key); if (v) v += strlen(key);
@ -619,6 +619,68 @@ int32_t hw_random(int32_t lowerlimit, int32_t upperlimit) {
return hw_random(diff) + lowerlimit; return hw_random(diff) + lowerlimit;
} }
#ifndef ESP8266
void *w_malloc(size_t size) {
int caps1 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
if (psramSafe) {
if (heap_caps_get_free_size(caps2) > 3*MIN_HEAP_SIZE && size < 512) std::swap(caps1, caps2); // use DRAM for small alloactions & when heap is plenty
return heap_caps_malloc_prefer(size, 2, caps1, caps2); // otherwise prefer PSRAM if it exists
}
return heap_caps_malloc(size, caps2);
}
void *w_realloc(void *ptr, size_t size) {
int caps1 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
if (psramSafe) {
if (heap_caps_get_free_size(caps2) > 3*MIN_HEAP_SIZE && size < 512) std::swap(caps1, caps2); // use DRAM for small alloactions & when heap is plenty
return heap_caps_realloc_prefer(ptr, size, 2, caps1, caps2); // otherwise prefer PSRAM if it exists
}
return heap_caps_realloc(ptr, size, caps2);
}
void *w_calloc(size_t count, size_t size) {
int caps1 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
if (psramSafe) {
if (heap_caps_get_free_size(caps2) > 3*MIN_HEAP_SIZE && size < 512) std::swap(caps1, caps2); // use DRAM for small alloactions & when heap is plenty
return heap_caps_calloc_prefer(count, size, 2, caps1, caps2); // otherwise prefer PSRAM if it exists
}
return heap_caps_calloc(count, size, caps2);
}
void *d_malloc(size_t size) {
int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
if (psramSafe) {
if (size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions
return heap_caps_malloc_prefer(size, 2, caps1, caps2); // otherwise prefer DRAM
}
return heap_caps_malloc(size, caps1);
}
void *d_realloc(void *ptr, size_t size) {
int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
if (psramSafe) {
if (size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions
return heap_caps_realloc_prefer(ptr, size, 2, caps1, caps2); // otherwise prefer DRAM
}
return heap_caps_realloc(ptr, size, caps1);
}
void *d_calloc(size_t count, size_t size) {
int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
if (psramSafe) {
if (size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions
return heap_caps_calloc_prefer(count, size, 2, caps1, caps2); // otherwise prefer DRAM
}
return heap_caps_calloc(count, size, caps1);
}
#endif
/* /*
* Fixed point integer based Perlin noise functions by @dedehai * Fixed point integer based Perlin noise functions by @dedehai
* Note: optimized for speed and to mimic fastled inoise functions, not for accuracy or best randomness * Note: optimized for speed and to mimic fastled inoise functions, not for accuracy or best randomness

View File

@ -529,6 +529,7 @@ void WLED::setup()
void WLED::beginStrip() void WLED::beginStrip()
{ {
// Initialize NeoPixel Strip and button // Initialize NeoPixel Strip and button
strip.setTransition(0); // temporarily prevent transitions to reduce segment copies
strip.finalizeInit(); // busses created during deserializeConfig() if config existed strip.finalizeInit(); // busses created during deserializeConfig() if config existed
strip.makeAutoSegments(); strip.makeAutoSegments();
strip.setBrightness(0); strip.setBrightness(0);
@ -557,6 +558,8 @@ void WLED::beginStrip()
applyPreset(bootPreset, CALL_MODE_INIT); applyPreset(bootPreset, CALL_MODE_INIT);
} }
strip.setTransition(transitionDelayDefault); // restore transitions
// init relay pin // init relay pin
if (rlyPin >= 0) { if (rlyPin >= 0) {
pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT); pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT);

View File

@ -602,6 +602,8 @@ WLED_GLOBAL bool wasConnected _INIT(false);
// color // color
WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random color so the new one is not the same WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random color so the new one is not the same
WLED_GLOBAL std::vector<CRGBPalette16> customPalettes; // custom palettes
WLED_GLOBAL uint8_t paletteBlend _INIT(0); // determines bending and wrapping of palette: 0: blend, wrap if moving (SEGMENT.speed>0); 1: blend, always wrap; 2: blend, never wrap; 3: don't blend or wrap
// transitions // transitions
WLED_GLOBAL uint8_t blendingStyle _INIT(0); // effect blending/transitionig style WLED_GLOBAL uint8_t blendingStyle _INIT(0); // effect blending/transitionig style
@ -612,6 +614,7 @@ WLED_GLOBAL unsigned long transitionStartTime;
WLED_GLOBAL bool jsonTransitionOnce _INIT(false); // flag to override transitionDelay (playlist, JSON API: "live" & "seg":{"i"} & "tt") WLED_GLOBAL bool jsonTransitionOnce _INIT(false); // flag to override transitionDelay (playlist, JSON API: "live" & "seg":{"i"} & "tt")
WLED_GLOBAL uint8_t randomPaletteChangeTime _INIT(5); // amount of time [s] between random palette changes (min: 1s, max: 255s) WLED_GLOBAL uint8_t randomPaletteChangeTime _INIT(5); // amount of time [s] between random palette changes (min: 1s, max: 255s)
WLED_GLOBAL bool useHarmonicRandomPalette _INIT(true); // use *harmonic* random palette generation (nicer looking) or truly random WLED_GLOBAL bool useHarmonicRandomPalette _INIT(true); // use *harmonic* random palette generation (nicer looking) or truly random
WLED_GLOBAL bool useRainbowWheel _INIT(false); // use "rainbow" color wheel instead of "spectrum" color wheel
// nightlight // nightlight
WLED_GLOBAL bool nightlightActive _INIT(false); WLED_GLOBAL bool nightlightActive _INIT(false);

View File

@ -225,7 +225,7 @@ void loadSettingsFromEEPROM()
if (lastEEPROMversion > 7) if (lastEEPROMversion > 7)
{ {
//strip.paletteFade = EEPROM.read(374); //strip.paletteFade = EEPROM.read(374);
strip.paletteBlend = EEPROM.read(382); paletteBlend = EEPROM.read(382);
for (int i = 0; i < 8; ++i) for (int i = 0; i < 8; ++i)
{ {

View File

@ -291,12 +291,11 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormValue(settingsScript,PSTR("CB"),Bus::getCCTBlend()); printSetFormValue(settingsScript,PSTR("CB"),Bus::getCCTBlend());
printSetFormValue(settingsScript,PSTR("FR"),strip.getTargetFps()); printSetFormValue(settingsScript,PSTR("FR"),strip.getTargetFps());
printSetFormValue(settingsScript,PSTR("AW"),Bus::getGlobalAWMode()); printSetFormValue(settingsScript,PSTR("AW"),Bus::getGlobalAWMode());
printSetFormCheckbox(settingsScript,PSTR("LD"),useGlobalLedBuffer);
printSetFormCheckbox(settingsScript,PSTR("PR"),BusManager::hasParallelOutput()); // get it from bus manager not global variable printSetFormCheckbox(settingsScript,PSTR("PR"),BusManager::hasParallelOutput()); // get it from bus manager not global variable
unsigned sumMa = 0; unsigned sumMa = 0;
for (int s = 0; s < BusManager::getNumBusses(); s++) { for (size_t s = 0; s < BusManager::getNumBusses(); s++) {
const Bus* bus = BusManager::getBus(s); const Bus *bus = BusManager::getBus(s);
if (!bus || !bus->isOk()) break; // should not happen but for safety if (!bus || !bus->isOk()) break; // should not happen but for safety
int offset = s < 10 ? '0' : 'A'; 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
@ -380,7 +379,8 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormValue(settingsScript,PSTR("TB"),nightlightTargetBri); printSetFormValue(settingsScript,PSTR("TB"),nightlightTargetBri);
printSetFormValue(settingsScript,PSTR("TL"),nightlightDelayMinsDefault); printSetFormValue(settingsScript,PSTR("TL"),nightlightDelayMinsDefault);
printSetFormValue(settingsScript,PSTR("TW"),nightlightMode); printSetFormValue(settingsScript,PSTR("TW"),nightlightMode);
printSetFormIndex(settingsScript,PSTR("PB"),strip.paletteBlend); printSetFormIndex(settingsScript,PSTR("PB"),paletteBlend);
printSetFormCheckbox(settingsScript,PSTR("RW"),useRainbowWheel);
printSetFormValue(settingsScript,PSTR("RL"),rlyPin); printSetFormValue(settingsScript,PSTR("RL"),rlyPin);
printSetFormCheckbox(settingsScript,PSTR("RM"),rlyMde); printSetFormCheckbox(settingsScript,PSTR("RM"),rlyMde);
printSetFormCheckbox(settingsScript,PSTR("RO"),rlyOpenDrain); printSetFormCheckbox(settingsScript,PSTR("RO"),rlyOpenDrain);
@ -666,16 +666,14 @@ void getSettingsJS(byte subPage, Print& settingsScript)
#ifndef WLED_DISABLE_2D #ifndef WLED_DISABLE_2D
settingsScript.printf_P(PSTR("maxPanels=%d;resetPanels();"),WLED_MAX_PANELS); settingsScript.printf_P(PSTR("maxPanels=%d;resetPanels();"),WLED_MAX_PANELS);
if (strip.isMatrix) { if (strip.isMatrix) {
if(strip.panels>0){ printSetFormValue(settingsScript,PSTR("PW"),strip.panel.size()>0?strip.panel[0].width:8); //Set generator Width and Height to first panel size for convenience
printSetFormValue(settingsScript,PSTR("PW"),strip.panel[0].width); //Set generator Width and Height to first panel size for convenience printSetFormValue(settingsScript,PSTR("PH"),strip.panel.size()>0?strip.panel[0].height:8);
printSetFormValue(settingsScript,PSTR("PH"),strip.panel[0].height); printSetFormValue(settingsScript,PSTR("MPC"),strip.panel.size());
}
printSetFormValue(settingsScript,PSTR("MPC"),strip.panels);
// panels // panels
for (unsigned i=0; i<strip.panels; i++) { for (unsigned i=0; i<strip.panel.size(); i++) {
settingsScript.printf_P(PSTR("addPanel(%d);"), i); settingsScript.printf_P(PSTR("addPanel(%d);"), i);
char pO[8] = { '\0' }; char pO[8] = { '\0' };
snprintf_P(pO, 7, PSTR("P%d"), i); // WLED_MAX_PANELS is 18 so pO will always only be 4 characters or less snprintf_P(pO, 7, PSTR("P%d"), i); // WLED_WLED_MAX_PANELS is less than 100 so pO will always only be 4 characters or less
pO[7] = '\0'; pO[7] = '\0';
unsigned l = strlen(pO); unsigned l = strlen(pO);
// create P0B, P1B, ..., P63B, etc for other PxxX // create P0B, P1B, ..., P63B, etc for other PxxX