Merge pull request #3088 from Aircoookie/led-gaps

LED matrix gaps
pull/3102/head^2
Christian Schwinne 2023-02-21 16:46:58 +01:00 zatwierdzone przez GitHub
commit 9249c74b7b
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
20 zmienionych plików z 3944 dodań i 3572 usunięć

Wyświetl plik

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

Wyświetl plik

@ -460,7 +460,7 @@ typedef struct Segment {
_dataLen(0),
_t(nullptr)
{
refreshLightCapabilities();
//refreshLightCapabilities();
}
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),
#ifndef WLED_DISABLE_2D
panels(1),
matrix{0,0,0,0},
#endif
// semi-private (just obscured) used in effect functions through macros
_currentPalette(CRGBPalette16(CRGB::Black)),
@ -727,8 +726,7 @@ class WS2812FX { // 96 bytes
fixInvalidSegments(),
setPixelColor(int n, uint32_t c),
show(void),
setTargetFps(uint8_t fps),
deserializeMap(uint8_t n=0);
setTargetFps(uint8_t fps);
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
@ -748,6 +746,7 @@ class WS2812FX { // 96 bytes
hasCCTBus(void),
// return true if the strip is being sent pixel updates
isUpdating(void),
deserializeMap(uint8_t n=0),
useLedsArray = false;
inline bool isServicing(void) { return _isServicing; }
@ -813,13 +812,6 @@ class WS2812FX { // 96 bytes
uint8_t
panels;
struct {
bool bottomStart : 1;
bool rightStart : 1;
bool vertical : 1;
bool serpentine : 1;
} matrix;
typedef struct panel_t {
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

Wyświetl plik

@ -78,21 +78,60 @@ void WS2812FX::setUpMatrix() {
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
for (size_t pan = 0; pan < panel.size(); pan++) {
Panel &p = panel[pan];
uint16_t h = p.vertical ? p.height : p.width;
uint16_t v = p.vertical ? p.width : p.height;
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;
x = (p.vertical?p.bottomStart:p.rightStart) ? h-i-1 : i;
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
DEBUG_PRINT(F("Matrix ledmap:"));
for (uint16_t i=0; i<customMappingSize; i++) {
@ -108,8 +147,8 @@ void WS2812FX::setUpMatrix() {
panel.clear();
Segment::maxWidth = _length;
Segment::maxHeight = 1;
resetSegments();
}
resetSegments();
}
#else
isMatrix = false; // no matter what config says

Wyświetl plik

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

Wyświetl plik

@ -140,16 +140,16 @@ class Bus {
static void setCCT(uint16_t cct) {
_cct = cct;
}
static void setCCTBlend(uint8_t b) {
if (b > 100) b = 100;
_cctBlend = (b * 127) / 100;
//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;
#endif
}
inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; }
inline uint8_t getAutoWhiteMode() { return _autoWhiteMode; }
static void setCCTBlend(uint8_t b) {
if (b > 100) b = 100;
_cctBlend = (b * 127) / 100;
//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;
#endif
}
inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; }
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 uint8_t getGlobalAWMode() { return _gAWM; }

Wyświetl plik

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

Wyświetl plik

@ -79,6 +79,17 @@
#define WLED_MAX_COLOR_ORDER_MAPPINGS 10
#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
#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

Wyświetl plik

@ -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";
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>`;
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>";
gId("ledmap").innerHTML = cont;
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">
<span class="checkmark"></span>
</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>`;
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>";
}
}
@ -2072,7 +2072,7 @@ function setSeg(s)
obj.seg.grp = grp;
obj.seg.spc = spc;
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
requestJson(obj);

Wyświetl plik

@ -9,6 +9,7 @@
var d=document;
var loc = false, locip;
var maxPanels=64;
var ctx = null; // WLEDMM
function H(){window.open("https://kno.wled.ge/features/2D");}
function B(){window.open("/settings","_self");}
function gId(n){return d.getElementById(n);}
@ -52,6 +53,30 @@
return;
}
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() {
@ -67,21 +92,21 @@
var pw = parseInt(d.Sf.PW.value);
var ph = parseInt(d.Sf.PH.value);
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="1">Bottom</option>
</select><select name="P${i}R">
</select><select name="P${i}R" oninput="UI()">
<option value="0">Left</option>
<option value="1">Right</option>
</select><br>
Orientation: <select name="P${i}V">
Orientation: <select name="P${i}V" oninput="UI()">
<option value="0">Horizontal</option>
<option value="1">Vertical</option>
</select><br>
Serpentine: <input type="checkbox" name="P${i}S"><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>
Offset X:<input id="P${i}X" name="P${i}X" type="number" min="0" max="256" value="0">
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>
Serpentine: <input type="checkbox" name="P${i}S" oninput="UI()"><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 name="P${i}X" type="number" min="0" max="255" value="0" oninput="UI()">
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>`;
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() {
resetPanels();
var pansH = parseInt(d.Sf.MPH.value);
var pansV = parseInt(d.Sf.MPV.value);
var pansH = parseInt(Sf.MPH.value);
var pansV = parseInt(Sf.MPV.value);
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 pv = d.Sf.PV.value==="1";
var pb = d.Sf.PB.value==="1";
var pr = d.Sf.PR.value==="1";
var pw = parseInt(d.Sf.PW.value);
var ph = parseInt(d.Sf.PH.value);
var ps = Sf.PS.checked;
var pv = Sf.PV.value==="1";
var pb = Sf.PB.value==="1";
var pr = Sf.PR.value==="1";
var pw = parseInt(Sf.PW.value);
var ph = parseInt(Sf.PH.value);
var h = pv ? pansV : pansH;
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 x = (pv?pb:pr) ? h-i-1 : i;
x = ps && j%2 ? h-x-1 : x;
gId("P"+p+"X").value = (pv?y:x) * pw;
gId("P"+p+"Y").value = (pv?x:y) * ph
gId("P"+p+"W").value = pw;
gId("P"+p+"H").value = ph;
Sf[`P${p}X`].value = (pv?y:x) * pw;
Sf[`P${p}Y`].value = (pv?x:y) * ph
Sf[`P${p}W`].value = pw;
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>
<style>@import url("style.css");</style>
</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>
<div id="mpdiv" style="display:none;">
<hr class="sml">
<h3>Matrix Generator</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>
Horizontal panels: <input name="MPH" type="number" min="1" max="8" value="1">
Vertical panels: <input name="MPV" type="number" min="1" max="8" value="1"><br>
1<sup>st</sup> panel: <select name="PB">
<option value="0">Top</option>
<option value="1">Bottom</option>
</select><select name="PR">
<option value="0">Left</option>
<option value="1">Right</option>
</select><br>
Orientation: <select name="PV">
<option value="0">Horizontal</option>
<option value="1">Vertical</option>
</select><br>
Serpentine: <input type="checkbox" name="PS"><br>
<i style="color:#fa0;">Can populate LED panel layout with pre-arranged matrix.<br>These values do not affect final layout.</i><br>
<button type="button" onclick="gen()">Populate</button>
<h3>Matrix Generator <button type="button" id="expGen" onclick="expand(this,gId('mxGen'));">&gt;</button></h3>
<div id="mxGen" style="display:none;">
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>
Horizontal panels: <input name="MPH" type="number" min="1" max="8" value="1">
Vertical panels: <input name="MPV" type="number" min="1" max="8" value="1"><br>
1<sup>st</sup> panel: <select name="PB">
<option value="0">Top</option>
<option value="1">Bottom</option>
</select><select name="PR">
<option value="0">Left</option>
<option value="1">Right</option>
</select><br>
Orientation: <select name="PV">
<option value="0">Horizontal</option>
<option value="1">Vertical</option>
</select><br>
Serpentine: <input type="checkbox" name="PS"><br>
<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">
<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>
<!--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>
<h3>LED panel layout</h3>
<div id="panels">
</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>
<hr>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form>
<div id="toast"></div>
</body>
</html>

Wyświetl plik

@ -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)
<br><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> %
<h3>Transitions</h3>
Crossfade: <input type="checkbox" name="TF"><br>

Plik diff jest za duży Load Diff

Plik diff jest za duży Load Diff

Plik diff jest za duży Load Diff

Plik diff jest za duży Load Diff

Wyświetl plik

@ -643,8 +643,14 @@ void serializeInfo(JsonObject root)
root[F("cpalcount")] = strip.customPalettes.size(); //number of custom palettes
JsonArray ledmaps = root.createNestedArray(F("maps"));
for (size_t i=0; i<10; i++) {
if ((ledMaps>>i) & 0x0001) ledmaps.add(i);
for (size_t i=0; i<WLED_MAX_LEDMAPS; 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");

Wyświetl plik

@ -208,6 +208,14 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
if (t <= 250) bootPreset = t;
gammaCorrectBri = request->hasArg(F("GB"));
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"));
t = request->arg(F("TD")).toInt();
@ -240,6 +248,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
}
simplifiedUI = request->hasArg(F("SU"));
#endif
DEBUG_PRINTLN(F("Enumerating ledmaps"));
enumerateLedmaps();
DEBUG_PRINTLN(F("Loading custom palettes"));
strip.loadCustomPalettes(); // (re)load all custom palettes
}
//SYNC
@ -409,15 +421,15 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
k[0] = 'W'; //weekdays
timerWeekday[i] = request->arg(k).toInt();
if (i<8) {
k[0] = 'M'; //start month
timerMonth[i] = request->arg(k).toInt() & 0x0F;
timerMonth[i] <<= 4;
k[0] = 'P'; //end month
timerMonth[i] += (request->arg(k).toInt() & 0x0F);
k[0] = 'D'; //start day
timerDay[i] = request->arg(k).toInt();
k[0] = 'E'; //end day
timerDayEnd[i] = request->arg(k).toInt();
k[0] = 'M'; //start month
timerMonth[i] = request->arg(k).toInt() & 0x0F;
timerMonth[i] <<= 4;
k[0] = 'P'; //end month
timerMonth[i] += (request->arg(k).toInt() & 0x0F);
k[0] = 'D'; //start day
timerDay[i] = request->arg(k).toInt();
k[0] = 'E'; //end day
timerDayEnd[i] = request->arg(k).toInt();
}
}
}
@ -645,15 +657,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
if (subPage == 10)
{
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
if (strip.isMatrix) {
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
for (uint8_t i=0; i<strip.panels; i++) {
WS2812FX::Panel p;
@ -673,11 +679,13 @@ 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.setUpMatrix(); // will check limits
}
#endif
@ -800,7 +808,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
//apply preset
if (updateVal(req.c_str(), "PL=", &presetCycCurr, presetCycMin, presetCycMax)) {
unloadPlaylist();
unloadPlaylist();
applyPreset(presetCycCurr);
}

Wyświetl plik

@ -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() {
ledMaps = 1;
for (size_t i=1; i<10; i++) {
char fileName[16];
for (size_t i=1; i<WLED_MAX_LEDMAPS; i++) {
char fileName[33];
sprintf_P(fileName, PSTR("/ledmap%d.json"), i);
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
}
}
}

Wyświetl plik

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

Wyświetl plik

@ -8,7 +8,7 @@
*/
// 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
//#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 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 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
@ -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 bool doInitBusses _INIT(false);
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
#endif
// Usermod manager
WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager());

Wyświetl plik

@ -364,7 +364,7 @@ void getSettingsJS(byte subPage, char* dest)
if (subPage == 2)
{
char nS[8];
char nS[32];
appendGPIOinfo();
@ -445,6 +445,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('c',SET_F("GB"),gammaCorrectBri);
sappend('c',SET_F("GC"),gammaCorrectCol);
dtostrf(gammaCorrectVal,3,1,nS); sappends('s',SET_F("GV"),nS);
sappend('c',SET_F("TF"),fadeTransition);
sappend('v',SET_F("TD"),transitionDelayDefault);
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("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
for (uint8_t i=0; i<strip.panels; i++) {
char n[5];