Merge pull request #3088 from Aircoookie/led-gaps

LED matrix gaps
This commit is contained in:
Christian Schwinne 2023-02-21 16:46:58 +01:00 committed by GitHub
commit 9249c74b7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 3944 additions and 3572 deletions

View File

@ -375,6 +375,7 @@ public:
* onStateChanged() is used to detect WLED state change * onStateChanged() is used to detect WLED state change
*/ */
void onStateChange(uint8_t mode) { void onStateChange(uint8_t mode) {
if (!initDone) return;
DEBUG_PRINT(F("PIR: offTimerStart=")); DEBUG_PRINTLN(offTimerStart); DEBUG_PRINT(F("PIR: offTimerStart=")); DEBUG_PRINTLN(offTimerStart);
if (PIRtriggered && offTimerStart) { if (PIRtriggered && offTimerStart) {
// checking PIRtriggered and offTimerStart will prevent cancellation upon On trigger // checking PIRtriggered and offTimerStart will prevent cancellation upon On trigger

View File

@ -460,7 +460,7 @@ typedef struct Segment {
_dataLen(0), _dataLen(0),
_t(nullptr) _t(nullptr)
{ {
refreshLightCapabilities(); //refreshLightCapabilities();
} }
Segment(uint16_t sStartX, uint16_t sStopX, uint16_t sStartY, uint16_t sStopY) : Segment(sStartX, sStopX) { Segment(uint16_t sStartX, uint16_t sStopX, uint16_t sStartY, uint16_t sStopY) : Segment(sStartX, sStopX) {
@ -659,7 +659,6 @@ class WS2812FX { // 96 bytes
isMatrix(false), isMatrix(false),
#ifndef WLED_DISABLE_2D #ifndef WLED_DISABLE_2D
panels(1), panels(1),
matrix{0,0,0,0},
#endif #endif
// semi-private (just obscured) used in effect functions through macros // semi-private (just obscured) used in effect functions through macros
_currentPalette(CRGBPalette16(CRGB::Black)), _currentPalette(CRGBPalette16(CRGB::Black)),
@ -727,8 +726,7 @@ class WS2812FX { // 96 bytes
fixInvalidSegments(), fixInvalidSegments(),
setPixelColor(int n, uint32_t c), setPixelColor(int n, uint32_t c),
show(void), show(void),
setTargetFps(uint8_t fps), setTargetFps(uint8_t fps);
deserializeMap(uint8_t n=0);
void fill(uint32_t c) { for (int i = 0; i < _length; i++) setPixelColor(i, c); } // fill whole strip with color (inline) void fill(uint32_t c) { for (int i = 0; i < _length; i++) setPixelColor(i, c); } // fill whole strip with color (inline)
void addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name); // add effect to the list; defined in FX.cpp void addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name); // add effect to the list; defined in FX.cpp
@ -748,6 +746,7 @@ class WS2812FX { // 96 bytes
hasCCTBus(void), hasCCTBus(void),
// return true if the strip is being sent pixel updates // return true if the strip is being sent pixel updates
isUpdating(void), isUpdating(void),
deserializeMap(uint8_t n=0),
useLedsArray = false; useLedsArray = false;
inline bool isServicing(void) { return _isServicing; } inline bool isServicing(void) { return _isServicing; }
@ -813,13 +812,6 @@ class WS2812FX { // 96 bytes
uint8_t uint8_t
panels; panels;
struct {
bool bottomStart : 1;
bool rightStart : 1;
bool vertical : 1;
bool serpentine : 1;
} matrix;
typedef struct panel_t { typedef struct panel_t {
uint16_t xOffset; // x offset relative to the top left of matrix in LEDs uint16_t xOffset; // x offset relative to the top left of matrix in LEDs
uint16_t yOffset; // y offset relative to the top left of matrix in LEDs uint16_t yOffset; // y offset relative to the top left of matrix in LEDs

View File

@ -78,21 +78,60 @@ void WS2812FX::setUpMatrix() {
customMappingTable[i] = (uint16_t)-1; customMappingTable[i] = (uint16_t)-1;
} }
// we will try to load a "gap" array (a JSON file)
// the array has to have the same amount of values as mapping array (or larger)
// "gap" array is used while building ledmap (mapping array)
// and discarded afterwards as it has no meaning after the process
// content of the file is just raw JSON array in the form of [val1,val2,val3,...]
// there are no other "key":"value" pairs in it
// allowed values are: -1 (missing pixel/no LED attached), 0 (inactive/unused pixel), 1 (active/used pixel)
char fileName[32]; strcpy_P(fileName, PSTR("/2d-gaps.json")); // reduce flash footprint
bool isFile = WLED_FS.exists(fileName);
size_t gapSize = 0;
int8_t *gapTable = nullptr;
if (isFile && requestJSONBufferLock(20)) {
DEBUG_PRINT(F("Reading LED gap from "));
DEBUG_PRINTLN(fileName);
// read the array into global JSON buffer
if (readObjectFromFile(fileName, nullptr, &doc)) {
// the array is similar to ledmap, except it has only 3 values:
// -1 ... missing pixel (do not increase pixel count)
// 0 ... inactive pixel (it does count, but should be mapped out (-1))
// 1 ... active pixel (it will count and will be mapped)
JsonArray map = doc.as<JsonArray>();
gapSize = map.size();
if (!map.isNull() && gapSize >= customMappingSize) { // not an empty map
gapTable = new int8_t[gapSize];
if (gapTable) for (size_t i = 0; i < gapSize; i++) {
gapTable[i] = constrain(map[i], -1, 1);
}
}
}
DEBUG_PRINTLN(F("Gaps loaded."));
releaseJSONBufferLock();
}
uint16_t x, y, pix=0; //pixel uint16_t x, y, pix=0; //pixel
for (size_t pan = 0; pan < panel.size(); pan++) { for (size_t pan = 0; pan < panel.size(); pan++) {
Panel &p = panel[pan]; Panel &p = panel[pan];
uint16_t h = p.vertical ? p.height : p.width; uint16_t h = p.vertical ? p.height : p.width;
uint16_t v = p.vertical ? p.width : p.height; uint16_t v = p.vertical ? p.width : p.height;
for (size_t j = 0; j < v; j++){ for (size_t j = 0; j < v; j++){
for(size_t i = 0; i < h; i++, pix++) { for(size_t i = 0; i < h; i++) {
y = (p.vertical?p.rightStart:p.bottomStart) ? v-j-1 : j; y = (p.vertical?p.rightStart:p.bottomStart) ? v-j-1 : j;
x = (p.vertical?p.bottomStart:p.rightStart) ? h-i-1 : i; x = (p.vertical?p.bottomStart:p.rightStart) ? h-i-1 : i;
x = p.serpentine && j%2 ? h-x-1 : x; x = p.serpentine && j%2 ? h-x-1 : x;
customMappingTable[(p.yOffset + (p.vertical?x:y)) * Segment::maxWidth + p.xOffset + (p.vertical?y:x)] = pix; size_t index = (p.yOffset + (p.vertical?x:y)) * Segment::maxWidth + p.xOffset + (p.vertical?y:x);
if (!gapTable || (gapTable && gapTable[index] > 0)) customMappingTable[index] = pix; // a useful pixel (otherwise -1 is retained)
if (!gapTable || (gapTable && gapTable[index] >= 0)) pix++; // not a missing pixel
} }
} }
} }
// delete gap array as we no longer need it
if (gapTable) delete[] gapTable;
#ifdef WLED_DEBUG #ifdef WLED_DEBUG
DEBUG_PRINT(F("Matrix ledmap:")); DEBUG_PRINT(F("Matrix ledmap:"));
for (uint16_t i=0; i<customMappingSize; i++) { for (uint16_t i=0; i<customMappingSize; i++) {
@ -108,8 +147,8 @@ void WS2812FX::setUpMatrix() {
panel.clear(); panel.clear();
Segment::maxWidth = _length; Segment::maxWidth = _length;
Segment::maxHeight = 1; Segment::maxHeight = 1;
resetSegments();
} }
resetSegments();
} }
#else #else
isMatrix = false; // no matter what config says isMatrix = false; // no matter what config says

View File

@ -750,15 +750,33 @@ uint8_t Segment::differs(Segment& b) const {
void Segment::refreshLightCapabilities() { void Segment::refreshLightCapabilities() {
uint8_t capabilities = 0; uint8_t capabilities = 0;
uint16_t segStartIdx = 0xFFFFU;
uint16_t segStopIdx = 0;
if (start < Segment::maxWidth * Segment::maxHeight) {
// we are withing 2D matrix (includes 1D segments)
for (int y = startY; y < stopY; y++) for (int x = start; x < stop; x++) {
uint16_t index = x + Segment::maxWidth * y;
if (index < strip.customMappingSize) index = strip.customMappingTable[index]; // convert logical address to physical
if (index < 0xFFFFU) {
if (segStartIdx > index) segStartIdx = index;
if (segStopIdx < index) segStopIdx = index;
}
}
} else {
// we are on the strip located after the matrix
segStartIdx = start;
segStopIdx = stop;
}
for (uint8_t b = 0; b < busses.getNumBusses(); b++) { for (uint8_t b = 0; b < busses.getNumBusses(); b++) {
Bus *bus = busses.getBus(b); Bus *bus = busses.getBus(b);
if (bus == nullptr || bus->getLength()==0) break; if (bus == nullptr || bus->getLength()==0) break;
if (!bus->isOk()) continue; if (!bus->isOk()) continue;
if (bus->getStart() >= stop) continue; if (bus->getStart() >= segStopIdx) continue;
if (bus->getStart() + bus->getLength() <= start) continue; if (bus->getStart() + bus->getLength() <= segStartIdx) continue;
uint8_t type = bus->getType(); //uint8_t type = bus->getType();
if (bus->hasRGB() || (cctFromRgb && bus->hasCCT())) capabilities |= SEG_CAPABILITY_RGB; if (bus->hasRGB() || (cctFromRgb && bus->hasCCT())) capabilities |= SEG_CAPABILITY_RGB;
if (!cctFromRgb && bus->hasCCT()) capabilities |= SEG_CAPABILITY_CCT; if (!cctFromRgb && bus->hasCCT()) capabilities |= SEG_CAPABILITY_CCT;
if (correctWB && (bus->hasRGB() || bus->hasCCT())) capabilities |= SEG_CAPABILITY_CCT; //white balance correction (CCT slider) if (correctWB && (bus->hasRGB() || bus->hasCCT())) capabilities |= SEG_CAPABILITY_CCT; //white balance correction (CCT slider)
@ -1012,10 +1030,11 @@ void WS2812FX::finalizeInit(void)
#endif #endif
} }
if (!isMatrix) { // if 2D then max values defined in setUpMatrix() using panel set-up if (isMatrix) setUpMatrix();
else {
Segment::maxWidth = _length; Segment::maxWidth = _length;
Segment::maxHeight = 1; Segment::maxHeight = 1;
} }
//initialize leds array. TBD: realloc if nr of leds change //initialize leds array. TBD: realloc if nr of leds change
if (Segment::_globalLeds) { if (Segment::_globalLeds) {
@ -1024,17 +1043,20 @@ void WS2812FX::finalizeInit(void)
Segment::_globalLeds = nullptr; Segment::_globalLeds = nullptr;
} }
if (useLedsArray) { if (useLedsArray) {
size_t arrSize = sizeof(CRGB) * MAX(_length, Segment::maxWidth*Segment::maxHeight);
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM) #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)
if (psramFound()) if (psramFound())
Segment::_globalLeds = (CRGB*) ps_malloc(sizeof(CRGB) * _length); Segment::_globalLeds = (CRGB*) ps_malloc(arrSize);
else else
#endif #endif
Segment::_globalLeds = (CRGB*) malloc(sizeof(CRGB) * _length); Segment::_globalLeds = (CRGB*) malloc(arrSize);
memset(Segment::_globalLeds, 0, sizeof(CRGB) * _length); memset(Segment::_globalLeds, 0, arrSize);
} }
//segments are created in makeAutoSegments(); //segments are created in makeAutoSegments();
DEBUG_PRINTLN(F("Loading custom palettes"));
loadCustomPalettes(); // (re)load all custom palettes loadCustomPalettes(); // (re)load all custom palettes
DEBUG_PRINTLN(F("Loading custom ledmaps"));
deserializeMap(); // (re)load default ledmap deserializeMap(); // (re)load default ledmap
} }
@ -1409,9 +1431,9 @@ void WS2812FX::makeAutoSegments(bool forceReset) {
} }
// do we have LEDs after the matrix? (ignore buses) // do we have LEDs after the matrix? (ignore buses)
if (autoSegments && _length > Segment::maxWidth*Segment::maxHeight /*&& getActiveSegmentsNum() == 2*/) { if (autoSegments && _length > Segment::maxWidth*Segment::maxHeight /*&& getActiveSegmentsNum() == 2*/) {
if (_segments.size() == getLastActiveSegmentId()+1) if (_segments.size() == getLastActiveSegmentId()+1U) {
_segments.push_back(Segment(Segment::maxWidth*Segment::maxHeight, _length)); _segments.push_back(Segment(Segment::maxWidth*Segment::maxHeight, _length));
else { } else {
size_t i = getLastActiveSegmentId() + 1; size_t i = getLastActiveSegmentId() + 1;
_segments[i].start = Segment::maxWidth*Segment::maxHeight; _segments[i].start = Segment::maxWidth*Segment::maxHeight;
_segments[i].stop = _length; _segments[i].stop = _length;
@ -1468,14 +1490,30 @@ void WS2812FX::makeAutoSegments(bool forceReset) {
void WS2812FX::fixInvalidSegments() { void WS2812FX::fixInvalidSegments() {
//make sure no segment is longer than total (sanity check) //make sure no segment is longer than total (sanity check)
for (size_t i = getSegmentsNum()-1; i > 0; i--) { for (size_t i = getSegmentsNum()-1; i > 0; i--) {
if (_segments[i].start >= _length) { _segments.erase(_segments.begin()+i); continue; } if (isMatrix) {
if (_segments[i].stop > _length) _segments[i].stop = _length; #ifndef WLED_DISABLE_2D
// this is always called as the last step after finalizeInit(), update covered bus types if (_segments[i].start >= Segment::maxWidth * Segment::maxHeight) {
_segments[i].refreshLightCapabilities(); // 1D segment at the end of matrix
if (_segments[i].start >= _length || _segments[i].startY > 0 || _segments[i].stopY > 1) { _segments.erase(_segments.begin()+i); continue; }
if (_segments[i].stop > _length) _segments[i].stop = _length;
continue;
}
if (_segments[i].start >= Segment::maxWidth || _segments[i].startY >= Segment::maxHeight) { _segments.erase(_segments.begin()+i); continue; }
if (_segments[i].stop > Segment::maxWidth) _segments[i].stop = Segment::maxWidth;
if (_segments[i].stopY > Segment::maxHeight) _segments[i].stopY = Segment::maxHeight;
#endif
} else {
if (_segments[i].start >= _length) { _segments.erase(_segments.begin()+i); continue; }
if (_segments[i].stop > _length) _segments[i].stop = _length;
}
} }
// this is always called as the last step after finalizeInit(), update covered bus types
for (segment &seg : _segments)
seg.refreshLightCapabilities();
} }
//true if all segments align with a bus, or if a segment covers the total length //true if all segments align with a bus, or if a segment covers the total length
//irrelevant in 2D set-up
bool WS2812FX::checkSegmentAlignment() { bool WS2812FX::checkSegmentAlignment() {
bool aligned = false; bool aligned = false;
for (segment &seg : _segments) { for (segment &seg : _segments) {
@ -1575,8 +1613,8 @@ void WS2812FX::loadCustomPalettes() {
} }
//load custom mapping table from JSON file (called from finalizeInit() or deserializeState()) //load custom mapping table from JSON file (called from finalizeInit() or deserializeState())
void WS2812FX::deserializeMap(uint8_t n) { bool WS2812FX::deserializeMap(uint8_t n) {
if (isMatrix) return; // 2D support creates its own ledmap // 2D support creates its own ledmap (on the fly) if a ledmap.json exists it will overwrite built one.
char fileName[32]; char fileName[32];
strcpy_P(fileName, PSTR("/ledmap")); strcpy_P(fileName, PSTR("/ledmap"));
@ -1586,24 +1624,24 @@ void WS2812FX::deserializeMap(uint8_t n) {
if (!isFile) { if (!isFile) {
// erase custom mapping if selecting nonexistent ledmap.json (n==0) // erase custom mapping if selecting nonexistent ledmap.json (n==0)
if (!n && customMappingTable != nullptr) { if (!isMatrix && !n && customMappingTable != nullptr) {
customMappingSize = 0; customMappingSize = 0;
delete[] customMappingTable; delete[] customMappingTable;
customMappingTable = nullptr; customMappingTable = nullptr;
} }
return; return false;
} }
if (!requestJSONBufferLock(7)) return; if (!requestJSONBufferLock(7)) return false;
DEBUG_PRINT(F("Reading LED map from "));
DEBUG_PRINTLN(fileName);
if (!readObjectFromFile(fileName, nullptr, &doc)) { if (!readObjectFromFile(fileName, nullptr, &doc)) {
releaseJSONBufferLock(); releaseJSONBufferLock();
return; //if file does not exist just exit return false; //if file does not exist just exit
} }
DEBUG_PRINT(F("Reading LED map from "));
DEBUG_PRINTLN(fileName);
// erase old custom ledmap // erase old custom ledmap
if (customMappingTable != nullptr) { if (customMappingTable != nullptr) {
customMappingSize = 0; customMappingSize = 0;
@ -1621,6 +1659,7 @@ void WS2812FX::deserializeMap(uint8_t n) {
} }
releaseJSONBufferLock(); releaseJSONBufferLock();
return true;
} }

View File

@ -140,16 +140,16 @@ class Bus {
static void setCCT(uint16_t cct) { static void setCCT(uint16_t cct) {
_cct = cct; _cct = cct;
} }
static void setCCTBlend(uint8_t b) { static void setCCTBlend(uint8_t b) {
if (b > 100) b = 100; if (b > 100) b = 100;
_cctBlend = (b * 127) / 100; _cctBlend = (b * 127) / 100;
//compile-time limiter for hardware that can't power both white channels at max //compile-time limiter for hardware that can't power both white channels at max
#ifdef WLED_MAX_CCT_BLEND #ifdef WLED_MAX_CCT_BLEND
if (_cctBlend > WLED_MAX_CCT_BLEND) _cctBlend = WLED_MAX_CCT_BLEND; if (_cctBlend > WLED_MAX_CCT_BLEND) _cctBlend = WLED_MAX_CCT_BLEND;
#endif #endif
} }
inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; } inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; }
inline uint8_t getAutoWhiteMode() { return _autoWhiteMode; } inline uint8_t getAutoWhiteMode() { return _autoWhiteMode; }
inline static void setGlobalAWMode(uint8_t m) { if (m < 5) _gAWM = m; else _gAWM = AW_GLOBAL_DISABLED; } inline static void setGlobalAWMode(uint8_t m) { if (m < 5) _gAWM = m; else _gAWM = AW_GLOBAL_DISABLED; }
inline static uint8_t getGlobalAWMode() { return _gAWM; } inline static uint8_t getGlobalAWMode() { return _gAWM; }

View File

@ -97,12 +97,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject matrix = hw_led[F("matrix")]; JsonObject matrix = hw_led[F("matrix")];
if (!matrix.isNull()) { if (!matrix.isNull()) {
strip.isMatrix = true; strip.isMatrix = true;
CJSON(strip.panels, matrix[F("mpc")]); CJSON(strip.panels, matrix[F("mpc")]);
CJSON(strip.matrix.bottomStart, matrix[F("pb")]);
CJSON(strip.matrix.rightStart, matrix[F("pr")]);
CJSON(strip.matrix.vertical, matrix[F("pv")]);
CJSON(strip.matrix.serpentine, matrix["ps"]);
strip.panel.clear(); strip.panel.clear();
JsonArray panels = matrix[F("panels")]; JsonArray panels = matrix[F("panels")];
uint8_t s = 0; uint8_t s = 0;
@ -130,8 +125,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
p.options = 0; p.options = 0;
strip.panel.push_back(p); strip.panel.push_back(p);
} }
// cannot call strip.setUpMatrix() here due to already locked JSON buffer
strip.setUpMatrix();
} }
#endif #endif
@ -333,12 +327,20 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(strip.paletteBlend, light[F("pal-mode")]); CJSON(strip.paletteBlend, light[F("pal-mode")]);
CJSON(autoSegments, light[F("aseg")]); CJSON(autoSegments, light[F("aseg")]);
CJSON(gammaCorrectVal, light["gc"]["val"]); // default 2.8
float light_gc_bri = light["gc"]["bri"]; float light_gc_bri = light["gc"]["bri"];
float light_gc_col = light["gc"]["col"]; // 2.8 float light_gc_col = light["gc"]["col"];
if (light_gc_bri > 1.5) gammaCorrectBri = true; if (light_gc_bri > 1.0f) gammaCorrectBri = true;
else if (light_gc_bri > 0.5) gammaCorrectBri = false; else gammaCorrectBri = false;
if (light_gc_col > 1.5) gammaCorrectCol = true; if (light_gc_col > 1.0f) gammaCorrectCol = true;
else if (light_gc_col > 0.5) gammaCorrectCol = false; else gammaCorrectCol = false;
if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3) {
if (gammaCorrectVal != 2.8f) calcGammaTable(gammaCorrectVal);
} else {
gammaCorrectVal = 1.0f; // no gamma correction
gammaCorrectBri = false;
gammaCorrectCol = false;
}
JsonObject light_tr = light["tr"]; JsonObject light_tr = light["tr"];
CJSON(fadeTransition, light_tr["mode"]); CJSON(fadeTransition, light_tr["mode"]);
@ -708,11 +710,6 @@ void serializeConfig() {
if (strip.isMatrix) { if (strip.isMatrix) {
JsonObject matrix = hw_led.createNestedObject(F("matrix")); JsonObject matrix = hw_led.createNestedObject(F("matrix"));
matrix[F("mpc")] = strip.panels; matrix[F("mpc")] = strip.panels;
matrix[F("pb")] = strip.matrix.bottomStart;
matrix[F("pr")] = strip.matrix.rightStart;
matrix[F("pv")] = strip.matrix.vertical;
matrix["ps"] = strip.matrix.serpentine;
JsonArray panels = matrix.createNestedArray(F("panels")); JsonArray panels = matrix.createNestedArray(F("panels"));
for (uint8_t i=0; i<strip.panel.size(); i++) { for (uint8_t i=0; i<strip.panel.size(); i++) {
JsonObject pnl = panels.createNestedObject(); JsonObject pnl = panels.createNestedObject();
@ -810,8 +807,9 @@ void serializeConfig() {
light[F("aseg")] = autoSegments; light[F("aseg")] = autoSegments;
JsonObject light_gc = light.createNestedObject("gc"); JsonObject light_gc = light.createNestedObject("gc");
light_gc["bri"] = (gammaCorrectBri) ? 2.8 : 1.0; light_gc["bri"] = (gammaCorrectBri) ? gammaCorrectVal : 1.0f; // keep compatibility
light_gc["col"] = (gammaCorrectCol) ? 2.8 : 1.0; light_gc["col"] = (gammaCorrectCol) ? gammaCorrectVal : 1.0f; // keep compatibility
light_gc["val"] = gammaCorrectVal;
JsonObject light_tr = light.createNestedObject("tr"); JsonObject light_tr = light.createNestedObject("tr");
light_tr["mode"] = fadeTransition; light_tr["mode"] = fadeTransition;

View File

@ -79,6 +79,17 @@
#define WLED_MAX_COLOR_ORDER_MAPPINGS 10 #define WLED_MAX_COLOR_ORDER_MAPPINGS 10
#endif #endif
#if defined(WLED_MAX_LEDMAPS) && (WLED_MAX_LEDMAPS > 32 || WLED_MAX_LEDMAPS < 10)
#undef WLED_MAX_LEDMAPS
#endif
#ifndef WLED_MAX_LEDMAPS
#ifdef ESP8266
#define WLED_MAX_LEDMAPS 10
#else
#define WLED_MAX_LEDMAPS 16
#endif
#endif
//Usermod IDs //Usermod IDs
#define USERMOD_ID_RESERVED 0 //Unused. Might indicate no usermod present #define USERMOD_ID_RESERVED 0 //Unused. Might indicate no usermod present
#define USERMOD_ID_UNSPECIFIED 1 //Default value for a general user mod that does not specify a custom ID #define USERMOD_ID_UNSPECIFIED 1 //Default value for a general user mod that does not specify a custom ID

View File

@ -808,9 +808,9 @@ function populateSegments(s)
if (!isM && !noNewSegs && (cfg.comp.seglen?parseInt(gId(`seg${lSeg}s`).value):0)+parseInt(gId(`seg${lSeg}e`).value)<ledCount) gId(`segr${lSeg}`).style.display = "inline"; if (!isM && !noNewSegs && (cfg.comp.seglen?parseInt(gId(`seg${lSeg}s`).value):0)+parseInt(gId(`seg${lSeg}e`).value)<ledCount) gId(`segr${lSeg}`).style.display = "inline";
gId('segutil2').style.display = (segCount > 1) ? "block":"none"; // rsbtn parent gId('segutil2').style.display = (segCount > 1) ? "block":"none"; // rsbtn parent
if (!isM && Array.isArray(li.maps) && li.maps.length>1) { if (Array.isArray(li.maps) && li.maps.length>1) {
let cont = `Ledmap:&nbsp;<select class="sel-sg" onchange="requestJson({'ledmap':parseInt(this.value)})"><option value="" selected>Unchanged</option>`; let cont = `Ledmap:&nbsp;<select class="sel-sg" onchange="requestJson({'ledmap':parseInt(this.value)})"><option value="" selected>Unchanged</option>`;
for (const k of (li.maps||[])) cont += `<option value="${k}">${k==0?'Default':'ledmap'+k+'.json'}</option>`; for (const k of (li.maps||[])) cont += `<option value="${k.id}">${k.id==0?'Default':(k.n?k.n:'ledmap'+k.id+'.json')}</option>`;
cont += "</select></div>"; cont += "</select></div>";
gId("ledmap").innerHTML = cont; gId("ledmap").innerHTML = cont;
gId("ledmap").classList.remove('hide'); gId("ledmap").classList.remove('hide');
@ -1875,9 +1875,9 @@ ${makePlSel(plJson[i].end?plJson[i].end:0, true)}
<input type="checkbox" id="p${i}sbchk"> <input type="checkbox" id="p${i}sbchk">
<span class="checkmark"></span> <span class="checkmark"></span>
</label>`; </label>`;
if (!isM && Array.isArray(lastinfo.maps) && lastinfo.maps.length>1) { if (Array.isArray(lastinfo.maps) && lastinfo.maps.length>1) {
content += `<div class="lbl-l">Ledmap:&nbsp;<div class="sel-p"><select class="sel-p" id="p${i}lmp"><option value="">Unchanged</option>`; content += `<div class="lbl-l">Ledmap:&nbsp;<div class="sel-p"><select class="sel-p" id="p${i}lmp"><option value="">Unchanged</option>`;
for (const k of (lastinfo.maps||[])) content += `<option value="${k}"${(i>0 && pJson[i].ledmap==k)?" selected":""}>${k==0?'Default':'ledmap'+k+'.json'}</option>`; for (const k of (lastinfo.maps||[])) content += `<option value="${k.id}"${(i>0 && pJson[i].ledmap==k.id)?" selected":""}>${k.id==0?'Default':(k.n?k.n:'ledmap'+k.id+'.json')}</option>`;
content += "</select></div></div>"; content += "</select></div></div>";
} }
} }
@ -2072,7 +2072,7 @@ function setSeg(s)
obj.seg.grp = grp; obj.seg.grp = grp;
obj.seg.spc = spc; obj.seg.spc = spc;
obj.seg.of = ofs; obj.seg.of = ofs;
if (isM) obj.seg.tp = gId(`seg${s}tp`).checked; if (isM && gId(`seg${s}tp`)) obj.seg.tp = gId(`seg${s}tp`).checked;
} }
resetUtil(); // close add segment dialog just in case resetUtil(); // close add segment dialog just in case
requestJson(obj); requestJson(obj);

View File

@ -9,6 +9,7 @@
var d=document; var d=document;
var loc = false, locip; var loc = false, locip;
var maxPanels=64; var maxPanels=64;
var ctx = null; // WLEDMM
function H(){window.open("https://kno.wled.ge/features/2D");} function H(){window.open("https://kno.wled.ge/features/2D");}
function B(){window.open("/settings","_self");} function B(){window.open("/settings","_self");}
function gId(n){return d.getElementById(n);} function gId(n){return d.getElementById(n);}
@ -52,6 +53,30 @@
return; return;
} }
gId("mpdiv").style.display = "block"; gId("mpdiv").style.display = "block";
draw();
}
var timeout;
function showToast(text, error = false)
{
var x = gId("toast");
x.innerHTML = text;
x.className = error ? "error":"show";
clearTimeout(timeout);
x.style.animation = 'none';
timeout = setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2900);
}
function uploadFile(name) {
var req = new XMLHttpRequest();
req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400)});
req.addEventListener('error', function(e){showToast(e.stack,true);});
req.open("POST", "/upload");
var formData = new FormData();
formData.append("data", d.Sf.data.files[0], name);
req.send(formData);
d.Sf.data.value = '';
return false;
} }
function addPanels() { function addPanels() {
@ -67,21 +92,21 @@
var pw = parseInt(d.Sf.PW.value); var pw = parseInt(d.Sf.PW.value);
var ph = parseInt(d.Sf.PH.value); var ph = parseInt(d.Sf.PH.value);
let b = `<div id="pnl${i}"><hr class="sml">Panel ${i}<br> let b = `<div id="pnl${i}"><hr class="sml">Panel ${i}<br>
1<sup>st</sup> LED: <select name="P${i}B"> 1<sup>st</sup> LED: <select name="P${i}B" oninput="UI()">
<option value="0">Top</option> <option value="0">Top</option>
<option value="1">Bottom</option> <option value="1">Bottom</option>
</select><select name="P${i}R"> </select><select name="P${i}R" oninput="UI()">
<option value="0">Left</option> <option value="0">Left</option>
<option value="1">Right</option> <option value="1">Right</option>
</select><br> </select><br>
Orientation: <select name="P${i}V"> Orientation: <select name="P${i}V" oninput="UI()">
<option value="0">Horizontal</option> <option value="0">Horizontal</option>
<option value="1">Vertical</option> <option value="1">Vertical</option>
</select><br> </select><br>
Serpentine: <input type="checkbox" name="P${i}S"><br> Serpentine: <input type="checkbox" name="P${i}S" oninput="UI()"><br>
Dimensions (WxH): <input id="P${i}W" name="P${i}W" type="number" min="1" max="128" value="${pw}"> x <input id="P${i}H" name="P${i}H" type="number" min="1" max="128" value="${ph}"><br> Dimensions (WxH): <input name="P${i}W" type="number" min="1" max="255" value="${pw}" oninput="UI()"> x <input name="P${i}H" type="number" min="1" max="255" value="${ph}" oninput="UI()"><br>
Offset X:<input id="P${i}X" name="P${i}X" type="number" min="0" max="256" value="0"> Offset X:<input name="P${i}X" type="number" min="0" max="255" value="0" oninput="UI()">
Y:<input id="P${i}Y" name="P${i}Y" type="number" min="0" max="256" value="0"><br><i>(offset from top-left corner in # LEDs)</i> Y:<input name="P${i}Y" type="number" min="0" max="255" value="0" oninput="UI()"><br><i>(offset from top-left corner in # LEDs)</i>
</div>`; </div>`;
p.insertAdjacentHTML("beforeend", b); p.insertAdjacentHTML("beforeend", b);
} }
@ -107,17 +132,17 @@ Y:<input id="P${i}Y" name="P${i}Y" type="number" min="0" max="256" value="0"><br
function gen() { function gen() {
resetPanels(); resetPanels();
var pansH = parseInt(d.Sf.MPH.value); var pansH = parseInt(Sf.MPH.value);
var pansV = parseInt(d.Sf.MPV.value); var pansV = parseInt(Sf.MPV.value);
var c = pansH*pansV; var c = pansH*pansV;
d.Sf.MPC.value = c; // number of panels Sf.MPC.value = c; // number of panels
var ps = d.Sf.PS.checked; var ps = Sf.PS.checked;
var pv = d.Sf.PV.value==="1"; var pv = Sf.PV.value==="1";
var pb = d.Sf.PB.value==="1"; var pb = Sf.PB.value==="1";
var pr = d.Sf.PR.value==="1"; var pr = Sf.PR.value==="1";
var pw = parseInt(d.Sf.PW.value); var pw = parseInt(Sf.PW.value);
var ph = parseInt(d.Sf.PH.value); var ph = parseInt(Sf.PH.value);
var h = pv ? pansV : pansH; var h = pv ? pansV : pansH;
var v = pv ? pansH : pansV; var v = pv ? pansH : pansV;
@ -127,13 +152,141 @@ Y:<input id="P${i}Y" name="P${i}Y" type="number" min="0" max="256" value="0"><br
var y = (pv?pr:pb) ? v-j-1: j; var y = (pv?pr:pb) ? v-j-1: j;
var x = (pv?pb:pr) ? h-i-1 : i; var x = (pv?pb:pr) ? h-i-1 : i;
x = ps && j%2 ? h-x-1 : x; x = ps && j%2 ? h-x-1 : x;
gId("P"+p+"X").value = (pv?y:x) * pw; Sf[`P${p}X`].value = (pv?y:x) * pw;
gId("P"+p+"Y").value = (pv?x:y) * ph Sf[`P${p}Y`].value = (pv?x:y) * ph
gId("P"+p+"W").value = pw; Sf[`P${p}W`].value = pw;
gId("P"+p+"H").value = ph; Sf[`P${p}H`].value = ph;
} }
} }
} }
function expand(o,i)
{
i.style.display = i.style.display!=="none" ? "none" : "";
o.style.rotate = i.style.display==="none" ? "none" : "90deg";
}
function draw() {
if (!ctx) {
//WLEDMM: add canvas, initialize and set UI
var canvas = gId("canvas");
canvas.width = window.innerWidth > 640?640:400; //Mobile gets 400, pc 640
canvas.height = canvas.width;
ctx = canvas.getContext('2d');
// window.requestAnimationFrame(animate);
}
//calc max height and width
var maxWidth = 0;
var maxHeight = 0;
for (let p=0; p<gId("panels").children.length; p++) {
var px = parseInt(Sf[`P${p}X`].value); //first led x
var py = parseInt(Sf[`P${p}Y`].value); //first led y
var pw = parseInt(Sf[`P${p}W`].value); //width
var ph = parseInt(Sf[`P${p}H`].value); //height
maxWidth = Math.max(maxWidth, px + pw);
maxHeight = Math.max(maxHeight, py + ph);
}
ctx.canvas.height = ctx.canvas.width / maxWidth * maxHeight;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
var space=0; // space between panels + margin
var ppL = (ctx.canvas.width - space * 2) / maxWidth; //pixels per led
ctx.lineWidth = 1;
ctx.strokeStyle="yellow";
ctx.strokeRect(0, 0, ctx.canvas.width, ctx.canvas.height); // add space between panels
for (let p=0; p<gId("panels").children.length; p++) {
var px = parseInt(Sf[`P${p}X`].value); //first led x
var py = parseInt(Sf[`P${p}Y`].value); //first led y
var pw = parseInt(Sf[`P${p}W`].value); //width
var ph = parseInt(Sf[`P${p}H`].value); //height
var pb = Sf[`P${p}B`].value == "1"; //bottom
var pr = Sf[`P${p}R`].value == "1"; //right
var pv = Sf[`P${p}V`].value == "1"; //vertical
var ps = Sf[`P${p}S`].checked; //serpentine
var topLeftX = px*ppL + space; //left margin
var topLeftY = py*ppL + space; //top margin
ctx.lineWidth = 3;
ctx.strokeStyle="white";
ctx.strokeRect(topLeftX, topLeftY, pw*ppL, ph*ppL); // add space between panels
var lnX;
var lnY;
//find start led
if (pb) //bottom
lnY = topLeftY + ph*ppL - ppL/2;
else //top
lnY = topLeftY + ppL/2;
if (pr) //right
lnX = topLeftX + pw*ppL - ppL/2;
else //left
lnX = topLeftX + ppL/2;
//first led
ctx.fillStyle = "green";
ctx.beginPath();
ctx.arc(lnX, lnY, ppL*0.5, 0, 2 * Math.PI);
ctx.fill();
//start line
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(lnX, lnY);
var longLineLength = (pv?ph:pw)*ppL - ppL;
for (let ln=0; ln<(pv?pw:ph); ln++) { //loop over panelwidth (or height of vertical?)
var serpLine = ps && ln%2!=0; //serp: turn around if even line
if (pv) //if vertical
lnY += (pb?-1:1) * longLineLength * (serpLine?-1:1); //if vertical change the Y
else
lnX += (pr?-1:1) * longLineLength * (serpLine?-1:1); //if horizontal change the X
ctx.lineTo(lnX, lnY); //draw the long line
if (ln<(pv?pw:ph)-1) { //not the last
//find the small line end point
if (pv) //vertical
lnX += (pr?-1:1) * ppL;
else //horizontal
lnY += (pb?-1:1) * ppL;
//if serpentine go next else go down
if (ps) { //serpentine
ctx.lineTo(lnX, lnY); //draw the serpentine line
} else {
//find the other end of the long line
if (pv) //vertical
lnY += (pb?1:-1) * longLineLength * (serpLine?-1:1); //min as we go back
else //horizontal
lnX += (pr?1:-1) * longLineLength * (serpLine?-1:1);
ctx.moveTo(lnX, lnY); //move to the start point of the next long line
}
}
}
ctx.stroke();
//last led
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(lnX, lnY, ppL*0.5, 0, 2 * Math.PI);
ctx.fill();
ctx.font = '40px Arial';
ctx.fillStyle = "orange";
ctx.fillText(p, topLeftX + pw/2*ppL - 10, topLeftY + ph/2*ppL + 10);
}
gId("MD").innerHTML = "Matrix Dimensions (W*H=LC): " + maxWidth + " x " + maxHeight + " = " + maxWidth * maxHeight;
}
</script> </script>
<style>@import url("style.css");</style> <style>@import url("style.css");</style>
</head> </head>
@ -151,36 +304,45 @@ Y:<input id="P${i}Y" name="P${i}Y" type="number" min="0" max="256" value="0"><br
</select><br> </select><br>
<div id="mpdiv" style="display:none;"> <div id="mpdiv" style="display:none;">
<hr class="sml"> <hr class="sml">
<h3>Matrix Generator</h3> <h3>Matrix Generator <button type="button" id="expGen" onclick="expand(this,gId('mxGen'));">&gt;</button></h3>
Panel dimensions (WxH): <input name="PW" type="number" min="1" max="128" value="8"> x <input name="PH" type="number" min="1" max="128" value="8"><br> <div id="mxGen" style="display:none;">
Horizontal panels: <input name="MPH" type="number" min="1" max="8" value="1"> Panel dimensions (WxH): <input name="PW" type="number" min="1" max="128" value="8"> x <input name="PH" type="number" min="1" max="128" value="8"><br>
Vertical panels: <input name="MPV" type="number" min="1" max="8" value="1"><br> Horizontal panels: <input name="MPH" type="number" min="1" max="8" value="1">
1<sup>st</sup> panel: <select name="PB"> Vertical panels: <input name="MPV" type="number" min="1" max="8" value="1"><br>
<option value="0">Top</option> 1<sup>st</sup> panel: <select name="PB">
<option value="1">Bottom</option> <option value="0">Top</option>
</select><select name="PR"> <option value="1">Bottom</option>
<option value="0">Left</option> </select><select name="PR">
<option value="1">Right</option> <option value="0">Left</option>
</select><br> <option value="1">Right</option>
Orientation: <select name="PV"> </select><br>
<option value="0">Horizontal</option> Orientation: <select name="PV">
<option value="1">Vertical</option> <option value="0">Horizontal</option>
</select><br> <option value="1">Vertical</option>
Serpentine: <input type="checkbox" name="PS"><br> </select><br>
<i style="color:#fa0;">Can populate LED panel layout with pre-arranged matrix.<br>These values do not affect final layout.</i><br> Serpentine: <input type="checkbox" name="PS"><br>
<button type="button" onclick="gen()">Populate</button> <i style="color:#fa0;">Pressing Populate will create LED panel layout with pre-arranged matrix.<br>Values above <i>will not</i> affect final layout.<br>
WARNING: You may need to update each panel parameters after they are generated.</i><br>
<button type="button" onclick="gen();expand(gId('expGen'),gId('mxGen'));">Populate</button>
</div>
<hr class="sml"> <hr class="sml">
<h3>Panel set-up</h3> <h3>Panel set-up</h3>
Number of panels: <input name="MPC" type="number" min="1" max="64" value="1" oninput="addPanels()"><br> Number of panels: <input name="MPC" type="number" min="1" max="64" value="1" oninput="addPanels();UI();"><br>
<i>A matrix is made of 1 or more physical LED panels.<br> <i>A matrix is made of 1 or more physical LED panels.<br>
<!--Panels should be arranged from top-left to bottom-right order, starting with lower panel number on the left (or top if transposed).<br>-->
Each panel can be of different size and/or have different LED orientation and/or starting point and/or layout.</i><br> Each panel can be of different size and/or have different LED orientation and/or starting point and/or layout.</i><br>
<h3>LED panel layout</h3> <h3>LED panel layout</h3>
<div id="panels"> <div id="panels">
</div> </div>
<hr class="sml">
<div id="MD"></div>
<canvas id="canvas"></canvas>
<div id="json" >Gap file: <input type="file" name="data" accept=".json"><button type="button" class="sml" onclick="uploadFile('/2d-gaps.json')">Upload</button></div>
<i>Note: Gap file is a <b>.json</b> file containing an array with number of elements equal to the matrix size.<br>
A value of -1 means that pixel at that position is missing, a value of 0 means never paint that pixel, and 1 means regular pixel.</i>
</div> </div>
<hr> <hr>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button> <button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form> </form>
<div id="toast"></div>
</body> </body>
</html> </html>

View File

@ -642,7 +642,8 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
Apply preset <input name="BP" type="number" class="m" min="0" max="250" required> at boot (0 uses defaults) Apply preset <input name="BP" type="number" class="m" min="0" max="250" required> at boot (0 uses defaults)
<br><br> <br><br>
Use Gamma correction for color: <input type="checkbox" name="GC"> (strongly recommended)<br> Use Gamma correction for color: <input type="checkbox" name="GC"> (strongly recommended)<br>
Use Gamma correction for brightness: <input type="checkbox" name="GB"> (not recommended)<br><br> Use Gamma correction for brightness: <input type="checkbox" name="GB"> (not recommended)<br>
Use Gamma value: <input name="GV" type="number" class="m" placeholder="2.8" min="1" max="3" step="0.1" required><br><br>
Brightness factor: <input name="BF" type="number" class="m" min="1" max="255" required> % Brightness factor: <input name="BF" type="number" class="m" min="1" max="255" required> %
<h3>Transitions</h3> <h3>Transitions</h3>
Crossfade: <input type="checkbox" name="TF"><br> Crossfade: <input type="checkbox" name="TF"><br>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -643,8 +643,14 @@ void serializeInfo(JsonObject root)
root[F("cpalcount")] = strip.customPalettes.size(); //number of custom palettes root[F("cpalcount")] = strip.customPalettes.size(); //number of custom palettes
JsonArray ledmaps = root.createNestedArray(F("maps")); JsonArray ledmaps = root.createNestedArray(F("maps"));
for (size_t i=0; i<10; i++) { for (size_t i=0; i<WLED_MAX_LEDMAPS; i++) {
if ((ledMaps>>i) & 0x0001) ledmaps.add(i); if ((ledMaps>>i) & 0x00000001U) {
JsonObject ledmaps0 = ledmaps.createNestedObject();
ledmaps0["id"] = i;
#ifndef ESP8266
if (i && ledmapNames[i-1]) ledmaps0["n"] = ledmapNames[i-1];
#endif
}
} }
JsonObject wifi_info = root.createNestedObject("wifi"); JsonObject wifi_info = root.createNestedObject("wifi");

View File

@ -208,6 +208,14 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
if (t <= 250) bootPreset = t; if (t <= 250) bootPreset = t;
gammaCorrectBri = request->hasArg(F("GB")); gammaCorrectBri = request->hasArg(F("GB"));
gammaCorrectCol = request->hasArg(F("GC")); gammaCorrectCol = request->hasArg(F("GC"));
gammaCorrectVal = request->arg(F("GV")).toFloat();
if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3)
calcGammaTable(gammaCorrectVal);
else {
gammaCorrectVal = 1.0f; // no gamma correction
gammaCorrectBri = false;
gammaCorrectCol = false;
}
fadeTransition = request->hasArg(F("TF")); fadeTransition = request->hasArg(F("TF"));
t = request->arg(F("TD")).toInt(); t = request->arg(F("TD")).toInt();
@ -240,6 +248,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
} }
simplifiedUI = request->hasArg(F("SU")); simplifiedUI = request->hasArg(F("SU"));
#endif #endif
DEBUG_PRINTLN(F("Enumerating ledmaps"));
enumerateLedmaps();
DEBUG_PRINTLN(F("Loading custom palettes"));
strip.loadCustomPalettes(); // (re)load all custom palettes
} }
//SYNC //SYNC
@ -409,15 +421,15 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
k[0] = 'W'; //weekdays k[0] = 'W'; //weekdays
timerWeekday[i] = request->arg(k).toInt(); timerWeekday[i] = request->arg(k).toInt();
if (i<8) { if (i<8) {
k[0] = 'M'; //start month k[0] = 'M'; //start month
timerMonth[i] = request->arg(k).toInt() & 0x0F; timerMonth[i] = request->arg(k).toInt() & 0x0F;
timerMonth[i] <<= 4; timerMonth[i] <<= 4;
k[0] = 'P'; //end month k[0] = 'P'; //end month
timerMonth[i] += (request->arg(k).toInt() & 0x0F); timerMonth[i] += (request->arg(k).toInt() & 0x0F);
k[0] = 'D'; //start day k[0] = 'D'; //start day
timerDay[i] = request->arg(k).toInt(); timerDay[i] = request->arg(k).toInt();
k[0] = 'E'; //end day k[0] = 'E'; //end day
timerDayEnd[i] = request->arg(k).toInt(); timerDayEnd[i] = request->arg(k).toInt();
} }
} }
} }
@ -645,15 +657,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
if (subPage == 10) if (subPage == 10)
{ {
strip.isMatrix = request->arg(F("SOMP")).toInt(); strip.isMatrix = request->arg(F("SOMP")).toInt();
// strip.panelH = MAX(1,MIN(128,request->arg(F("PH")).toInt()));
// strip.panelW = MAX(1,MIN(128,request->arg(F("PW")).toInt()));
strip.panel.clear(); // release memory if allocated strip.panel.clear(); // release memory if allocated
if (strip.isMatrix) { if (strip.isMatrix) {
strip.panels = MAX(1,MIN(WLED_MAX_PANELS,request->arg(F("MPC")).toInt())); strip.panels = MAX(1,MIN(WLED_MAX_PANELS,request->arg(F("MPC")).toInt()));
strip.matrix.bottomStart = request->arg(F("PB")).toInt();
strip.matrix.rightStart = request->arg(F("PR")).toInt();
strip.matrix.vertical = request->arg(F("PV")).toInt();
strip.matrix.serpentine = request->hasArg(F("PS"));
strip.panel.reserve(strip.panels); // pre-allocate memory strip.panel.reserve(strip.panels); // pre-allocate memory
for (uint8_t i=0; i<strip.panels; i++) { for (uint8_t i=0; i<strip.panels; i++) {
WS2812FX::Panel p; WS2812FX::Panel p;
@ -673,11 +679,13 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
pO[l] = 'H'; p.height = request->arg(pO).toInt(); pO[l] = 'H'; p.height = request->arg(pO).toInt();
strip.panel.push_back(p); strip.panel.push_back(p);
} }
strip.setUpMatrix(); // will check limits
strip.makeAutoSegments(true);
strip.deserializeMap();
} else { } else {
Segment::maxWidth = strip.getLengthTotal(); Segment::maxWidth = strip.getLengthTotal();
Segment::maxHeight = 1; Segment::maxHeight = 1;
} }
strip.setUpMatrix(); // will check limits
} }
#endif #endif
@ -800,7 +808,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
//apply preset //apply preset
if (updateVal(req.c_str(), "PL=", &presetCycCurr, presetCycMin, presetCycMax)) { if (updateVal(req.c_str(), "PL=", &presetCycCurr, presetCycMin, presetCycMax)) {
unloadPlaylist(); unloadPlaylist();
applyPreset(presetCycCurr); applyPreset(presetCycCurr);
} }

