Merge branch 'wled:main' into main

This commit is contained in:
Jason Somers
2025-07-17 22:13:01 -04:00
committed by GitHub
163 changed files with 7404 additions and 6334 deletions

File diff suppressed because it is too large Load Diff

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
*/
#include "wled.h"
#include "FX.h"
#include "palettes.h"
// 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
Segment::maxWidth = 1;
Segment::maxHeight = 1;
for (size_t i = 0; i < panel.size(); i++) {
Panel &p = panel[i];
for (const Panel &p : panel) {
if (p.xOffset + p.width > Segment::maxWidth) {
Segment::maxWidth = p.xOffset + p.width;
}
@@ -37,21 +35,24 @@ void WS2812FX::setUpMatrix() {
}
// 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."));
isMatrix = false;
Segment::maxWidth = _length;
Segment::maxHeight = 1;
panels = 0;
panel.clear(); // release memory allocated by panels
panel.shrink_to_fit(); // release memory if allocated
resetSegments();
return;
}
suspend();
waitForIt();
customMappingSize = 0; // prevent use of mapping if anything goes wrong
if (customMappingTable) free(customMappingTable);
customMappingTable = static_cast<uint16_t*>(malloc(sizeof(uint16_t)*getLengthTotal()));
d_free(customMappingTable);
customMappingTable = static_cast<uint16_t*>(d_malloc(sizeof(uint16_t)*getLengthTotal())); // prefer to not use SPI RAM
if (customMappingTable) {
customMappingSize = getLengthTotal();
@@ -85,7 +86,7 @@ void WS2812FX::setUpMatrix() {
JsonArray map = pDoc->as<JsonArray>();
gapSize = map.size();
if (!map.isNull() && gapSize >= matrixSize) { // not an empty map
gapTable = static_cast<int8_t*>(malloc(gapSize));
gapTable = static_cast<int8_t*>(p_malloc(gapSize));
if (gapTable) for (size_t i = 0; i < gapSize; i++) {
gapTable[i] = constrain(map[i], -1, 1);
}
@@ -96,8 +97,7 @@ void WS2812FX::setUpMatrix() {
}
unsigned x, y, pix=0; //pixel
for (size_t pan = 0; pan < panel.size(); pan++) {
Panel &p = panel[pan];
for (const Panel &p : panel) {
unsigned h = p.vertical ? p.height : p.width;
unsigned v = p.vertical ? p.width : p.height;
for (size_t j = 0; j < v; j++){
@@ -113,7 +113,8 @@ void WS2812FX::setUpMatrix() {
}
// delete gap array as we no longer need it
if (gapTable) free(gapTable);
p_free(gapTable);
resume();
#ifdef WLED_DEBUG
DEBUG_PRINT(F("Matrix ledmap:"));
@@ -126,7 +127,6 @@ void WS2812FX::setUpMatrix() {
} else { // memory allocation error
DEBUG_PRINTLN(F("ERROR 2D LED map allocation error."));
isMatrix = false;
panels = 0;
panel.clear();
Segment::maxWidth = _length;
Segment::maxHeight = 1;
@@ -144,103 +144,50 @@ void WS2812FX::setUpMatrix() {
///////////////////////////////////////////////////////////
#ifndef WLED_DISABLE_2D
// 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)
// pixel is clipped if it falls outside clipping range
// 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 {
#ifndef WLED_DISABLE_MODE_BLEND
if (_clipStart != _clipStop && blendingStyle != BLEND_STYLE_FADE) {
const bool invertX = _clipStart > _clipStop;
if (blendingStyle != BLEND_STYLE_FADE && isInTransition() && _clipStart != _clipStop) {
const bool invertX = _clipStart > _clipStop;
const bool invertY = _clipStartY > _clipStopY;
const int startX = invertX ? _clipStop : _clipStart;
const int stopX = invertX ? _clipStart : _clipStop;
const int startY = invertY ? _clipStopY : _clipStartY;
const int stopY = invertY ? _clipStartY : _clipStopY;
const int cStartX = invertX ? _clipStop : _clipStart;
const int cStopX = invertX ? _clipStart : _clipStop;
const int cStartY = invertY ? _clipStopY : _clipStartY;
const int cStopY = invertY ? _clipStartY : _clipStopY;
if (blendingStyle == BLEND_STYLE_FAIRY_DUST) {
const unsigned width = stopX - startX; // assumes full segment width (faster than virtualWidth())
const unsigned len = width * (stopY - startY); // assumes full segment height (faster than virtualHeight())
const unsigned width = cStopX - cStartX; // assumes full segment width (faster than virtualWidth())
const unsigned len = width * (cStopY - cStartY); // assumes full segment height (faster than virtualHeight())
if (len < 2) return false;
const unsigned shuffled = hashInt(x + y * width) % len;
const unsigned pos = (shuffled * 0xFFFFU) / len;
return progress() > pos;
return progress() <= pos;
}
bool xInside = (x >= startX && x < stopX); if (invertX) xInside = !xInside;
bool yInside = (y >= startY && y < stopY); if (invertY) yInside = !yInside;
const bool clip = (invertX && invertY) ? !_modeBlend : _modeBlend;
if (xInside && yInside) return clip; // covers window & corners (inverted)
if (blendingStyle == BLEND_STYLE_CIRCULAR_IN || blendingStyle == BLEND_STYLE_CIRCULAR_OUT) {
const int cx = (cStopX-cStartX+1) / 2;
const int cy = (cStopY-cStartY+1) / 2;
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;
}
#endif
return false;
}
void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) const
{
if (!isActive()) return; // not active
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)
#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);
}
if (x >= (int)vWidth() || y >= (int)vHeight() || x < 0 || y < 0) return; // if pixel would fall out of virtual segment just exit
setPixelColorXYRaw(x, y, col);
}
#ifdef WLED_USE_AA_PIXELS
@@ -289,39 +236,17 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) const
// returns RGBW values of pixel
uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const {
if (!isActive()) return 0; // not active
const int vW = vWidth();
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);
if (x >= (int)vWidth() || y >= (int)vHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit
return getPixelColorXYRaw(x,y);
}
// 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
const unsigned cols = vWidth();
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; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration
uint32_t last;
if (blur_x) {
const uint8_t keepx = smear ? 255 : 255 - blur_x;
@@ -330,20 +255,20 @@ void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) {
uint32_t carryover = BLACK;
uint32_t curnew = BLACK;
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);
curnew = color_fade(cur, keepx);
if (x > 0) {
if (carryover) curnew = color_add(curnew, carryover);
uint32_t prev = color_add(lastnew, part);
// optimization: only set pixel if color has changed
if (last != prev) setPixelColorXY(x - 1, row, prev);
} else setPixelColorXY(x, row, curnew); // first pixel
if (last != prev) setPixelColorRaw(XY(x - 1, row), prev);
} else setPixelColorRaw(XY(x, row), curnew); // first pixel
lastnew = curnew;
last = cur; // save original value for comparison on next iteration
carryover = part;
}
setPixelColorXY(cols-1, row, curnew); // set last pixel
setPixelColorRaw(XY(cols-1, row), curnew); // set last pixel
}
}
if (blur_y) {
@@ -353,20 +278,20 @@ void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) {
uint32_t carryover = BLACK;
uint32_t curnew = BLACK;
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);
curnew = color_fade(cur, keepy);
if (y > 0) {
if (carryover) curnew = color_add(curnew, carryover);
uint32_t prev = color_add(lastnew, part);
// optimization: only set pixel if color has changed
if (last != prev) setPixelColorXY(col, y - 1, prev);
} else setPixelColorXY(col, y, curnew); // first pixel
if (last != prev) setPixelColorRaw(XY(col, y - 1), prev);
} else setPixelColorRaw(XY(col, y), curnew); // first pixel
lastnew = curnew;
last = cur; //save original value for comparison on next iteration
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;
}
*/
void Segment::moveX(int delta, bool wrap) {
void Segment::moveX(int delta, bool wrap) const {
if (!isActive() || !delta) return; // not active
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 auto XY = [&](unsigned x, unsigned y){ return x + y*vW; };
int absDelta = abs(delta);
if (absDelta >= vW) return;
uint32_t newPxCol[vW];
@@ -465,16 +391,17 @@ void Segment::moveX(int delta, bool wrap) {
for (int x = 0; x < stop; x++) {
int srcX = x + newDelta;
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
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 auto XY = [&](unsigned x, unsigned y){ return x + y*vW; };
int absDelta = abs(delta);
if (absDelta >= vH) return;
uint32_t newPxCol[vH];
@@ -491,9 +418,9 @@ void Segment::moveY(int delta, bool wrap) {
for (int y = 0; y < stop; y++) {
int srcY = y + newDelta;
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 delta number of pixels to move
// @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;
switch (dir) {
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 (soft) {
// Xiaolin Wus algorithm
@@ -549,9 +476,6 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col,
x++;
}
} else {
// pre-scale color for all pixels
col = color_fade(col, _segBri);
_colorScaled = true;
// Bresenhams Algorithm
int d = 3 - (2*radius);
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;
}
}
_colorScaled = false;
}
}
// 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
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)
// draw soft bounding circle
if (soft) drawCircle(cx, cy, radius, col, soft);
// pre-scale color for all pixels
col = color_fade(col, _segBri);
_colorScaled = true;
// fill it
for (int y = -radius; y <= radius; y++) {
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);
}
}
_colorScaled = false;
}
//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
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)
@@ -633,15 +552,12 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3
int y = int(intersectY);
if (steep) std::swap(x,y); // temporaryly swap if steep
// pixel coverage is determined by fractional part of y co-ordinate
setPixelColorXY(x, y, color_blend(c, getPixelColorXY(x, y), keep));
setPixelColorXY(x+int(steep), y+int(!steep), color_blend(c, getPixelColorXY(x+int(steep), y+int(!steep)), seep));
blendPixelColorXY(x, y, c, seep);
blendPixelColorXY(x+int(steep), y+int(!steep), c, keep);
intersectY += gradient;
if (steep) std::swap(x,y); // restore if steep
}
} else {
// pre-scale color for all pixels
c = color_fade(c, _segBri);
_colorScaled = true;
// Bresenham's algorithm
int err = (dx>dy ? dx : -dy)/2; // error direction
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 < dy) { err += dx; y0 += sy; }
}
_colorScaled = false;
}
}
@@ -663,29 +578,26 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3
// draws a raster font character on canvas
// 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 (chr < 32 || chr > 126) return; // only ASCII 32-126 supported
chr -= 32; // align with font table entries
const int font = w*h;
CRGB col = CRGB(color);
CRGBPalette16 grad = CRGBPalette16(col, col2 ? CRGB(col2) : col);
if(usePalGrad) grad = SEGPALETTE; // selected palette as gradient
// if col2 == BLACK then use currently selected palette for gradient otherwise create gradient from color and col2
CRGBPalette16 grad = col2 ? CRGBPalette16(CRGB(color), CRGB(col2)) : SEGPALETTE; // selected palette as gradient
//if (w<5 || w>6 || h!=8) return;
for (int i = 0; i<h; i++) { // character height
uint8_t bits = 0;
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 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 60: bits = pgm_read_byte_near(&console_font_5x12[(chr * h) + i]); break; // 5x12 font
default: return;
}
CRGBW c = ColorFromPalette(grad, (i+1)*255/h, _segBri, LINEARBLEND_NOWRAP);
_colorScaled = true;
CRGBW c = ColorFromPalette(grad, (i+1)*255/h, 255, LINEARBLEND_NOWRAP); // NOBLEND is faster
for (int j = 0; j<w; j++) { // character width
int x0, y0;
switch (rotate) {
@@ -697,15 +609,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 (((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))
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
// extract the fractional parts and derive their inverses
unsigned xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy;

1876
wled00/FX_fcn.cpp Normal file → Executable file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -30,28 +30,6 @@
#define PSPRINTLN(x)
#endif
// memory and transition manager
struct partMem {
void* particleMemPointer; // pointer to particle memory
uint32_t buffersize; // buffer size in bytes
uint8_t particleType; // type of particles currently in memory: 0 = none, particle struct size otherwise (required for 1D<->2D transitions)
uint8_t id; // ID of segment this memory belongs to
uint8_t watchdog; // counter to handle deallocation
uint8_t inTransition; // to track PS to PS FX transitions (is set to new FX ID during transitions), not set if not both FX are PS FX
uint8_t currentFX; // current FX ID, is set when transition is complete, used to detect back and forth transitions
bool finalTransfer; // used to update buffer in rendering function after transition has ended
bool transferParticles; // if set, particles in buffer are transferred to new FX
};
void* particleMemoryManager(const uint32_t requestedParticles, size_t structSize, uint32_t &availableToPS, uint32_t numParticlesUsed, const uint8_t effectID); // update particle memory pointer, handles memory transitions
void particleHandover(void *buffer, size_t structSize, int32_t numParticles);
void updateUsedParticles(const uint32_t allocated, const uint32_t available, const uint8_t percentage, uint32_t &used);
bool segmentIsOverlay(void); // check if segment is fully overlapping with at least one underlying segment
partMem* getPartMem(void); // returns pointer to memory struct for current segment or nullptr
void updateRenderingBuffer(uint32_t requiredpixels, bool isFramebuffer, bool initialize); // allocate CRGB rendering buffer, update size if needed
void transferBuffer(uint32_t width, uint32_t height, bool useAdditiveTransfer = false); // transfer the buffer to the segment (supports 1D and 2D)
void servicePSmem(); // increments watchdog, frees memory if idle too long
// limit speed of particles (used in 1D and 2D)
static inline int32_t limitSpeed(const int32_t speed) {
return speed > PS_P_MAXSPEED ? PS_P_MAXSPEED : (speed < -PS_P_MAXSPEED ? -PS_P_MAXSPEED : speed); // note: this is slightly faster than using min/max at the cost of 50bytes of flash
@@ -60,7 +38,7 @@ static inline int32_t limitSpeed(const int32_t speed) {
#ifndef WLED_DISABLE_PARTICLESYSTEM2D
// memory allocation
#define ESP8266_MAXPARTICLES 300 // enough up to 20x20 pixels
#define ESP8266_MAXPARTICLES 256 // enough up to 16x16 pixels
#define ESP8266_MAXSOURCES 24
#define ESP32S2_MAXPARTICLES 1024 // enough up to 32x32 pixels
#define ESP32S2_MAXSOURCES 64
@@ -118,7 +96,7 @@ typedef union {
// struct for additional particle settings (option)
typedef struct { // 2 bytes
uint8_t size; // particle size, 255 means 10 pixels in diameter
uint8_t size; // particle size, 255 means 10 pixels in diameter, 0 means use global size (including single pixel rendering)
uint8_t forcecounter; // counter for applying forces to individual particles
} PSadvancedParticle;
@@ -149,7 +127,7 @@ typedef struct {
int8_t var; // variation of emitted speed (adds random(+/- var) to speed)
int8_t vx; // emitting speed
int8_t vy;
uint8_t size; // particle size (advanced property)
uint8_t size; // particle size (advanced property), global size is added on top to this size
} PSsource;
// class uses approximately 60 bytes
@@ -178,7 +156,6 @@ public:
void pointAttractor(const uint32_t particleindex, PSparticle &attractor, const uint8_t strength, const bool swallow);
// set options note: inlining the set function uses more flash so dont optimize
void setUsedParticles(const uint8_t percentage); // set the percentage of particles used in the system, 255=100%
inline uint32_t getAvailableParticles(void) { return availableParticles; } // available particles in the buffer, use this to check if buffer changed during FX init
void setCollisionHardness(const uint8_t hardness); // hardness for particle collisions (255 means full hard)
void setWallHardness(const uint8_t hardness); // hardness for bouncing on the wall if bounceXY is set
void setWallRoughness(const uint8_t roughness); // wall roughness randomizes wall collisions
@@ -210,12 +187,12 @@ public:
private:
//rendering functions
void ParticleSys_render();
[[gnu::hot]] void renderParticle(const uint32_t particleindex, const uint32_t brightness, const CRGB& color, const bool wrapX, const bool wrapY);
void render();
[[gnu::hot]] void renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB& color, const bool wrapX, const bool wrapY);
//paricle physics applied by system if flags are set
void applyGravity(); // applies gravity to all particles
void handleCollisions();
[[gnu::hot]] void collideParticles(PSparticle &particle1, PSparticle &particle2, const int32_t dx, const int32_t dy, const int32_t collDistSq);
[[gnu::hot]] void collideParticles(PSparticle &particle1, PSparticle &particle2, const int32_t dx, const int32_t dy, const uint32_t collDistSq);
void fireParticleupdate();
//utility functions
void updatePSpointers(const bool isadvanced, const bool sizecontrol); // update the data pointers to current segment data space
@@ -223,9 +200,9 @@ private:
void getParticleXYsize(PSadvancedParticle *advprops, PSsizeControl *advsize, uint32_t &xsize, uint32_t &ysize);
[[gnu::hot]] void bounce(int8_t &incomingspeed, int8_t &parallelspeed, int32_t &position, const uint32_t maxposition); // bounce on a wall
// note: variables that are accessed often are 32bit for speed
CRGB *framebuffer; // local frame buffer for rendering
PSsettings2D particlesettings; // settings used when updating particles (can also used by FX to move sources), do not edit properties directly, use functions above
uint32_t numParticles; // total number of particles allocated by this system note: during transitions, less are available, use availableParticles
uint32_t availableParticles; // number of particles available for use (can be more or less than numParticles, assigned by memory manager)
uint32_t numParticles; // total number of particles allocated by this system
uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster
int32_t collisionHardness;
uint32_t wallHardness;
@@ -233,16 +210,13 @@ private:
uint32_t particleHardRadius; // hard surface radius of a particle, used for collision detection (32bit for speed)
uint16_t collisionStartIdx; // particle array start index for collision detection
uint8_t fireIntesity = 0; // fire intensity, used for fire mode (flash use optimization, better than passing an argument to render function)
uint8_t fractionOfParticlesUsed; // percentage of particles used in the system (255=100%), used during transition updates
uint8_t forcecounter; // counter for globally applied forces
uint8_t gforcecounter; // counter for global gravity
int8_t gforce; // gravity strength, default is 8 (negative is allowed, positive is downwards)
// global particle properties for basic particles
uint8_t particlesize; // global particle size, 0 = 1 pixel, 1 = 2 pixels, 255 = 10 pixels (note: this is also added to individual sized particles)
uint8_t particlesize; // global particle size, 0 = 1 pixel, 1 = 2 pixels, 255 = 10 pixels (note: this is also added to individual sized particles, set to 0 or 1 for standard advanced particle rendering)
uint8_t motionBlur; // motion blur, values > 100 gives smoother animations. Note: motion blurring does not work if particlesize is > 0
uint8_t smearBlur; // 2D smeared blurring of full frame
uint8_t effectID; // ID of the effect that is using this particle system, used for transitions
uint32_t lastRender; // last time the particles were rendered, intermediate fix for speedup
};
void blur2D(CRGB *colorbuffer, const uint32_t xsize, uint32_t ysize, const uint32_t xblur, const uint32_t yblur, const uint32_t xstart = 0, uint32_t ystart = 0, const bool isparticle = false);
@@ -258,7 +232,7 @@ bool allocateParticleSystemMemory2D(const uint32_t numparticles, const uint32_t
////////////////////////
#ifndef WLED_DISABLE_PARTICLESYSTEM1D
// memory allocation
#define ESP8266_MAXPARTICLES_1D 450
#define ESP8266_MAXPARTICLES_1D 320
#define ESP8266_MAXSOURCES_1D 16
#define ESP32S2_MAXPARTICLES_1D 1300
#define ESP32S2_MAXSOURCES_1D 32
@@ -315,7 +289,7 @@ typedef union {
// struct for additional particle settings (optional)
typedef struct {
uint8_t sat; //color saturation
uint8_t size; // particle size, 255 means 10 pixels in diameter
uint8_t size; // particle size, 255 means 10 pixels in diameter, this overrides global size setting
uint8_t forcecounter;
} PSadvancedParticle1D;
@@ -343,13 +317,12 @@ public:
int32_t sprayEmit(const PSsource1D &emitter);
void particleMoveUpdate(PSparticle1D &part, PSparticleFlags1D &partFlags, PSsettings1D *options = NULL, PSadvancedParticle1D *advancedproperties = NULL); // move function
//particle physics
[[gnu::hot]] void applyForce(PSparticle1D &part, const int8_t xforce, uint8_t &counter); //apply a force to a single particle
[[gnu::hot]] void applyForce(PSparticle1D &part, const int8_t xforce, uint8_t &counter); //apply a force to a single particle
void applyForce(const int8_t xforce); // apply a force to all particles
void applyGravity(PSparticle1D &part, PSparticleFlags1D &partFlags); // applies gravity to single particle (use this for sources)
void applyFriction(const int32_t coefficient); // apply friction to all used particles
// set options
void setUsedParticles(const uint8_t percentage); // set the percentage of particles used in the system, 255=100%
inline uint32_t getAvailableParticles(void) { return availableParticles; } // available particles in the buffer, use this to check if buffer changed during FX init
void setWallHardness(const uint8_t hardness); // hardness for bouncing on the wall if bounceXY is set
void setSize(const uint32_t x); //set particle system size (= strip length)
void setWrap(const bool enable);
@@ -360,7 +333,7 @@ public:
void setColorByPosition(const bool enable);
void setMotionBlur(const uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero
void setSmearBlur(const uint8_t bluramount); // enable 1D smeared blurring of full frame
void setParticleSize(const uint8_t size); //size 0 = 1 pixel, size 1 = 2 pixels, is overruled by advanced particle size
void setParticleSize(const uint8_t size); //size 0 = 1 pixel, size 1 = 2 pixels, is overruled if advanced particle is used
void setGravity(int8_t force = 8);
void enableParticleCollisions(bool enable, const uint8_t hardness = 255);
@@ -377,23 +350,24 @@ public:
private:
//rendering functions
void ParticleSys_render(void);
void renderParticle(const uint32_t particleindex, const uint32_t brightness, const CRGB &color, const bool wrap);
void render(void);
[[gnu::hot]] void renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB &color, const bool wrap);
//paricle physics applied by system if flags are set
void applyGravity(); // applies gravity to all particles
void handleCollisions();
[[gnu::hot]] void collideParticles(PSparticle1D &particle1, const PSparticleFlags1D &particle1flags, PSparticle1D &particle2, const PSparticleFlags1D &particle2flags, int32_t dx, int32_t relativeVx, const int32_t collisiondistance);
[[gnu::hot]] void collideParticles(PSparticle1D &particle1, const PSparticleFlags1D &particle1flags, PSparticle1D &particle2, const PSparticleFlags1D &particle2flags, const int32_t dx, const uint32_t dx_abs, const uint32_t collisiondistance);
//utility functions
void updatePSpointers(const bool isadvanced); // update the data pointers to current segment data space
//void updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize); // advanced size control
[[gnu::hot]] void bounce(int8_t &incomingspeed, int8_t &parallelspeed, int32_t &position, const uint32_t maxposition); // bounce on a wall
// note: variables that are accessed often are 32bit for speed
#ifndef ESP8266
CRGB *framebuffer; // local frame buffer for rendering
#endif
PSsettings1D particlesettings; // settings used when updating particles
uint32_t numParticles; // total number of particles allocated by this system note: never use more than this, even if more are available (only this many advanced particles are allocated)
uint32_t availableParticles; // number of particles available for use (can be more or less than numParticles, assigned by memory manager)
uint8_t fractionOfParticlesUsed; // percentage of particles used in the system (255=100%), used during transition updates
uint32_t numParticles; // total number of particles allocated by this system
uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster
int32_t collisionHardness;
uint32_t particleHardRadius; // hard surface radius of a particle, used for collision detection
@@ -403,11 +377,9 @@ private:
uint8_t forcecounter; // counter for globally applied forces
uint16_t collisionStartIdx; // particle array start index for collision detection
//global particle properties for basic particles
uint8_t particlesize; // global particle size, 0 = 1 pixel, 1 = 2 pixels
uint8_t particlesize; // global particle size, 0 = 1 pixel, 1 = 2 pixels, is overruled by advanced particle size
uint8_t motionBlur; // enable motion blur, values > 100 gives smoother animations
uint8_t smearBlur; // smeared blurring of full frame
uint8_t effectID; // ID of the effect that is using this particle system, used for transitions
uint32_t lastRender; // last time the particles were rendered, intermediate fix for speedup
};
bool initParticleSystem1D(ParticleSystem1D *&PartSys, const uint32_t requestedsources, const uint8_t fractionofparticles = 255, const uint32_t additionalbytes = 0, const bool advanced = false);

View File

@@ -32,8 +32,31 @@ extern bool useParallelI2S;
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
//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
extern "C" {
void *p_malloc(size_t); // prefer PSRAM over DRAM
void *p_calloc(size_t, size_t); // prefer PSRAM over DRAM
void *p_realloc(void *, size_t); // prefer PSRAM over DRAM
inline void p_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 p_malloc malloc
#define p_calloc calloc
#define p_realloc realloc
#define p_free free
#define d_malloc malloc
#define d_calloc calloc
#define d_realloc realloc
#define d_free free
#endif
//color mangling macros
#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
@@ -72,7 +95,7 @@ void Bus::calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw) {
} else {
cct = (approximateKelvinFromRGB(c) - 1900) >> 5; // convert K (from RGB value) to relative format
}
//0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold)
if (cct < _cctBlend) ww = 255;
else ww = ((255-cct) * 255) / (255 - _cctBlend);
@@ -106,7 +129,6 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr)
, _colorOrder(bc.colorOrder)
, _milliAmpsPerLed(bc.milliAmpsPerLed)
, _milliAmpsMax(bc.milliAmpsMax)
, _data(nullptr)
{
DEBUGBUS_PRINTLN(F("Bus: Creating digital bus."));
if (!isDigital(bc.type) || !bc.count) { DEBUGBUS_PRINTLN(F("Not digial or empty bus!")); return; }
@@ -127,14 +149,14 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr)
_hasRgb = hasRGB(bc.type);
_hasWhite = hasWhite(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;
if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus
_busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr);
_valid = (_busPtr != nullptr) && bc.count > 0;
// fix for wled#4759
if (_valid) for (unsigned i = 0; i < _skip; i++) {
PolyBus::setPixelColor(_busPtr, _iType, i, 0, COL_ORDER_GRB); // set sacrificial pixels to black (CO does not matter here)
}
DEBUGBUS_PRINTF_P(PSTR("Bus: %successfully inited #%u (len:%u, type:%u (RGB:%d, W:%d, CCT:%d), pins:%u,%u [itype:%u] mA=%d/%d)\n"),
_valid?"S":"Uns",
(int)nr,
@@ -212,56 +234,18 @@ void BusDigital::show() {
uint8_t cctWW = 0, cctCW = 0;
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 (_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) {
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
for (unsigned i = 0; i < hwLen; i++) {
// use 0 as color order, actual order does not matter here as we just update the channel values as-is
uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, i, 0), _bri);
if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW); // this will unfortunately corrupt (segment) CCT data on every bus
PolyBus::setPixelColor(_busPtr, _iType, i, c, 0, (cctCW<<8) | cctWW); // repaint all pixels with new brightness
}
if (newBri < _bri) {
PolyBus::setBrightness(_busPtr, _iType, newBri); // limit brightness to stay within current limits
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
for (unsigned i = 0; i < hwLen; i++) {
// use 0 as color order, actual order does not matter here as we just update the channel values as-is
uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, i, 0), _bri);
if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW); // this will unfortunately corrupt (segment) CCT data on every bus
PolyBus::setPixelColor(_busPtr, _iType, i, c, 0, (cctCW<<8) | cctWW); // repaint all pixels with new brightness
}
}
PolyBus::show(_busPtr, _iType, !_data); // faster if buffer consistency is not important
PolyBus::show(_busPtr, _iType, _skip); // faster if buffer consistency is not important (no skipped LEDs)
// restore bus brightness to its original value
// this is done right after show, so this is only OK if LED updates are completed before show() returns
// or async show has a separate buffer (ESP32 RMT and I2S are ok)
@@ -292,86 +276,61 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) {
if (!_valid) return;
if (hasWhite()) c = autoWhiteCalc(c);
if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
if (_data) {
size_t offset = pix * getNumberOfChannels();
uint8_t* dataptr = _data + offset;
if (hasRGB()) {
*dataptr++ = R(c);
*dataptr++ = G(c);
*dataptr++ = B(c);
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;
}
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
uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const {
if (!_valid) return 0;
if (_data) {
const size_t offset = pix * getNumberOfChannels();
uint32_t c;
if (!hasRGB()) {
c = RGBW32(_data[offset], _data[offset], _data[offset], _data[offset]);
} else {
c = RGBW32(_data[offset], _data[offset+1], _data[offset+2], hasWhite() ? _data[offset+3] : 0);
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;
}
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;
if (pinArray) for (unsigned i = 0; i < numPins; i++) pinArray[i] = _pins[i];
return numPins;
}
unsigned BusDigital::getBusSize() const {
return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) + (_data ? _len * getNumberOfChannels() : 0) : 0);
size_t BusDigital::getBusSize() const {
return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) : 0);
}
void BusDigital::setColorOrder(uint8_t colorOrder) {
@@ -380,7 +339,7 @@ void BusDigital::setColorOrder(uint8_t 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() {
return {
{TYPE_WS2812_RGB, "D", PSTR("WS281x")},
@@ -414,8 +373,6 @@ void BusDigital::begin() {
void BusDigital::cleanup() {
DEBUGBUS_PRINTLN(F("Digital Cleanup."));
PolyBus::cleanup(_busPtr, _iType);
free(_data);
_data = nullptr;
_iType = I_NONE;
_valid = false;
_busPtr = nullptr;
@@ -453,7 +410,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
{
if (!isPWM(bc.type)) return;
unsigned numPins = numPWMPins(bc.type);
const unsigned numPins = numPWMPins(bc.type);
[[maybe_unused]] const bool dithering = _needsRefresh;
_frequency = bc.frequency ? bc.frequency : WLED_PWM_FREQ;
// duty cycle resolution (_depth) can be extracted from this formula: CLOCK_FREQUENCY > _frequency * 2^_depth
@@ -461,36 +418,40 @@ BusPwm::BusPwm(const BusConfig &bc)
managed_pin_type pins[numPins];
for (unsigned i = 0; i < numPins; i++) pins[i] = {(int8_t)bc.pins[i], true};
if (!PinManager::allocateMultiplePins(pins, numPins, PinOwner::BusPwm)) return;
#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
if (PinManager::allocateMultiplePins(pins, numPins, PinOwner::BusPwm)) {
#ifdef ESP8266
pinMode(_pins[i], OUTPUT);
analogWriteRange((1<<_depth)-1);
analogWriteFreq(_frequency);
#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)
// 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);
DEBUGBUS_PRINTLN(F("No more free LEDC channels!"));
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
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]);
}
@@ -561,7 +522,7 @@ void BusPwm::show() {
constexpr unsigned bitShift = 8; // 256 clocks for dead time, ~3us at 80MHz
#else
// 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 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)
@@ -588,7 +549,7 @@ void BusPwm::show() {
unsigned duty = (_data[i] * pwmBri) / 255;
unsigned deadTime = 0;
if (_type == TYPE_ANALOG_2CH && Bus::getCCTBlend() == 0) {
if (_type == TYPE_ANALOG_2CH && Bus::_cctBlend == 0) {
// add dead time between signals (when using dithering, two full 8bit pulses are required)
deadTime = (1+dithering) << bitShift;
// we only need to take care of shortening the signal at (almost) full brightness otherwise pulses may overlap
@@ -620,14 +581,14 @@ void BusPwm::show() {
}
}
unsigned BusPwm::getPins(uint8_t* pinArray) const {
size_t BusPwm::getPins(uint8_t* pinArray) const {
if (!_valid) return 0;
unsigned numPins = numPWMPins(_type);
if (pinArray) for (unsigned i = 0; i < numPins; i++) pinArray[i] = _pins[i];
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() {
return {
{TYPE_ANALOG_1CH, "A", PSTR("PWM White")},
@@ -695,13 +656,13 @@ void BusOnOff::show() {
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 (pinArray) pinArray[0] = _pin;
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() {
return {
{TYPE_ONOFF, "", PSTR("On/Off")},
@@ -731,7 +692,7 @@ BusNetwork::BusNetwork(const BusConfig &bc)
_hasCCT = false;
_UDPchannels = _hasWhite + 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);
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 +721,12 @@ void BusNetwork::show() {
_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];
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() {
return {
{TYPE_NET_DDP_RGB, "N", PSTR("DDP RGB (network)")}, // should be "NNNN" to determine 4 "pin" fields
@@ -776,13 +737,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_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_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() {
DEBUGBUS_PRINTLN(F("Virtual Cleanup."));
free(_data);
d_free(_data);
_data = nullptr;
_type = I_NONE;
_valid = false;
@@ -790,11 +751,11 @@ void BusNetwork::cleanup() {
//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)) {
return sizeof(BusNetwork) + (count * Bus::getNumberOfChannels(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)) {
return sizeof(BusOnOff);
} else {
@@ -803,7 +764,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
// front buffers are always allocated per bus
unsigned size = 0;
@@ -832,22 +793,24 @@ unsigned BusManager::memUsage() {
}
int BusManager::add(const BusConfig &bc) {
DEBUGBUS_PRINTF_P(PSTR("Bus: Adding bus (%d - %d >= %d)\n"), getNumBusses(), getNumVirtualBusses(), WLED_MAX_BUSSES);
if (getNumBusses() - getNumVirtualBusses() >= WLED_MAX_BUSSES) return -1;
unsigned numDigital = 0;
for (const auto &bus : busses) if (bus->isDigital() && !bus->is2Pin()) numDigital++;
DEBUGBUS_PRINTF_P(PSTR("Bus: Adding bus (p:%d v:%d)\n"), getNumBusses(), getNumVirtualBusses());
unsigned digital = 0;
unsigned analog = 0;
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)) {
busses.push_back(make_unique<BusNetwork>(bc));
//busses.push_back(new BusNetwork(bc));
} else if (Bus::isDigital(bc.type)) {
busses.push_back(make_unique<BusDigital>(bc, numDigital));
//busses.push_back(new BusDigital(bc, numDigital));
busses.push_back(make_unique<BusDigital>(bc, Bus::is2Pin(bc.type) ? twoPin : digital));
} else if (Bus::isOnOff(bc.type)) {
busses.push_back(make_unique<BusOnOff>(bc));
//busses.push_back(new BusOnOff(bc));
} else {
busses.push_back(make_unique<BusPwm>(bc));
//busses.push_back(new BusPwm(bc));
}
return busses.size();
}
@@ -865,7 +828,7 @@ static String LEDTypesToJson(const std::vector<LEDType>& types) {
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 json = "[";
json += LEDTypesToJson(BusDigital::getLEDTypes());
@@ -891,7 +854,6 @@ void BusManager::removeAll() {
DEBUGBUS_PRINTLN(F("Removing all."));
//prevents crashes due to deleting busses while in use.
while (!canAllShow()) yield();
//for (auto &bus : busses) delete bus; // needed when not using std::unique_ptr C++ >11
busses.clear();
PolyBus::setParallelI2S1Output(false);
}
@@ -980,9 +942,8 @@ void BusManager::show() {
void IRAM_ATTR BusManager::setPixelColor(unsigned pix, uint32_t c) {
for (auto &bus : busses) {
unsigned bstart = bus->getStart();
if (pix < bstart || pix >= bstart + bus->getLength()) continue;
bus->setPixelColor(pix - bstart, c);
if (!bus->containsPixel(pix)) continue;
bus->setPixelColor(pix - bus->getStart(), c);
}
}
@@ -997,9 +958,8 @@ void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) {
uint32_t BusManager::getPixelColor(unsigned pix) {
for (auto &bus : busses) {
unsigned bstart = bus->getStart();
if (!bus->containsPixel(pix)) continue;
return bus->getPixelColor(pix - bstart);
return bus->getPixelColor(pix - bus->getStart());
}
return 0;
}
@@ -1016,12 +976,11 @@ bool PolyBus::_useParallelI2S = false;
// Bus static member definition
int16_t Bus::_cct = -1;
uint8_t Bus::_cctBlend = 0;
uint8_t Bus::_cctBlend = 0; // 0 - 127
uint8_t Bus::_gAWM = 255;
uint16_t BusDigital::_milliAmpsTotal = 0;
std::vector<std::unique_ptr<Bus>> BusManager::busses;
//std::vector<Bus*> BusManager::busses;
uint16_t BusManager::_gMilliAmpsUsed = 0;
uint16_t BusManager::_gMilliAmpsMax = ABL_MILLIAMPS_DEFAULT;

View File

@@ -114,17 +114,17 @@ class Bus {
_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 show() = 0;
virtual void show() = 0;
virtual bool canShow() const { return true; }
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 setColorOrder(uint8_t co) {}
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 uint8_t getColorOrder() const { return COL_ORDER_RGB; }
virtual unsigned skippedLeds() const { return 0; }
@@ -132,7 +132,7 @@ class Bus {
virtual uint16_t getLEDCurrent() const { return 0; }
virtual uint16_t getUsedCurrent() 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 hasWhite() const { return _hasWhite; }
@@ -148,7 +148,7 @@ class Bus {
inline void setStart(uint16_t start) { _start = start; }
inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; }
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 uint8_t getType() const { return _type; }
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; }
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 unsigned getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); }
static constexpr size_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK
static constexpr size_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(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);
}
@@ -189,9 +189,9 @@ class Bus {
static inline void setGlobalAWMode(uint8_t m) { if (m < 5) _gAWM = m; else _gAWM = AW_GLOBAL_DISABLED; }
static inline uint8_t getGlobalAWMode() { return _gAWM; }
static inline void setCCT(int16_t cct) { _cct = cct; }
static inline uint8_t getCCTBlend() { return _cctBlend; }
static inline void setCCTBlend(uint8_t b) {
_cctBlend = (std::min((int)b,100) * 127) / 100;
static inline uint8_t getCCTBlend() { return (_cctBlend * 100 + 64) / 127; } // returns 0-100, 100% = 127. +64 for rounding
static inline void setCCTBlend(uint8_t b) { // input is 0-100
_cctBlend = (std::min((int)b,100) * 127 + 50) / 100; // +50 for rounding, b=100% -> 127
//compile-time limiter for hardware that can't power both white channels at max
#ifdef WLED_MAX_CCT_BLEND
if (_cctBlend > WLED_MAX_CCT_BLEND) _cctBlend = WLED_MAX_CCT_BLEND;
@@ -243,13 +243,13 @@ class BusDigital : public Bus {
void setColorOrder(uint8_t colorOrder) override;
[[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override;
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; }
uint16_t getFrequency() const override { return _frequencykHz; }
uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; }
uint16_t getUsedCurrent() const override { return _milliAmpsTotal; }
uint16_t getMaxCurrent() const override { return _milliAmpsMax; }
unsigned getBusSize() const override;
size_t getBusSize() const override;
void begin() override;
void cleanup();
@@ -263,7 +263,6 @@ class BusDigital : public Bus {
uint16_t _frequencykHz;
uint8_t _milliAmpsPerLed;
uint16_t _milliAmpsMax;
uint8_t *_data;
void *_busPtr;
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;
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; }
unsigned getBusSize() const override { return sizeof(BusPwm); }
size_t getBusSize() const override { return sizeof(BusPwm); }
void show() override;
inline void cleanup() { deallocatePins(); }
@@ -318,8 +317,8 @@ class BusOnOff : public Bus {
void setPixelColor(unsigned pix, uint32_t c) override;
uint32_t getPixelColor(unsigned pix) const override;
unsigned getPins(uint8_t* pinArray) const override;
unsigned getBusSize() const override { return sizeof(BusOnOff); }
size_t getPins(uint8_t* pinArray) const override;
size_t getBusSize() const override { return sizeof(BusOnOff); }
void show() override;
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
[[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override;
[[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override;
unsigned getPins(uint8_t* pinArray = nullptr) const override;
unsigned getBusSize() const override { return sizeof(BusNetwork) + (isOk() ? _len * _UDPchannels : 0); }
void show() override;
void cleanup();
size_t getPins(uint8_t* pinArray = nullptr) const override;
size_t getBusSize() const override { return sizeof(BusNetwork) + (isOk() ? _len * _UDPchannels : 0); }
void show() override;
void cleanup();
static std::vector<LEDType> getLEDTypes();
@@ -367,11 +366,10 @@ struct BusConfig {
uint8_t autoWhite;
uint8_t pins[5] = {255, 255, 255, 255, 255};
uint16_t frequency;
bool doubleBuffer;
uint8_t milliAmpsPerLed;
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))
, start(pstart)
, colorOrder(pcolorOrder)
@@ -379,7 +377,6 @@ struct BusConfig {
, skipAmount(skip)
, autoWhite(aw)
, frequency(clock_kHz)
, doubleBuffer(dblBfr)
, milliAmpsPerLed(maPerLed)
, milliAmpsMax(maMax)
{
@@ -411,7 +408,7 @@ struct BusConfig {
return true;
}
unsigned memUsage(unsigned nr = 0) const;
size_t memUsage(unsigned nr = 0) const;
};

View File

@@ -469,12 +469,20 @@ class PolyBus {
}
static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel) {
#if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3))
// NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
if (_useParallelI2S && (channel >= 8)) {
// Parallel I2S channels are to be used first, so subtract 8 to get the RMT channel number
channel -= 8;
}
#endif
#if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3))
// since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation
if (!_useParallelI2S && channel > 0) channel--; // accommodate I2S1 which is used as 1st bus on classic ESP32
// if user selected parallel I2S, RMT is used 1st (8 channels) followed by parallel I2S (8 channels)
#endif
void* busPtr = nullptr;
switch (busType) {
case I_NONE: break;
@@ -862,12 +870,12 @@ class PolyBus {
// I2S1 bus or paralell buses
#ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I2_NEO_3: if (_useParallelI2S) (static_cast<B_32_IP_NEO_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast<B_32_I2_NEO_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast<B_32_IP_NEO_4*>(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast<B_32_I2_NEO_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast<B_32_IP_NEO_4*>(busPtr))->SetPixelColor(pix, col); else (static_cast<B_32_I2_NEO_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_I2_400_3: if (_useParallelI2S) (static_cast<B_32_IP_400_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast<B_32_I2_400_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_32_I2_TM1_4: if (_useParallelI2S) (static_cast<B_32_IP_TM1_4*>(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast<B_32_I2_TM1_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_I2_TM1_4: if (_useParallelI2S) (static_cast<B_32_IP_TM1_4*>(busPtr))->SetPixelColor(pix, col); else (static_cast<B_32_I2_TM1_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_I2_TM2_3: if (_useParallelI2S) (static_cast<B_32_IP_TM2_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast<B_32_I2_TM2_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_32_I2_UCS_3: if (_useParallelI2S) (static_cast<B_32_IP_UCS_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast<B_32_I2_UCS_3*>(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break;
case I_32_I2_UCS_4: if (_useParallelI2S) (static_cast<B_32_IP_UCS_4*>(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast<B_32_I2_UCS_4*>(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break;
case I_32_I2_UCS_3: if (_useParallelI2S) (static_cast<B_32_IP_UCS_3*>(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); else (static_cast<B_32_I2_UCS_3*>(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break;
case I_32_I2_UCS_4: if (_useParallelI2S) (static_cast<B_32_IP_UCS_4*>(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); else (static_cast<B_32_I2_UCS_4*>(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break;
case I_32_I2_APA106_3: if (_useParallelI2S) (static_cast<B_32_IP_APA106_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast<B_32_I2_APA106_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_32_I2_FW6_5: if (_useParallelI2S) (static_cast<B_32_IP_FW6_5*>(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); else (static_cast<B_32_I2_FW6_5*>(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break;
case I_32_I2_2805_5: if (_useParallelI2S) (static_cast<B_32_IP_2805_5*>(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); else (static_cast<B_32_I2_2805_5*>(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break;
@@ -1423,12 +1431,13 @@ class PolyBus {
return I_8266_U0_SM16825_5 + offset;
}
#else //ESP32
uint8_t offset = 0; // 0 = RMT (num 1-8), 1 = I2S0 (used by Audioreactive), 2 = I2S1
uint8_t offset = 0; // 0 = RMT (num 1-8), 1 = I2S1 [I2S0 is used by Audioreactive]
#if defined(CONFIG_IDF_TARGET_ESP32S2)
// ESP32-S2 only has 4 RMT channels
if (_useParallelI2S) {
if (num > 11) return I_NONE;
if (num > 3) offset = 1; // use x8 parallel I2S0 channels (use last to allow Audioreactive)
if (num < 8) offset = 1; // use x8 parallel I2S0 channels followed by RMT
// Note: conflicts with AudioReactive if enabled
} else {
if (num > 4) return I_NONE;
if (num > 3) offset = 1; // only one I2S0 (use last to allow Audioreactive)
@@ -1441,7 +1450,7 @@ class PolyBus {
// On ESP32-S3 only the first 4 RMT channels are usable for transmitting
if (_useParallelI2S) {
if (num > 11) return I_NONE;
if (num > 3) offset = 1; // use x8 parallel I2S LCD channels
if (num < 8) offset = 1; // use x8 parallel I2S LCD channels, followed by RMT
} else {
if (num > 3) return I_NONE; // do not use single I2S (as it is not supported)
}
@@ -1449,7 +1458,7 @@ class PolyBus {
// standard ESP32 has 8 RMT and x1/x8 I2S1 channels
if (_useParallelI2S) {
if (num > 15) return I_NONE;
if (num > 7) offset = 1; // 8 RMT followed by 8 I2S
if (num < 8) offset = 1; // 8 I2S followed by 8 RMT
} else {
if (num > 9) return I_NONE;
if (num == 0) offset = 1; // prefer I2S1 for 1st bus (less flickering but more RAM needed)

View File

@@ -74,7 +74,7 @@ void doublePressAction(uint8_t b)
if (!macroDoublePress[b]) {
switch (b) {
//case 0: toggleOnOff(); colorUpdated(CALL_MODE_BUTTON); break; //instant short press on button 0 if no macro set
case 1: ++effectPalette %= strip.getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break;
case 1: ++effectPalette %= getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break;
}
} else {
applyPreset(macroDoublePress[b], CALL_MODE_BUTTON_PRESET);
@@ -226,8 +226,8 @@ void handleAnalog(uint8_t b)
effectIntensity = aRead;
} else if (macroDoublePress[b] == 247) {
// selected palette
effectPalette = map(aRead, 0, 252, 0, strip.getPaletteCount()-1);
effectPalette = constrain(effectPalette, 0, strip.getPaletteCount()-1); // map is allowed to "overshoot", so we need to contrain the result
effectPalette = map(aRead, 0, 252, 0, getPaletteCount()-1);
effectPalette = constrain(effectPalette, 0, getPaletteCount()-1); // map is allowed to "overshoot", so we need to contrain the result
} else if (macroDoublePress[b] == 200) {
// primary color, hue, full saturation
colorHStoRGB(aRead*256,255,colPri);

View File

@@ -6,6 +6,35 @@
* 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
#define CJSON(a,b) a = b | a
@@ -20,7 +49,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
//long vid = doc[F("vid")]; // 2010020
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
#ifdef WLED_USE_ETHERNET
JsonObject ethernet = doc[F("eth")];
CJSON(ethernetType, ethernet["type"]);
// NOTE: Ethernet configuration takes priority over other use of pins
@@ -38,8 +67,24 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject nw = doc["nw"];
#ifndef WLED_DISABLE_ESPNOW
CJSON(enableESPNow, nw[F("espnow")]);
getStringFromJson(linked_remote, nw[F("linked_remote")], 13);
linked_remote[12] = '\0';
linked_remotes.clear();
JsonVariant lrem = nw[F("linked_remote")];
if (!lrem.isNull()) {
if (lrem.is<JsonArray>()) {
for (size_t i = 0; i < lrem.size(); i++) {
std::array<char, 13> entry{};
getStringFromJson(entry.data(), lrem[i], 13);
entry[12] = '\0';
linked_remotes.emplace_back(entry);
}
}
else { // legacy support for single MAC address in config
std::array<char, 13> entry{};
getStringFromJson(entry.data(), lrem, 13);
entry[12] = '\0';
linked_remotes.emplace_back(entry);
}
}
#endif
size_t n = 0;
@@ -120,7 +165,6 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
uint8_t cctBlending = hw_led[F("cb")] | Bus::getCCTBlend();
Bus::setCCTBlend(cctBlending);
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)
CJSON(useParallelI2S, hw_led[F("prl")]);
#endif
@@ -130,12 +174,13 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject matrix = hw_led[F("matrix")];
if (!matrix.isNull()) {
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();
JsonArray panels = matrix[F("panels")];
int s = 0;
unsigned s = 0;
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) {
WS2812FX::Panel p;
CJSON(p.bottomStart, pnl["b"]);
@@ -147,30 +192,21 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(p.height, pnl["h"]);
CJSON(p.width, pnl["w"]);
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
DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), ESP.getFreeHeap());
JsonArray ins = hw_led["ins"];
if (fromFS || !ins.isNull()) {
DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), ESP.getFreeHeap());
if (!ins.isNull()) {
int s = 0; // bus iterator
if (fromFS) BusManager::removeAll(); // can't safely manipulate busses directly in network callback
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};
JsonArray pinArr = elm["pin"];
if (pinArr.size() == 0) continue;
@@ -199,11 +235,101 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
}
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
//busConfigs.push_back(std::move(BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer, maPerLed, maMax)));
busConfigs.emplace_back(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);
doInitBusses = true; // finalization done in beginStrip()
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
@@ -292,30 +418,28 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
macroLongPress[s] = 0;
macroDoublePress[s] = 0;
}
} else {
} else if (fromFS) {
// new install/missing configuration (button 0 has defaults)
if (fromFS) {
// relies upon only being called once with fromFS == true, which is currently true.
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)) {
btnPin[s] = -1;
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;
// relies upon only being called once with fromFS == true, which is currently true.
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)) {
btnPin[s] = -1;
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;
}
}
@@ -392,10 +516,11 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject light = doc[F("light")];
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(useRainbowWheel, light[F("rw")]);
CJSON(gammaCorrectVal, light["gc"]["val"]); // default 2.8
CJSON(gammaCorrectVal, light["gc"]["val"]); // default 2.2
float light_gc_bri = light["gc"]["bri"];
float light_gc_col = light["gc"]["col"];
if (light_gc_bri > 1.0f) gammaCorrectBri = true;
@@ -407,7 +532,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
gammaCorrectBri = false;
gammaCorrectCol = false;
}
NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up table
NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up tables
JsonObject light_tr = light["tr"];
int tdd = light_tr["dur"] | -1;
@@ -611,8 +736,11 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (pwdCorrect) { //only accept these values from cfg.json if ota is unlocked (else from wsec.json)
CJSON(otaLock, ota[F("lock")]);
CJSON(wifiLock, ota[F("lock-wifi")]);
#ifndef WLED_DISABLE_OTA
CJSON(aOtaEnabled, ota[F("aota")]);
#endif
getStringFromJson(otaPass, pwd, 33); //normally not present due to security
CJSON(otaSameSubnet, ota[F("same-subnet")]);
}
#ifdef WLED_ENABLE_DMX
@@ -647,37 +775,19 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
static const char s_cfg_json[] PROGMEM = "/cfg.json";
void deserializeConfigFromFS() {
bool success = deserializeConfigSec();
bool deserializeConfigFromFS() {
[[maybe_unused]] bool success = deserializeConfigSec();
#ifdef WLED_ADD_EEPROM_SUPPORT
if (!success) { //if file does not exist, try reading from EEPROM
deEEPSettings();
return;
}
#endif
if (!requestJSONBufferLock(1)) return;
if (!requestJSONBufferLock(1)) return false;
DEBUG_PRINTLN(F("Reading settings from /cfg.json..."));
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);
serializeConfig();
// 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
// Therefore, must also initialize ethernet from this function
@@ -685,10 +795,10 @@ void deserializeConfigFromFS() {
bool needsSave = deserializeConfig(root, true);
releaseJSONBufferLock();
if (needsSave) serializeConfig(); // usermods required new parameters
return needsSave;
}
void serializeConfig() {
void serializeConfigToFS() {
serializeConfigSec();
DEBUG_PRINTLN(F("Writing settings to /cfg.json..."));
@@ -697,6 +807,17 @@ void serializeConfig() {
JsonObject root = pDoc->to<JsonObject>();
serializeConfig(root);
File f = WLED_FS.open(FPSTR(s_cfg_json), "w");
if (f) serializeJson(root, f);
f.close();
releaseJSONBufferLock();
configNeedsWrite = false;
}
void serializeConfig(JsonObject root) {
JsonArray rev = root.createNestedArray("rev");
rev.add(1); //major settings revision
rev.add(0); //minor settings revision
@@ -714,7 +835,10 @@ void serializeConfig() {
JsonObject nw = root.createNestedObject("nw");
#ifndef WLED_DISABLE_ESPNOW
nw[F("espnow")] = enableESPNow;
nw[F("linked_remote")] = linked_remote;
JsonArray lrem = nw.createNestedArray(F("linked_remote"));
for (size_t i = 0; i < linked_remotes.size(); i++) {
lrem.add(linked_remotes[i].data());
}
#endif
JsonArray nw_ins = nw.createNestedArray("ins");
@@ -789,14 +913,13 @@ void serializeConfig() {
JsonObject hw_led = hw.createNestedObject("led");
hw_led[F("total")] = strip.getLengthTotal(); //provided for compatibility on downgrade and per-output ABL
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[F("cr")] = strip.cctFromRgb;
hw_led[F("ic")] = cctICused;
hw_led[F("cb")] = Bus::getCCTBlend();
hw_led["fps"] = strip.getTargetFps();
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)
hw_led[F("prl")] = BusManager::hasParallelOutput();
#endif
@@ -805,7 +928,7 @@ void serializeConfig() {
// 2D Matrix Settings
if (strip.isMatrix) {
JsonObject matrix = hw_led.createNestedObject(F("matrix"));
matrix[F("mpc")] = strip.panels;
matrix[F("mpc")] = strip.panel.size();
JsonArray panels = matrix.createNestedArray(F("panels"));
for (size_t i = 0; i < strip.panel.size(); i++) {
JsonObject pnl = panels.createNestedObject();
@@ -915,8 +1038,9 @@ void serializeConfig() {
JsonObject light = root.createNestedObject(F("light"));
light[F("scale-bri")] = briMultiplier;
light[F("pal-mode")] = strip.paletteBlend;
light[F("pal-mode")] = paletteBlend;
light[F("aseg")] = strip.autoSegments;
light[F("rw")] = useRainbowWheel;
JsonObject light_gc = light.createNestedObject("gc");
light_gc["bri"] = (gammaCorrectBri) ? gammaCorrectVal : 1.0f; // keep compatibility
@@ -1092,7 +1216,10 @@ void serializeConfig() {
ota[F("lock")] = otaLock;
ota[F("lock-wifi")] = wifiLock;
ota[F("pskl")] = strlen(otaPass);
#ifndef WLED_DISABLE_OTA
ota[F("aota")] = aOtaEnabled;
#endif
ota[F("same-subnet")] = otaSameSubnet;
#ifdef WLED_ENABLE_DMX
JsonObject dmx = root.createNestedObject("dmx");
@@ -1111,13 +1238,6 @@ void serializeConfig() {
JsonObject usermods_settings = root.createNestedObject("um");
UsermodManager::addToConfig(usermods_settings);
File f = WLED_FS.open(FPSTR(s_cfg_json), "w");
if (f) serializeJson(root, f);
f.close();
releaseJSONBufferLock();
doSerializeConfig = false;
}
@@ -1170,7 +1290,9 @@ bool deserializeConfigSec() {
getStringFromJson(otaPass, ota[F("pwd")], 33);
CJSON(otaLock, ota[F("lock")]);
CJSON(wifiLock, ota[F("lock-wifi")]);
#ifndef WLED_DISABLE_OTA
CJSON(aOtaEnabled, ota[F("aota")]);
#endif
releaseJSONBufferLock();
return true;
@@ -1210,7 +1332,9 @@ void serializeConfigSec() {
ota[F("pwd")] = otaPass;
ota[F("lock")] = otaLock;
ota[F("lock-wifi")] = wifiLock;
#ifndef WLED_DISABLE_OTA
ota[F("aota")] = aOtaEnabled;
#endif
File f = WLED_FS.open(FPSTR(s_wsec_json), "w");
if (f) serializeJson(root, f);

View File

@@ -86,6 +86,23 @@ uint32_t color_fade(uint32_t c1, uint8_t amount, bool video)
return scaledcolor;
}
/*
* color adjustment in HSV color space (converts RGB to HSV and back), color conversions are not 100% accurate!
shifts hue, increase brightness, decreases saturation (if not black)
note: inputs are 32bit to speed up the function, useful input value ranges are 0-255
*/
uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten) {
if(rgb == 0 | hueShift + lighten + brighten == 0) return rgb; // black or no change
CHSV32 hsv;
rgb2hsv(rgb, hsv); //convert to HSV
hsv.h += (hueShift << 8); // shift hue (hue is 16 bits)
hsv.s = max((int32_t)0, (int32_t)hsv.s - (int32_t)lighten); // desaturate
hsv.v = min((uint32_t)255, (uint32_t)hsv.v + brighten); // increase brightness
uint32_t rgb_adjusted;
hsv2rgb(hsv, rgb_adjusted); // convert back to RGB TODO: make this into 16 bit conversion
return rgb_adjusted;
}
// 1:1 replacement of fastled function optimized for ESP, slightly faster, more accurate and uses less flash (~ -200bytes)
uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType)
{
@@ -208,14 +225,14 @@ CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette)
makepastelpalette = true;
}
// apply saturation & gamma correction
// apply saturation
CRGB RGBpalettecolors[4];
for (int i = 0; i < 4; i++) {
if (makepastelpalette && palettecolors[i].saturation > 180) {
palettecolors[i].saturation -= 160; //desaturate all four colors
}
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],
@@ -232,6 +249,54 @@ CRGBPalette16 generateRandomPalette() // generate fully random palette
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)
{
unsigned int remainder, region, p, q, t;
@@ -516,14 +581,17 @@ uint16_t approximateKelvinFromRGB(uint32_t rgb) {
}
}
// gamma lookup table used for color correction (filled on 1st use (cfg.cpp & set.cpp))
// gamma lookup tables used for color correction (filled on 1st use (cfg.cpp & set.cpp))
uint8_t NeoGammaWLEDMethod::gammaT[256];
uint8_t NeoGammaWLEDMethod::gammaT_inv[256];
// re-calculates & fills gamma table
// re-calculates & fills gamma tables
void NeoGammaWLEDMethod::calcGammaTable(float gamma)
{
float gamma_inv = 1.0f / gamma; // inverse gamma
for (size_t i = 0; i < 256; i++) {
gammaT[i] = (int)(powf((float)i / 255.0f, gamma) * 255.0f + 0.5f);
gammaT_inv[i] = (int)(powf((float)i / 255.0f, gamma_inv) * 255.0f + 0.5f);
}
}
@@ -547,3 +615,17 @@ uint32_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct32(uint32_t color)
b = gammaT[b];
return RGBW32(r, g, b, w);
}
uint32_t IRAM_ATTR_YN NeoGammaWLEDMethod::inverseGamma32(uint32_t color)
{
if (!gammaCorrectCol) return color;
uint8_t w = W(color);
uint8_t r = R(color);
uint8_t g = G(color);
uint8_t b = B(color);
w = gammaT_inv[w];
r = gammaT_inv[r];
g = gammaT_inv[g];
b = gammaT_inv[b];
return RGBW32(r, g, b, w);
}

View File

@@ -1,3 +1,4 @@
#pragma once
#ifndef WLED_CONST_H
#define WLED_CONST_H
@@ -44,66 +45,51 @@
#endif
#endif
#ifndef WLED_MAX_BUSSES
#ifdef ESP8266
#define WLED_MAX_DIGITAL_CHANNELS 3
#define WLED_MAX_ANALOG_CHANNELS 5
#define WLED_MAX_BUSSES 4 // will allow 3 digital & 1 analog RGB
#define WLED_MIN_VIRTUAL_BUSSES 3 // 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
#ifdef ESP8266
#define WLED_MAX_DIGITAL_CHANNELS 3
#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
#else
#ifdef ESP8266
#if WLED_MAX_BUSSES > 5
#error Maximum number of buses is 5.
#endif
#ifndef WLED_MAX_ANALOG_CHANNELS
#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
#define WLED_MIN_VIRTUAL_BUSSES 3
#else
#if WLED_MAX_BUSSES > 20
#error Maximum number of buses is 20.
#endif
#ifndef WLED_MAX_ANALOG_CHANNELS
#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
#if !defined(LEDC_CHANNEL_MAX) || !defined(LEDC_SPEED_MODE_MAX)
#include "driver/ledc.h" // needed for analog/LEDC channel counts
#endif
#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_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_DIGITAL_CHANNELS 12 // x4 RMT + x1/x8 I2S0
//#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_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_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
// 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)
static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
// 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
@@ -151,6 +137,8 @@
#endif
#endif
#define WLED_MAX_PANELS 18 // must not be more than 32
//Usermod IDs
#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
@@ -210,6 +198,7 @@
#define USERMOD_ID_DEEP_SLEEP 55 //Usermod "usermod_deep_sleep.h"
#define USERMOD_ID_RF433 56 //Usermod "usermod_v2_RF433.h"
#define USERMOD_ID_BRIGHTNESS_FOLLOW_SUN 57 //Usermod "usermod_v2_brightness_follow_sun.h"
#define USERMOD_ID_USER_FX 58 //Usermod "user_fx"
//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
@@ -336,18 +325,6 @@
#define TYPE_NET_ARTNET_RGBW 89 //network ArtNet RGB bus (master broadcast bus, unused)
#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
#define COL_ORDER_GRB 0 //GRB(w),defaut
#define COL_ORDER_RGB 1 //common for WS2811
@@ -435,6 +412,7 @@
#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_NOT_IMPL 4 // Not implemented
#define ERR_NORAM_PX 7 // not enough RAM for pixels
#define ERR_NORAM 8 // effect RAM depleted
#define ERR_JSON 9 // JSON parsing failed (input too large?)
#define ERR_FS_BEGIN 10 // Could not init filesystem (no partition?)
@@ -474,30 +452,29 @@
#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
// 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
#ifndef MAX_LEDS
#ifdef ESP8266
#define MAX_LEDS 1664 //can't rely on memory limit to limit this to 1600 LEDs
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
#define MAX_LEDS 2048 //due to memory constraints
#else
#define MAX_LEDS 8192
#endif
#ifdef ESP8266
#define MAX_LEDS 1536 //can't rely on memory limit to limit this to 1536 LEDs
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
#define MAX_LEDS 2048 //due to memory constraints S2
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
#define MAX_LEDS 4096
#else
#define MAX_LEDS 16384
#endif
#endif
#ifndef MAX_LED_MEMORY
#ifdef ESP8266
#define MAX_LED_MEMORY 4000
#define MAX_LED_MEMORY 4096
#else
#if defined(ARDUINO_ARCH_ESP32S2)
#define MAX_LED_MEMORY 16000
#define MAX_LED_MEMORY 16384
#elif defined(ARDUINO_ARCH_ESP32C3)
#define MAX_LED_MEMORY 32000
#define MAX_LED_MEMORY 32768
#else
#define MAX_LED_MEMORY 64000
#define MAX_LED_MEMORY 65536
#endif
#endif
#endif

View File

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

View File

@@ -268,28 +268,28 @@
<button class="btn btn-s" id="rsbtn" onclick="rSegs()">Reset segments</button>
</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 id="bsp">Blend:
<select id="bs" class="sel-sg" onchange="requestJson({'bs':parseInt(this.value)})">
<option value="0">Fade</option>
<option value="1">Fairy Dust</option>
<option value="2">Swipe right</option>
<option value="3">Swipe left</option>
<option value="16">Push right</option>
<option value="17">Push left</option>
<option value="4">Pinch-out</option>
<option value="5">Inside-out</option>
<option value="6" data-type="2D">Swipe up</option>
<option value="7" data-type="2D">Swipe down</option>
<option value="8" data-type="2D">Open H</option>
<option value="9" data-type="2D">Open V</option>
<option value="18" data-type="2D">Push up</option>
<option value="19" data-type="2D">Push down</option>
<option value="20" data-type="2D">Push TL</option>
<option value="21" data-type="2D">Push TR</option>
<option value="22" data-type="2D">Push BR</option>
<option value="23" data-type="2D">Push BL</option>
</select>
</p>
<div id="bsp" class="sel-p"><select id="bs" class="sel-ple" onchange="requestJson({'bs':parseInt(this.value)})">
<option value="0">Fade</option>
<option value="1">Fairy Dust</option>
<option value="2">Swipe right</option>
<option value="3">Swipe left</option>
<option value="16">Push right</option>
<option value="17">Push left</option>
<option value="4">Outside-in</option>
<option value="5">Inside-out</option>
<option value="6" data-type="2D">Swipe up</option>
<option value="7" data-type="2D">Swipe down</option>
<option value="8" data-type="2D">Open H</option>
<option value="9" data-type="2D">Open V</option>
<option value="18" data-type="2D">Push up</option>
<option value="19" data-type="2D">Push down</option>
<option value="10" data-type="2D">Swipe TL</option>
<option value="11" data-type="2D">Swipe TR</option>
<option value="12" data-type="2D">Swipe BR</option>
<option value="13" data-type="2D">Swipe BL</option>
<option value="14" data-type="2D">Circular Out</option>
<option value="15" data-type="2D">Circular In</option>
</select></div>
<p id="ledmap" class="hide"></p>
</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:
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="rangetouch.js"></script>

View File

@@ -35,9 +35,10 @@ var cfg = {
// [year, month (0 -> January, 11 -> December), day, duration in days, image url]
var hol = [
[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
[2025, 3, 20, 2, "https://aircoookie.github.io/easter.png"], // easter 2025
[2024, 2, 31, 2, "https://aircoookie.github.io/easter.png"], // easter 2024
[0, 2, 17, 1, "https://images.alphacoders.com/491/491123.jpg"], // st. Patrick's day
[2026, 3, 5, 2, "https://aircoookie.github.io/easter.png"], // easter 2026
[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, 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 gId(c) {return d.getElementById(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 isNumeric(n) {return !isNaN(parseFloat(n)) && isFinite(n);}
@@ -805,6 +806,26 @@ function populateSegments(s)
`<option value="4" ${inst.m12==4?' selected':''}>Pinwheel</option>`+
`</select></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>`+
`<div class="sel-p"><select class="sel-p" id="seg${i}si" onchange="setSi(${i})">`+
`<option value="0" ${inst.si==0?' selected':''}>BeatSin</option>`+
@@ -860,6 +881,7 @@ function populateSegments(s)
`</tr>`+
`</table>`+
`<div class="h bp" id="seg${i}len"></div>`+
blend +
(!isMSeg ? rvXck : '') +
(isMSeg&&stoY-staY>1&&stoX-staX>1 ? map2D : '') +
(s.AudioReactive && s.AudioReactive.on ? "" : sndSim) +
@@ -1420,7 +1442,7 @@ function makeWS() {
ws = null;
}
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;
reqsLegal = true;
}
@@ -1448,41 +1470,32 @@ function readState(s,command=false)
else gId('bsp').classList.remove('hide')
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;
segLmax = 0;
for (let i = 0; i < (s.seg||[]).length; i++)
{
if (sellvl == 0 && s.seg[i].id == s.mainseg) {
selc = i;
sellvl = 1;
}
if (s.seg[i].sel) {
if (sellvl < 2) selc = i; // get first selected segment
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];
let i = {};
// determine light capabilities from selected segments
for (let seg of (s.seg||[])) {
let w = (seg.stop - seg.start);
let h = seg.stopY ? (seg.stopY - seg.startY) : 1;
let lc = seg.lc;
if (w*h > segLmax) segLmax = w*h;
if (seg.sel) {
if (isEmpty(i) || (i.id == s.mainseg && !i.sel)) i = seg; // get first selected segment (and replace mainseg if it is not selected)
hasRGB |= !!(lc & 0x01);
hasWhite |= !!(lc & 0x02);
hasCCT |= !!(lc & 0x04);
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 (sellvl == 1) {
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);
if (isEmpty(i)) {
showToast('No segments!', true);
updateUI();
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");
@@ -2339,6 +2352,13 @@ function setSi(s)
requestJson(obj);
}
function setBm(s)
{
var value = gId(`seg${s}bm`).selectedIndex;
var obj = {"seg": {"id": s, "bm": value}};
requestJson(obj);
}
function setTp(s)
{
var tp = gId(`seg${s}tp`).checked;
@@ -2762,7 +2782,7 @@ setInterval(()=>{
gId('heart').style.color = `hsl(${hc}, 100%, 50%)`;
}, 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;
function cnfReset()
@@ -3155,7 +3175,8 @@ function mergeDeep(target, ...sources)
return mergeDeep(target, ...sources);
}
function tooltip(cont=null) {
function tooltip(cont=null)
{
d.querySelectorAll((cont?cont+" ":"")+"[title]").forEach((element)=>{
element.addEventListener("pointerover", ()=>{
// save title

View File

@@ -202,7 +202,6 @@
if (maxM >= 10000) { //ESP32 RMT uses double buffer?
mul = 2;
}
if (d.Sf.LD.checked) dbl = len * ch; // double buffering
}
return len * ch * mul + dbl;
}
@@ -326,7 +325,7 @@
LC.style.color="#fff";
return; // do not check conflicts
} else {
LC.max = d.max_gpio;
LC.max = d.max_gpio-1;
LC.min = -1;
}
}
@@ -354,7 +353,7 @@
});
const S2 = (oMaxB == 14) && (maxV == 4);
const S3 = (oMaxB == 14) && (maxV == 6);
if (oMaxB == 19 || S2 || S3) { // TODO: crude ESP32 & S2/S3 detection
if (oMaxB == 32 || S2 || S3) { // TODO: crude ESP32 & S2/S3 detection
if (maxLC > 300 || dC <= 2) {
d.Sf["PR"].checked = false;
gId("prl").classList.add("hide");
@@ -641,7 +640,6 @@ Swap: <select id="xw${s}" name="XW${s}">
d.getElementsByName("MA"+i)[0].value = v.maxpwr;
});
d.getElementsByName("PR")[0].checked = l.prl | 0;
d.getElementsByName("LD")[0].checked = l.ld;
d.getElementsByName("MA")[0].value = l.maxpwr;
d.getElementsByName("ABL")[0].checked = l.maxpwr > 0;
}
@@ -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>
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>
Use global LED buffer: <input type="checkbox" name="LD" onchange="UI()"><br>
<hr class="sml">
<div id="color_order_mapping">
Color Order Override:
@@ -866,7 +863,6 @@ Swap: <select id="xw${s}" name="XW${s}">
<h3>Transitions</h3>
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>
Use harmonic <i>Random Cycle</i> Palette: <input type="checkbox" name="TH"><br>
<h3>Timed light</h3>
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>
@@ -903,8 +899,10 @@ Swap: <select id="xw${s}" name="XW${s}">
<option value="2">Linear (never wrap)</option>
<option value="3">None (not recommended)</option>
</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
<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="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">

View File

@@ -56,7 +56,10 @@
<hr>
<h3>Software Update</h3>
<button type="button" onclick="U()">Manual OTA Update</button><br>
Enable ArduinoOTA: <input type="checkbox" name="AO">
<div id="aOTA">Enable ArduinoOTA: <input type="checkbox" name="AO"></div>
Only allow update from same network/WiFi: <input type="checkbox" name="SU"><br>
<i class="warn">&#9888; If you are using multiple VLANs (i.e. IoT or guest network) either set PIN or disable this option.<br>
Disabling this option will make your device less secure.</i><br>
<hr id="backup">
<h3>Backup & Restore</h3>
<div class="warn">&#9888; Restoring presets/configuration will OVERWRITE your current presets/configuration.<br>

View File

@@ -168,8 +168,8 @@
<h3>Clock</h3>
Analog Clock overlay: <input type="checkbox" name="OL" onchange="Cs()"><br>
<div id="cac">
First LED: <input name="O1" type="number" min="0" max="255" required> Last LED: <input name="O2" type="number" min="0" max="255" required><br>
12h LED: <input name="OM" type="number" min="0" max="255" required><br>
First LED: <input name="O1" type="number" min="0" max="1024" required> Last LED: <input name="O2" type="number" min="0" max="1024" required><br>
12h LED: <input name="OM" type="number" min="0" max="1024" required><br>
Show 5min marks: <input type="checkbox" name="O5"><br>
Seconds (as trail): <input type="checkbox" name="OS"><br>
Show clock overlay only if all LEDs are solid black: <input type="checkbox" name="OB"><br>

View File

@@ -13,7 +13,7 @@
function S() {
getLoc();
// load settings and insert values into DOM
fetch(getURL('/cfg.json'), {
fetch(getURL('/json/cfg'), {
method: 'get'
})
.then(res => {

View File

@@ -136,12 +136,52 @@ Static subnet mask:<br>
getLoc();
loadJS(getURL('/settings/s.js?p=1'), false); // If we set async false, file is loaded and executed, then next statement is processed
if (loc) d.Sf.action = getURL('/settings/wifi');
setTimeout(tE, 500); // wait for DOM to load before calling tE()
}
var rC = 0; // remote count
// toggle visibility of ESP-NOW remote list based on checkbox state
function tE() {
// keep the hidden input with MAC addresses, only toggle visibility of the list UI
gId('rlc').style.display = d.Sf.RE.checked ? 'block' : 'none';
}
// reset remotes: initialize empty list (called from xml.cpp)
function rstR() {
gId('rml').innerHTML = ''; // clear remote list
}
// add remote MAC to the list
function aR(id, mac) {
if (!/^[0-9A-F]{12}$/i.test(mac)) return; // check for valid hex string
let inputs = d.querySelectorAll("#rml input");
for (let i of (inputs || [])) {
if (i.value === mac) return;
}
let l = gId('rml'), r = cE('div'), i = cE('input');
i.type = 'text';
i.name = id;
i.value = mac;
i.maxLength = 12;
i.minLength = 12;
//i.onchange = uR;
r.appendChild(i);
let b = cE('button');
b.type = 'button';
b.className = 'sml';
b.innerText = '-';
b.onclick = (e) => {
r.remove();
};
r.appendChild(b);
l.appendChild(r);
rC++;
gId('+').style.display = gId("rml").childElementCount < 10 ? 'inline' : 'none'; // can't append to list anymore, hide button
}
</script>
<style>@import url("style.css");</style>
</head>
<body onload="S()">
<form id="form_s" name="Sf" method="post">
<form id="form_s" name="Sf" method="post">
<div class="toprow">
<div class="helpB"><button type="button" onclick="H('features/settings/#wifi-settings')">?</button></div>
<button type="button" onclick="B()">Back</button><button type="submit">Save & Connect</button><hr>
@@ -202,11 +242,16 @@ Static subnet mask:<br>
<i class="warn">This firmware build does not include ESP-NOW support.<br></i>
</div>
<div id="ESPNOW">
Enable ESP-NOW: <input type="checkbox" name="RE"><br>
Enable ESP-NOW: <input type="checkbox" name="RE" onchange="tE()"><br>
<i>Listen for events over ESP-NOW<br>
Keep disabled if not using a remote or wireless sync, increases power consumption.<br></i>
Paired Remote MAC: <input type="text" name="RMAC" minlength="12" maxlength="12"><br>
Last device seen: <span class="rlid" onclick="d.Sf.RMAC.value=this.textContent;" style="cursor:pointer;">None</span> <br>
Keep disabled if not using a remote or ESP-NOW sync, increases power consumption.<br></i>
<div id="rlc">
Last device seen: <span class="rlid" id="ld">None</span>
<button type="button" class="sml" id="+" onclick="aR('RM'+rC,gId('ld').textContent)">+</button><br>
Linked MACs (10 max):<br>
<div id="rml">
</div>
</div>
</div>
<div id="ethd">

View File

@@ -3,9 +3,20 @@
<head>
<meta content='width=device-width' name='viewport'>
<title>WLED Update</title>
<script src="common.js" async type="text/javascript"></script>
<script>
function B() { window.history.back(); }
function U() { document.getElementById("uf").style.display="none";document.getElementById("msg").style.display="block"; }
var cnfr = false;
function cR() {
if (!cnfr) {
var bt = gId('rev');
bt.style.color = "red";
bt.innerText = "Revert!";
cnfr = true;
return;
}
window.open(getURL("/update?revert"),"_self");
}
function GetV() {/*injected values here*/}
</script>
<style>
@@ -15,15 +26,17 @@
<body onload="GetV()">
<h2>WLED Software Update</h2>
<form method='POST' action='./update' id='uf' enctype='multipart/form-data' onsubmit="U()">
<form method='POST' action='./update' id='upd' enctype='multipart/form-data' onsubmit="toggle('upd')">
Installed version: <span class="sip">##VERSION##</span><br>
Download the latest binary: <a href="https://github.com/wled-dev/WLED/releases" target="_blank"
style="vertical-align: text-bottom; display: inline-flex;">
<img src="https://img.shields.io/github/release/wled-dev/WLED.svg?style=flat-square"></a><br>
<input type='file' name='update' required><br> <!--should have accept='.bin', but it prevents file upload from android app-->
<button type="submit">Update!</button><br>
<hr class="sml">
<button id="rev" type="button" onclick="cR()">Revert update</button><br>
<button type="button" onclick="B()">Back</button>
</form>
<div id="msg"><b>Updating...</b><br>Please do not close or refresh the page :)</div>
<div id="Noupd" class="hide"><b>Updating...</b><br>Please do not close or refresh the page :)</div>
</body>
</html>

View File

@@ -22,7 +22,7 @@ void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) {
const uint8_t personality = dmx_get_current_personality(dmx->inputPortNum);
DMXMode = std::min(DMX_MODE_PRESET, std::max(DMX_MODE_SINGLE_RGB, int(personality)));
doSerializeConfig = true;
configNeedsWrite = true;
DEBUG_PRINTF("DMX personality changed to to: %d\n", DMXMode);
}
}
@@ -40,7 +40,7 @@ void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) {
const uint16_t addr = dmx_get_start_address(dmx->inputPortNum);
DMXAddress = std::min(512, int(addr));
doSerializeConfig = true;
configNeedsWrite = true;
DEBUG_PRINTF("DMX start addr changed to: %d\n", DMXAddress);
}
}

View File

@@ -38,8 +38,7 @@ void handleDDPPacket(e131_packet_t* p) {
if (realtimeMode != REALTIME_MODE_DDP) ddpSeenPush = false; // just starting, no push yet
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP);
if (!realtimeOverride || (realtimeMode && useMainSegmentOnly)) {
if (useMainSegmentOnly) strip.getMainSegment().beginDraw();
if (!realtimeOverride) {
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);
}
@@ -150,10 +149,9 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8
realtimeLock(realtimeTimeoutMs, mde);
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return;
if (realtimeOverride) return;
wChannel = (availDMXLen > 3) ? e131_data[dataOffset+3] : 0;
if (useMainSegmentOnly) strip.getMainSegment().beginDraw();
for (unsigned i = 0; i < totalLen; i++)
setRealtimePixel(i, e131_data[dataOffset+0], e131_data[dataOffset+1], e131_data[dataOffset+2], wChannel);
break;
@@ -163,7 +161,7 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8
if (availDMXLen < 4) return;
realtimeLock(realtimeTimeoutMs, mde);
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return;
if (realtimeOverride) return;
wChannel = (availDMXLen > 4) ? e131_data[dataOffset+4] : 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);
}
if (useMainSegmentOnly) strip.getMainSegment().beginDraw();
for (unsigned i = 0; i < totalLen; i++)
setRealtimePixel(i, e131_data[dataOffset+1], e131_data[dataOffset+2], e131_data[dataOffset+3], wChannel);
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+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 ((e131_data[dataOffset+5] & 0b00000100) != seg.mirror_y) { seg.setOption(SEG_OPTION_MIRROR_Y, e131_data[dataOffset+5] & 0b00000100); }
if ((e131_data[dataOffset+5] & 0b00001000) != seg.transpose) { seg.setOption(SEG_OPTION_TRANSPOSED, e131_data[dataOffset+5] & 0b00001000); }
if ((e131_data[dataOffset+5] & 0b00110000) / 8 != seg.map1D2D) {
seg.map1D2D = (e131_data[dataOffset+5] & 0b00110000) / 8;
if (bool(e131_data[dataOffset+5] & 0b00000010) != seg.reverse_y) { seg.reverse_y = bool(e131_data[dataOffset+5] & 0b00000010); }
if (bool(e131_data[dataOffset+5] & 0b00000100) != seg.mirror_y) { seg.mirror_y = bool(e131_data[dataOffset+5] & 0b00000100); }
if (bool(e131_data[dataOffset+5] & 0b00001000) != seg.transpose) { seg.transpose = bool(e131_data[dataOffset+5] & 0b00001000); }
if ((e131_data[dataOffset+5] & 0b00110000) >> 4 != seg.map1D2D) {
seg.map1D2D = (e131_data[dataOffset+5] & 0b00110000) >> 4;
}
// 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
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];
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_RGBW:
{
bool is4Chan = (DMXMode == DMX_MODE_MULTIPLE_RGBW);
const bool is4Chan = (DMXMode == DMX_MODE_MULTIPLE_RGBW);
const unsigned dmxChannelsPerLed = is4Chan ? 4 : 3;
const unsigned ledsPerUniverse = is4Chan ? MAX_4_CH_LEDS_PER_UNIVERSE : MAX_3_CH_LEDS_PER_UNIVERSE;
uint8_t stripBrightness = bri;
@@ -303,7 +300,7 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8
}
realtimeLock(realtimeTimeoutMs, mde);
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return;
if (realtimeOverride) return;
if (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();
if (!is4Chan) {
for (unsigned i = previousLeds; i < ledsTotal; i++) {
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;
}
for (unsigned i = previousLeds; i < ledsTotal; i++) {
setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], is4Chan ? e131_data[dmxOffset+3] : 0);
dmxOffset += dmxChannelsPerLed;
}
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_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) {
pollReplyCount++;

View File

@@ -25,9 +25,10 @@ void IRAM_ATTR touchButtonISR();
//cfg.cpp
bool deserializeConfig(JsonObject doc, bool fromFS = false);
void deserializeConfigFromFS();
bool deserializeConfigFromFS();
bool deserializeConfigSec();
void serializeConfig();
void serializeConfig(JsonObject doc);
void serializeConfigToFS();
void serializeConfigSec();
template<typename DestType>
@@ -157,20 +158,29 @@ class NeoGammaWLEDMethod {
public:
[[gnu::hot]] static uint8_t Correct(uint8_t value); // apply Gamma to single channel
[[gnu::hot]] static uint32_t Correct32(uint32_t color); // apply Gamma to RGBW32 color (WLED specific, not used by NPB)
static void calcGammaTable(float gamma); // re-calculates & fills gamma table
[[gnu::hot]] static uint32_t inverseGamma32(uint32_t color); // apply inverse Gamma to RGBW32 color
static void calcGammaTable(float gamma); // re-calculates & fills gamma tables
static inline uint8_t rawGamma8(uint8_t val) { return gammaT[val]; } // get value from Gamma table (WLED specific, not used by NPB)
static inline uint8_t rawInverseGamma8(uint8_t val) { return gammaT_inv[val]; } // get value from inverse Gamma table (WLED specific, not used by NPB)
private:
static uint8_t gammaT[];
static uint8_t gammaT_inv[];
};
#define gamma32(c) NeoGammaWLEDMethod::Correct32(c)
#define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c)
#define gamma32inv(c) NeoGammaWLEDMethod::inverseGamma32(c)
#define gamma8inv(c) NeoGammaWLEDMethod::rawInverseGamma8(c)
[[gnu::hot, gnu::pure]] uint32_t color_blend(uint32_t c1, uint32_t c2 , uint8_t blend);
inline uint32_t color_blend16(uint32_t c1, uint32_t c2, uint16_t b) { return color_blend(c1, c2, b >> 8); };
[[gnu::hot, gnu::pure]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false);
[[gnu::hot, gnu::pure]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false);
[[gnu::hot, gnu::pure]] uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten);
[[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 generateRandomPalette();
void loadCustomPalettes();
extern std::vector<CRGBPalette16> customPalettes;
inline size_t getPaletteCount() { return 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]))); }
void hsv2rgb(const CHSV32& hsv, uint32_t& rgb);
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb);
@@ -222,9 +232,8 @@ void onHueConnect(void* arg, AsyncClient* client);
void sendHuePoll();
void onHueData(void* arg, AsyncClient* client, void *data, size_t len);
#include "FX.h" // must be below colors.cpp declarations (potentially due to duplicate declarations of e.g. color_blend)
//image_loader.cpp
class Segment;
#ifdef WLED_ENABLE_GIF
bool fileSeekCallback(unsigned long position);
unsigned long filePositionCallback(void);
@@ -260,9 +269,7 @@ void handleIR();
#include "ESPAsyncWebServer.h"
#include "src/dependencies/json/ArduinoJson-v6.h"
#include "src/dependencies/json/AsyncJson-v6.h"
#include "FX.h"
bool deserializeSegment(JsonObject elem, byte it, byte presetId = 0);
bool deserializeState(JsonObject root, byte callMode = CALL_MODE_DIRECT_CHANGE, byte presetId = 0);
void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true);
void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true, bool selectedSegmentsOnly = false);
@@ -276,8 +283,8 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0);
//led.cpp
void setValuesFromSegment(uint8_t s);
void setValuesFromMainSeg();
void setValuesFromFirstSelectedSeg();
#define setValuesFromMainSeg() setValuesFromSegment(strip.getMainSegmentId())
#define setValuesFromFirstSelectedSeg() setValuesFromSegment(strip.getFirstSelectedSegId())
void toggleOnOff();
void applyBri();
void applyFinalBri();
@@ -486,12 +493,14 @@ void userLoop();
#include "soc/wdev_reg.h"
#define HW_RND_REGISTER REG_READ(WDEV_RND_REG)
#endif
#define inoise8 perlin8 // 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)
[[gnu::pure]] int getNumVal(const String* req, uint16_t pos);
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)
[[gnu::pure]] int getNumVal(const String &req, uint16_t pos);
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)
[[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 printSetFormValue(Print& settingsScript, const char* key, int val);
size_t printSetFormValue(Print& settingsScript, const char* key, const char* val);
@@ -514,6 +523,15 @@ void enumerateLedmaps();
[[gnu::hot]] uint8_t get_random_wheel_index(uint8_t pos);
[[gnu::hot, gnu::pure]] float mapf(float x, float in_min, float in_max, float out_min, float out_max);
uint32_t hashInt(uint32_t s);
int32_t perlin1D_raw(uint32_t x, bool is16bit = false);
int32_t perlin2D_raw(uint32_t x, uint32_t y, bool is16bit = false);
int32_t perlin3D_raw(uint32_t x, uint32_t y, uint32_t z, bool is16bit = false);
uint16_t perlin16(uint32_t x);
uint16_t perlin16(uint32_t x, uint32_t y);
uint16_t perlin16(uint32_t x, uint32_t y, uint32_t z);
uint8_t perlin8(uint16_t x);
uint8_t perlin8(uint16_t x, uint16_t y);
uint8_t perlin8(uint16_t x, uint16_t y, uint16_t z);
// fast (true) random numbers using hardware RNG, all functions return values in the range lowerlimit to upperlimit-1
// note: for true random numbers with high entropy, do not call faster than every 200ns (5MHz)
@@ -532,6 +550,29 @@ 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 lowerlimit, uint32_t upperlimit) { uint32_t range = upperlimit - lowerlimit; return lowerlimit + hw_random8(range); }; // input range 0-255
// PSRAM allocation wrappers
#ifndef ESP8266
extern "C" {
void *p_malloc(size_t); // prefer PSRAM over DRAM
void *p_calloc(size_t, size_t); // prefer PSRAM over DRAM
void *p_realloc(void *, size_t); // prefer PSRAM over DRAM
inline void p_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 p_malloc malloc
#define p_calloc calloc
#define p_realloc realloc
#define p_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
// Modeled after std::lock_guard
class JSONBufferGuard {

View File

@@ -39,7 +39,7 @@ void closeFile() {
uint32_t s = millis();
#endif
f.close();
DEBUGFS_PRINTF("took %d ms\n", millis() - s);
DEBUGFS_PRINTF("took %lu ms\n", millis() - s);
doCloseFile = false;
}
@@ -69,14 +69,14 @@ static bool bufferedFind(const char *target, bool fromStart = true) {
if(buf[count] == target[index]) {
if(++index >= targetLen) { // return true if all chars in the target match
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;
}
}
count++;
}
}
DEBUGFS_PRINTF("No match, took %d ms\n", millis() - s);
DEBUGFS_PRINTF("No match, took %lu ms\n", millis() - s);
return false;
}
@@ -111,7 +111,7 @@ static bool bufferedFindSpace(size_t targetLen, bool fromStart = true) {
f.seek((f.position() - bufsize) + count +1 - targetLen);
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;
}
} else {
@@ -125,7 +125,7 @@ static bool bufferedFindSpace(size_t targetLen, bool fromStart = true) {
count++;
}
}
DEBUGFS_PRINTF("No match, took %d ms\n", millis() - s);
DEBUGFS_PRINTF("No match, took %lu ms\n", millis() - s);
return false;
}
@@ -151,13 +151,13 @@ static bool bufferedFindObjectEnd() {
if (buf[count] == '}') objDepth--;
if (objDepth == 0) {
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;
}
count++;
}
}
DEBUGFS_PRINTF("No match, took %d ms\n", millis() - s);
DEBUGFS_PRINTF("No match, took %lu ms\n", millis() - s);
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
f.print(key);
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;
return true;
}
@@ -251,7 +251,7 @@ static bool appendObjectToFile(const char* key, const JsonDocument* content, uin
f.write('}');
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;
}
@@ -321,7 +321,7 @@ bool writeObjectToFile(const char* file, const char* key, const JsonDocument* co
}
doCloseFile = true;
DEBUGFS_PRINTF("Replaced/deleted, took %d ms\n", millis() - s);
DEBUGFS_PRINTF("Replaced/deleted, took %lu ms\n", millis() - s);
return true;
}
@@ -356,7 +356,7 @@ bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest, c
else deserializeJson(*dest, f);
f.close();
DEBUGFS_PRINTF("Read, took %d ms\n", millis() - s);
DEBUGFS_PRINTF("Read, took %lu ms\n", millis() - s);
return true;
}
@@ -392,7 +392,7 @@ static const uint8_t *getPresetCache(size_t &size) {
if ((presetsModifiedTime != presetsCachedTime) || (presetsCachedValidate != cacheInvalidate)) {
if (presetsCached) {
free(presetsCached);
p_free(presetsCached);
presetsCached = nullptr;
}
}
@@ -403,7 +403,7 @@ static const uint8_t *getPresetCache(size_t &size) {
presetsCachedTime = presetsModifiedTime;
presetsCachedValidate = cacheInvalidate;
presetsCachedSize = 0;
presetsCached = (uint8_t*)ps_malloc(file.size() + 1);
presetsCached = (uint8_t*)p_malloc(file.size() + 1);
if (presetsCached) {
presetsCachedSize = file.size();
file.read(presetsCached, presetsCachedSize);
@@ -419,7 +419,7 @@ static const uint8_t *getPresetCache(size_t &size) {
#endif
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.indexOf(F("sec")) > -1) return false;
#ifdef ARDUINO_ARCH_ESP32

View File

@@ -78,7 +78,7 @@ void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t
byte renderImageToSegment(Segment &seg) {
if (!seg.name) return IMAGE_ERROR_NO_NAME;
// disable during effect transition, causes flickering, multiple allocations and depending on image, part of old FX remaining
if (seg.mode != seg.currentMode()) return IMAGE_ERROR_WAITING;
//if (seg.mode != seg.currentMode()) return IMAGE_ERROR_WAITING;
if (activeSeg && activeSeg != &seg) return IMAGE_ERROR_SEG_LIMIT; // only one segment at a time
activeSeg = &seg;

View File

@@ -272,5 +272,5 @@ void parseWiFiCommand(char* rpcData) {
improvActive = 2;
forceReconnect = true;
serializeConfig();
serializeConfigToFS();
}

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_REDPLUS : 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_GREENMINUS : 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, getPaletteCount() -1)); break;
case IR44_BLUEPLUS : changeEffectIntensity( 16); break;
case IR44_BLUEMINUS : changeEffectIntensity(-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_DIY3 : presetFallback(3, FX_MODE_FIRE_FLICKER, 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_AUTO : changeEffect(FX_MODE_STATIC); 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_DOWN: decBrightness(); 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) {
case 0: changeColor(COLOR_RED); 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.
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.
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

View File

@@ -14,11 +14,62 @@
/*
* 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 = 0)
{
byte id = elem["id"] | it;
if (id >= strip.getMaxSegments()) return false;
if (id >= WS2812FX::getMaxSegments()) return false;
bool newSeg = false;
int stop = elem["stop"] | -1;
@@ -26,16 +77,37 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
// append segment
if (id >= strip.getSegmentsNum()) {
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
newSeg = true;
}
//DEBUG_PRINTLN(F("-- JSON deserialize segment."));
Segment& seg = strip.getSegment(id);
//DEBUG_PRINTF_P(PSTR("-- Original segment: %p (%p)\n"), &seg, seg.data);
const Segment prev = seg; //make a backup so we can tell if something changed (calling copy constructor)
//DEBUG_PRINTF_P(PSTR("-- Duplicate segment: %p (%p)\n"), &prev, prev.data);
// we do not want to make segment copy as it may use a lot of RAM (effect data and pixel buffer)
// so we will create a copy of segment options and compare it with original segment when done processing
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;
if (stop < 0) {
@@ -53,7 +125,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
elem.remove("rpt"); // remove for recursive call
elem.remove("n"); // remove for recursive call
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;
if (start >= strip.getLengthTotal()) break;
//TODO: add support for 2D
@@ -67,28 +139,11 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
if (elem["n"]) {
// name field exists
if (seg.name) { //clear old name
free(seg.name);
seg.name = nullptr;
}
const char * name = elem["n"].as<const char*>();
size_t len = 0;
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");
}
seg.setName(name); // will resolve empty and null correctly
} else if (start != seg.start || stop != seg.stop) {
// clearing or setting segment without name field
if (seg.name) {
free(seg.name);
seg.name = nullptr;
}
seg.clearName();
}
uint16_t grp = elem["grp"] | seg.grouping;
@@ -106,6 +161,12 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
bool transpose = getBoolVal(elem[F("tp")], seg.transpose);
#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 offset = elem[F("of")] | INT32_MAX;
if (offset != INT32_MAX) {
@@ -127,8 +188,8 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
}
byte segbri = seg.opacity;
if (getVal(elem["bri"], &segbri)) {
if (segbri > 0) seg.setOpacity(segbri);
if (getVal(elem["bri"], segbri)) {
if (segbri > 0) seg.setOpacity(segbri); // use transition
seg.setOption(SEG_OPTION_ON, segbri); // use transition
}
@@ -184,13 +245,13 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
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
}
} else {
// non RGB & non White segment (usually On/Off bus)
seg.setColor(0, ULTRAWHITE);
seg.setColor(1, BLACK);
seg.setColor(0, ULTRAWHITE); // use transition
seg.setColor(1, BLACK); // use transition
}
}
@@ -206,7 +267,6 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
}
#endif
//seg.map1D2D = constrain(map1D2D, 0, 7); // done in setGeometry()
seg.set = constrain(set, 0, 3);
seg.soundSim = constrain(soundSim, 0, 3);
seg.selected = selected;
@@ -219,57 +279,58 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
#endif
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 (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["ix"], &seg.intensity);
getVal(elem["sx"], seg.speed);
getVal(elem["ix"], seg.intensity);
uint8_t pal = seg.palette;
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["c2"], &seg.custom2);
getVal(elem["c1"], seg.custom1);
getVal(elem["c2"], seg.custom2);
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.check1 = getBoolVal(elem["o1"], seg.check1);
seg.check2 = getBoolVal(elem["o2"], seg.check2);
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
if (!iarr.isNull()) {
uint8_t oldMap1D2D = seg.map1D2D;
seg.map1D2D = M12_Pixels; // no mapping
// set brightness immediately and disable transition
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.setBrightness(scaledBri(bri), true);
// freeze and init to black
if (!seg.freeze) {
seg.freeze = true;
seg.fill(BLACK);
seg.clear();
}
start = 0, stop = 0;
set = 0; //0 nothing set, 1 start set, 2 range set
unsigned iStart = 0, iStop = 0;
unsigned iSet = 0; //0 nothing set, 1 start set, 2 range set
for (size_t i = 0; i < iarr.size(); i++) {
if(iarr[i].is<JsonInteger>()) {
if (!set) {
start = abs(iarr[i].as<int>());
set++;
if (iarr[i].is<JsonInteger>()) {
if (!iSet) {
iStart = abs(iarr[i].as<int>());
iSet++;
} else {
stop = abs(iarr[i].as<int>());
set++;
iStop = abs(iarr[i].as<int>());
iSet++;
}
} else { //color
uint8_t rgbw[] = {0,0,0,0};
@@ -285,17 +346,16 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
}
}
if (set < 2 || stop <= start) stop = start + 1;
uint32_t c = gamma32(RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3]));
while (start < stop) seg.setPixelColor(start++, c);
set = 0;
if (iSet < 2 || iStop <= iStart) iStop = iStart + 1;
uint32_t c = RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3]);
while (iStart < iStop) seg.setRawPixelColor(iStart++, c); // sets pixel color without 1D->2D expansion, grouping or spacing
iSet = 0;
}
}
seg.map1D2D = oldMap1D2D; // restore mapping
strip.trigger(); // force segment update
}
// 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;
}
@@ -311,7 +371,8 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
#endif
bool onBefore = bri;
getVal(root["bri"], &bri);
getVal(root["bri"], bri);
if (bri != briOld) stateChanged = true;
bool on = root["on"] | (bri > 0);
if (!on != !bri) toggleOnOff();
@@ -338,10 +399,8 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
}
}
#ifndef WLED_DISABLE_MODE_BLEND
blendingStyle = root[F("bs")] | blendingStyle;
blendingStyle &= 0x1F;
#endif
// temporary transition (applies only once)
tr = root[F("tt")] | -1;
@@ -354,6 +413,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
if (tr >= 0) strip.timebase = (unsigned long)tr - millis();
JsonObject nl = root["nl"];
if (!nl.isNull()) stateChanged = true;
nightlightActive = getBoolVal(nl["on"], nightlightActive);
nightlightDelayMins = nl["dur"] | nightlightDelayMins;
nightlightMode = nl["mode"] | nightlightMode;
@@ -380,6 +440,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS;
if (realtimeMode && useMainSegmentOnly) {
strip.getMainSegment().freeze = !realtimeOverride;
realtimeOverride = REALTIME_OVERRIDE_NONE; // ignore request for override if using main segment only
}
if (root.containsKey("live")) {
@@ -397,18 +458,14 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
if (!segVar.isNull()) {
// we may be called during strip.service() so we must not modify segments while effects are executing
strip.suspend();
const unsigned long start = millis();
while (strip.isServicing() && millis() - start < strip.getFrameTime()) yield(); // wait until frame is over
#ifdef WLED_DEBUG
if (millis() - start > 0) DEBUG_PRINTLN(F("JSON: Waited for strip to finish servicing."));
#endif
strip.waitForIt();
if (segVar.is<JsonObject>()) {
int id = segVar["id"] | -1;
//if "seg" is not an array and ID not specified, apply to all selected/checked segments
if (id < 0) {
//apply all selected segments
for (size_t s = 0; s < strip.getSegmentsNum(); s++) {
Segment &sg = strip.getSegment(s);
const Segment &sg = strip.getSegment(s);
if (sg.isActive() && sg.isSelected()) {
deserializeSegment(segVar, s, presetId);
}
@@ -458,7 +515,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
DEBUG_PRINTF_P(PSTR("Preset direct: %d\n"), currentPreset);
} 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)
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);
// 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)
@@ -474,11 +531,11 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
}
if (root.containsKey(F("rmcpal")) && root[F("rmcpal")].as<bool>()) {
if (strip.customPalettes.size()) {
if (customPalettes.size()) {
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);
strip.loadCustomPalettes();
loadCustomPalettes();
}
}
@@ -497,13 +554,13 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
//if (restart) forceReconnect = true;
}
stateUpdated(callMode);
if (stateChanged) stateUpdated(callMode);
if (presetToRestore) currentPreset = presetToRestore;
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;
if (segmentBounds) {
@@ -526,6 +583,7 @@ void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool
root["bri"] = (segbri) ? segbri : 255;
root["cct"] = seg.cct;
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
else if (forPreset) root["n"] = "";
@@ -570,6 +628,7 @@ void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool
root["o3"] = seg.check3;
root["si"] = seg.soundSim;
root["m12"] = seg.map1D2D;
root["bm"] = seg.blendMode;
}
void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segmentBounds, bool selectedSegmentsOnly)
@@ -578,9 +637,7 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme
root["on"] = (bri > 0);
root["bri"] = briLast;
root[F("transition")] = transitionDelay/100; //in 100ms
#ifndef WLED_DISABLE_MODE_BLEND
root[F("bs")] = blendingStyle;
#endif
}
if (!forPreset) {
@@ -611,7 +668,7 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme
root[F("mainseg")] = strip.getMainSegmentId();
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 (forPreset && segmentBounds && !selectedSegmentsOnly) { //disable segments not part of preset
JsonObject seg0 = seg.createNestedObject();
@@ -620,7 +677,7 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme
} else
break;
}
Segment &sg = strip.getSegment(s);
const Segment &sg = strip.getSegment(s);
if (forPreset && selectedSegmentsOnly && !sg.isSelected()) continue;
if (sg.isActive()) {
JsonObject seg0 = seg.createNestedObject();
@@ -644,7 +701,7 @@ void serializeInfo(JsonObject root)
leds[F("pwr")] = BusManager::currentMilliamps();
leds["fps"] = strip.getFps();
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("seglock")] = false; //might be used in the future to prevent modifications to segment config
leds[F("bootps")] = bootPreset;
@@ -658,13 +715,13 @@ void serializeInfo(JsonObject root)
#endif
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();
for (size_t s = 0; s < nSegs; s++) {
if (!strip.getSegment(s).isActive()) continue;
unsigned lc = strip.getSegment(s).getLightCapabilities();
totalLC |= lc;
lcarr.add(lc);
lcarr.add(lc); // deprecated, use state.seg[].lc
}
leds["lc"] = totalLC;
@@ -712,8 +769,8 @@ void serializeInfo(JsonObject root)
#endif
root[F("fxcount")] = strip.getModeCount();
root[F("palcount")] = strip.getPaletteCount();
root[F("cpalcount")] = strip.customPalettes.size(); //number of custom palettes
root[F("palcount")] = getPaletteCount();
root[F("cpalcount")] = customPalettes.size(); //number of custom palettes
JsonArray ledmaps = root.createNestedArray(F("maps"));
for (size_t i=0; i<WLED_MAX_LEDMAPS; i++) {
@@ -876,15 +933,15 @@ void serializePalettes(JsonObject root, int page)
int itemPerPage = 8;
#endif
int customPalettes = strip.customPalettes.size();
int palettesCount = strip.getPaletteCount() - customPalettes;
int customPalettesCount = customPalettes.size();
int palettesCount = getPaletteCount() - customPalettesCount;
int maxPage = (palettesCount + customPalettes -1) / itemPerPage;
int maxPage = (palettesCount + customPalettesCount -1) / itemPerPage;
if (page > maxPage) page = maxPage;
int start = itemPerPage * page;
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
JsonObject palettes = root.createNestedObject("p");
@@ -920,7 +977,7 @@ void serializePalettes(JsonObject root, int page)
break;
default:
if (i >= palettesCount)
setPaletteColors(curPalette, strip.customPalettes[i - palettesCount]);
setPaletteColors(curPalette, customPalettes[i - palettesCount]);
else if (i < 13) // palette 6 - 12, fastled palettes
setPaletteColors(curPalette, *fastledPalettes[i-6]);
else {
@@ -1036,16 +1093,21 @@ class LockedJsonResponse: public AsyncJsonResponse {
void serveJson(AsyncWebServerRequest* request)
{
byte subJson = 0;
enum class json_target {
all, state, info, state_info, nodes, effects, palettes, fxdata, networks, config
};
json_target subJson = json_target::all;
const String& url = request->url();
if (url.indexOf("state") > 0) subJson = JSON_PATH_STATE;
else if (url.indexOf("info") > 0) subJson = JSON_PATH_INFO;
else if (url.indexOf("si") > 0) subJson = JSON_PATH_STATE_INFO;
else if (url.indexOf(F("nodes")) > 0) subJson = JSON_PATH_NODES;
else if (url.indexOf(F("eff")) > 0) subJson = JSON_PATH_EFFECTS;
else if (url.indexOf(F("palx")) > 0) subJson = JSON_PATH_PALETTES;
else if (url.indexOf(F("fxda")) > 0) subJson = JSON_PATH_FXDATA;
else if (url.indexOf(F("net")) > 0) subJson = JSON_PATH_NETWORKS;
if (url.indexOf("state") > 0) subJson = json_target::state;
else if (url.indexOf("info") > 0) subJson = json_target::info;
else if (url.indexOf("si") > 0) subJson = json_target::state_info;
else if (url.indexOf(F("nodes")) > 0) subJson = json_target::nodes;
else if (url.indexOf(F("eff")) > 0) subJson = json_target::effects;
else if (url.indexOf(F("palx")) > 0) subJson = json_target::palettes;
else if (url.indexOf(F("fxda")) > 0) subJson = json_target::fxdata;
else if (url.indexOf(F("net")) > 0) subJson = json_target::networks;
else if (url.indexOf(F("cfg")) > 0) subJson = json_target::config;
#ifdef WLED_ENABLE_JSONLIVE
else if (url.indexOf("live") > 0) {
serveLiveLeds(request);
@@ -1056,9 +1118,6 @@ void serveJson(AsyncWebServerRequest* request)
request->send_P(200, FPSTR(CONTENT_TYPE_JSON), JSON_palette_names);
return;
}
else if (url.indexOf(F("cfg")) > 0 && handleFileRead(request, F("/cfg.json"))) {
return;
}
else if (url.length() > 6) { //not just /json
serveJsonError(request, 501, ERR_NOT_IMPL);
return;
@@ -1070,32 +1129,35 @@ void serveJson(AsyncWebServerRequest* request)
}
// releaseJSONBufferLock() will be called when "response" is destroyed (from AsyncWebServer)
// make sure you delete "response" if no "request->send(response);" is made
LockedJsonResponse *response = new LockedJsonResponse(pDoc, subJson==JSON_PATH_FXDATA || subJson==JSON_PATH_EFFECTS); // will clear and convert JsonDocument into JsonArray if necessary
LockedJsonResponse *response = new LockedJsonResponse(pDoc, subJson==json_target::fxdata || subJson==json_target::effects); // will clear and convert JsonDocument into JsonArray if necessary
JsonVariant lDoc = response->getRoot();
switch (subJson)
{
case JSON_PATH_STATE:
case json_target::state:
serializeState(lDoc); break;
case JSON_PATH_INFO:
case json_target::info:
serializeInfo(lDoc); break;
case JSON_PATH_NODES:
case json_target::nodes:
serializeNodes(lDoc); break;
case JSON_PATH_PALETTES:
case json_target::palettes:
serializePalettes(lDoc, request->hasParam(F("page")) ? request->getParam(F("page"))->value().toInt() : 0); break;
case JSON_PATH_EFFECTS:
case json_target::effects:
serializeModeNames(lDoc); break;
case JSON_PATH_FXDATA:
case json_target::fxdata:
serializeModeData(lDoc); break;
case JSON_PATH_NETWORKS:
case json_target::networks:
serializeNetworks(lDoc); break;
default: //all
case json_target::config:
serializeConfig(lDoc); break;
case json_target::state_info:
case json_target::all:
JsonObject state = lDoc.createNestedObject("state");
serializeState(state);
JsonObject info = lDoc.createNestedObject("info");
serializeInfo(info);
if (subJson != JSON_PATH_STATE_INFO)
if (subJson == json_target::all)
{
JsonArray effects = lDoc.createNestedArray(F("effects"));
serializeModeNames(effects); // remove WLED-SR extensions from effect names

View File

@@ -4,11 +4,9 @@
* LED methods
*/
void setValuesFromMainSeg() { setValuesFromSegment(strip.getMainSegmentId()); }
void setValuesFromFirstSelectedSeg() { setValuesFromSegment(strip.getFirstSelectedSegId()); }
void setValuesFromSegment(uint8_t s)
{
Segment& seg = strip.getSegment(s);
// applies chosen setment properties to legacy values
void setValuesFromSegment(uint8_t s) {
const Segment& seg = strip.getSegment(s);
colPri[0] = R(seg.colors[0]);
colPri[1] = G(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...)
// problem: if the first selected segment already has the value to be set, other selected segments are not updated
void applyValuesToSelectedSegs()
{
// copy of first selected segment to tell if value was updated
unsigned firstSel = strip.getFirstSelectedSegId();
Segment selsegPrev = strip.getSegment(firstSel);
// applies global legacy values (colPri, colSec, effectCurrent...) to each selected segment
void applyValuesToSelectedSegs() {
for (unsigned i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (i != firstSel && (!seg.isActive() || !seg.isSelected())) continue;
if (effectSpeed != selsegPrev.speed) {seg.speed = effectSpeed; stateChanged = true;}
if (effectIntensity != selsegPrev.intensity) {seg.intensity = effectIntensity; stateChanged = true;}
if (effectPalette != selsegPrev.palette) {seg.setPalette(effectPalette);}
if (effectCurrent != selsegPrev.mode) {seg.setMode(effectCurrent);}
if (!(seg.isActive() && seg.isSelected())) continue;
if (effectSpeed != seg.speed) {seg.speed = effectSpeed; stateChanged = true;}
if (effectIntensity != seg.intensity) {seg.intensity = effectIntensity; stateChanged = true;}
if (effectPalette != seg.palette) {seg.setPalette(effectPalette);}
if (effectCurrent != seg.mode) {seg.setMode(effectCurrent);}
uint32_t col0 = RGBW32(colPri[0], colPri[1], colPri[2], colPri[3]);
uint32_t col1 = RGBW32(colSec[0], colSec[1], colSec[2], colSec[3]);
if (col0 != selsegPrev.colors[0]) {seg.setColor(0, col0);}
if (col1 != selsegPrev.colors[1]) {seg.setColor(1, col1);}
if (col0 != seg.colors[0]) {seg.setColor(0, col0);}
if (col1 != seg.colors[1]) {seg.setColor(1, col1);}
}
}
@@ -73,7 +65,8 @@ byte scaledBri(byte in)
//applies global temporary brightness (briT) to strip
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);
strip.setBrightness(scaledBri(briT));
}
@@ -94,7 +87,7 @@ void applyFinalBri() {
void stateUpdated(byte callMode) {
//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
setValuesFromFirstSelectedSeg();
setValuesFromFirstSelectedSeg(); // a much better approach would be to use main segment: setValuesFromMainSeg()
if (bri != briOld || stateChanged) {
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
interfaceUpdateCallMode = callMode;
stateChanged = false;
} else {
if (nightlightActive && !nightlightActiveOld && callMode != CALL_MODE_NOTIFICATION && callMode != CALL_MODE_NO_NOTIFY) {
notify(CALL_MODE_NIGHTLIGHT);
@@ -134,15 +126,16 @@ void stateUpdated(byte callMode) {
jsonTransitionOnce = false;
transitionActive = false;
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;
}
if (transitionActive) {
briOld = briT;
} else
strip.setTransitionMode(true); // force all segments to transition mode
transitionActive = true;
transitionStartTime = now;
stateChanged = false;
}

View File

@@ -27,7 +27,7 @@ static void parseMQTTBriPayload(char* payload)
static void onMqttConnect(bool sessionPresent)
{
//(re)subscribe to required topics
char subuf[MQTT_MAX_TOPIC_LEN + 6];
char subuf[MQTT_MAX_TOPIC_LEN + 9];
if (mqttDeviceTopic[0] != 0) {
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
@@ -52,6 +52,13 @@ static void onMqttConnect(bool sessionPresent)
UsermodManager::onMqttConnect(sessionPresent);
DEBUG_PRINTLN(F("MQTT ready"));
#ifndef USERMOD_SMARTNEST
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/status"));
mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT
#endif
publishMqtt();
}
@@ -68,8 +75,8 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp
}
if (index == 0) { // start (1st partial packet or the only packet)
if (payloadStr) free(payloadStr); // fail-safe: release buffer
payloadStr = static_cast<char*>(malloc(total+1)); // allocate new buffer
p_free(payloadStr); // release buffer if it exists
payloadStr = static_cast<char*>(p_malloc(total+1)); // allocate new buffer
}
if (payloadStr == nullptr) return; // buffer not allocated
@@ -94,7 +101,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp
} else {
// Non-Wled Topic used here. Probably a usermod subscribed to this topic.
UsermodManager::onMqttMessage(topic, payloadStr);
free(payloadStr);
p_free(payloadStr);
payloadStr = nullptr;
return;
}
@@ -124,7 +131,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp
// topmost topic (just wled/MAC)
parseMQTTBriPayload(payloadStr);
}
free(payloadStr);
p_free(payloadStr);
payloadStr = nullptr;
}
@@ -174,10 +181,6 @@ void publishMqtt()
strcat_P(subuf, PSTR("/c"));
mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263)
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/status"));
mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT
// TODO: use a DynamicBufferList. Requires a list-read-capable MQTT client API.
DynamicBuffer buf(1024);
bufferPrint pbuf(buf.data(), buf.size());
@@ -196,7 +199,8 @@ bool initMqtt()
if (!mqttEnabled || mqttServer[0] == 0 || !WLED_CONNECTED) return false;
if (mqtt == nullptr) {
mqtt = new AsyncMqttClient();
void *ptr = p_malloc(sizeof(AsyncMqttClient));
mqtt = new (ptr) AsyncMqttClient(); // use placement new (into PSRAM), client will never be deleted
if (!mqtt) return false;
mqtt->onMessage(onMqttMessage);
mqtt->onConnect(onMqttConnect);

View File

@@ -90,9 +90,8 @@ void _overlayAnalogCountdown()
void handleOverlayDraw() {
UsermodManager::handleOverlayDraw();
if (analogClockSolidBlack) {
const Segment* segments = strip.getSegments();
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.mode > 0 || segment.colors[0] > 0) {
return;

1161
wled00/palettes.h Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
#include "pin_manager.h"
#include "wled.h"
#include "pin_manager.h"
#ifdef ARDUINO_ARCH_ESP32
#ifdef bitRead
@@ -100,7 +100,8 @@ bool PinManager::allocateMultiplePins(const managed_pin_type * mptArray, byte ar
// as this can greatly simplify configuration arrays
continue;
}
if (!isPinOk(gpio, mptArray[i].isOutput)) {
// allow any GPIO for Ethernet (compile time assigned)
if (!(isPinOk(gpio, mptArray[i].isOutput) || tag==PinOwner::Ethernet)) {
DEBUG_PRINTF_P(PSTR("PIN ALLOC: FAIL Invalid pin attempted to be allocated: GPIO %d as %s\n."), gpio, mptArray[i].isOutput ? PSTR("output"): PSTR("input"));
shouldFail = true;
}
@@ -228,19 +229,18 @@ bool PinManager::isPinOk(byte gpio, bool output)
#else
if ((strncmp_P(PSTR("ESP32-U4WDH"), ESP.getChipModel(), 11) == 0) || // this is the correct identifier, but....
(strncmp_P(PSTR("ESP32-PICO-D2"), ESP.getChipModel(), 13) == 0)) { // https://github.com/espressif/arduino-esp32/issues/10683
(strncmp_P(PSTR("ESP32-PICO-D"), ESP.getChipModel(), 12) == 0)) { // https://github.com/espressif/arduino-esp32/issues/10683
// this chip has 4 MB of internal Flash and different packaging, so available pins are different!
if (((gpio > 5) && (gpio < 9)) || (gpio == 11))
return false;
if ((gpio > 5 && gpio < 9) || gpio == 11) return false; // U4WDH/PICO-D2 & PICO-D4: GPIO 6, 7, 8, 11 are used for SPI flash; 9 & 10 are free
if (gpio == 16 || gpio == 17) return false; // U4WDH/PICO-D?: GPIO 16 and 17 are used for PSRAM
} else if (strncmp_P(PSTR("ESP32-PICO-V3"), ESP.getChipModel(), 13) == 0) {
if (gpio == 6 || gpio == 11) return false; // PICO-V3: uses GPIO 6 and 11 for flash
if (strstr_P(ESP.getChipModel(), PSTR("V3-02")) != nullptr && (gpio == 9 || gpio == 10)) return false; // PICO-V3-02: uses GPIO 9 and 10 for PSRAM; 7, 8 are free
} else {
// for classic ESP32 (non-mini) modules, these are the SPI flash pins
if (gpio > 5 && gpio < 12) return false; //SPI flash pins
}
if (((strncmp_P(PSTR("ESP32-PICO"), ESP.getChipModel(), 10) == 0) ||
(strncmp_P(PSTR("ESP32-U4WDH"), ESP.getChipModel(), 11) == 0))
&& (gpio == 16 || gpio == 17)) return false; // PICO-D4/U4WDH: gpio16+17 are in use for onboard SPI FLASH
if (gpio == 16 || gpio == 17) return !psramFound(); //PSRAM pins on ESP32 (these are IO)
if (gpio == 16 || gpio == 17) return !psramFound(); // PSRAM pins on ESP32-D0WDR2-V3 (these are IO)
#endif
if (output) return digitalPinCanOutput(gpio);
else return true;

View File

@@ -3,11 +3,6 @@
/*
* Registers pins so there is no attempt for two interfaces to use the same pin
*/
#include <Arduino.h>
#ifdef ARDUINO_ARCH_ESP32
#include "driver/ledc.h" // needed for analog/LEDC channel counts
#endif
#include "const.h" // for USERMOD_* values
#ifdef ESP8266
#define WLED_NUM_PINS (GPIO_PIN_COUNT+1) // somehow they forgot GPIO 16 (0-16==17)

View File

@@ -29,8 +29,9 @@ bool presetNeedsSaving() {
static void doSaveState() {
bool persist = (presetToSave < 251);
unsigned long start = millis();
while (strip.isUpdating() && millis()-start < (2*FRAMETIME_FIXED)+1) yield(); // wait 2 frames
unsigned long maxWait = millis() + strip.getFrameTime();
while (strip.isUpdating() && millis() < maxWait) delay(1); // wait for strip to finish updating, accessing FS during sendout causes glitches
if (!requestJSONBufferLock(10)) return;
initPresetsFile(); // just in case if someone deleted presets.json using /edit
@@ -56,14 +57,10 @@ static void doSaveState() {
*/
#if defined(ARDUINO_ARCH_ESP32)
if (!persist) {
if (tmpRAMbuffer!=nullptr) free(tmpRAMbuffer);
p_free(tmpRAMbuffer);
size_t len = measureJson(*pDoc) + 1;
DEBUG_PRINTLN(len);
// if possible use SPI RAM on ESP32
if (psramSafe && psramFound())
tmpRAMbuffer = (char*) ps_malloc(len);
else
tmpRAMbuffer = (char*) malloc(len);
tmpRAMbuffer = (char*)p_malloc(len);
if (tmpRAMbuffer!=nullptr) {
serializeJson(*pDoc, tmpRAMbuffer, len);
} else {
@@ -80,8 +77,8 @@ static void doSaveState() {
// clean up
saveLedmap = -1;
presetToSave = 0;
free(saveName);
free(quickLoad);
p_free(saveName);
p_free(quickLoad);
saveName = nullptr;
quickLoad = nullptr;
playlistSave = false;
@@ -168,9 +165,9 @@ void handlePresets()
DEBUG_PRINTF_P(PSTR("Applying preset: %u\n"), (unsigned)tmpPreset);
#if defined(ARDUINO_ARCH_ESP32S3) || defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32C3)
unsigned long start = millis();
while (strip.isUpdating() && millis() - start < FRAMETIME_FIXED) yield(); // wait for strip to finish updating, accessing FS during sendout causes glitches
#if defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32C3)
unsigned long maxWait = millis() + strip.getFrameTime();
while (strip.isUpdating() && millis() < maxWait) delay(1); // wait for strip to finish updating, accessing FS during sendout causes glitches
#endif
#ifdef ARDUINO_ARCH_ESP32
@@ -206,7 +203,7 @@ void handlePresets()
#if defined(ARDUINO_ARCH_ESP32)
//Aircoookie recommended not to delete buffer
if (tmpPreset==255 && tmpRAMbuffer!=nullptr) {
free(tmpRAMbuffer);
p_free(tmpRAMbuffer);
tmpRAMbuffer = nullptr;
}
#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)]
void savePreset(byte index, const char* pname, JsonObject sObj)
{
if (!saveName) saveName = static_cast<char*>(malloc(33));
if (!quickLoad) quickLoad = static_cast<char*>(malloc(9));
if (!saveName) saveName = static_cast<char*>(p_malloc(33));
if (!quickLoad) quickLoad = static_cast<char*>(p_malloc(9));
if (!saveName || !quickLoad) return;
if (index == 0 || (index > 250 && index < 255)) return;
@@ -242,7 +239,7 @@ void savePreset(byte index, const char* pname, JsonObject sObj)
if (!sObj[FPSTR(bootPS)].isNull()) {
bootPreset = sObj[FPSTR(bootPS)] | bootPreset;
sObj.remove(FPSTR(bootPS));
doSerializeConfig = true;
configNeedsWrite = true;
}
if (sObj.size()==0 || sObj["o"].isNull()) { // no "o" means not a playlist or custom API call, saving of state is async (not immediately)
@@ -267,8 +264,8 @@ void savePreset(byte index, const char* pname, JsonObject sObj)
presetsModifiedTime = toki.second(); //unix time
updateFSInfo();
}
free(saveName);
free(quickLoad);
p_free(saveName);
p_free(quickLoad);
saveName = nullptr;
quickLoad = nullptr;
} else {

View File

@@ -181,16 +181,10 @@ static bool remoteJson(int button)
return parsed;
}
// Callback function that will be executed when data is received
// Callback function that will be executed when data is received from a linked remote
void handleWiZdata(uint8_t *incomingData, size_t len) {
message_structure_t *incoming = reinterpret_cast<message_structure_t *>(incomingData);
if (strcmp(last_signal_src, linked_remote) != 0) {
DEBUG_PRINT(F("ESP Now Message Received from Unlinked Sender: "));
DEBUG_PRINTLN(last_signal_src);
return;
}
if (len != sizeof(message_structure_t)) {
DEBUG_PRINTF_P(PSTR("Unknown incoming ESP Now message received of length %u\n"), len);
return;

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 sn[5] = "SN"; sn[2] = 48+n; sn[4] = 0; //subnet mask
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 oldPass[65]; strcpy(oldPass, multiWiFi[n].clientPass);
@@ -91,8 +91,21 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
bool oldESPNow = enableESPNow;
enableESPNow = request->hasArg(F("RE"));
if (oldESPNow != enableESPNow) forceReconnect = true;
strlcpy(linked_remote, request->arg(F("RMAC")).c_str(), 13);
strlwr(linked_remote); //Normalize MAC format to lowercase
linked_remotes.clear(); // clear old remotes
for (size_t n = 0; n < 10; n++) {
char rm[4];
snprintf(rm, sizeof(rm), "RM%d", n); // "RM0" to "RM9"
if (request->hasArg(rm)) {
const String& arg = request->arg(rm);
if (arg.isEmpty()) continue;
std::array<char, 13> mac{};
strlcpy(mac.data(), request->arg(rm).c_str(), 13);
strlwr(mac.data());
if (mac[0] != '\0') {
linked_remotes.emplace_back(mac);
}
}
}
#endif
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
@@ -129,6 +142,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
unsigned length, start, maMax;
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();
BusManager::setMilliampsMax(ablMilliampsMax);
@@ -136,17 +150,17 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
strip.correctWB = request->hasArg(F("CCT"));
strip.cctFromRgb = request->hasArg(F("CR"));
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());
strip.setTargetFps(request->arg(F("FR")).toInt());
useGlobalLedBuffer = request->hasArg(F("LD"));
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
useParallelI2S = request->hasArg(F("PR"));
#endif
bool busesChanged = false;
for (int s = 0; s < 36; s++) { // theoretical limit is 36 : "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
int offset = s < 10 ? '0' : 'A';
int offset = s < 10 ? '0' : 'A' - 10;
char lp[4] = "L0"; lp[2] = offset+s; lp[3] = 0; //ascii 0-9 //strip data pin
char lc[4] = "LC"; lc[2] = offset+s; lc[3] = 0; //strip length
char co[4] = "CO"; co[2] = offset+s; co[3] = 0; //strip color order
@@ -207,12 +221,12 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
maMax = 0;
} else {
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
// actual finalization is done in WLED::loop() (removing old busses and adding new)
// this may happen even before this loop is finished so we do "doInitBusses" after the loop
busConfigs.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;
}
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed
@@ -220,7 +234,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
// we will not bother with pre-allocating ColorOrderMappings vector
BusManager::getColorOrderMap().reset();
for (int s = 0; s < WLED_MAX_COLOR_ORDER_MAPPINGS; s++) {
int offset = s < 10 ? '0' : 'A';
int offset = s < 10 ? '0' : 'A' - 10;
char xs[4] = "XS"; xs[2] = offset+s; xs[3] = 0; //start LED
char xc[4] = "XC"; xc[2] = offset+s; xc[3] = 0; //strip length
char xo[4] = "XO"; xo[2] = offset+s; xo[3] = 0; //color order
@@ -259,7 +273,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
disablePullUp = (bool)request->hasArg(F("IP"));
touchThreshold = request->arg(F("TT")).toInt();
for (int i = 0; i < WLED_MAX_BUTTONS; i++) {
int offset = i < 10 ? '0' : 'A';
int offset = i < 10 ? '0' : 'A' - 10;
char bt[4] = "BT"; bt[2] = offset+i; bt[3] = 0; // button pin (use A,B,C,... if WLED_MAX_BUTTONS>10)
char be[4] = "BE"; be[2] = offset+i; be[3] = 0; // button type (use A,B,C,... if WLED_MAX_BUTTONS>10)
int hw_btn_pin = request->arg(bt).toInt();
@@ -327,13 +341,14 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
gammaCorrectBri = false;
gammaCorrectCol = false;
}
NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up table
NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up tables
t = request->arg(F("TD")).toInt();
if (t >= 0) transitionDelayDefault = t;
t = request->arg(F("TP")).toInt();
randomPaletteChangeTime = MIN(255,MAX(1,t));
useHarmonicRandomPalette = request->hasArg(F("TH"));
useRainbowWheel = request->hasArg(F("RW"));
nightlightTargetBri = request->arg(F("TB")).toInt();
t = request->arg(F("TL")).toInt();
@@ -342,7 +357,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
nightlightMode = request->arg(F("TW")).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();
if (t > 0) briMultiplier = t;
@@ -358,7 +373,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
DEBUG_PRINTLN(F("Enumerating ledmaps"));
enumerateLedmaps();
DEBUG_PRINTLN(F("Loading custom palettes"));
strip.loadCustomPalettes(); // (re)load all custom palettes
loadCustomPalettes(); // (re)load all custom palettes
}
//SYNC
@@ -593,8 +608,11 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
{
otaLock = request->hasArg(F("NO"));
wifiLock = request->hasArg(F("OW"));
#ifndef WLED_DISABLE_OTA
aOtaEnabled = request->hasArg(F("AO"));
#endif
//createEditHandler(correctPIN && !otaLock);
otaSameSubnet = request->hasArg(F("SU"));
}
}
@@ -771,14 +789,14 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
if (subPage == SUBPAGE_2D)
{
strip.isMatrix = request->arg(F("SOMP")).toInt();
strip.panel.clear(); // release memory if allocated
strip.panel.clear();
if (strip.isMatrix) {
strip.panels = MAX(1,MIN(WLED_MAX_PANELS,request->arg(F("MPC")).toInt()));
strip.panel.reserve(strip.panels); // pre-allocate memory
for (unsigned i=0; i<strip.panels; i++) {
unsigned panels = constrain(request->arg(F("MPC")).toInt(), 1, WLED_MAX_PANELS);
strip.panel.reserve(panels); // pre-allocate memory
for (unsigned i=0; i<panels; i++) {
WS2812FX::Panel p;
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';
unsigned l = strlen(pO);
// create P0B, P1B, ..., P63B, etc for other PxxX
@@ -793,20 +811,17 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
pO[l] = 'H'; p.height = request->arg(pO).toInt();
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
lastEditTime = millis();
// do not save if factory reset or LED settings (which are saved after LED re-init)
doSerializeConfig = subPage != SUBPAGE_LEDS && !(subPage == SUBPAGE_SEC && doReboot);
if (subPage == SUBPAGE_UM) doReboot = request->hasArg(F("RBT")); // prevent race condition on dual core system (set reboot here, after doSerializeConfig has been set)
configNeedsWrite = subPage != SUBPAGE_LEDS && !(subPage == SUBPAGE_SEC && doReboot);
if (subPage == SUBPAGE_UM) doReboot = request->hasArg(F("RBT")); // prevent race condition on dual core system (set reboot here, after configNeedsWrite has been set)
#ifndef WLED_DISABLE_ALEXA
if (subPage == SUBPAGE_SYNC) alexaInit();
#endif
@@ -824,7 +839,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
//segment select (sets main segment)
pos = req.indexOf(F("SM="));
if (pos > 0 && !realtimeMode) {
strip.setMainSegmentId(getNumVal(&req, pos));
strip.setMainSegmentId(getNumVal(req, pos));
}
byte selectedSeg = strip.getFirstSelectedSegId();
@@ -833,7 +848,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("SS="));
if (pos > 0) {
unsigned t = getNumVal(&req, pos);
unsigned t = getNumVal(req, pos);
if (t < strip.getSegmentsNum()) {
selectedSeg = t;
singleSegment = true;
@@ -843,7 +858,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
Segment& selseg = strip.getSegment(selectedSeg);
pos = req.indexOf(F("SV=")); //segment selected
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
selseg.selected = t;
}
@@ -872,19 +887,19 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
uint16_t spcI = selseg.spacing;
pos = req.indexOf(F("&S=")); //segment start
if (pos > 0) {
startI = std::abs(getNumVal(&req, pos));
startI = std::abs(getNumVal(req, pos));
}
pos = req.indexOf(F("S2=")); //segment stop
if (pos > 0) {
stopI = std::abs(getNumVal(&req, pos));
stopI = std::abs(getNumVal(req, pos));
}
pos = req.indexOf(F("GP=")); //segment grouping
if (pos > 0) {
grpI = std::max(1,getNumVal(&req, pos));
grpI = std::max(1,getNumVal(req, pos));
}
pos = req.indexOf(F("SP=")); //segment spacing
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
selseg.setGeometry(startI, stopI, grpI, spcI, UINT16_MAX, startY, stopY, selseg.map1D2D);
@@ -898,7 +913,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("SB=")); //Segment brightness/opacity
if (pos > 0) {
byte segbri = getNumVal(&req, pos);
byte segbri = getNumVal(req, pos);
selseg.setOption(SEG_OPTION_ON, segbri); // use transition
if (segbri) {
selseg.setOpacity(segbri);
@@ -907,7 +922,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("SW=")); //segment power
if (pos > 0) {
switch (getNumVal(&req, pos)) {
switch (getNumVal(req, pos)) {
case 0: selseg.setOption(SEG_OPTION_ON, false); 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
@@ -915,16 +930,16 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
}
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
if (pos > 0) presetCycMin = getNumVal(&req, pos);
if (pos > 0) presetCycMin = getNumVal(req, pos);
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
if (updateVal(req.c_str(), "PL=", &presetCycCurr, presetCycMin, presetCycMax)) {
if (updateVal(req.c_str(), "PL=", presetCycCurr, presetCycMin, presetCycMax)) {
applyPreset(presetCycCurr);
}
@@ -932,25 +947,25 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
if (pos > 0) doAdvancePlaylist = true;
//set brightness
updateVal(req.c_str(), "&A=", &bri);
updateVal(req.c_str(), "&A=", bri);
bool col0Changed = false, col1Changed = false, col2Changed = false;
//set colors
col0Changed |= updateVal(req.c_str(), "&R=", &colIn[0]);
col0Changed |= updateVal(req.c_str(), "&G=", &colIn[1]);
col0Changed |= updateVal(req.c_str(), "&B=", &colIn[2]);
col0Changed |= updateVal(req.c_str(), "&W=", &colIn[3]);
col0Changed |= updateVal(req.c_str(), "&R=", colIn[0]);
col0Changed |= updateVal(req.c_str(), "&G=", colIn[1]);
col0Changed |= updateVal(req.c_str(), "&B=", colIn[2]);
col0Changed |= updateVal(req.c_str(), "&W=", colIn[3]);
col1Changed |= updateVal(req.c_str(), "R2=", &colInSec[0]);
col1Changed |= updateVal(req.c_str(), "G2=", &colInSec[1]);
col1Changed |= updateVal(req.c_str(), "B2=", &colInSec[2]);
col1Changed |= updateVal(req.c_str(), "W2=", &colInSec[3]);
col1Changed |= updateVal(req.c_str(), "R2=", colInSec[0]);
col1Changed |= updateVal(req.c_str(), "G2=", colInSec[1]);
col1Changed |= updateVal(req.c_str(), "B2=", colInSec[2]);
col1Changed |= updateVal(req.c_str(), "W2=", colInSec[3]);
#ifdef WLED_ENABLE_LOXONE
//lox parser
pos = req.indexOf(F("LX=")); // Lox primary color
if (pos > 0) {
int lxValue = getNumVal(&req, pos);
int lxValue = getNumVal(req, pos);
if (parseLx(lxValue, colIn)) {
bri = 255;
nightlightActive = false; //always disable nightlight when toggling
@@ -959,7 +974,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
}
pos = req.indexOf(F("LY=")); // Lox secondary color
if (pos > 0) {
int lxValue = getNumVal(&req, pos);
int lxValue = getNumVal(req, pos);
if(parseLx(lxValue, colInSec)) {
bri = 255;
nightlightActive = false; //always disable nightlight when toggling
@@ -971,11 +986,11 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
//set hue
pos = req.indexOf(F("HU="));
if (pos > 0) {
uint16_t temphue = getNumVal(&req, pos);
uint16_t temphue = getNumVal(req, pos);
byte tempsat = 255;
pos = req.indexOf(F("SA="));
if (pos > 0) {
tempsat = getNumVal(&req, pos);
tempsat = getNumVal(req, pos);
}
byte sec = req.indexOf(F("H2"));
colorHStoRGB(temphue, tempsat, (sec>0) ? colInSec : colIn);
@@ -986,25 +1001,25 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("&K="));
if (pos > 0) {
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;
}
//set color from HEX or 32bit DEC
pos = req.indexOf(F("CL="));
if (pos > 0) {
colorFromDecOrHexString(colIn, req.substring(pos + 3).c_str());
colorFromDecOrHexString(colIn, (char*)req.substring(pos + 3).c_str());
col0Changed = true;
}
pos = req.indexOf(F("C2="));
if (pos > 0) {
colorFromDecOrHexString(colInSec, req.substring(pos + 3).c_str());
colorFromDecOrHexString(colInSec, (char*)req.substring(pos + 3).c_str());
col1Changed = true;
}
pos = req.indexOf(F("C3="));
if (pos > 0) {
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]);
selseg.setColor(2, col2); // defined above (SS= or main)
col2Changed = true;
@@ -1013,7 +1028,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
//set to random hue SR=0->1st SR=1->2nd
pos = req.indexOf(F("SR"));
if (pos > 0) {
byte sec = getNumVal(&req, pos);
byte sec = getNumVal(req, pos);
setRandomColor(sec? colInSec : colIn);
col0Changed |= (!sec); col1Changed |= sec;
}
@@ -1039,19 +1054,19 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
bool fxModeChanged = false, speedChanged = false, intensityChanged = false, paletteChanged = false;
bool custom1Changed = false, custom2Changed = false, custom3Changed = false, check1Changed = false, check2Changed = false, check3Changed = false;
// 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
fxModeChanged = true;
}
speedChanged = updateVal(req.c_str(), "SX=", &speedIn);
intensityChanged = updateVal(req.c_str(), "IX=", &intensityIn);
paletteChanged = updateVal(req.c_str(), "FP=", &paletteIn, 0, strip.getPaletteCount()-1);
custom1Changed = updateVal(req.c_str(), "X1=", &custom1In);
custom2Changed = updateVal(req.c_str(), "X2=", &custom2In);
custom3Changed = updateVal(req.c_str(), "X3=", &custom3In);
check1Changed = updateVal(req.c_str(), "M1=", &check1In);
check2Changed = updateVal(req.c_str(), "M2=", &check2In);
check3Changed = updateVal(req.c_str(), "M3=", &check3In);
speedChanged = updateVal(req.c_str(), "SX=", speedIn);
intensityChanged = updateVal(req.c_str(), "IX=", intensityIn);
paletteChanged = updateVal(req.c_str(), "FP=", paletteIn, 0, getPaletteCount()-1);
custom1Changed = updateVal(req.c_str(), "X1=", custom1In);
custom2Changed = updateVal(req.c_str(), "X2=", custom2In);
custom3Changed = updateVal(req.c_str(), "X3=", custom3In);
check1Changed = updateVal(req.c_str(), "M1=", check1In);
check2Changed = updateVal(req.c_str(), "M2=", check2In);
check3Changed = updateVal(req.c_str(), "M3=", check3In);
stateChanged |= (fxModeChanged || speedChanged || intensityChanged || paletteChanged || custom1Changed || custom2Changed || custom3Changed || check1Changed || check2Changed || check3Changed);
@@ -1077,13 +1092,13 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
//set advanced overlay
pos = req.indexOf(F("OL="));
if (pos > 0) {
overlayCurrent = getNumVal(&req, pos);
overlayCurrent = getNumVal(req, pos);
}
//apply macro (deprecated, added for compatibility with pre-0.11 automations)
pos = req.indexOf(F("&M="));
if (pos > 0) {
applyPreset(getNumVal(&req, pos) + 16);
applyPreset(getNumVal(req, pos) + 16);
}
//toggle send UDP direct notifications
@@ -1102,7 +1117,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("&T="));
if (pos > 0) {
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 1: if (bri == 0) bri = briLast; break; //on, only if it was previously off
@@ -1121,7 +1136,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
nightlightActive = false;
} else {
nightlightActive = true;
if (!aNlDef) nightlightDelayMins = getNumVal(&req, pos);
if (!aNlDef) nightlightDelayMins = getNumVal(req, pos);
else nightlightDelayMins = nightlightDelayMinsDefault;
nightlightStartTime = millis();
}
@@ -1135,7 +1150,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
//set nightlight target brightness
pos = req.indexOf(F("NT="));
if (pos > 0) {
nightlightTargetBri = getNumVal(&req, pos);
nightlightTargetBri = getNumVal(req, pos);
nightlightActiveOld = false; //re-init
}
@@ -1143,35 +1158,36 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("NF="));
if (pos > 0)
{
nightlightMode = getNumVal(&req, pos);
nightlightMode = getNumVal(req, pos);
nightlightActiveOld = false; //re-init
}
if (nightlightMode > NL_MODE_SUN) nightlightMode = NL_MODE_SUN;
pos = req.indexOf(F("TT="));
if (pos > 0) transitionDelay = getNumVal(&req, pos);
if (pos > 0) transitionDelay = getNumVal(req, pos);
strip.setTransition(transitionDelay);
//set time (unix timestamp)
pos = req.indexOf(F("ST="));
if (pos > 0) {
setTimeFromAPI(getNumVal(&req, pos));
setTimeFromAPI(getNumVal(req, pos));
}
//set countdown goal (unix timestamp)
pos = req.indexOf(F("CT="));
if (pos > 0) {
countdownTime = getNumVal(&req, pos);
countdownTime = getNumVal(req, pos);
if (countdownTime - toki.second() > 0) countdownOverTriggered = false;
}
pos = req.indexOf(F("LO="));
if (pos > 0) {
realtimeOverride = getNumVal(&req, pos);
realtimeOverride = getNumVal(req, pos);
if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS;
if (realtimeMode && useMainSegmentOnly) {
strip.getMainSegment().freeze = !realtimeOverride;
realtimeOverride = REALTIME_OVERRIDE_NONE; // ignore request for override if using main segment only
}
}
@@ -1184,12 +1200,12 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("U0=")); //user var 0
if (pos > 0) {
userVar0 = getNumVal(&req, pos);
userVar0 = getNumVal(req, pos);
}
pos = req.indexOf(F("U1=")); //user var 1
if (pos > 0) {
userVar1 = getNumVal(&req, pos);
userVar1 = getNumVal(req, pos);
}
// you can add more if you need

View File

@@ -6,7 +6,7 @@
#define UDP_SEG_SIZE 36
#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 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
//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
//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
udpOut[11] = 12;
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)
size_t s = 0, nsegs = strip.getSegmentsNum();
for (size_t i = 0; i < nsegs; i++) {
Segment &selseg = strip.getSegment(i);
const Segment &selseg = strip.getSegment(i);
if (!selseg.isActive()) continue;
unsigned ofs = 41 + s*UDP_SEG_SIZE; //start of segment offset byte
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);
packetSize += UDP_SEG_SIZE;
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);
buffer.packet++;
packetSize = 0;
@@ -266,13 +266,13 @@ static void parseNotifyPacket(const uint8_t *udpIn) {
strip.resume();
}
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 id = udpIn[0 +ofs];
DEBUG_PRINTF_P(PSTR("UDP segment received: %u\n"), id);
if (id > strip.getSegmentsNum()) break;
else if (id == strip.getSegmentsNum()) {
if (receiveSegmentBounds && id < strip.getMaxSegments()) strip.appendSegment();
if (receiveSegmentBounds && id < WS2812FX::getMaxSegments()) strip.appendSegment();
else break;
}
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
// 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);
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) {
DEBUG_PRINTF_P(PSTR("Apply sliders: %u\n"), id);
selseg.custom1 = udpIn[29+ofs];
@@ -406,31 +406,26 @@ static void parseNotifyPacket(const uint8_t *udpIn) {
stateUpdated(CALL_MODE_NOTIFICATION);
}
// realtimeLock() is called from UDP notifications, JSON API or serial Ada
void realtimeLock(uint32_t timeoutMs, byte md)
{
if (!realtimeMode && !realtimeOverride) {
unsigned stop, start;
if (useMainSegmentOnly) {
Segment& mainseg = strip.getMainSegment();
start = mainseg.start;
stop = mainseg.stop;
mainseg.clear(); // clear entire segment (in case sender transmits less pixels)
mainseg.freeze = true;
// if WLED was off and using main segment only, freeze non-main segments so they stay off
if (bri == 0) {
for (size_t s = 0; s < strip.getSegmentsNum(); s++) {
strip.getSegment(s).freeze = true;
}
for (size_t s = 0; s < strip.getSegmentsNum(); s++) strip.getSegment(s).freeze = true;
}
} else {
start = 0;
stop = strip.getLengthTotal();
// clear entire strip
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) {
@@ -452,6 +447,7 @@ void exitRealtime() {
realtimeIP[0] = 0;
if (useMainSegmentOnly) { // unfreeze live segment again
strip.getMainSegment().freeze = false;
strip.trigger();
} else {
strip.show(); // possible fix for #3589
}
@@ -481,7 +477,8 @@ void handleNotifications()
if (e131NewData && millis() - strip.getLastShow() > 15)
{
e131NewData = false;
strip.show();
if (useMainSegmentOnly) strip.trigger();
else strip.show();
}
//unlock strip when realtime UDP times out
@@ -508,13 +505,13 @@ void handleNotifications()
uint8_t lbuf[packetSize];
rgbUdp.read(lbuf, packetSize);
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_HYPERION);
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return;
if (realtimeOverride) return;
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++) {
setRealtimePixel(id, lbuf[i], lbuf[i+1], lbuf[i+2], 0);
}
if (!(realtimeMode && useMainSegmentOnly)) strip.show();
if (useMainSegmentOnly) strip.trigger();
else strip.show();
return;
}
}
@@ -583,7 +580,7 @@ void handleNotifications()
realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP();
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_TPM2NET);
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return;
if (realtimeOverride) return;
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
@@ -592,35 +589,33 @@ void handleNotifications()
unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED
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++) {
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
}
if (tpmPacketCount == numPackets) { //reset packet count and show if all packets were received
tpmPacketCount = 0;
strip.show();
if (useMainSegmentOnly) strip.trigger();
else strip.show();
}
return;
}
//UDP realtime: 1 warls 2 drgb 3 drgbw
if (udpIn[0] > 0 && udpIn[0] < 5)
//UDP realtime: 1 warls 2 drgb 3 drgbw 4 dnrgb 5 dnrgbw
if (udpIn[0] > 0 && udpIn[0] < 6)
{
realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP();
DEBUG_PRINTLN(realtimeIP);
if (packetSize < 2) return;
if (udpIn[1] == 0)
{
realtimeTimeout = 0;
if (udpIn[1] == 0) {
realtimeTimeout = 0; // cancel realtime mode immediately
return;
} else {
realtimeLock(udpIn[1]*1000 +1, REALTIME_MODE_UDP);
}
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return;
if (realtimeOverride) return;
unsigned totalLen = strip.getLengthTotal();
if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); // set up parameters for get/setPixelColor()
if (udpIn[0] == 1 && packetSize > 5) //warls
{
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]);
}
}
strip.show();
if (useMainSegmentOnly) strip.trigger();
else strip.show();
return;
}
@@ -679,20 +675,7 @@ void handleNotifications()
void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w)
{
unsigned pix = i + arlsOffset;
if (pix < strip.getLengthTotal()) {
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);
}
}
strip.setRealtimePixelColor(pix, RGBW32(r,g,b,w));
}
/*********************************************************************************************\
@@ -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 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
WiFiUDP ddpUdp;
@@ -959,14 +942,22 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs
// usermods hook can override processing
if (UsermodManager::onEspNowMessage(address, data, len)) return;
// handle WiZ Mote data
if (data[0] == 0x91 || data[0] == 0x81 || data[0] == 0x80) {
handleWiZdata(data, len);
bool knownRemote = false;
for (const auto& mac : linked_remotes) {
if (strlen(mac.data()) == 12 && strcmp(last_signal_src, mac.data()) == 0) {
knownRemote = true;
break;
}
}
if (!knownRemote) {
DEBUG_PRINT(F("ESP Now Message Received from Unlinked Sender: "));
DEBUG_PRINTLN(last_signal_src);
return;
}
if (strlen(linked_remote) == 12 && strcmp(last_signal_src, linked_remote) != 0) {
DEBUG_PRINTLN(F("ESP-NOW unpaired remote sender."));
// handle WiZ Mote data
if (data[0] == 0x91 || data[0] == 0x81 || data[0] == 0x80) {
handleWiZdata(data, len);
return;
}

View File

@@ -39,7 +39,13 @@ bool UsermodManager::getUMData(um_data_t **data, uint8_t mod_id) {
return false;
}
void UsermodManager::addToJsonState(JsonObject& obj) { for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) (*mod)->addToJsonState(obj); }
void UsermodManager::addToJsonInfo(JsonObject& obj) { for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) (*mod)->addToJsonInfo(obj); }
void UsermodManager::addToJsonInfo(JsonObject& obj) {
auto um_id_list = obj.createNestedArray("um");
for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) {
um_id_list.add((*mod)->getId());
(*mod)->addToJsonInfo(obj);
}
}
void UsermodManager::readFromJsonState(JsonObject& obj) { for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) (*mod)->readFromJsonState(obj); }
void UsermodManager::addToConfig(JsonObject& obj) { for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) (*mod)->addToConfig(obj); }
bool UsermodManager::readFromConfig(JsonObject& obj) {

View File

@@ -4,17 +4,17 @@
//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
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[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;
if (str[0] == 'w' && strlen(str) > 1) {str++; wrap = true;}
if (str[0] == '~') {
@@ -22,19 +22,19 @@ void parseNumber(const char* str, byte* val, byte minv, byte maxv)
if (out == 0) {
if (str[1] == '0') return;
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 {
*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 {
if (wrap && *val == maxv && out > 0) out = minv;
else if (wrap && *val == minv && out < 0) out = maxv;
if (wrap && val == maxv && out > 0) out = minv;
else if (wrap && val == minv && out < 0) out = maxv;
else {
out += *val;
out += val;
if (out > maxv) out = maxv;
if (out < minv) out = minv;
}
*val = out;
val = out;
}
return;
} 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)
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 < 0) return false; //ignore e.g. {"ps":-1}
*val = elem;
val = elem;
return true;
} else if (elem.is<const char*>()) {
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);
if (v) v += strlen(key);
@@ -506,12 +506,12 @@ um_data_t* simulateSound(uint8_t simulationId)
break;
case UMS_10_13:
for (int i = 0; i<16; i++)
fftResult[i] = inoise8(beatsin8_t(90 / (i+1), 0, 200)*15 + (ms>>10), ms>>3);
fftResult[i] = perlin8(beatsin8_t(90 / (i+1), 0, 200)*15 + (ms>>10), ms>>3);
volumeSmth = fftResult[8];
break;
case UMS_14_3:
for (int i = 0; i<16; i++)
fftResult[i] = inoise8(beatsin8_t(120 / (i+1), 10, 30)*10 + (ms>>14), ms>>3);
fftResult[i] = perlin8(beatsin8_t(120 / (i+1), 10, 30)*10 + (ms>>14), ms>>3);
volumeSmth = fftResult[8];
break;
}
@@ -618,3 +618,240 @@ int32_t hw_random(int32_t lowerlimit, int32_t upperlimit) {
uint32_t diff = upperlimit - lowerlimit;
return hw_random(diff) + lowerlimit;
}
#ifndef ESP8266
void *p_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 *p_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 *p_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
* Note: optimized for speed and to mimic fastled inoise functions, not for accuracy or best randomness
*/
#define PERLIN_SHIFT 1
// calculate gradient for corner from hash value
static inline __attribute__((always_inline)) int32_t hashToGradient(uint32_t h) {
// using more steps yields more "detailed" perlin noise but looks less like the original fastled version (adjust PERLIN_SHIFT to compensate, also changes range and needs proper adustment)
// return (h & 0xFF) - 128; // use PERLIN_SHIFT 7
// return (h & 0x0F) - 8; // use PERLIN_SHIFT 3
// return (h & 0x07) - 4; // use PERLIN_SHIFT 2
return (h & 0x03) - 2; // use PERLIN_SHIFT 1 -> closest to original fastled version
}
// Gradient functions for 1D, 2D and 3D Perlin noise note: forcing inline produces smaller code and makes it 3x faster!
static inline __attribute__((always_inline)) int32_t gradient1D(uint32_t x0, int32_t dx) {
uint32_t h = x0 * 0x27D4EB2D;
h ^= h >> 15;
h *= 0x92C3412B;
h ^= h >> 13;
h ^= h >> 7;
return (hashToGradient(h) * dx) >> PERLIN_SHIFT;
}
static inline __attribute__((always_inline)) int32_t gradient2D(uint32_t x0, int32_t dx, uint32_t y0, int32_t dy) {
uint32_t h = (x0 * 0x27D4EB2D) ^ (y0 * 0xB5297A4D);
h ^= h >> 15;
h *= 0x92C3412B;
h ^= h >> 13;
return (hashToGradient(h) * dx + hashToGradient(h>>PERLIN_SHIFT) * dy) >> (1 + PERLIN_SHIFT);
}
static inline __attribute__((always_inline)) int32_t gradient3D(uint32_t x0, int32_t dx, uint32_t y0, int32_t dy, uint32_t z0, int32_t dz) {
// fast and good entropy hash from corner coordinates
uint32_t h = (x0 * 0x27D4EB2D) ^ (y0 * 0xB5297A4D) ^ (z0 * 0x1B56C4E9);
h ^= h >> 15;
h *= 0x92C3412B;
h ^= h >> 13;
return ((hashToGradient(h) * dx + hashToGradient(h>>(1+PERLIN_SHIFT)) * dy + hashToGradient(h>>(1 + 2*PERLIN_SHIFT)) * dz) * 85) >> (8 + PERLIN_SHIFT); // scale to 16bit, x*85 >> 8 = x/3
}
// fast cubic smoothstep: t*(3 - 2t²), optimized for fixed point, scaled to avoid overflows
static uint32_t smoothstep(const uint32_t t) {
uint32_t t_squared = (t * t) >> 16;
uint32_t factor = (3 << 16) - ((t << 1));
return (t_squared * factor) >> 18; // scale to avoid overflows and give best resolution
}
// simple linear interpolation for fixed-point values, scaled for perlin noise use
static inline int32_t lerpPerlin(int32_t a, int32_t b, int32_t t) {
return a + (((b - a) * t) >> 14); // match scaling with smoothstep to yield 16.16bit values
}
// 1D Perlin noise function that returns a value in range of -24691 to 24689
int32_t perlin1D_raw(uint32_t x, bool is16bit) {
// integer and fractional part coordinates
int32_t x0 = x >> 16;
int32_t x1 = x0 + 1;
if(is16bit) x1 = x1 & 0xFF; // wrap back to zero at 0xFF instead of 0xFFFF
int32_t dx0 = x & 0xFFFF;
int32_t dx1 = dx0 - 0x10000;
// gradient values for the two corners
int32_t g0 = gradient1D(x0, dx0);
int32_t g1 = gradient1D(x1, dx1);
// interpolate and smooth function
int32_t tx = smoothstep(dx0);
int32_t noise = lerpPerlin(g0, g1, tx);
return noise;
}
// 2D Perlin noise function that returns a value in range of -20633 to 20629
int32_t perlin2D_raw(uint32_t x, uint32_t y, bool is16bit) {
int32_t x0 = x >> 16;
int32_t y0 = y >> 16;
int32_t x1 = x0 + 1;
int32_t y1 = y0 + 1;
if(is16bit) {
x1 = x1 & 0xFF; // wrap back to zero at 0xFF instead of 0xFFFF
y1 = y1 & 0xFF;
}
int32_t dx0 = x & 0xFFFF;
int32_t dy0 = y & 0xFFFF;
int32_t dx1 = dx0 - 0x10000;
int32_t dy1 = dy0 - 0x10000;
int32_t g00 = gradient2D(x0, dx0, y0, dy0);
int32_t g10 = gradient2D(x1, dx1, y0, dy0);
int32_t g01 = gradient2D(x0, dx0, y1, dy1);
int32_t g11 = gradient2D(x1, dx1, y1, dy1);
uint32_t tx = smoothstep(dx0);
uint32_t ty = smoothstep(dy0);
int32_t nx0 = lerpPerlin(g00, g10, tx);
int32_t nx1 = lerpPerlin(g01, g11, tx);
int32_t noise = lerpPerlin(nx0, nx1, ty);
return noise;
}
// 3D Perlin noise function that returns a value in range of -16788 to 16381
int32_t perlin3D_raw(uint32_t x, uint32_t y, uint32_t z, bool is16bit) {
int32_t x0 = x >> 16;
int32_t y0 = y >> 16;
int32_t z0 = z >> 16;
int32_t x1 = x0 + 1;
int32_t y1 = y0 + 1;
int32_t z1 = z0 + 1;
if(is16bit) {
x1 = x1 & 0xFF; // wrap back to zero at 0xFF instead of 0xFFFF
y1 = y1 & 0xFF;
z1 = z1 & 0xFF;
}
int32_t dx0 = x & 0xFFFF;
int32_t dy0 = y & 0xFFFF;
int32_t dz0 = z & 0xFFFF;
int32_t dx1 = dx0 - 0x10000;
int32_t dy1 = dy0 - 0x10000;
int32_t dz1 = dz0 - 0x10000;
int32_t g000 = gradient3D(x0, dx0, y0, dy0, z0, dz0);
int32_t g001 = gradient3D(x0, dx0, y0, dy0, z1, dz1);
int32_t g010 = gradient3D(x0, dx0, y1, dy1, z0, dz0);
int32_t g011 = gradient3D(x0, dx0, y1, dy1, z1, dz1);
int32_t g100 = gradient3D(x1, dx1, y0, dy0, z0, dz0);
int32_t g101 = gradient3D(x1, dx1, y0, dy0, z1, dz1);
int32_t g110 = gradient3D(x1, dx1, y1, dy1, z0, dz0);
int32_t g111 = gradient3D(x1, dx1, y1, dy1, z1, dz1);
uint32_t tx = smoothstep(dx0);
uint32_t ty = smoothstep(dy0);
uint32_t tz = smoothstep(dz0);
int32_t nx0 = lerpPerlin(g000, g100, tx);
int32_t nx1 = lerpPerlin(g010, g110, tx);
int32_t nx2 = lerpPerlin(g001, g101, tx);
int32_t nx3 = lerpPerlin(g011, g111, tx);
int32_t ny0 = lerpPerlin(nx0, nx1, ty);
int32_t ny1 = lerpPerlin(nx2, nx3, ty);
int32_t noise = lerpPerlin(ny0, ny1, tz);
return noise;
}
// scaling functions for fastled replacement
uint16_t perlin16(uint32_t x) {
return ((perlin1D_raw(x) * 1159) >> 10) + 32803; //scale to 16bit and offset (fastled range: about 4838 to 60766)
}
uint16_t perlin16(uint32_t x, uint32_t y) {
return ((perlin2D_raw(x, y) * 1537) >> 10) + 32725; //scale to 16bit and offset (fastled range: about 1748 to 63697)
}
uint16_t perlin16(uint32_t x, uint32_t y, uint32_t z) {
return ((perlin3D_raw(x, y, z) * 1731) >> 10) + 33147; //scale to 16bit and offset (fastled range: about 4766 to 60840)
}
uint8_t perlin8(uint16_t x) {
return (((perlin1D_raw((uint32_t)x << 8, true) * 1353) >> 10) + 32769) >> 8; //scale to 16 bit, offset, then scale to 8bit
}
uint8_t perlin8(uint16_t x, uint16_t y) {
return (((perlin2D_raw((uint32_t)x << 8, (uint32_t)y << 8, true) * 1620) >> 10) + 32771) >> 8; //scale to 16 bit, offset, then scale to 8bit
}
uint8_t perlin8(uint16_t x, uint16_t y, uint16_t z) {
return (((perlin3D_raw((uint32_t)x << 8, (uint32_t)y << 8, (uint32_t)z << 8, true) * 2015) >> 10) + 33168) >> 8; //scale to 16 bit, offset, then scale to 8bit
}

View File

@@ -193,14 +193,14 @@ void WLED::loop()
if (aligned) strip.makeAutoSegments();
else strip.fixInvalidSegments();
BusManager::setBrightness(bri); // fix re-initialised bus' brightness
doSerializeConfig = true;
configNeedsWrite = true;
}
if (loadLedmap >= 0) {
strip.deserializeMap(loadLedmap);
loadLedmap = -1;
}
yield();
if (doSerializeConfig) serializeConfig();
if (configNeedsWrite) serializeConfigToFS();
yield();
handleWs();
@@ -223,7 +223,7 @@ void WLED::loop()
}
#endif
if (doReboot && (!doInitBusses || !doSerializeConfig)) // if busses have to be inited & saved, wait until next iteration
if (doReboot && (!doInitBusses || !configNeedsWrite)) // if busses have to be inited & saved, wait until next iteration
reset();
// DEBUG serial logging (every 30s)
@@ -342,7 +342,6 @@ void WLED::setup()
#else
DEBUG_PRINTLN(F("arduino-esp32 v1.0.x\n")); // we can't say in more detail.
#endif
DEBUG_PRINTF_P(PSTR("CPU: %s rev.%d, %d core(s), %d MHz.\n"), ESP.getChipModel(), (int)ESP.getChipRevision(), ESP.getChipCores(), ESP.getCpuFreqMHz());
DEBUG_PRINTF_P(PSTR("FLASH: %d MB, Mode %d "), (ESP.getFlashChipSize()/1024)/1024, (int)ESP.getFlashChipMode());
#ifdef WLED_DEBUG
@@ -424,7 +423,7 @@ void WLED::setup()
multiWiFi.push_back(WiFiConfig(CLIENT_SSID,CLIENT_PASS)); // initialise vector with default WiFi
DEBUG_PRINTLN(F("Reading config"));
deserializeConfigFromFS();
bool needsCfgSave = deserializeConfigFromFS();
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
#if defined(STATUSLED) && STATUSLED>=0
@@ -444,6 +443,8 @@ void WLED::setup()
UsermodManager::setup();
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
if (needsCfgSave) serializeConfigToFS(); // usermods required new parameters; need to wait for strip to be initialised #4752
if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0)
showWelcomePage = true;
WiFi.persistent(false);
@@ -530,6 +531,7 @@ void WLED::setup()
void WLED::beginStrip()
{
// 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.makeAutoSegments();
strip.setBrightness(0);
@@ -558,6 +560,8 @@ void WLED::beginStrip()
applyPreset(bootPreset, CALL_MODE_INIT);
}
strip.setTransition(transitionDelayDefault); // restore transitions
// init relay pin
if (rlyPin >= 0) {
pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT);
@@ -739,9 +743,6 @@ void WLED::initInterfaces()
e131.begin(e131Multicast, e131Port, e131Universe, E131_MAX_UNIVERSE_COUNT);
ddp.begin(false, DDP_DEFAULT_PORT);
reconnectHue();
#ifndef WLED_DISABLE_MQTT
initMqtt();
#endif
interfacesInited = true;
wasConnected = true;
}
@@ -751,7 +752,9 @@ void WLED::handleConnection()
static bool scanDone = true;
static byte stacO = 0;
const unsigned long now = millis();
#ifdef WLED_DEBUG
const unsigned long nowS = now/1000;
#endif
const bool wifiConfigured = WLED_WIFI_CONFIGURED;
// ignore connection handling if WiFi is configured and scan still running

View File

@@ -64,6 +64,9 @@
//This is generally a terrible idea, but improves boot success on boards with a 3.3v regulator + cap setup that can't provide 400mA peaks
//#define WLED_DISABLE_BROWNOUT_DET
#include <cstddef>
#include <vector>
// Library inclusions.
#include <Arduino.h>
#ifdef ESP8266
@@ -417,7 +420,7 @@ WLED_GLOBAL bool cctICused _INIT(false); // CCT IC used (Athom 15W bulb
#endif
WLED_GLOBAL bool gammaCorrectCol _INIT(true); // use gamma correction on colors
WLED_GLOBAL bool gammaCorrectBri _INIT(false); // use gamma correction on brightness
WLED_GLOBAL float gammaCorrectVal _INIT(2.8f); // gamma correction value
WLED_GLOBAL float gammaCorrectVal _INIT(2.2f); // gamma correction value
WLED_GLOBAL byte colPri[] _INIT_N(({ 255, 160, 0, 0 })); // current RGB(W) primary color. colPri[] should be updated if you want to change the color.
WLED_GLOBAL byte colSec[] _INIT_N(({ 0, 0, 0, 0 })); // current RGB(W) secondary color
@@ -538,7 +541,8 @@ WLED_GLOBAL bool serialCanTX _INIT(false);
WLED_GLOBAL bool enableESPNow _INIT(false); // global on/off for ESP-NOW
WLED_GLOBAL byte statusESPNow _INIT(ESP_NOW_STATE_UNINIT); // state of ESP-NOW stack (0 uninitialised, 1 initialised, 2 error)
WLED_GLOBAL bool useESPNowSync _INIT(false); // use ESP-NOW wireless technology for sync
WLED_GLOBAL char linked_remote[13] _INIT(""); // MAC of ESP-NOW remote (Wiz Mote)
//WLED_GLOBAL char linked_remote[13] _INIT(""); // MAC of ESP-NOW remote (Wiz Mote)
WLED_GLOBAL std::vector<std::array<char, 13>> linked_remotes; // MAC of ESP-NOW remotes (Wiz Mote)
WLED_GLOBAL char last_signal_src[13] _INIT(""); // last seen ESP-NOW sender
#endif
@@ -558,7 +562,7 @@ WLED_GLOBAL byte currentTimezone _INIT(WLED_TIMEZONE); // Timezone ID. Refer
WLED_GLOBAL int utcOffsetSecs _INIT(WLED_UTC_OFFSET); // Seconds to offset from UTC before timzone calculation
WLED_GLOBAL byte overlayCurrent _INIT(0); // 0: no overlay 1: analog clock 2: was single-digit clock 3: was cronixie
WLED_GLOBAL byte overlayMin _INIT(0), overlayMax _INIT(DEFAULT_LED_COUNT - 1); // boundaries of overlay mode
WLED_GLOBAL uint16_t overlayMin _INIT(0), overlayMax _INIT(DEFAULT_LED_COUNT - 1); // boundaries of overlay mode
WLED_GLOBAL byte analogClock12pixel _INIT(0); // The pixel in your strip where "midnight" would be
WLED_GLOBAL bool analogClockSecondsTrail _INIT(false); // Display seconds as trail of LEDs instead of a single pixel
@@ -584,7 +588,12 @@ WLED_GLOBAL bool otaLock _INIT(true); // prevents OTA firmware update
WLED_GLOBAL bool otaLock _INIT(false); // prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks
#endif
WLED_GLOBAL bool wifiLock _INIT(false); // prevents access to WiFi settings when OTA lock is enabled
#ifndef WLED_DISABLE_OTA
WLED_GLOBAL bool aOtaEnabled _INIT(true); // ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on
#else
WLED_GLOBAL bool aOtaEnabled _INIT(false); // ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on
#endif
WLED_GLOBAL bool otaSameSubnet _INIT(true); // prevent OTA updates from other subnets (e.g. internet) if no PIN is set
WLED_GLOBAL char settingsPIN[5] _INIT(WLED_PIN); // PIN for settings pages
WLED_GLOBAL bool correctPIN _INIT(!strlen(settingsPIN));
WLED_GLOBAL unsigned long lastEditTime _INIT(0);
@@ -602,6 +611,8 @@ WLED_GLOBAL bool wasConnected _INIT(false);
// color
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 blending 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
WLED_GLOBAL uint8_t blendingStyle _INIT(0); // effect blending/transitionig style
@@ -612,6 +623,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 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 useRainbowWheel _INIT(false); // use "rainbow" color wheel instead of "spectrum" color wheel
// nightlight
WLED_GLOBAL bool nightlightActive _INIT(false);
@@ -877,7 +889,7 @@ WLED_GLOBAL byte errorFlag _INIT(0);
WLED_GLOBAL String messageHead, messageSub;
WLED_GLOBAL byte optionType;
WLED_GLOBAL bool doSerializeConfig _INIT(false); // flag to initiate saving of config
WLED_GLOBAL bool configNeedsWrite _INIT(false); // flag to initiate saving of config
WLED_GLOBAL bool doReboot _INIT(false); // flag to initiate reboot from async handlers
WLED_GLOBAL bool psramSafe _INIT(true); // is it safe to use PSRAM (on ESP32 rev.1; compiler fix used "-mfix-esp32-psram-cache-issue")

View File

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

View File

@@ -1,5 +1,10 @@
#include "wled.h"
#ifdef ESP8266
#include <Updater.h>
#else
#include <Update.h>
#endif
#include "html_ui.h"
#include "html_settings.h"
#include "html_other.h"
@@ -16,6 +21,7 @@ static const char s_redirecting[] PROGMEM = "Redirecting...";
static const char s_content_enc[] PROGMEM = "Content-Encoding";
static const char s_unlock_ota [] PROGMEM = "Please unlock OTA in security settings!";
static const char s_unlock_cfg [] PROGMEM = "Please unlock settings using PIN code!";
static const char s_rebooting [] PROGMEM = "Rebooting now...";
static const char s_notimplemented[] PROGMEM = "Not implemented";
static const char s_accessdenied[] PROGMEM = "Access Denied";
static const char _common_js[] PROGMEM = "/common.js";
@@ -31,6 +37,22 @@ static bool isIp(const String &str) {
return true;
}
static bool inSubnet(const IPAddress &ip, const IPAddress &subnet, const IPAddress &mask) {
return (((uint32_t)ip & (uint32_t)mask) == ((uint32_t)subnet & (uint32_t)mask));
}
static bool inSameSubnet(const IPAddress &client) {
return inSubnet(client, Network.localIP(), Network.subnetMask());
}
static bool inLocalSubnet(const IPAddress &client) {
return inSubnet(client, IPAddress(10,0,0,0), IPAddress(255,0,0,0)) // 10.x.x.x
|| inSubnet(client, IPAddress(192,168,0,0), IPAddress(255,255,0,0)) // 192.168.x.x
|| inSubnet(client, IPAddress(172,16,0,0), IPAddress(255,240,0,0)) // 172.16.x.x
|| (inSubnet(client, IPAddress(4,3,2,0), IPAddress(255,255,255,0)) && apActive) // WLED AP
|| inSameSubnet(client); // same subnet as WLED device
}
/*
* Integrated HTTP web server page declarations
*/
@@ -130,7 +152,7 @@ static String msgProcessor(const String& var)
if (optt < 60) //redirect to settings after optionType seconds
{
messageBody += F("<script>setTimeout(RS,");
messageBody +=String(optt*1000);
messageBody += String(optt*1000);
messageBody += F(")</script>");
} else if (optt < 120) //redirect back after optionType-60 seconds, unused
{
@@ -176,7 +198,7 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename,
doReboot = true;
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("Configuration restore successful.\nRebooting..."));
} else {
if (filename.indexOf(F("palette")) >= 0 && filename.indexOf(F(".json")) >= 0) strip.loadCustomPalettes();
if (filename.indexOf(F("palette")) >= 0 && filename.indexOf(F(".json")) >= 0) loadCustomPalettes();
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("File Uploaded!"));
}
cacheInvalidate++;
@@ -270,7 +292,7 @@ void initServer()
});
server.on(F("/reset"), HTTP_GET, [](AsyncWebServerRequest *request){
serveMessage(request, 200,F("Rebooting now..."),F("Please wait ~10 seconds..."),129);
serveMessage(request, 200, FPSTR(s_rebooting), F("Please wait ~10 seconds."), 131);
doReboot = true;
});
@@ -328,7 +350,7 @@ void initServer()
interfaceUpdateCallMode = CALL_MODE_WS_SEND; // schedule WS update
serveJson(request); return; //if JSON contains "v"
} else {
doSerializeConfig = true; //serializeConfig(); //Save new settings to FS
configNeedsWrite = true; //Save new settings to FS
}
}
request->send(200, CONTENT_TYPE_JSON, F("{\"success\":true}"));
@@ -365,7 +387,6 @@ void initServer()
createEditHandler(correctPIN);
static const char _update[] PROGMEM = "/update";
#ifndef WLED_DISABLE_OTA
//init ota page
server.on(_update, HTTP_GET, [](AsyncWebServerRequest *request){
if (otaLock) {
@@ -386,10 +407,16 @@ void initServer()
if (Update.hasError()) {
serveMessage(request, 500, F("Update failed!"), F("Please check your file and retry!"), 254);
} else {
serveMessage(request, 200, F("Update successful!"), F("Rebooting..."), 131);
serveMessage(request, 200, F("Update successful!"), FPSTR(s_rebooting), 131);
doReboot = true;
}
},[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){
IPAddress client = request->client()->remoteIP();
if (((otaSameSubnet && !inSameSubnet(client)) && !strlen(settingsPIN)) || (!otaSameSubnet && !inLocalSubnet(client))) {
DEBUG_PRINTLN(F("Attempted OTA update from different/non-local subnet!"));
request->send(401, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_accessdenied));
return;
}
if (!correctPIN || otaLock) return;
if(!index){
DEBUG_PRINTLN(F("OTA Update Start"));
@@ -419,12 +446,6 @@ void initServer()
}
}
});
#else
server.on(_update, HTTP_GET, [](AsyncWebServerRequest *request){
serveMessage(request, 501, FPSTR(s_notimplemented), F("OTA updating is disabled in this build."), 254);
});
#endif
#ifdef WLED_ENABLE_DMX
server.on(F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){
@@ -580,6 +601,11 @@ void serveSettings(AsyncWebServerRequest* request, bool post) {
}
if (post) { //settings/set POST request, saving
IPAddress client = request->client()->remoteIP();
if (!inLocalSubnet(client)) { // includes same subnet check
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_redirecting), 123);
return;
}
if (subPage != SUBPAGE_WIFI || !(wifiLock && otaLock)) handleSettingsSet(request, subPage);
char s[32];
@@ -631,7 +657,19 @@ void serveSettings(AsyncWebServerRequest* request, bool post) {
case SUBPAGE_DMX : content = PAGE_settings_dmx; len = PAGE_settings_dmx_length; break;
#endif
case SUBPAGE_UM : content = PAGE_settings_um; len = PAGE_settings_um_length; break;
case SUBPAGE_UPDATE : content = PAGE_update; len = PAGE_update_length; break;
case SUBPAGE_UPDATE : content = PAGE_update; len = PAGE_update_length;
#ifdef ARDUINO_ARCH_ESP32
if (request->hasArg(F("revert")) && inLocalSubnet(request->client()->remoteIP()) && Update.canRollBack()) {
doReboot = Update.rollBack();
if (doReboot) {
serveMessage(request, 200, F("Reverted to previous version!"), FPSTR(s_rebooting), 133);
} else {
serveMessage(request, 500, F("Rollback failed!"), F("Please reboot and retry."), 254);
}
return;
}
#endif
break;
#ifndef WLED_DISABLE_2D
case SUBPAGE_2D : content = PAGE_settings_2D; len = PAGE_settings_2D_length; break;
#endif

View File

@@ -216,7 +216,11 @@ void getSettingsJS(byte subPage, Print& settingsScript)
#ifndef WLED_DISABLE_ESPNOW
printSetFormCheckbox(settingsScript,PSTR("RE"),enableESPNow);
printSetFormValue(settingsScript,PSTR("RMAC"),linked_remote);
settingsScript.printf_P(PSTR("rstR();")); // reset remote list
for (size_t i = 0; i < linked_remotes.size(); i++) {
settingsScript.printf_P(PSTR("aR(\"RM%u\",\"%s\");"), i, linked_remotes[i].data()); // add remote to list
}
settingsScript.print(F("tE();")); // fill fields
#else
//hide remote settings if not compiled
settingsScript.print(F("toggle('ESPNOW');")); // hide ESP-NOW setting
@@ -258,10 +262,6 @@ void getSettingsJS(byte subPage, Print& settingsScript)
#ifndef WLED_DISABLE_ESPNOW
if (strlen(last_signal_src) > 0) { //Have seen an ESP-NOW Remote
printSetClassElementHTML(settingsScript,PSTR("rlid"),0,last_signal_src);
} else if (!enableESPNow) {
printSetClassElementHTML(settingsScript,PSTR("rlid"),0,(char*)F("(Enable ESP-NOW to listen)"));
} else {
printSetClassElementHTML(settingsScript,PSTR("rlid"),0,(char*)F("None"));
}
#endif
}
@@ -291,14 +291,13 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormValue(settingsScript,PSTR("CB"),Bus::getCCTBlend());
printSetFormValue(settingsScript,PSTR("FR"),strip.getTargetFps());
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
unsigned sumMa = 0;
for (int s = 0; s < BusManager::getNumBusses(); s++) {
const Bus* bus = BusManager::getBus(s);
for (size_t s = 0; s < BusManager::getNumBusses(); s++) {
const Bus *bus = BusManager::getBus(s);
if (!bus || !bus->isOk()) break; // should not happen but for safety
int offset = s < 10 ? '0' : 'A';
int offset = s < 10 ? '0' : 'A' - 10;
char lp[4] = "L0"; lp[2] = offset+s; lp[3] = 0; //ascii 0-9 //strip data pin
char lc[4] = "LC"; lc[2] = offset+s; lc[3] = 0; //strip length
char co[4] = "CO"; co[2] = offset+s; co[3] = 0; //strip color order
@@ -380,7 +379,8 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormValue(settingsScript,PSTR("TB"),nightlightTargetBri);
printSetFormValue(settingsScript,PSTR("TL"),nightlightDelayMinsDefault);
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);
printSetFormCheckbox(settingsScript,PSTR("RM"),rlyMde);
printSetFormCheckbox(settingsScript,PSTR("RO"),rlyOpenDrain);
@@ -591,10 +591,15 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormCheckbox(settingsScript,PSTR("NO"),otaLock);
printSetFormCheckbox(settingsScript,PSTR("OW"),wifiLock);
printSetFormCheckbox(settingsScript,PSTR("AO"),aOtaEnabled);
printSetFormCheckbox(settingsScript,PSTR("SU"),otaSameSubnet);
char tmp_buf[128];
snprintf_P(tmp_buf,sizeof(tmp_buf),PSTR("WLED %s (build %d)"),versionString,VERSION);
printSetClassElementHTML(settingsScript,PSTR("sip"),0,tmp_buf);
settingsScript.printf_P(PSTR("sd=\"%s\";"), serverDescription);
#ifdef WLED_DISABLE_OTA
//hide settings if not compiled
settingsScript.print(F("toggle('aOTA');")); // hide ArduinoOTA checkbox
#endif
}
#ifdef WLED_ENABLE_DMX // include only if DMX is enabled
@@ -658,6 +663,9 @@ void getSettingsJS(byte subPage, Print& settingsScript)
VERSION);
printSetClassElementHTML(settingsScript,PSTR("sip"),0,tmp_buf);
#ifndef ARDUINO_ARCH_ESP32
settingsScript.print(F("toggle('rev');")); // hide revert button on ESP8266
#endif
}
if (subPage == SUBPAGE_2D) // 2D matrices
@@ -666,16 +674,14 @@ void getSettingsJS(byte subPage, Print& settingsScript)
#ifndef WLED_DISABLE_2D
settingsScript.printf_P(PSTR("maxPanels=%d;resetPanels();"),WLED_MAX_PANELS);
if (strip.isMatrix) {
if(strip.panels>0){
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[0].height);
}
printSetFormValue(settingsScript,PSTR("MPC"),strip.panels);
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("PH"),strip.panel.size()>0?strip.panel[0].height:8);
printSetFormValue(settingsScript,PSTR("MPC"),strip.panel.size());
// 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);
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';
unsigned l = strlen(pO);
// create P0B, P1B, ..., P63B, etc for other PxxX