Merge branch 'wled:main' into main
This commit is contained in:
1041
wled00/FX.cpp
1041
wled00/FX.cpp
File diff suppressed because it is too large
Load Diff
723
wled00/FX.h
723
wled00/FX.h
File diff suppressed because it is too large
Load Diff
@@ -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 Wu’s 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;
|
||||
// Bresenham’s 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
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
@@ -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 ¶llelspeed, 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 ¶llelspeed, 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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
294
wled00/cfg.cpp
294
wled00/cfg.cpp
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
145
wled00/const.h
145
wled00/const.h
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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');"> 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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 "rainbow" 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;">⚠ Unlimited FPS Mode is experimental ⚠<br></div>
|
||||
<div id="fpsNone" class="warn" style="display: none;">⚠ Unlimited FPS Mode is experimental ⚠<br></div>
|
||||
<div id="fpsHigh" class="warn" style="display: none;">⚠ 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">
|
||||
|
||||
@@ -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">⚠ 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">⚠ Restoring presets/configuration will OVERWRITE your current presets/configuration.<br>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -272,5 +272,5 @@ void parseWiFiCommand(char* rpcData) {
|
||||
improvActive = 2;
|
||||
|
||||
forceReconnect = true;
|
||||
serializeConfig();
|
||||
serializeConfigToFS();
|
||||
}
|
||||
@@ -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
|
||||
|
||||
282
wled00/json.cpp
282
wled00/json.cpp
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
1161
wled00/palettes.h
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
174
wled00/set.cpp
174
wled00/set.cpp
@@ -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
|
||||
|
||||
|
||||
103
wled00/udp.cpp
103
wled00/udp.cpp
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
269
wled00/util.cpp
269
wled00/util.cpp
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user