View File

@ -503,12 +503,49 @@ um_data_t* simulateSound(uint8_t simulationId)
} }
// enumerate all ledmapX.json files on FS and extract ledmap names if existing
void enumerateLedmaps() { void enumerateLedmaps() {
ledMaps = 1; ledMaps = 1;
for (size_t i=1; i<10; i++) { for (size_t i=1; i<WLED_MAX_LEDMAPS; i++) {
char fileName[16]; char fileName[33];
sprintf_P(fileName, PSTR("/ledmap%d.json"), i); sprintf_P(fileName, PSTR("/ledmap%d.json"), i);
bool isFile = WLED_FS.exists(fileName); bool isFile = WLED_FS.exists(fileName);
if (isFile) ledMaps |= 1 << i;
#ifndef ESP8266
if (ledmapNames[i-1]) { //clear old name
delete[] ledmapNames[i-1];
ledmapNames[i-1] = nullptr;
}
#endif
if (isFile) {
ledMaps |= 1 << i;
#ifndef ESP8266
if (requestJSONBufferLock(21)) {
if (readObjectFromFile(fileName, nullptr, &doc)) {
size_t len = 0;
if (!doc["n"].isNull()) {
// name field exists
const char *name = doc["n"].as<const char*>();
if (name != nullptr) len = strlen(name);
if (len > 0 && len < 33) {
ledmapNames[i-1] = new char[len+1];
if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], name, 33);
}
}
if (!ledmapNames[i-1]) {
char tmp[33];
snprintf_P(tmp, 32, PSTR("ledmap%d.json"), i);
len = strlen(tmp);
ledmapNames[i-1] = new char[len+1];
if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], tmp, 33);
}
}
releaseJSONBufferLock();
}
#endif
}
} }
} }

View File

@ -171,15 +171,14 @@ void WLED::loop()
} }
delete busConfigs[i]; busConfigs[i] = nullptr; delete busConfigs[i]; busConfigs[i] = nullptr;
} }
strip.finalizeInit(); strip.finalizeInit(); // also loads default ledmap if present
loadLedmap = 0;
if (aligned) strip.makeAutoSegments(); if (aligned) strip.makeAutoSegments();
else strip.fixInvalidSegments(); else strip.fixInvalidSegments();
yield(); yield();
serializeConfig(); serializeConfig();
} }
if (loadLedmap >= 0) { if (loadLedmap >= 0) {
strip.deserializeMap(loadLedmap); if (!strip.deserializeMap(loadLedmap) && strip.isMatrix && loadLedmap == 0) strip.setUpMatrix();
loadLedmap = -1; loadLedmap = -1;
} }

View File

@ -8,7 +8,7 @@
*/ */
// version code in format yymmddb (b = daily build) // version code in format yymmddb (b = daily build)
#define VERSION 2302120 #define VERSION 2302150
//uncomment this if you have a "my_config.h" file you'd like to use //uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG //#define WLED_USE_MY_CONFIG
@ -319,6 +319,7 @@ WLED_GLOBAL bool correctWB _INIT(false); // CCT color correction of RGB co
WLED_GLOBAL bool cctFromRgb _INIT(false); // CCT is calculated from RGB instead of using seg.cct WLED_GLOBAL bool cctFromRgb _INIT(false); // CCT is calculated from RGB instead of using seg.cct
WLED_GLOBAL bool gammaCorrectCol _INIT(true ); // use gamma correction on colors WLED_GLOBAL bool gammaCorrectCol _INIT(true ); // use gamma correction on colors
WLED_GLOBAL bool gammaCorrectBri _INIT(false); // use gamma correction on brightness WLED_GLOBAL bool gammaCorrectBri _INIT(false); // use gamma correction on brightness
WLED_GLOBAL float gammaCorrectVal _INIT(2.8f); // gamma correction value
WLED_GLOBAL byte col[] _INIT_N(({ 255, 160, 0, 0 })); // current RGB(W) primary color. col[] should be updated if you want to change the color. WLED_GLOBAL byte col[] _INIT_N(({ 255, 160, 0, 0 })); // current RGB(W) primary color. col[] should be updated if you want to change the color.
WLED_GLOBAL byte colSec[] _INIT_N(({ 0, 0, 0, 0 })); // current RGB(W) secondary color WLED_GLOBAL byte colSec[] _INIT_N(({ 0, 0, 0, 0 })); // current RGB(W) secondary color
@ -677,7 +678,14 @@ WLED_GLOBAL WS2812FX strip _INIT(WS2812FX());
WLED_GLOBAL BusConfig* busConfigs[WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES] _INIT({nullptr}); //temporary, to remember values from network callback until after WLED_GLOBAL BusConfig* busConfigs[WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES] _INIT({nullptr}); //temporary, to remember values from network callback until after
WLED_GLOBAL bool doInitBusses _INIT(false); WLED_GLOBAL bool doInitBusses _INIT(false);
WLED_GLOBAL int8_t loadLedmap _INIT(-1); WLED_GLOBAL int8_t loadLedmap _INIT(-1);
#ifndef ESP8266
WLED_GLOBAL char *ledmapNames[WLED_MAX_LEDMAPS-1] _INIT_N(({nullptr}));
#endif
#if WLED_MAX_LEDMAPS>16
WLED_GLOBAL uint32_t ledMaps _INIT(0); // bitfield representation of available ledmaps
#else
WLED_GLOBAL uint16_t ledMaps _INIT(0); // bitfield representation of available ledmaps WLED_GLOBAL uint16_t ledMaps _INIT(0); // bitfield representation of available ledmaps
#endif
// Usermod manager // Usermod manager
WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager()); WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager());

View File

@ -364,7 +364,7 @@ void getSettingsJS(byte subPage, char* dest)
if (subPage == 2) if (subPage == 2)
{ {
char nS[8]; char nS[32];
appendGPIOinfo(); appendGPIOinfo();
@ -445,6 +445,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('c',SET_F("GB"),gammaCorrectBri); sappend('c',SET_F("GB"),gammaCorrectBri);
sappend('c',SET_F("GC"),gammaCorrectCol); sappend('c',SET_F("GC"),gammaCorrectCol);
dtostrf(gammaCorrectVal,3,1,nS); sappends('s',SET_F("GV"),nS);
sappend('c',SET_F("TF"),fadeTransition); sappend('c',SET_F("TF"),fadeTransition);
sappend('v',SET_F("TD"),transitionDelayDefault); sappend('v',SET_F("TD"),transitionDelayDefault);
sappend('c',SET_F("PF"),strip.paletteFade); sappend('c',SET_F("PF"),strip.paletteFade);
@ -738,10 +739,6 @@ void getSettingsJS(byte subPage, char* dest)
sappend('v',SET_F("PH"),strip.panel[0].height); sappend('v',SET_F("PH"),strip.panel[0].height);
} }
sappend('v',SET_F("MPC"),strip.panels); sappend('v',SET_F("MPC"),strip.panels);
sappend('v',SET_F("PB"),strip.matrix.bottomStart);
sappend('v',SET_F("PR"),strip.matrix.rightStart);
sappend('v',SET_F("PV"),strip.matrix.vertical);
sappend('c',SET_F("PS"),strip.matrix.serpentine);
// panels // panels
for (uint8_t i=0; i<strip.panels; i++) { for (uint8_t i=0; i<strip.panels; i++) {
char n[5]; char n[5];