From b384b005f0b4154bad4230473acd3a129621b23b Mon Sep 17 00:00:00 2001 From: ewowi Date: Thu, 15 Jan 2026 12:02:20 +0100 Subject: [PATCH 1/6] add DigNext2, no pins in safeMode backend ======= - moduleIO: add DigNext2, readPins safeMode check - Physical layer: loop: check nrOfChannels < maxChannels - moduleDrivers, lights control, infrared: readPins safeMode check - MM effects GOL: return if no data allocated --- docs/gettingstarted/installer.md | 2 +- src/MoonBase/Modules/ModuleIO.h | 103 ++++++++++++-------- src/MoonBase/Utilities.cpp | 2 +- src/MoonBase/Utilities.h | 12 +-- src/MoonLight/Layers/PhysicalLayer.cpp | 1 + src/MoonLight/Layers/VirtualLayer.cpp | 13 ++- src/MoonLight/Modules/ModuleDrivers.h | 5 + src/MoonLight/Modules/ModuleLightsControl.h | 5 + src/MoonLight/Nodes/Drivers/D_Infrared.h | 5 + src/MoonLight/Nodes/Effects/E_MoonModules.h | 10 +- src/MoonLight/Nodes/Effects/E_WLED.h | 6 +- src/MoonLight/Nodes/Modifiers/M_MoonLight.h | 4 +- 12 files changed, 107 insertions(+), 61 deletions(-) diff --git a/docs/gettingstarted/installer.md b/docs/gettingstarted/installer.md index 27fd3453a..a1bb3758a 100644 --- a/docs/gettingstarted/installer.md +++ b/docs/gettingstarted/installer.md @@ -21,7 +21,7 @@ Recommended low-cost DIY board up to 10K LEDs: [ESP32-S3 n16r8](https://s.click. ![esp32-s3-n16r8v](../firmware/installer/images/esp32-s3-n8r8v.jpg){: style="width:100px"} -Recommended state-of-the-art DIY board, up to 16K - 98K LEDs !: [ESP32-P4-Nano](https://www.waveshare.com/esp32-p4-nano.htm){:target="_blank"} +Recommended state-of-the-art DIY board, up to 16-98K LEDs: [ESP32-P4-Nano](https://www.waveshare.com/esp32-p4-nano.htm){:target="_blank"} ![esp32-p4-nano](../firmware/installer/images/esp32-p4-nano.jpg){: style="width:100px"} diff --git a/src/MoonBase/Modules/ModuleIO.h b/src/MoonBase/Modules/ModuleIO.h index 2c335af01..f248545be 100644 --- a/src/MoonBase/Modules/ModuleIO.h +++ b/src/MoonBase/Modules/ModuleIO.h @@ -72,10 +72,11 @@ enum IO_PinUsageEnum { enum IO_BoardsEnum { board_none, // + board_QuinLEDDig2Go, + board_QuinLEDDigNext2, board_QuinLEDDigUnoV3, board_QuinLEDDigQuadV3, board_QuinLEDDigOctaV2, - board_QuinLEDDig2Go, // board_QuinLEDPenta, // board_QuinLEDPentaPlus, board_SergUniShieldV5, @@ -114,10 +115,11 @@ class ModuleIO : public Module { control = addControl(controls, "boardPreset", "select"); control["default"] = 0; addControlValue(control, BUILD_TARGET); // 0 none + addControlValue(control, "QuinLED Dig2Go"); + addControlValue(control, "QuinLED DigNext2"); addControlValue(control, "QuinLED Dig Uno v3"); addControlValue(control, "QuinLED Dig Quad v3"); addControlValue(control, "QuinLED Dig Octa v2"); - addControlValue(control, "QuinLED Dig2Go"); addControlValue(control, "Serg Universal Shield"); addControlValue(control, "Serg Mini Shield"); addControlValue(control, "Mathieu SE16 v1"); @@ -330,42 +332,6 @@ class ModuleIO : public Module { pinAssigner.assignPin(10, pin_PHY_CS); // WIZ850IO nCS pinAssigner.assignPin(45, pin_PHY_IRQ); // WIZ850IO nINT pinAssigner.assignPin(4, pin_Infrared); - } else if (boardID == board_QuinLEDDigUnoV3) { - // Dig-Uno-V3 - // esp32-d0 (4MB) - object["maxPower"] = 50; // max 75, but 10A fuse - pinAssigner.assignPin(16, pin_LED); - pinAssigner.assignPin(3, pin_LED); - pinAssigner.assignPin(0, pin_ButtonPush); - pinAssigner.assignPin(15, pin_Relay); - // pinAssigner.assignPin(2, pin_I2S_SD); - // pinAssigner.assignPin(12, pin_I2S_WS); - // pinAssigner.assignPin(13, pin_Temperature); - // pinAssigner.assignPin(15, pin_I2S_SCK); - // pinAssigner.assignPin(16, pin_LED_03); - // pinAssigner.assignPin(32, pin_Exposed); - } else if (boardID == board_QuinLEDDigQuadV3) { - // Dig-Quad-V3 - // esp32-d0 (4MB) - object["maxPower"] = 150; - uint8_t ledPins[] = {16, 3, 1, 4}; // LED_PINS - for (uint8_t gpio : ledPins) pinAssigner.assignPin(gpio, pin_LED); - pinAssigner.assignPin(0, pin_ButtonPush); - - pinAssigner.assignPin(15, pin_Relay); - - // pinAssigner.assignPin(2, pin_I2S_SD; - // pinAssigner.assignPin(12, pin_I2S_WS; - // pinAssigner.assignPin(13, pin_Temperature; - // pinAssigner.assignPin(15, pin_I2S_SCK; - // pinAssigner.assignPin(32, pin_Exposed; - } else if (boardID == board_QuinLEDDigOctaV2) { - // Dig-Octa-32-8L - object["maxPower"] = 400; // 10A Fuse * 8 ... 400 W - uint8_t ledPins[] = {0, 1, 2, 3, 4, 5, 12, 13}; // LED_PINS - for (uint8_t gpio : ledPins) pinAssigner.assignPin(gpio, pin_LED); - pinAssigner.assignPin(33, pin_Relay); - pinAssigner.assignPin(34, pin_ButtonPush); } else if (boardID == board_QuinLEDDig2Go) { // dig2go object["maxPower"] = 10; // USB powered: 2A / 10W @@ -402,6 +368,62 @@ class ModuleIO : public Module { // pinAssigner.assignPin(16, pin_I2C_SCL); // pinAssigner.assignPin(13, pin_Relay_LightsOn); // pinAssigner.assignPin(5, pin_LED); + } else if (boardID == board_QuinLEDDigNext2) { + // digNext2 + object["maxPower"] = 65; + pinAssigner.assignPin(2, pin_LED); + pinAssigner.assignPin(4, pin_LED); + pinAssigner.assignPin(5, pin_Relay_LightsOn); + pinAssigner.assignPin(20, pin_Relay_LightsOn); + pinAssigner.assignPin(21, pin_Relay_LightsOn); + pinAssigner.assignPin(22, pin_Relay_LightsOn); + pinAssigner.assignPin(7, pin_I2S_SD); + pinAssigner.assignPin(8, pin_I2S_WS); + // pinAssigner.assignPin(?, pin_I2S_SCK); + pinAssigner.assignPin(15, pin_I2C_SDA); + pinAssigner.assignPin(14, pin_I2C_SCL); + pinAssigner.assignPin(34, pin_Button_Push_LightsOn); + pinAssigner.assignPin(35, pin_ButtonPush); + pinAssigner.assignPin(0, pin_Exposed); + pinAssigner.assignPin(25, pin_Exposed); + pinAssigner.assignPin(32, pin_Exposed); + pinAssigner.assignPin(33, pin_Exposed); + } else if (boardID == board_QuinLEDDigUnoV3) { + // Dig-Uno-V3 + // esp32-d0 (4MB) + object["maxPower"] = 50; // max 75, but 10A fuse + pinAssigner.assignPin(16, pin_LED); + pinAssigner.assignPin(3, pin_LED); + pinAssigner.assignPin(0, pin_ButtonPush); + pinAssigner.assignPin(15, pin_Relay); + // pinAssigner.assignPin(2, pin_I2S_SD); + // pinAssigner.assignPin(12, pin_I2S_WS); + // pinAssigner.assignPin(13, pin_Temperature); + // pinAssigner.assignPin(15, pin_I2S_SCK); + // pinAssigner.assignPin(16, pin_LED_03); + // pinAssigner.assignPin(32, pin_Exposed); + } else if (boardID == board_QuinLEDDigQuadV3) { + // Dig-Quad-V3 + // esp32-d0 (4MB) + object["maxPower"] = 150; + uint8_t ledPins[] = {16, 3, 1, 4}; // LED_PINS + for (uint8_t gpio : ledPins) pinAssigner.assignPin(gpio, pin_LED); + pinAssigner.assignPin(0, pin_ButtonPush); + + pinAssigner.assignPin(15, pin_Relay); + + // pinAssigner.assignPin(2, pin_I2S_SD; + // pinAssigner.assignPin(12, pin_I2S_WS; + // pinAssigner.assignPin(13, pin_Temperature; + // pinAssigner.assignPin(15, pin_I2S_SCK; + // pinAssigner.assignPin(32, pin_Exposed; + } else if (boardID == board_QuinLEDDigOctaV2) { + // Dig-Octa-32-8L + object["maxPower"] = 400; // 10A Fuse * 8 ... 400 W + uint8_t ledPins[] = {0, 1, 2, 3, 4, 5, 12, 13}; // LED_PINS + for (uint8_t gpio : ledPins) pinAssigner.assignPin(gpio, pin_LED); + pinAssigner.assignPin(33, pin_Relay); + pinAssigner.assignPin(34, pin_ButtonPush); } else if (boardID == board_SergMiniShield) { object["maxPower"] = 50; // 10A Fuse ... pinAssigner.assignPin(16, pin_LED); @@ -605,6 +627,11 @@ class ModuleIO : public Module { } void readPins() { + if (safeModeMB) { + EXT_LOGW(ML_TAG, "Safe mode enabled, not adding pins"); + return; + } + uint8_t pinRS485TX = UINT8_MAX; uint8_t pinRS485RX = UINT8_MAX; uint8_t pinRS485DE = UINT8_MAX; diff --git a/src/MoonBase/Utilities.cpp b/src/MoonBase/Utilities.cpp index 9004315b9..a59a1e8b4 100644 --- a/src/MoonBase/Utilities.cpp +++ b/src/MoonBase/Utilities.cpp @@ -88,7 +88,7 @@ bool copyFile(const char* srcPath, const char* dstPath) { size_t n; while ((n = src.read(buf, sizeof(buf))) > 0) { if (dst.write(buf, n) != n) { - Serial.println("Write failed!"); + EXT_LOGE(MB_TAG, "Write failed!"); src.close(); dst.close(); return false; diff --git a/src/MoonBase/Utilities.h b/src/MoonBase/Utilities.h index fb3712202..602ba360f 100644 --- a/src/MoonBase/Utilities.h +++ b/src/MoonBase/Utilities.h @@ -192,9 +192,9 @@ T* allocMB(size_t n, const char* name = nullptr) { T* res = (T*)heap_caps_calloc_prefer(n, sizeof(T), 2, MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT); // calloc is malloc + memset(0); if (res) { totalAllocatedMB += heap_caps_get_allocated_size(res); - // EXT_LOGD(MB_TAG, "Allocated %s: %d x %d bytes in %s s:%d (tot:%d)", name?name:"", n, sizeof(T), isInPSRAM(res)?"PSRAM":"RAM", heap_caps_get_allocated_size(res), totalAllocatedMB); + // EXT_LOGD(MB_TAG, "Allocated %s: %d x %d bytes in %s s:%d (tot:%d)", name?name:"x", n, sizeof(T), isInPSRAM(res)?"PSRAM":"RAM", heap_caps_get_allocated_size(res), totalAllocatedMB); } else - EXT_LOGE(MB_TAG, "heap_caps_malloc of %d x %d not succeeded", n, sizeof(T)); + EXT_LOGE(MB_TAG, "heap_caps_malloc for %s of %d x %d not succeeded", name?name:"x", n, sizeof(T)); return res; } @@ -202,9 +202,9 @@ template T* reallocMB(T* p, size_t n, const char* name = nullptr) { T* res = (T*)heap_caps_realloc_prefer(p, n * sizeof(T), 2, MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT); // calloc is malloc + memset(0); if (res) { - // EXT_LOGD(MB_TAG, "Re-Allocated %s: %d x %d bytes in %s s:%d", name?name:"", n, sizeof(T), isInPSRAM(res)?"PSRAM":"RAM", heap_caps_get_allocated_size(res)); + // EXT_LOGD(MB_TAG, "Re-Allocated %s: %d x %d bytes in %s s:%d", name?name:"x", n, sizeof(T), isInPSRAM(res)?"PSRAM":"RAM", heap_caps_get_allocated_size(res)); } else - EXT_LOGE(MB_TAG, "heap_caps_malloc of %d x %d not succeeded", n, sizeof(T)); + EXT_LOGE(MB_TAG, "heap_caps_malloc for %s of %d x %d not succeeded", name?name:"x", n, sizeof(T)); return res; } @@ -213,11 +213,11 @@ template void freeMB(T*& p, const char* name = nullptr) { if (p) { totalAllocatedMB -= heap_caps_get_allocated_size(p); - // EXT_LOGD(MB_TAG, "free %s: x x %d bytes in %s, s:%d (tot:%d)", name?name:"", sizeof(T), isInPSRAM(p)?"PSRAM":"RAM", heap_caps_get_allocated_size(p), totalAllocatedMB); + // EXT_LOGD(MB_TAG, "free %s: x x %d bytes in %s, s:%d (tot:%d)", name?name:"x", sizeof(T), isInPSRAM(p)?"PSRAM":"RAM", heap_caps_get_allocated_size(p), totalAllocatedMB); heap_caps_free(p); p = nullptr; } else - EXT_LOGW(MB_TAG, "Nothing to free: pointer is null"); + EXT_LOGW(MB_TAG, "Nothing to free for %s: pointer is null", name?name:"x"); } // allocate vector diff --git a/src/MoonLight/Layers/PhysicalLayer.cpp b/src/MoonLight/Layers/PhysicalLayer.cpp index 957779100..b31fdf5a7 100644 --- a/src/MoonLight/Layers/PhysicalLayer.cpp +++ b/src/MoonLight/Layers/PhysicalLayer.cpp @@ -83,6 +83,7 @@ void PhysicalLayer::setup() { } void PhysicalLayer::loop() { + if (lights.header.nrOfChannels >= lights.maxChannels) return; // in case alloc mem is not successful // runs the loop of all effects / nodes in the layer for (VirtualLayer* layer : layers) { if (layer) { diff --git a/src/MoonLight/Layers/VirtualLayer.cpp b/src/MoonLight/Layers/VirtualLayer.cpp index f640948a7..caefc0692 100644 --- a/src/MoonLight/Layers/VirtualLayer.cpp +++ b/src/MoonLight/Layers/VirtualLayer.cpp @@ -167,12 +167,12 @@ void VirtualLayer::setLight(const nrOfLights_t indexV, const uint8_t* channels, } break; } - } else { + } else { // no mapping uint32_t index = indexV * layerP->lights.header.channelsPerLight + offset; - // if (index + length <= layerP->lights.maxChannels) { // no mapping + // if (index + length <= layerP->lights.maxChannels) { memcpy(&layerP->lights.channelsE[index], channels, length); // } else { - // EXT_LOGW(ML_TAG, "%d + %d >= %d", indexV, length, layerP->lights.maxChannels); + // EXT_LOGW(ML_TAG, "%d + %d >= %d (%d %d)", indexV, length, layerP->lights.maxChannels, index, offset); // } } } @@ -215,9 +215,9 @@ T VirtualLayer::getLight(const nrOfLights_t indexV, uint8_t offset) const { return T(); // not implemented yet break; } - } else { + } else { // no mapping uint32_t index = indexV * layerP->lights.header.channelsPerLight + offset; - // if (index + sizeof(T) <= layerP->lights.maxChannels) { // no mapping + // if (index + sizeof(T) <= layerP->lights.maxChannels) { return *(T*)&layerP->lights.channelsE[index]; // } else { // // some operations will go out of bounds e.g. VUMeter, uncomment below lines if you wanna test on a specific effect @@ -322,8 +322,7 @@ void VirtualLayer::fill_rainbow(const uint8_t initialhue, const uint8_t deltahue void VirtualLayer::createMappingTableAndAddOneToOne() { if (mappingTableSize != size.x * size.y * size.z) { - EXT_LOGD(ML_TAG, "Allocating mappingTable: nrOfLights=%d, sizeof(PhysMap)=%d, total bytes=%d", - size.x * size.y * size.z, sizeof(PhysMap), size.x * size.y * size.z * sizeof(PhysMap)); + EXT_LOGD(ML_TAG, "Allocating mappingTable: nrOfLights=%d, sizeof(PhysMap)=%d, total bytes=%d", size.x * size.y * size.z, sizeof(PhysMap), size.x * size.y * size.z * sizeof(PhysMap)); PhysMap* newTable = reallocMB(mappingTable, size.x * size.y * size.z, "mappingTable"); if (newTable) { mappingTable = newTable; diff --git a/src/MoonLight/Modules/ModuleDrivers.h b/src/MoonLight/Modules/ModuleDrivers.h index 3a1ea8846..59650ee4b 100644 --- a/src/MoonLight/Modules/ModuleDrivers.h +++ b/src/MoonLight/Modules/ModuleDrivers.h @@ -32,6 +32,11 @@ class ModuleDrivers : public NodeManager { } void readPins() { + if (safeModeMB) { + EXT_LOGW(ML_TAG, "Safe mode enabled, not adding pins"); + return; + } + _moduleIO->read( [&](ModuleState& state) { // find the pins in board definitions diff --git a/src/MoonLight/Modules/ModuleLightsControl.h b/src/MoonLight/Modules/ModuleLightsControl.h index 40e4bd0ab..d50e27dd8 100644 --- a/src/MoonLight/Modules/ModuleLightsControl.h +++ b/src/MoonLight/Modules/ModuleLightsControl.h @@ -215,6 +215,11 @@ class ModuleLightsControl : public Module { } void readPins() { + if (safeModeMB) { + EXT_LOGW(ML_TAG, "Safe mode enabled, not adding pins"); + return; + } + moduleIO.read( [&](ModuleState& state) { pinRelayLightsOn = UINT8_MAX; diff --git a/src/MoonLight/Nodes/Drivers/D_Infrared.h b/src/MoonLight/Nodes/Drivers/D_Infrared.h index 83602ed77..c34ffb741 100644 --- a/src/MoonLight/Nodes/Drivers/D_Infrared.h +++ b/src/MoonLight/Nodes/Drivers/D_Infrared.h @@ -38,6 +38,11 @@ class IRDriver : public Node { uint8_t irPreset = 1; void readPins() { + if (safeModeMB) { + EXT_LOGW(ML_TAG, "Safe mode enabled, not adding pins"); + return; + } + moduleIO->read( [&](ModuleState& state) { pinInfrared = UINT8_MAX; diff --git a/src/MoonLight/Nodes/Effects/E_MoonModules.h b/src/MoonLight/Nodes/Effects/E_MoonModules.h index 1abb4014a..29576587d 100644 --- a/src/MoonLight/Nodes/Effects/E_MoonModules.h +++ b/src/MoonLight/Nodes/Effects/E_MoonModules.h @@ -181,11 +181,11 @@ class GameOfLifeEffect : public Node { } void onSizeChanged(const Coord3D& prevSize) override { - dataSize = ((layer->size.x * layer->size.y * layer->size.z + 7) / 8); + if (cells) freeMB(cells); + if (futureCells) freeMB(futureCells); + if (cellColors) freeMB(cellColors); - freeMB(cells); - freeMB(futureCells); - freeMB(cellColors); + dataSize = (layer->size.x * layer->size.y * layer->size.z + 7) / 8; cells = allocMB(dataSize); futureCells = allocMB(dataSize); @@ -193,7 +193,9 @@ class GameOfLifeEffect : public Node { if (!cells || !futureCells || !cellColors) { EXT_LOGE(ML_TAG, "allocation of cells || !futureCells || !cellColors failed"); + return; } + EXT_LOGD(ML_TAG, "allocation of cells futureCells cellColors successful %d %d", dataSize, layer->nrOfLights); startNewGameOfLife(); } diff --git a/src/MoonLight/Nodes/Effects/E_WLED.h b/src/MoonLight/Nodes/Effects/E_WLED.h index ab8162bbd..66fdc5984 100644 --- a/src/MoonLight/Nodes/Effects/E_WLED.h +++ b/src/MoonLight/Nodes/Effects/E_WLED.h @@ -1143,7 +1143,7 @@ class PopCornEffect : public Node { uint16_t ledIndex = popcorn[i].pos; CRGB col = ColorFromPalette(layerP.palette, popcorn[i].colIndex * (256 / maxNumPopcorn)); if (ledIndex < layer->size.y) { - layer->setRGB(ledIndex, col); + // layer->setRGB(Coord3D(0, ledIndex), col); for (int x = 0; x < layer->size.x; x++) for (int z = 0; z < layer->size.z; z++) layer->setRGB(Coord3D(x, ledIndex, z), col); } @@ -1652,7 +1652,9 @@ class FlowEffect : public Node { for (int i = 0; i < zoneLen; i++) { uint8_t colorIndex = (i * 255 / zoneLen) - counter; uint16_t led = (z & 0x01) ? i : (zoneLen - 1) - i; - layer->setRGB(Coord3D(0, pos + led), ColorFromPalette(layerP.palette, colorIndex)); + CRGB color = ColorFromPalette(layerP.palette, colorIndex); + for (int x = 0; x < layer->size.x; x++) + for (int z = 0; z < layer->size.z; z++) layer->setRGB(Coord3D(x, pos + led, z), color); } } } diff --git a/src/MoonLight/Nodes/Modifiers/M_MoonLight.h b/src/MoonLight/Nodes/Modifiers/M_MoonLight.h index ac1fa393c..ce597d991 100644 --- a/src/MoonLight/Nodes/Modifiers/M_MoonLight.h +++ b/src/MoonLight/Nodes/Modifiers/M_MoonLight.h @@ -55,8 +55,8 @@ class MirrorModifier : public Node { static const char* tags() { return "💎🐙"; } bool mirrorX = true; - bool mirrorY = false; - bool mirrorZ = false; + bool mirrorY = true; + bool mirrorZ = true; void setup() override { addControl(mirrorX, "mirrorX", "checkbox"); From 8cd436288fd619640b36c5c751a01a7b9d9e1b8d Mon Sep 17 00:00:00 2001 From: ewowi Date: Thu, 15 Jan 2026 18:24:38 +0100 Subject: [PATCH 2/6] add esp32-d0-pico2, add dig-next2, mutex tuning docs ===== - Installer: add esp32-d0-pico2 - io: add dig-next2 (pico2) - esp32-d0.ini: esp32-d0-pico2 backend ======= - main: memcpy channels within swapMutex - Physical layer: memset channels within swapMutex - Virtual layer: get/setLight no mapping array range check (GOL needs it) - WIP - Effects: check on freeMB, add name argument --- docs/gettingstarted/installer.md | 3 +- docs/moonbase/inputoutput.md | 2 + firmware/esp32-d0.ini | 9 +++++ firmware/installer/images/esp32-d0-pico2.jpg | Bin 0 -> 3232 bytes .../installer/manifest_esp32-d0-pico2.json | 18 +++++++++ platformio.ini | 2 +- src/MoonBase/Nodes.cpp | 2 +- src/MoonLight/Layers/PhysicalLayer.cpp | 19 ++++++--- src/MoonLight/Layers/VirtualLayer.cpp | 25 ++++++------ src/MoonLight/Modules/ModuleDrivers.h | 2 +- src/MoonLight/Modules/ModuleEffects.h | 2 +- src/MoonLight/Nodes/Effects/E_MoonLight.h | 16 ++++---- src/MoonLight/Nodes/Effects/E_MoonModules.h | 30 ++++++++------ src/MoonLight/Nodes/Effects/E_WLED.h | 38 ++++++++++++------ src/main.cpp | 2 +- 15 files changed, 114 insertions(+), 56 deletions(-) create mode 100644 firmware/installer/images/esp32-d0-pico2.jpg create mode 100644 firmware/installer/manifest_esp32-d0-pico2.json diff --git a/docs/gettingstarted/installer.md b/docs/gettingstarted/installer.md index a1bb3758a..b067eb1e5 100644 --- a/docs/gettingstarted/installer.md +++ b/docs/gettingstarted/installer.md @@ -45,8 +45,9 @@ After a successful install, go to **Logs & Console**, press **Reset Device**, an | Name | Image* | Flash | Shop & Board presets | |------|--------|-------|----------------------| -| esp32-d0 | ![esp32-d0](../firmware/installer/images/esp32-d0.jpg){: style="width:100px"} | | [Dig Uno](https://quinled.info/pre-assembled-quinled-dig-uno):
![Dig Uno](https://quinled.info/wp-content/uploads/2020/02/QuinLED-Dig-Uno-v3_front.png){: style="width:100px"}
[Dig Quad](https://quinled.info/pre-assembled-quinled-dig-quad):
![Dig Quad](https://quinled.info/wp-content/uploads/2021/11/QuinLED-Dig-Quad-AB_v3r1-2048x1154.png){: style="width:100px"}
[Dig2Go](https://quinled.info/quinled-dig2go):
![Dig2Go](https://shop.allnetchina.cn/cdn/shop/products/Led_4.jpg?v=1680836018&width=1600){: style="width:100px"} | +| esp32-d0 | ![esp32-d0](../firmware/installer/images/esp32-d0.jpg){: style="width:100px"} | | [Dig2Go](https://quinled.info/quinled-dig2go):
![Dig2Go](https://shop.allnetchina.cn/cdn/shop/products/Led_4.jpg?v=1680836018&width=1600){: style="width:100px"}
[Dig Uno](https://quinled.info/pre-assembled-quinled-dig-uno):
![Dig Uno](https://quinled.info/wp-content/uploads/2020/02/QuinLED-Dig-Uno-v3_front.png){: style="width:100px"}
[Dig Quad](https://quinled.info/pre-assembled-quinled-dig-quad):
![Dig Quad](https://quinled.info/wp-content/uploads/2021/11/QuinLED-Dig-Quad-AB_v3r1-2048x1154.png){: style="width:100px"} | | esp32-d0-16mb | ![esp32-d0-16mb](../firmware/installer/images/esp32-d0-16mb.jpg){: style="width:100px"} | | [Dig Octa](https://quinled.info/quinled-dig-octa):
![Dig Octa](https://quinled.info/wp-content/uploads/2024/10/20240924_141857-2048x1444.png){: style="width:100px"}
[Serg ESP32](https://www.tindie.com/products/serg74/esp32-wroom-usb-c-d1-mini32-form-factor-board){:target="_blank"} and [Shield](https://www.tindie.com/products/serg74/wled-shield-board-for-addressable-leds)
![Shield](https://cdn.tindiemedia.com/images/resize/44YE-eNQ9pJQUh_SmtwwfBXFbAE=/p/fit-in/1370x912/filters:fill(fff)/i/93057/products/2021-08-14T14%3A44%3A14.418Z-shield_v3-1.jpg?1628927139){: style="width:100px"} | +| esp32-d0-pico2 | ![esp32-d0-pico2](../firmware/installer/images/esp32-d0-pico2.jpg){: style="width:100px"} | | [DigNext2](https://quinled.info/dig-next-2):
![DigNext2](https://quinled.info/wp-content/uploads/2026/01/P1087754-Enhanced-NR-2560x1358.jpg){: style="width:100px"} | | esp32-s3-n8r8v | ![esp32-s3-n8r8v](../firmware/installer/images/esp32-s3-n8r8v.jpg){: style="width:100px"} | | SE-16p
![SE-16p](../firmware/installer/images/esp32-s3-stephanelec-16p.jpg){: style="width:100px"} | | esp32-s3-n16r8v | ![esp32-s3-n16r8v](../firmware/installer/images/esp32-s3-n8r8v.jpg){: style="width:100px"} | | [Ali*](https://s.click.aliexpress.com/e/_DBAtJ2H){:target="_blank"} | | esp32-s3-atoms3r | ![esp32-s3-atoms3r](../firmware/installer/images/esp32-s3-atoms3r.jpg){: style="width:100px"} | | [M5Stack store](https://shop.m5stack.com/products/atoms3r-dev-kit){:target="_blank"} | diff --git a/docs/moonbase/inputoutput.md b/docs/moonbase/inputoutput.md index 37e1ddc37..a36e881fa 100644 --- a/docs/moonbase/inputoutput.md +++ b/docs/moonbase/inputoutput.md @@ -53,12 +53,14 @@ For each board the following presets are defined: ### QuinLed boards ![Dig2Go](https://shop.allnetchina.cn/cdn/shop/products/Led_4.jpg?v=1680836018&width=1600){: style="width:100px"} +![DigNext2](https://quinled.info/wp-content/uploads/2026/01/P1087754-Enhanced-NR-2560x1358.jpg){: style="width:100px"} ![Dig Uno](https://quinled.info/wp-content/uploads/2020/02/QuinLED-Dig-Uno-v3_front.png){: style="width:100px"} ![Dig Quad](https://quinled.info/wp-content/uploads/2021/11/QuinLED-Dig-Quad-AB_v3r1-2048x1154.png){: style="width:100px"} ![Dig Octa](https://quinled.info/wp-content/uploads/2024/10/20240924_141857-2048x1444.png){: style="width:100px"} * [Dig2Go](https://quinled.info/quinled-dig2go/), [Dig Uno](https://quinled.info/pre-assembled-quinled-dig-uno/), [Dig Quad](https://quinled.info/pre-assembled-quinled-dig-quad/): Choose the esp32-d0 (4MB) board in the [MoonLight Installer](../../gettingstarted/installer/) * Dig2Go: Shipped with a 300 LED, GRBW led strip: Choose layout with 300 lights (e.g. Single Column for 1D, Panel 15x20 for 2D). Select Light preset GRBW in the LED Driver. +* [DigNext2](https://quinled.info/dig-next-2): Choose the esp32-d0-pico2 board in the [MoonLight Installer](../../gettingstarted/installer/) * [Dig Octa](https://quinled.info/quinled-dig-octa/): Choose the esp32-d0-16mb board in the [MoonLight Installer](../../gettingstarted/installer/) * On first install, erase flash first (Especially when other firmware like WLED was on it) as MoonLight uses a partition scheme with 3MB of flash (currently no OTA support). * After install, select the QuinLED board preset to have the pins assigned correctly. diff --git a/firmware/esp32-d0.ini b/firmware/esp32-d0.ini index 225fc1c58..effd78196 100644 --- a/firmware/esp32-d0.ini +++ b/firmware/esp32-d0.ini @@ -113,3 +113,12 @@ build_flags = ${env.build_flags} lib_deps = ${env.lib_deps} ; RAM: [=== ] 26.2% (used 85912 bytes from 327680 bytes) ; Flash: [======= ] 65.9% (used 2073762 bytes from 3145728 bytes) + + +[env:esp32-pico-2] +extends = env:esp32dev +board = esp32-pico-devkitm-2 ; https://github.com/platformio/platform-espressif32/blob/3c076807e1f55b90799b50b946e76a0508e97778/boards/esp32-pico-devkitm-2.json#L8 +board_build.partitions = boards/ESP32_8MB.csv +build_flags = ${esp32-d0-base.build_flags} + -D HTTPD_STACK_SIZE=6144 +lib_deps = ${esp32-d0-base.lib_deps} \ No newline at end of file diff --git a/firmware/installer/images/esp32-d0-pico2.jpg b/firmware/installer/images/esp32-d0-pico2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..397466b50e33c375240a68a0c2e771337ea09859 GIT binary patch literal 3232 zcma)9Su`7p7Nxk9qA6OGroH9}YD`gMQK7iza?zM7Rb$jVQ)ARz)D$)LxLALpF4KhDSAYp-=q!XJi@S($m6X=rFzp}JZozdDA7hL(eg z?$`84&t>J`#2-)0w5e3;%E~uY4Si>)=fAkk%?)i`V*wF)Wi_ahims=(zqO6y*4Bo* z$15}%4N=k#kBE6@2Q&J^a&~sc+S+bzZnmSN9fN5i68DFPhrGNz9qgTwli!q-6lZ2; z3=R%Fe*8EjB)Fg;KQ=bT%*;#z3~p_0v9-0Ss3(ArJ_Rij0((m#?j@{Wh2< zA+0JXAOHXWc!5%-IT6TcU;LNcFHNbhJRIvwW8bBOM1>&O*;uPq^0sMcSW2N<8fF3c z`vt7w3!*pT9?7qzqYc|KnQ1k2i!2w?t;v3^r1S$S)+3ehsqsZ_RY8E~In@D7p9M_O zp%chhTN4!hzW^V-Scktl2NV5)BPm+ckej6KxmJPk{%#GXmaa`OdKm_TV5Slic-^&i@-5K2q6>k=V)KOr6J*d@n^wn8l@Z^6JDRm2S#5 zK_&FSrcIdSf1*M>5cTSiBz<1+>L8!J@jN(=Ag_{x1cC*|954FA*jw6oNrha^bR?f& z<)5`=XMA?{vez@aJ8GDV6AXzR8%`NrPxC?fqtwDLPkJZ%RLivLm36T3`N#4i7Gh5x@ID0@-GuwoQh z#6&>uSGt0%Xzz)p=KH3Mu9g)`XFkw_On(q0)bF-umjUZCjr!ZJW}y#N=1Hv6^AGwo zC#JJs3!i27vgxZryvpNyJ$h2Q*q?i9XMjCn{arzHp!gOgTe)#2= z-}KjF@3c~8!Evk8H-BUJ2F^(jChFcak+Zld*gh(OjA{|L`q&&-oGOj*yk@Z+ztA~w z#MN*+=b``Wjc4)9`bCw-%um>mysvgir7=>$8~Snhm*LKz62eU_pGL68ebqRiw3-%1 z>sirQmUKw+bF+Y>1H(a9Z|+D~HKeLnABhky-&gA^FxhpDX*9J-DhpNj@FU3XJ7Y$; z%9w!Lv@wZZ`m>OYa)rn>*B7w)0Hd<;x>5e^A9xKw;6jN}-=sscHN7Lb#}~uFbq8k#cNF4xy*#ZFxF!S0SSSy6mR$^;49WEsYUu&79T@H+-; z+HwYNarmd{=35ly*c+zrHKW$~hX9YRn5Mp)8nPXmnl}1KtD+p8m1OzJnUSCaP;}4{ z*F)t3=4hVOF7vzB=%aRUIUW~=DsL*p;fcGuCWz}eGDgcI%*Pg&qx+}yxuetZUHlg3 zhg~Suw_(wPK-}H=DYzvm+sWO=GU$+}akkhE+))T4qt3?|6QgU;{|h;}X0Yu~?G}pR z{zmMKuf_qMss!?IodjE&Md>7d{Q?qtk(9tG)?@%u(IhrEC{_}4bk6u(wqd#HCnaUC zTtiIR_C&FPf6LDlzyQ5mpZkO7EvIf&<`~MO8*Y*b?S5V7-n&U_O@?>&F7H7Mn;VIu z&bctv(wg6dgVr`EW*qr32PuCFx1C~I6yIm)I&@nLh(VIPbK~z+OsXir)Mf_c_QS7* z_3+ngJrF;7!5X5+p==<$AO%5xmw-=C*&0>x$ocqfX%(BwHrg&LYRZ+b!}sKP-Ruh+ zNTp`6VZWEyoM_rtu4THFf$1+2>RiL|Y1`Mb(~yZvJn~GNK|)8L;94EJ5dV-Jgfgt~$=b&IEXnhgAc4&O+_yeW!NnHAR|KNmyg4`fGS%U0HD)8v&|k z5|nwk;Bw?Qc}>EO154Xo6hECI=pkE4uY_pqS&DA+7xyKCH?VArQfEqUtgl6IYtxg$ zG^LNYihtWo`U2zB>TKUDnD4d7gmK>cTw3>fu9DZOilE0ipHsni5$Vw3powCVmP(~T z$VAy7q$2>Pn`)o?iD3)C4U3zMxgZ}cjyC`S^Q(4bcc}%N)i~+V)#ULGbVtWq4Y8kz z>{>OO!6oL|Q(<#mmwRaUvh`F^6;A!6=u%82Nah7xf4?64vh#YTas!DQ$sy-S;1{ay z6!hKjBqZj|eFm=K#&PDHj(7SKq;)a|zDt%RR%ZHP1BLzjW4>Raon#mOtSS<<|M~!Z z!jiB1gk;Gzm7s`MTzqWbN7@uJg*By3Q5+YCXe|FG0Iq!Id#CfUPSnD@x9 z>}zg!e~=M@O$c-7WZ9MgGBTv;SkV!vnYlguo?f`OxU(l-NmdVX-MACLnJnX}?$P&j zo?I3;=u0P}UyK`O2JOJ=B@gNOMEw8a$nw86$Z&CHK5Ya5<8Q=5UAXNpjC<#d5vCs4 zRHzI#Xsu1DHr0r}X7Gg#TSl$K_M~_6k7BnS?d6wGz{*$nlaCsE6SBaZBioP%qnp8b zmiwlY=Bin%Pi)bAvF5-I0da!ceRGtC`PJY|(!EuoQD!^;@vkdP3QYGSP|*fLlk=ia z6=_dJvC>B3b#=snEUV(5V2FM- zc)H9I`R+j)k#)TZZc~yKLhFOP!q#I-P*YZ4+hmh&gBizF+(WdxY`*Y!1gBeM#&;S1 zqq<5ES6b9JEomh^VzbQ!-hb+9U>bn$rK=7Sfcwi<8(;o$buJCzLq%5+MOQju^<6wL zjP*S?bFw!7uxohWd?A*x^V5SP(!Kk>*Y64$2>k_nV*z{%3m&|EI*g`-Hi?RlH%Gxv z+gDgR1FQ0W*PwK#mgJ-M_Vy1R4MA&1kuTS(`=i_=U4Xy?FK?GNsuWismow{G9@K-bh(), control["value"].as().c_str(), control["type"].as(), (void*)pointer); + EXT_LOGD(ML_TAG, "%s = %s t:%s p:%p", control["name"].as(), control["value"].as().c_str(), control["type"].as(), (void*)pointer); if (pointer) { if (control["type"] == "slider" || control["type"] == "select" || control["type"] == "pin" || control["type"] == "number") { diff --git a/src/MoonLight/Layers/PhysicalLayer.cpp b/src/MoonLight/Layers/PhysicalLayer.cpp index b31fdf5a7..474f6652f 100644 --- a/src/MoonLight/Layers/PhysicalLayer.cpp +++ b/src/MoonLight/Layers/PhysicalLayer.cpp @@ -120,6 +120,7 @@ void PhysicalLayer::loopDrivers() { requestMapVirtual = false; } + // for physical layer nodes if (prevSize != lights.header.size) EXT_LOGD(ML_TAG, "onSizeChanged P %d,%d,%d -> %d,%d,%d", prevSize.x, prevSize.y, prevSize.z, lights.header.size.x, lights.header.size.y, lights.header.size.z); for (Node* node : nodes) { @@ -155,18 +156,24 @@ void PhysicalLayer::onLayoutPre() { EXT_LOGD(ML_TAG, "pass %d mp:%d", pass, monitorPass); if (pass == 1) { + // Hold mutex while modifying shared state! + if (layerP.lights.useDoubleBuffer) xSemaphoreTake(swapMutex, portMAX_DELAY); + lights.header.nrOfLights = 0; // for pass1 and pass2 as in pass2 virtual layer needs it lights.header.size = {0, 0, 0}; - if (layerP.lights.useDoubleBuffer) xSemaphoreTake(swapMutex, portMAX_DELAY); EXT_LOGD(ML_TAG, "positions in progress (%d -> 1)", lights.header.isPositions); - lights.header.isPositions = 1; // in progress... + lights.header.isPositions = 1; // Stops effectTask from starting NEW frames + if (layerP.lights.useDoubleBuffer) xSemaphoreGive(swapMutex); - delay(100); // wait to stop effects + delay(100); // Wait for any in-progress frame to complete + + // Now safe to zero the buffer - effectTask won't start new frames while isPositions == 1 + if (layerP.lights.useDoubleBuffer) xSemaphoreTake(swapMutex, portMAX_DELAY); + memset(lights.channelsE, 0, lights.maxChannels); + if (layerP.lights.useDoubleBuffer) xSemaphoreGive(swapMutex); - // set all channels to 0 (e.g for multichannel to not activate unused channels, e.g. fancy modes on MHs) - memset(lights.channelsE, 0, lights.maxChannels); // set all the channels to 0, positions in channelsE - // dealloc pins + // dealloc pins (non-critical, can be outside mutex) if (!monitorPass) { memset(ledsPerPin, 0xFF, sizeof(ledsPerPin)); // UINT16_MAX is 2 * 0xFF memset(ledPinsAssigned, 0, sizeof(ledPinsAssigned)); diff --git a/src/MoonLight/Layers/VirtualLayer.cpp b/src/MoonLight/Layers/VirtualLayer.cpp index caefc0692..5572da2a4 100644 --- a/src/MoonLight/Layers/VirtualLayer.cpp +++ b/src/MoonLight/Layers/VirtualLayer.cpp @@ -57,6 +57,7 @@ void VirtualLayer::loop() { } } + // for virtual nodes if (prevSize != size) EXT_LOGD(ML_TAG, "onSizeChanged V %d,%d,%d -> %d,%d,%d", prevSize.x, prevSize.y, prevSize.z, size.x, size.y, size.z); for (Node* node : nodes) { if (prevSize != size) { @@ -169,11 +170,11 @@ void VirtualLayer::setLight(const nrOfLights_t indexV, const uint8_t* channels, } } else { // no mapping uint32_t index = indexV * layerP->lights.header.channelsPerLight + offset; - // if (index + length <= layerP->lights.maxChannels) { - memcpy(&layerP->lights.channelsE[index], channels, length); - // } else { - // EXT_LOGW(ML_TAG, "%d + %d >= %d (%d %d)", indexV, length, layerP->lights.maxChannels, index, offset); - // } + if (index + length <= layerP->lights.maxChannels) { + memcpy(&layerP->lights.channelsE[index], channels, length); + } else { + EXT_LOGW(ML_TAG, "%d + %d >= %d (%d %d)", indexV, length, layerP->lights.maxChannels, index, offset); + } } } @@ -217,13 +218,13 @@ T VirtualLayer::getLight(const nrOfLights_t indexV, uint8_t offset) const { } } else { // no mapping uint32_t index = indexV * layerP->lights.header.channelsPerLight + offset; - // if (index + sizeof(T) <= layerP->lights.maxChannels) { - return *(T*)&layerP->lights.channelsE[index]; - // } else { - // // some operations will go out of bounds e.g. VUMeter, uncomment below lines if you wanna test on a specific effect - // EXT_LOGW(ML_TAG, "%d + %d >= %d", indexV, sizeof(T), layerP->lights.maxChannels); - // return T(); - // } + if (index + sizeof(T) <= layerP->lights.maxChannels) { + return *(T*)&layerP->lights.channelsE[index]; + } else { + // some operations will go out of bounds e.g. VUMeter, uncomment below lines if you wanna test on a specific effect + EXT_LOGW(ML_TAG, "%d + %d >= %d", indexV, sizeof(T), layerP->lights.maxChannels); + return T(); + } } } diff --git a/src/MoonLight/Modules/ModuleDrivers.h b/src/MoonLight/Modules/ModuleDrivers.h index 59650ee4b..d2cdcf494 100644 --- a/src/MoonLight/Modules/ModuleDrivers.h +++ b/src/MoonLight/Modules/ModuleDrivers.h @@ -168,7 +168,7 @@ class ModuleDrivers : public NodeManager { node->moduleIO = _moduleIO; // to get pin allocations node->moduleNodes = (Module*)this; // to request UI update node->setup(); // run the setup of the effect - node->onSizeChanged(Coord3D()); + node->onSizeChanged(Coord3D()); // to init memory allocations // layers[0]->nodes.reserve(index+1); // from here it runs concurrently in the drivers task diff --git a/src/MoonLight/Modules/ModuleEffects.h b/src/MoonLight/Modules/ModuleEffects.h index af5b75022..a6a2a1616 100644 --- a/src/MoonLight/Modules/ModuleEffects.h +++ b/src/MoonLight/Modules/ModuleEffects.h @@ -270,7 +270,7 @@ class ModuleEffects : public NodeManager { // node->moduleIO = _moduleIO; // to get pin allocations node->moduleNodes = (Module*)this; // to request UI update node->setup(); // run the setup of the effect - node->onSizeChanged(Coord3D()); + node->onSizeChanged(Coord3D()); // to init memory allocations // layers[0]->nodes.reserve(index+1); // from here it runs concurrently in the effects task diff --git a/src/MoonLight/Nodes/Effects/E_MoonLight.h b/src/MoonLight/Nodes/Effects/E_MoonLight.h index bcefa71cb..2ae138c36 100644 --- a/src/MoonLight/Nodes/Effects/E_MoonLight.h +++ b/src/MoonLight/Nodes/Effects/E_MoonLight.h @@ -69,10 +69,10 @@ class StarSkyEffect : public Node { ~StarSkyEffect() { resetStars(); } void resetStars() { - if (stars_indexes) freeMB(stars_indexes); - if (stars_fade_dir) freeMB(stars_fade_dir); - if (stars_brightness) freeMB(stars_brightness); - if (stars_colors) freeMB(stars_colors); + if (stars_indexes) freeMB(stars_indexes, name()); + if (stars_fade_dir) freeMB(stars_fade_dir, name()); + if (stars_brightness) freeMB(stars_brightness, name()); + if (stars_colors) freeMB(stars_colors, name()); stars_indexes = nullptr; stars_fade_dir = nullptr; stars_brightness = nullptr; @@ -1682,11 +1682,13 @@ class RingRandomFlowEffect : public RingEffect { uint8_t* hue = nullptr; - ~RingRandomFlowEffect() { freeMB(hue); } + ~RingRandomFlowEffect() { + if (hue) freeMB(hue, name()); + } void onSizeChanged(const Coord3D& prevSize) override { - freeMB(hue); - hue = allocMB(layer->size.y); + if (hue) freeMB(hue, name()); + hue = allocMB(layer->size.y, name()); if (!hue) { EXT_LOGE(ML_TAG, "allocate hue failed"); } diff --git a/src/MoonLight/Nodes/Effects/E_MoonModules.h b/src/MoonLight/Nodes/Effects/E_MoonModules.h index 29576587d..cf72a8ce7 100644 --- a/src/MoonLight/Nodes/Effects/E_MoonModules.h +++ b/src/MoonLight/Nodes/Effects/E_MoonModules.h @@ -130,7 +130,7 @@ class GameOfLifeEffect : public Node { uint16_t spaceshipCRC; uint16_t cubeGliderCRC; bool soloGlider; - uint16_t generation; + uint16_t generation = 0; bool birthNumbers[9]; bool surviveNumbers[9]; CRGB prevPalette; @@ -175,21 +175,23 @@ class GameOfLifeEffect : public Node { int dataSize = 0; ~GameOfLifeEffect() override { - freeMB(cells); - freeMB(futureCells); - freeMB(cellColors); + if (cells) freeMB(cells, name()); + if (futureCells) freeMB(futureCells, name()); + if (cellColors) freeMB(cellColors, name()); } void onSizeChanged(const Coord3D& prevSize) override { - if (cells) freeMB(cells); - if (futureCells) freeMB(futureCells); - if (cellColors) freeMB(cellColors); + EXT_LOGW(ML_TAG, "GameOfLife onSizeChanged %d,%d,%d -> %d,%d,%d", prevSize.x, prevSize.y, prevSize.z, layer->size.x, layer->size.y, layer->size.z); + + if (cells) freeMB(cells, name()); + if (futureCells) freeMB(futureCells, name()); + if (cellColors) freeMB(cellColors, name()); dataSize = (layer->size.x * layer->size.y * layer->size.z + 7) / 8; - cells = allocMB(dataSize); - futureCells = allocMB(dataSize); - cellColors = allocMB(layer->size.x * layer->size.y * layer->size.z); + cells = allocMB(dataSize, name()); + futureCells = allocMB(dataSize, name()); + cellColors = allocMB(layer->size.x * layer->size.y * layer->size.z, name()); if (!cells || !futureCells || !cellColors) { EXT_LOGE(ML_TAG, "allocation of cells || !futureCells || !cellColors failed"); @@ -254,8 +256,8 @@ class GameOfLifeEffect : public Node { const int zAxis = (layer->layerDimension == _3D) ? 1 : 0; // Avoids looping through z axis neighbors if 2D bool disableWrap = !wrap || soloGlider || generation % 1500 == 0 || zAxis; // Loop through all cells. Count neighbors, apply rules, setPixel - for (int x = 0; x < layer->size.x; x++) - for (int y = 0; y < layer->size.y; y++) + for (int x = 0; x < layer->size.x; x++) { + for (int y = 0; y < layer->size.y; y++) { for (int z = 0; z < layer->size.z; z++) { Coord3D cPos = Coord3D(x, y, z); uint16_t cIndex = layer->XYZUnModified(cPos); @@ -316,6 +318,8 @@ class GameOfLifeEffect : public Node { } } } + } + } if (aliveCount == 5) soloGlider = true; @@ -340,7 +344,7 @@ class GameOfLifeEffect : public Node { if (generation % 16 == 0) oscillatorCRC = crc; if (gliderLength && generation % gliderLength == 0) spaceshipCRC = crc; if (cubeGliderLength && generation % cubeGliderLength == 0) cubeGliderCRC = crc; - (generation)++; + generation++; step = millis(); } }; // GameOfLife diff --git a/src/MoonLight/Nodes/Effects/E_WLED.h b/src/MoonLight/Nodes/Effects/E_WLED.h index 66fdc5984..cc02a8cf0 100644 --- a/src/MoonLight/Nodes/Effects/E_WLED.h +++ b/src/MoonLight/Nodes/Effects/E_WLED.h @@ -28,10 +28,12 @@ class BouncingBallsEffect : public Node { Ball (*balls)[maxNumBalls] = nullptr; //[maxColumns][maxNumBalls]; uint16_t ballsSize = 0; - ~BouncingBallsEffect() override { freeMB(balls); } + ~BouncingBallsEffect() override { + if (balls) freeMB(balls, name()); + } void onSizeChanged(const Coord3D& prevSize) override { - Ball(*newAlloc)[maxNumBalls] = reallocMB(balls, layer->size.x); + Ball(*newAlloc)[maxNumBalls] = reallocMB(balls, layer->size.x, name()); if (newAlloc) { balls = newAlloc; @@ -332,7 +334,9 @@ class GEQEffect : public Node { uint16_t* previousBarHeight = nullptr; uint8_t previousBarHeightSize = 0; - ~GEQEffect() { freeMB(previousBarHeight); } + ~GEQEffect() { + if (previousBarHeight) freeMB(previousBarHeight, name()); + } void onSizeChanged(const Coord3D& prevSize) override { uint16_t* newAlloc = reallocMB(previousBarHeight, layer->size.x); @@ -566,7 +570,9 @@ class PacManEffect : public Node { pacmancharacters_t* character = nullptr; uint8_t nrOfCharacters = 0; - ~PacManEffect() { freeMB(character); } + ~PacManEffect() { + if (character) freeMB(character, name()); + } void onSizeChanged(const Coord3D& prevSize) override { initializePacMan(); } @@ -1005,7 +1011,9 @@ class TetrixEffect : public Node { } } - ~TetrixEffect() override { freeMB(drops); }; + ~TetrixEffect() override { + if (drops) freeMB(drops, name()); + }; void loop() override { if (!drops) return; @@ -1335,7 +1343,9 @@ class OctopusEffect : public Node { uint16_t rMapSize = 0; uint32_t step; - ~OctopusEffect() { freeMB(rMap); } + ~OctopusEffect() { + if (rMap) freeMB(rMap, name()); + } void setRMap() { const uint8_t C_X = layer->size.x / 2 + (offset.x - 50) * layer->size.x / 100; @@ -1647,12 +1657,12 @@ class FlowEffect : public Node { layer->fill_solid(ColorFromPalette(layerP.palette, -counter)); - for (int z = 0; z < zones; z++) { - uint16_t pos = offset + z * zoneLen; + for (int zone = 0; zone < zones; zone++) { + uint16_t pos = offset + zone * zoneLen; for (int i = 0; i < zoneLen; i++) { uint8_t colorIndex = (i * 255 / zoneLen) - counter; - uint16_t led = (z & 0x01) ? i : (zoneLen - 1) - i; - CRGB color = ColorFromPalette(layerP.palette, colorIndex); + uint16_t led = (zone & 0x01) ? i : (zoneLen - 1) - i; + CRGB color = ColorFromPalette(layerP.palette, colorIndex); for (int x = 0; x < layer->size.x; x++) for (int z = 0; z < layer->size.z; z++) layer->setRGB(Coord3D(x, pos + led, z), color); } @@ -1737,7 +1747,9 @@ class RainEffect : public Node { Spark* drops = nullptr; uint16_t nrOfDrops = 0; - ~RainEffect() override { freeMB(drops); } + ~RainEffect() override { + if (drops) freeMB(drops, name()); + } void onSizeChanged(const Coord3D& prevSize) override { Spark* newAlloc = reallocMB(drops, layer->size.x); @@ -1813,7 +1825,9 @@ class DripEffect : public Node { Spark (*drops)[maxNumDrops] = nullptr; //[maxColumns][maxNumBalls]; uint16_t nrOfDrops = 0; - ~DripEffect() override { freeMB(drops); } + ~DripEffect() override { + if (drops) freeMB(drops, name()); + } void onSizeChanged(const Coord3D& prevSize) override { Spark(*newAlloc)[maxNumDrops] = reallocMB(drops, layer->size.x); diff --git a/src/main.cpp b/src/main.cpp index 77ce13848..cd8b54809 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -132,8 +132,8 @@ void effectTask(void* pvParameters) { if (layerP.lights.header.isPositions == 0 && !newFrameReady) { // within mutex as driver task can change this if (layerP.lights.useDoubleBuffer) { - xSemaphoreGive(swapMutex); memcpy(layerP.lights.channelsE, layerP.lights.channelsD, layerP.lights.header.nrOfChannels); // Copy previous frame (channelsD) to working buffer (channelsE) + xSemaphoreGive(swapMutex); } layerP.loop(); From c31fdac075857eedd0d5a6c79471dc97e9a4e08d Mon Sep 17 00:00:00 2001 From: ewowi Date: Thu, 15 Jan 2026 22:42:26 +0100 Subject: [PATCH 3/6] A number of changes, GOL bugfix (fTB 100) backend ======= - Utilities: reallocMB2 keeps old var and size - Modifiers: rippleYZ -> rippleXZ as 1D effects are on the Y axis - Effects: Scrolling Text: fTB 100, not 255 --- docs/gettingstarted/installer.md | 2 +- docs/moonlight/modifiers.md | 2 +- firmware/esp32-d0.ini | 6 +- misc/livescripts/E_lines.sc | 2 +- platformio.ini | 2 +- src/MoonBase/Utilities.h | 12 +++ src/MoonLight/Layers/VirtualLayer.cpp | 9 +- src/MoonLight/Layers/VirtualLayer.h | 2 +- src/MoonLight/Modules/ModuleEffects.h | 4 +- src/MoonLight/Nodes/Effects/E_MoonLight.h | 20 ++--- src/MoonLight/Nodes/Effects/E_MoonModules.h | 4 +- src/MoonLight/Nodes/Effects/E_WLED.h | 95 +++++---------------- src/MoonLight/Nodes/Modifiers/M_MoonLight.h | 22 ++--- 13 files changed, 67 insertions(+), 115 deletions(-) diff --git a/docs/gettingstarted/installer.md b/docs/gettingstarted/installer.md index b067eb1e5..1c57dcaea 100644 --- a/docs/gettingstarted/installer.md +++ b/docs/gettingstarted/installer.md @@ -47,7 +47,7 @@ After a successful install, go to **Logs & Console**, press **Reset Device**, an |------|--------|-------|----------------------| | esp32-d0 | ![esp32-d0](../firmware/installer/images/esp32-d0.jpg){: style="width:100px"} | | [Dig2Go](https://quinled.info/quinled-dig2go):
![Dig2Go](https://shop.allnetchina.cn/cdn/shop/products/Led_4.jpg?v=1680836018&width=1600){: style="width:100px"}
[Dig Uno](https://quinled.info/pre-assembled-quinled-dig-uno):
![Dig Uno](https://quinled.info/wp-content/uploads/2020/02/QuinLED-Dig-Uno-v3_front.png){: style="width:100px"}
[Dig Quad](https://quinled.info/pre-assembled-quinled-dig-quad):
![Dig Quad](https://quinled.info/wp-content/uploads/2021/11/QuinLED-Dig-Quad-AB_v3r1-2048x1154.png){: style="width:100px"} | | esp32-d0-16mb | ![esp32-d0-16mb](../firmware/installer/images/esp32-d0-16mb.jpg){: style="width:100px"} | | [Dig Octa](https://quinled.info/quinled-dig-octa):
![Dig Octa](https://quinled.info/wp-content/uploads/2024/10/20240924_141857-2048x1444.png){: style="width:100px"}
[Serg ESP32](https://www.tindie.com/products/serg74/esp32-wroom-usb-c-d1-mini32-form-factor-board){:target="_blank"} and [Shield](https://www.tindie.com/products/serg74/wled-shield-board-for-addressable-leds)
![Shield](https://cdn.tindiemedia.com/images/resize/44YE-eNQ9pJQUh_SmtwwfBXFbAE=/p/fit-in/1370x912/filters:fill(fff)/i/93057/products/2021-08-14T14%3A44%3A14.418Z-shield_v3-1.jpg?1628927139){: style="width:100px"} | -| esp32-d0-pico2 | ![esp32-d0-pico2](../firmware/installer/images/esp32-d0-pico2.jpg){: style="width:100px"} | | [DigNext2](https://quinled.info/dig-next-2):
![DigNext2](https://quinled.info/wp-content/uploads/2026/01/P1087754-Enhanced-NR-2560x1358.jpg){: style="width:100px"} | +| [esp32-d0-pico2](https://documentation.espressif.com/esp32-pico-mini-02_datasheet_en.pdf) | ![esp32-d0-pico2](../firmware/installer/images/esp32-d0-pico2.jpg){: style="width:100px"} | | [DigNext2](https://quinled.info/dig-next-2):
![DigNext2](https://quinled.info/wp-content/uploads/2026/01/P1087754-Enhanced-NR-2560x1358.jpg){: style="width:100px"} | | esp32-s3-n8r8v | ![esp32-s3-n8r8v](../firmware/installer/images/esp32-s3-n8r8v.jpg){: style="width:100px"} | | SE-16p
![SE-16p](../firmware/installer/images/esp32-s3-stephanelec-16p.jpg){: style="width:100px"} | | esp32-s3-n16r8v | ![esp32-s3-n16r8v](../firmware/installer/images/esp32-s3-n8r8v.jpg){: style="width:100px"} | | [Ali*](https://s.click.aliexpress.com/e/_DBAtJ2H){:target="_blank"} | | esp32-s3-atoms3r | ![esp32-s3-atoms3r](../firmware/installer/images/esp32-s3-atoms3r.jpg){: style="width:100px"} | | [M5Stack store](https://shop.m5stack.com/products/atoms3r-dev-kit){:target="_blank"} | diff --git a/docs/moonlight/modifiers.md b/docs/moonlight/modifiers.md index 8b1f3e04d..db2bbbcb9 100644 --- a/docs/moonlight/modifiers.md +++ b/docs/moonlight/modifiers.md @@ -22,6 +22,6 @@ The following Modifiers are defined in MoonLight. Some found there origin in WLE | Rotate | ![Rotate](https://github.com/user-attachments/assets/c622a9df-318a-4f83-81c0-f5a5c7bafb7b) | Rotate | | | Checkerboard | ![Checkerboard](https://github.com/user-attachments/assets/54970267-35af-406c-9558-c1f4219a71c0) | Checkerboard | | | Pinwheel 🧊 | ![PinWheel](https://github.com/user-attachments/assets/e5dbadbe-eeb1-41e5-b197-ec4bd5366aea) | PinWheel | Projects 1D/2D effects onto 2D/3D layouts in a pinwheel pattern.
**Swirl**: bend the pinwheel
**Rotation Symmetry**: rotational symmetry of the pattern
**Petals** Virtual width
**Ztwist** twist the pattern along the z-axis
Height: distance from center to corner | -| RippleYZ 🧊 | ![RippleYZ](https://github.com/user-attachments/assets/0918efac-6367-420f-b0e3-d796d9551953) | RippleYZ | 1D/2D effect will be rippled to 2D/3D (🚨)
Shrink: shrinks the original size towards Y and Z, towardsY: copies X into Y, towardsZ: copies XY into Z | +| RippleXZ 🧊 | ![RippleXZ](https://github.com/user-attachments/assets/0918efac-6367-420f-b0e3-d796d9551953) | RippleXZ | 1D/2D effect will be rippled to 2D/3D (🚨)
Shrink: shrinks the original size towards X and Z, towardsX: copies Y into X, towardsZ: copies XY into Z | 🚨: some effects already do this theirselves e.g. FreqMatrix runs on 1D but copies to 2D and 3D if size allows. diff --git a/firmware/esp32-d0.ini b/firmware/esp32-d0.ini index effd78196..7d64f2cc3 100644 --- a/firmware/esp32-d0.ini +++ b/firmware/esp32-d0.ini @@ -115,10 +115,10 @@ lib_deps = ${env.lib_deps} ; Flash: [======= ] 65.9% (used 2073762 bytes from 3145728 bytes) -[env:esp32-pico-2] +[env:esp32-pico2] extends = env:esp32dev -board = esp32-pico-devkitm-2 ; https://github.com/platformio/platform-espressif32/blob/3c076807e1f55b90799b50b946e76a0508e97778/boards/esp32-pico-devkitm-2.json#L8 -board_build.partitions = boards/ESP32_8MB.csv +board = esp32-pico-devkitm-2 ; https://github.com/platformio/platform-espressif32/blob/master/boards/esp32-pico-devkitm-2.json +board_build.partitions = default_8MB.csv ; boards/ESP32_8MB.csv build_flags = ${esp32-d0-base.build_flags} -D HTTPD_STACK_SIZE=6144 lib_deps = ${esp32-d0-base.lib_deps} \ No newline at end of file diff --git a/misc/livescripts/E_lines.sc b/misc/livescripts/E_lines.sc index e1b45fd87..cbfdf4646 100644 --- a/misc/livescripts/E_lines.sc +++ b/misc/livescripts/E_lines.sc @@ -1,5 +1,5 @@ void loop() { - fadeToBlackBy(255); + fadeToBlackBy(100); int x = millis() / 100; for (int y = 0; y < height; y++) { setRGB(y*width+x%width, CRGB(255,0,0)); diff --git a/platformio.ini b/platformio.ini index 297b29066..af2808636 100644 --- a/platformio.ini +++ b/platformio.ini @@ -56,7 +56,7 @@ build_flags = -D BUILD_TARGET=\"$PIOENV\" -D APP_NAME=\"MoonLight\" ; 🌙 Must only contain characters from [a-zA-Z0-9-_] as this is converted into a filename -D APP_VERSION=\"0.7.1\" ; semver compatible version string - -D APP_DATE=\"20260114\" ; 🌙 + -D APP_DATE=\"20260115\" ; 🌙 -D PLATFORM_VERSION=\"pioarduino-55.03.35\" ; 🌙 make sure it matches with above plaftform diff --git a/src/MoonBase/Utilities.h b/src/MoonBase/Utilities.h index 602ba360f..c2e6841b7 100644 --- a/src/MoonBase/Utilities.h +++ b/src/MoonBase/Utilities.h @@ -208,6 +208,18 @@ T* reallocMB(T* p, size_t n, const char* name = nullptr) { return res; } +template +void reallocMB2(T* &p, size_t &pSize, size_t n, const char* name = nullptr) { + T* res = (T*)heap_caps_realloc_prefer(p, n * sizeof(T), 2, MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT); // calloc is malloc + memset(0); + if (res) { + // EXT_LOGD(MB_TAG, "Re-Allocated %s: %d x %d bytes in %s s:%d", name?name:"x", n, sizeof(T), isInPSRAM(res)?"PSRAM":"RAM", heap_caps_get_allocated_size(res)); + p = res; + pSize = n; + } else { + EXT_LOGE(MB_TAG, "heap_caps_malloc for %s of %d x %d not succeeded, keeping old %d", name?name:"x", n, sizeof(T), pSize); + } +} + // free memory template void freeMB(T*& p, const char* name = nullptr) { diff --git a/src/MoonLight/Layers/VirtualLayer.cpp b/src/MoonLight/Layers/VirtualLayer.cpp index 5572da2a4..e97b08bd7 100644 --- a/src/MoonLight/Layers/VirtualLayer.cpp +++ b/src/MoonLight/Layers/VirtualLayer.cpp @@ -324,14 +324,7 @@ void VirtualLayer::fill_rainbow(const uint8_t initialhue, const uint8_t deltahue void VirtualLayer::createMappingTableAndAddOneToOne() { if (mappingTableSize != size.x * size.y * size.z) { EXT_LOGD(ML_TAG, "Allocating mappingTable: nrOfLights=%d, sizeof(PhysMap)=%d, total bytes=%d", size.x * size.y * size.z, sizeof(PhysMap), size.x * size.y * size.z * sizeof(PhysMap)); - PhysMap* newTable = reallocMB(mappingTable, size.x * size.y * size.z, "mappingTable"); - if (newTable) { - mappingTable = newTable; - EXT_LOGD(ML_TAG, "realloc mappingTable %d -> %dx%dx%d", mappingTableSize, size.x, size.y, size.z); - mappingTableSize = size.x * size.y * size.z; - } else { - EXT_LOGW(ML_TAG, "realloc mappingTable failed keeping oldSize %d", mappingTableSize); - } + reallocMB2(mappingTable, mappingTableSize, size.x * size.y * size.z, "mappingTable"); } if (mappingTable && mappingTableSize) memset(mappingTable, 0, mappingTableSize * sizeof(PhysMap)); // on layout, set mappingTable to default PhysMap diff --git a/src/MoonLight/Layers/VirtualLayer.h b/src/MoonLight/Layers/VirtualLayer.h index 4854a023c..0048438ff 100644 --- a/src/MoonLight/Layers/VirtualLayer.h +++ b/src/MoonLight/Layers/VirtualLayer.h @@ -78,7 +78,7 @@ class VirtualLayer { // they will be reused to avoid fragmentation PhysMap* mappingTable = nullptr; - nrOfLights_t mappingTableSize = 0; + size_t mappingTableSize = 0; std::vector, VectorRAMAllocator > > mappingTableIndexes; nrOfLights_t mappingTableIndexesSizeUsed = 0; diff --git a/src/MoonLight/Modules/ModuleEffects.h b/src/MoonLight/Modules/ModuleEffects.h index a6a2a1616..449d90a0b 100644 --- a/src/MoonLight/Modules/ModuleEffects.h +++ b/src/MoonLight/Modules/ModuleEffects.h @@ -156,7 +156,7 @@ class ModuleEffects : public NodeManager { addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); - addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); // find all the .sc files on FS File rootFolder = ESPFS.open("/"); @@ -252,7 +252,7 @@ class ModuleEffects : public NodeManager { if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); - if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); #if FT_LIVESCRIPT if (!node) { diff --git a/src/MoonLight/Nodes/Effects/E_MoonLight.h b/src/MoonLight/Nodes/Effects/E_MoonLight.h index 2ae138c36..7bce32266 100644 --- a/src/MoonLight/Nodes/Effects/E_MoonLight.h +++ b/src/MoonLight/Nodes/Effects/E_MoonLight.h @@ -327,7 +327,7 @@ class ScrollingTextEffect : public Node { } void loop() override { - layer->fadeToBlackBy(); + layer->fadeToBlackBy(100); #define nrOfChoices 7 uint8_t choice; @@ -1667,8 +1667,9 @@ class MarioTestEffect : public Node { class RingEffect : public Node { protected: - void setRing(int ring, CRGB colour) { // so britisch ;-) - layer->setRGB(Coord3D(0, ring), colour); // 1D effect on y-axis (default) + void setRing(int ring, CRGB colour) { // so britisch ;-) + for (int x = 0; x < layer->size.x; x++) + for (int z = 0; z < layer->size.z; z++) layer->setRGB(Coord3D(x, ring, z), colour); // 1D effect on y-axis (default) } }; @@ -1681,26 +1682,21 @@ class RingRandomFlowEffect : public RingEffect { // void setup() override {} //so no palette control is created uint8_t* hue = nullptr; + size_t hueSize = 0; ~RingRandomFlowEffect() { if (hue) freeMB(hue, name()); } - void onSizeChanged(const Coord3D& prevSize) override { - if (hue) freeMB(hue, name()); - hue = allocMB(layer->size.y, name()); - if (!hue) { - EXT_LOGE(ML_TAG, "allocate hue failed"); - } - } + void onSizeChanged(const Coord3D& prevSize) override { reallocMB2(hue, hueSize, layer->size.y, "hue"); } void loop() override { if (hue) { hue[0] = random(0, 255); - for (int r = 0; r < layer->size.y; r++) { + for (int r = 0; r < hueSize; r++) { setRing(r, CHSV(hue[r], 255, 255)); } - for (int r = (layer->size.y - 1); r >= 1; r--) { + for (int r = (hueSize - 1); r >= 1; r--) { hue[r] = hue[(r - 1)]; // set this ruing based on the inner } // FastLED.delay(SPEED); diff --git a/src/MoonLight/Nodes/Effects/E_MoonModules.h b/src/MoonLight/Nodes/Effects/E_MoonModules.h index cf72a8ce7..322e58ba7 100644 --- a/src/MoonLight/Nodes/Effects/E_MoonModules.h +++ b/src/MoonLight/Nodes/Effects/E_MoonModules.h @@ -181,7 +181,7 @@ class GameOfLifeEffect : public Node { } void onSizeChanged(const Coord3D& prevSize) override { - EXT_LOGW(ML_TAG, "GameOfLife onSizeChanged %d,%d,%d -> %d,%d,%d", prevSize.x, prevSize.y, prevSize.z, layer->size.x, layer->size.y, layer->size.z); + // EXT_LOGW(ML_TAG, "GameOfLife onSizeChanged %d,%d,%d -> %d,%d,%d", prevSize.x, prevSize.y, prevSize.z, layer->size.x, layer->size.y, layer->size.z); if (cells) freeMB(cells, name()); if (futureCells) freeMB(futureCells, name()); @@ -298,7 +298,7 @@ class GameOfLifeEffect : public Node { } else if (!cellValue && birthNumbers[neighbors]) { // Reproduction setBitValue(futureCells, cIndex, true); - uint8_t colorIndex = nColors[random8(colorCount)]; + uint8_t colorIndex = (colorCount > 0) ? nColors[random8(colorCount)] : random8(); if (random8(100) < mutation) colorIndex = random8(); cellColors[cIndex] = colorIndex; layer->setRGB(cPos, colorByAge ? CRGB::Green : ColorFromPalette(layerP.palette, colorIndex)); diff --git a/src/MoonLight/Nodes/Effects/E_WLED.h b/src/MoonLight/Nodes/Effects/E_WLED.h index cc02a8cf0..995387371 100644 --- a/src/MoonLight/Nodes/Effects/E_WLED.h +++ b/src/MoonLight/Nodes/Effects/E_WLED.h @@ -26,22 +26,13 @@ class BouncingBallsEffect : public Node { } Ball (*balls)[maxNumBalls] = nullptr; //[maxColumns][maxNumBalls]; - uint16_t ballsSize = 0; + size_t ballsSize = 0; ~BouncingBallsEffect() override { if (balls) freeMB(balls, name()); } - void onSizeChanged(const Coord3D& prevSize) override { - Ball(*newAlloc)[maxNumBalls] = reallocMB(balls, layer->size.x, name()); - - if (newAlloc) { - balls = newAlloc; - ballsSize = layer->size.x; - } else { - EXT_LOGE(ML_TAG, "(re)allocate balls failed"); - } - } + void onSizeChanged(const Coord3D& prevSize) override { reallocMB2(balls, ballsSize, layer->size.x, "balls"); } void loop() override { if (!balls) return; @@ -58,7 +49,7 @@ class BouncingBallsEffect : public Node { // for (size_t i = 0; i < maxNumBalls; i++) balls[i].lastBounceTime = time; // } - for (int x = 0; x < MIN(layer->size.x, ballsSize); x++) { + for (int x = 0; x < ballsSize; x++) { for (size_t i = 0; i < MIN(numBalls, maxNumBalls); i++) { float timeSinceLastBounce = (time - balls[x][i].lastBounceTime) / ((255 - grav) / 64 + 1); float timeSec = timeSinceLastBounce / 1000.0f; @@ -332,21 +323,13 @@ class GEQEffect : public Node { } uint16_t* previousBarHeight = nullptr; - uint8_t previousBarHeightSize = 0; + size_t previousBarHeightSize = 0; ~GEQEffect() { if (previousBarHeight) freeMB(previousBarHeight, name()); } - void onSizeChanged(const Coord3D& prevSize) override { - uint16_t* newAlloc = reallocMB(previousBarHeight, layer->size.x); - if (newAlloc) { - previousBarHeight = newAlloc; - previousBarHeightSize = layer->size.x; - } else { - EXT_LOGE(ML_TAG, "(re)allocate previousBarHeight failed"); - } - } + void onSizeChanged(const Coord3D& prevSize) override { reallocMB2(previousBarHeight, previousBarHeightSize, layer->size.x, "previousBarHeight"); } void loop() override { const int NUM_BANDS = NUM_GEQ_CHANNELS; // ::map(layer->custom1, 0, 255, 1, 16); @@ -568,7 +551,7 @@ class PacManEffect : public Node { } pacmancharacters_t* character = nullptr; - uint8_t nrOfCharacters = 0; + size_t nrOfCharacters = 0; ~PacManEffect() { if (character) freeMB(character, name()); @@ -597,13 +580,7 @@ class PacManEffect : public Node { EXT_LOGD(ML_TAG, "#l:%d #pd:%d #g:%d #pd:%d", layer->nrOfLights, numPowerDotsControl, numGhosts, numPowerDots); - pacmancharacters_t* newAlloc = reallocMB(character, numGhosts + numPowerDots + 1); // +1 is the PacMan character - if (newAlloc) { - character = newAlloc; - nrOfCharacters = numGhosts + numPowerDots + 1; - } else { - EXT_LOGE(ML_TAG, "(re)allocate character failed"); // keep old (if existed) - } + reallocMB2(character, nrOfCharacters, numGhosts + numPowerDots + 1, "character"); // +1 is the PacMan character if (nrOfCharacters > 0) { character[PACMAN].color = CRGB::Yellow; @@ -994,16 +971,11 @@ class TetrixEffect : public Node { } Tetris* drops = nullptr; - uint16_t nrOfDrops = 0; + size_t nrOfDrops = 0; void onSizeChanged(const Coord3D& prevSize) override { - Tetris* newAlloc = reallocMB(drops, layer->size.x); - if (newAlloc) { - drops = newAlloc; - nrOfDrops = layer->size.x; - } else { - EXT_LOGE(ML_TAG, "(re)allocate drops failed"); // keep old (if existed) - } + reallocMB2(drops, nrOfDrops, layer->size.x, "drops"); + for (int i = 0; i < nrOfDrops; i++) { drops[i].stack = 0; // reset brick stack size drops[i].step = millis() + 2000; // start by fading out strip @@ -1340,7 +1312,7 @@ class OctopusEffect : public Node { Coord3D prevLedSize; Map_t* rMap = nullptr; - uint16_t rMapSize = 0; + size_t rMapSize = 0; uint32_t step; ~OctopusEffect() { @@ -1363,16 +1335,7 @@ class OctopusEffect : public Node { } } - void onSizeChanged(const Coord3D& prevSize) override { - Map_t* newAlloc = reallocMB(rMap, layer->size.x * layer->size.y); - if (newAlloc) { - rMap = newAlloc; - rMapSize = layer->size.x * layer->size.y; - setRMap(); - } else { - EXT_LOGE(ML_TAG, "(re)allocate rMap failed"); - } - } + void onSizeChanged(const Coord3D& prevSize) override { reallocMB2(rMap, rMapSize, layer->size.x * layer->size.y, "rMap"); } void loop() override { if (rMap) { // check if rMap allocation successful @@ -1745,25 +1708,19 @@ class RainEffect : public Node { } Spark* drops = nullptr; - uint16_t nrOfDrops = 0; + size_t nrOfDrops = 0; ~RainEffect() override { if (drops) freeMB(drops, name()); } void onSizeChanged(const Coord3D& prevSize) override { - Spark* newAlloc = reallocMB(drops, layer->size.x); + reallocMB2(drops, nrOfDrops, layer->size.x, "drops"); - if (newAlloc) { - drops = newAlloc; - nrOfDrops = layer->size.x; - for (int x = 0; x < layer->size.x; x++) { - drops[x].pos = 0; - drops[x].col = 0; - drops[x].vel = 0; - } - } else { - EXT_LOGE(ML_TAG, "(re)allocate drops failed"); + for (int x = 0; x < nrOfDrops; x++) { + drops[x].pos = 0; + drops[x].col = 0; + drops[x].vel = 0; } } @@ -1823,25 +1780,19 @@ class DripEffect : public Node { } Spark (*drops)[maxNumDrops] = nullptr; //[maxColumns][maxNumBalls]; - uint16_t nrOfDrops = 0; + size_t nrOfDrops = 0; ~DripEffect() override { if (drops) freeMB(drops, name()); } void onSizeChanged(const Coord3D& prevSize) override { - Spark(*newAlloc)[maxNumDrops] = reallocMB(drops, layer->size.x); + reallocMB2(drops, nrOfDrops, layer->size.x, "drops"); - if (newAlloc) { - drops = newAlloc; - nrOfDrops = layer->size.x; - for (int x = 0; x < layer->size.x; x++) { - for (int j = 0; j < maxNumDrops; j++) { - drops[x][j].colIndex = init; // Set to init so loop() will initialize properly - } + for (int x = 0; x < nrOfDrops; x++) { + for (int j = 0; j < maxNumDrops; j++) { + drops[x][j].colIndex = init; // Set to init so loop() will initialize properly } - } else { - EXT_LOGE(ML_TAG, "(re)allocate drops failed"); } } diff --git a/src/MoonLight/Nodes/Modifiers/M_MoonLight.h b/src/MoonLight/Nodes/Modifiers/M_MoonLight.h index ce597d991..85c2fc5ac 100644 --- a/src/MoonLight/Nodes/Modifiers/M_MoonLight.h +++ b/src/MoonLight/Nodes/Modifiers/M_MoonLight.h @@ -195,19 +195,19 @@ class PinwheelModifier : public Node { }; // Idea and first implementation (WLEDMM Art-Net) by @Troy -class RippleYZModifier : public Node { +class RippleXZModifier : public Node { public: - static const char* name() { return "RippleYZ"; } + static const char* name() { return "RippleXZ"; } static uint8_t dim() { return _3D; } static const char* tags() { return "💎💫"; } bool shrink = true; - bool towardsY = true; + bool towardsX = true; bool towardsZ = false; void setup() override { addControl(shrink, "shrink", "checkbox"); - addControl(towardsY, "towardsY", "checkbox"); + addControl(towardsX, "towardsX", "checkbox"); addControl(towardsZ, "towardsZ", "checkbox"); } @@ -220,25 +220,25 @@ class RippleYZModifier : public Node { // layer->size.y++; // layer->size.z++; if (shrink) { - if (towardsY) layer->size.y = 1; + if (towardsX) layer->size.x = 1; if (towardsZ) layer->size.z = 1; } } void modifyPosition(Coord3D& position) override { if (shrink) { - if (towardsY) position.y = 0; + if (towardsX) position.x = 0; if (towardsZ) position.z = 0; } } void loop() override { // 1D->2D: each Y is rippled through the X-axis - if (towardsY) { + if (towardsX) { if (layer->effectDimension == _1D && layer->layerDimension > _1D) { - for (int x = layer->size.x - 1; x >= 1; x--) { - for (int y = 0; y < layer->size.y; y++) { - layer->setRGB(Coord3D(x, y, 0), layer->getRGB(Coord3D(x - 1, y, 0))); + for (int y = layer->size.y - 1; y >= 1; y--) { + for (int x = 0; x < layer->size.x; x++) { + layer->setRGB(Coord3D(x, y, 0), layer->getRGB(Coord3D(x, y-1, 0))); } } } @@ -257,7 +257,7 @@ class RippleYZModifier : public Node { } } } -}; // RippleYZ +}; // RippleXZ // RotateModifier rotates the light position around the center of the layout. // It can flip the x and y coordinates, reverse the rotation direction, and alternate the rotation From bf6d78da7c2d3e27b4a321e43783aa3ccc59e788 Mon Sep 17 00:00:00 2001 From: ewowi Date: Fri, 16 Jan 2026 08:49:03 +0100 Subject: [PATCH 4/6] Small changes --- firmware/esp32-d0.ini | 4 +--- src/MoonLight/Nodes/Effects/E_MoonLight.h | 10 +++++----- src/MoonLight/Nodes/Effects/E_MoonModules.h | 18 ++++++++---------- src/MoonLight/Nodes/Effects/E_WLED.h | 14 +++++++------- src/MoonLight/Nodes/Modifiers/M_MoonLight.h | 4 ++-- 5 files changed, 23 insertions(+), 27 deletions(-) diff --git a/firmware/esp32-d0.ini b/firmware/esp32-d0.ini index 7d64f2cc3..2e0de27ea 100644 --- a/firmware/esp32-d0.ini +++ b/firmware/esp32-d0.ini @@ -37,7 +37,6 @@ lib_deps = ${esp32-d0-base.lib_deps} [env:esp32-d0-16mb] -extends = env:esp32dev board = esp32_16MB board_build.partitions = boards/ESP32_16MB_3MBFlash.csv ; standard for 16MB flash: 3MB firmware, 10 MB filesystem build_flags = ${esp32-d0-base.build_flags} @@ -115,8 +114,7 @@ lib_deps = ${env.lib_deps} ; Flash: [======= ] 65.9% (used 2073762 bytes from 3145728 bytes) -[env:esp32-pico2] -extends = env:esp32dev +[env:esp32-d0-pico2] board = esp32-pico-devkitm-2 ; https://github.com/platformio/platform-espressif32/blob/master/boards/esp32-pico-devkitm-2.json board_build.partitions = default_8MB.csv ; boards/ESP32_8MB.csv build_flags = ${esp32-d0-base.build_flags} diff --git a/src/MoonLight/Nodes/Effects/E_MoonLight.h b/src/MoonLight/Nodes/Effects/E_MoonLight.h index 7bce32266..8f322dbd9 100644 --- a/src/MoonLight/Nodes/Effects/E_MoonLight.h +++ b/src/MoonLight/Nodes/Effects/E_MoonLight.h @@ -69,10 +69,10 @@ class StarSkyEffect : public Node { ~StarSkyEffect() { resetStars(); } void resetStars() { - if (stars_indexes) freeMB(stars_indexes, name()); - if (stars_fade_dir) freeMB(stars_fade_dir, name()); - if (stars_brightness) freeMB(stars_brightness, name()); - if (stars_colors) freeMB(stars_colors, name()); + if (stars_indexes) freeMB(stars_indexes, "indexes"); + if (stars_fade_dir) freeMB(stars_fade_dir, "fade"); + if (stars_brightness) freeMB(stars_brightness, "brightness"); + if (stars_colors) freeMB(stars_colors, "colors"); stars_indexes = nullptr; stars_fade_dir = nullptr; stars_brightness = nullptr; @@ -1685,7 +1685,7 @@ class RingRandomFlowEffect : public RingEffect { size_t hueSize = 0; ~RingRandomFlowEffect() { - if (hue) freeMB(hue, name()); + if (hue) freeMB(hue, "hue"); } void onSizeChanged(const Coord3D& prevSize) override { reallocMB2(hue, hueSize, layer->size.y, "hue"); } diff --git a/src/MoonLight/Nodes/Effects/E_MoonModules.h b/src/MoonLight/Nodes/Effects/E_MoonModules.h index 322e58ba7..00d567c65 100644 --- a/src/MoonLight/Nodes/Effects/E_MoonModules.h +++ b/src/MoonLight/Nodes/Effects/E_MoonModules.h @@ -173,28 +173,26 @@ class GameOfLifeEffect : public Node { } int dataSize = 0; + size_t cellsSize = 0; ~GameOfLifeEffect() override { - if (cells) freeMB(cells, name()); - if (futureCells) freeMB(futureCells, name()); - if (cellColors) freeMB(cellColors, name()); + if (cells) freeMB(cells, "cells"); + if (futureCells) freeMB(futureCells, "futureCells"); + if (cellColors) freeMB(cellColors, "cellColors"); } void onSizeChanged(const Coord3D& prevSize) override { // EXT_LOGW(ML_TAG, "GameOfLife onSizeChanged %d,%d,%d -> %d,%d,%d", prevSize.x, prevSize.y, prevSize.z, layer->size.x, layer->size.y, layer->size.z); - if (cells) freeMB(cells, name()); - if (futureCells) freeMB(futureCells, name()); - if (cellColors) freeMB(cellColors, name()); - dataSize = (layer->size.x * layer->size.y * layer->size.z + 7) / 8; - cells = allocMB(dataSize, name()); - futureCells = allocMB(dataSize, name()); - cellColors = allocMB(layer->size.x * layer->size.y * layer->size.z, name()); + reallocMB2(cells, cellsSize, dataSize, "cells"); + reallocMB2(futureCells, cellsSize, dataSize, "futureCells"); + reallocMB2(cellColors, cellsSize, layer->size.x * layer->size.y * layer->size.z, "cellColors"); if (!cells || !futureCells || !cellColors) { EXT_LOGE(ML_TAG, "allocation of cells || !futureCells || !cellColors failed"); + // freeMB will be done when GOL is deleted return; } EXT_LOGD(ML_TAG, "allocation of cells futureCells cellColors successful %d %d", dataSize, layer->nrOfLights); diff --git a/src/MoonLight/Nodes/Effects/E_WLED.h b/src/MoonLight/Nodes/Effects/E_WLED.h index 995387371..cca5b1e08 100644 --- a/src/MoonLight/Nodes/Effects/E_WLED.h +++ b/src/MoonLight/Nodes/Effects/E_WLED.h @@ -29,7 +29,7 @@ class BouncingBallsEffect : public Node { size_t ballsSize = 0; ~BouncingBallsEffect() override { - if (balls) freeMB(balls, name()); + if (balls) freeMB(balls, "balls"); } void onSizeChanged(const Coord3D& prevSize) override { reallocMB2(balls, ballsSize, layer->size.x, "balls"); } @@ -326,7 +326,7 @@ class GEQEffect : public Node { size_t previousBarHeightSize = 0; ~GEQEffect() { - if (previousBarHeight) freeMB(previousBarHeight, name()); + if (previousBarHeight) freeMB(previousBarHeight, "previousBarHeight"); } void onSizeChanged(const Coord3D& prevSize) override { reallocMB2(previousBarHeight, previousBarHeightSize, layer->size.x, "previousBarHeight"); } @@ -554,7 +554,7 @@ class PacManEffect : public Node { size_t nrOfCharacters = 0; ~PacManEffect() { - if (character) freeMB(character, name()); + if (character) freeMB(character, "character"); } void onSizeChanged(const Coord3D& prevSize) override { initializePacMan(); } @@ -984,7 +984,7 @@ class TetrixEffect : public Node { } ~TetrixEffect() override { - if (drops) freeMB(drops, name()); + if (drops) freeMB(drops, "drops"); }; void loop() override { @@ -1316,7 +1316,7 @@ class OctopusEffect : public Node { uint32_t step; ~OctopusEffect() { - if (rMap) freeMB(rMap, name()); + if (rMap) freeMB(rMap, "rMap"); } void setRMap() { @@ -1711,7 +1711,7 @@ class RainEffect : public Node { size_t nrOfDrops = 0; ~RainEffect() override { - if (drops) freeMB(drops, name()); + if (drops) freeMB(drops, "drops"); } void onSizeChanged(const Coord3D& prevSize) override { @@ -1783,7 +1783,7 @@ class DripEffect : public Node { size_t nrOfDrops = 0; ~DripEffect() override { - if (drops) freeMB(drops, name()); + if (drops) freeMB(drops, "drops"); } void onSizeChanged(const Coord3D& prevSize) override { diff --git a/src/MoonLight/Nodes/Modifiers/M_MoonLight.h b/src/MoonLight/Nodes/Modifiers/M_MoonLight.h index 85c2fc5ac..8817339ac 100644 --- a/src/MoonLight/Nodes/Modifiers/M_MoonLight.h +++ b/src/MoonLight/Nodes/Modifiers/M_MoonLight.h @@ -233,7 +233,7 @@ class RippleXZModifier : public Node { } void loop() override { - // 1D->2D: each Y is rippled through the X-axis + // 1D->2D: ripple effect propagates along the X-axis if (towardsX) { if (layer->effectDimension == _1D && layer->layerDimension > _1D) { for (int y = layer->size.y - 1; y >= 1; y--) { @@ -244,7 +244,7 @@ class RippleXZModifier : public Node { } } - // 2D->3D: each XY plane is rippled through the z-axis + // 2D->3D: each XY plane propagates along the Z-axis if (towardsZ) { // not relevant for 2D fixtures if (layer->effectDimension < _3D && layer->layerDimension == _3D) { for (int z = layer->size.z - 1; z >= 1; z--) { From dfbe14bd7403fa842303ebaf52479b4b8ae356ad Mon Sep 17 00:00:00 2001 From: ewowi Date: Fri, 16 Jan 2026 10:45:16 +0100 Subject: [PATCH 5/6] RippleXZ fix and GOL memory --- platformio.ini | 2 +- src/MoonLight/Nodes/Effects/E_MoonModules.h | 70 +++++++++++---------- src/MoonLight/Nodes/Modifiers/M_MoonLight.h | 6 +- 3 files changed, 40 insertions(+), 38 deletions(-) diff --git a/platformio.ini b/platformio.ini index af2808636..df2540490 100644 --- a/platformio.ini +++ b/platformio.ini @@ -56,7 +56,7 @@ build_flags = -D BUILD_TARGET=\"$PIOENV\" -D APP_NAME=\"MoonLight\" ; 🌙 Must only contain characters from [a-zA-Z0-9-_] as this is converted into a filename -D APP_VERSION=\"0.7.1\" ; semver compatible version string - -D APP_DATE=\"20260115\" ; 🌙 + -D APP_DATE=\"20260116\" ; 🌙 -D PLATFORM_VERSION=\"pioarduino-55.03.35\" ; 🌙 make sure it matches with above plaftform diff --git a/src/MoonLight/Nodes/Effects/E_MoonModules.h b/src/MoonLight/Nodes/Effects/E_MoonModules.h index 00d567c65..9fea467a6 100644 --- a/src/MoonLight/Nodes/Effects/E_MoonModules.h +++ b/src/MoonLight/Nodes/Effects/E_MoonModules.h @@ -147,7 +147,7 @@ class GameOfLifeEffect : public Node { if (!cells || !futureCells || !cellColors) return; // Setup Grid - memset(cells, 0, dataSize); + memset(cells, 0, cellsSize); memset(cellColors, 0, layer->size.x * layer->size.y * layer->size.z); for (int x = 0; x < layer->size.x; x++) @@ -162,18 +162,18 @@ class GameOfLifeEffect : public Node { // layer->setRGB(Coord3D(x,y,z), bgColor); // Color set in redraw loop } } - memcpy(futureCells, cells, dataSize); + memcpy(futureCells, cells, cellsSize); soloGlider = false; // Change CRCs - uint16_t crc = crc16((const unsigned char*)cells, dataSize); + uint16_t crc = crc16((const unsigned char*)cells, cellsSize); oscillatorCRC = crc, spaceshipCRC = crc, cubeGliderCRC = crc; gliderLength = lcm(layer->size.y, layer->size.x) * 4; cubeGliderLength = gliderLength * 6; // Change later for rectangular cuboid } - int dataSize = 0; size_t cellsSize = 0; + size_t cellColorsSize = 0; ~GameOfLifeEffect() override { if (cells) freeMB(cells, "cells"); @@ -184,18 +184,16 @@ class GameOfLifeEffect : public Node { void onSizeChanged(const Coord3D& prevSize) override { // EXT_LOGW(ML_TAG, "GameOfLife onSizeChanged %d,%d,%d -> %d,%d,%d", prevSize.x, prevSize.y, prevSize.z, layer->size.x, layer->size.y, layer->size.z); - dataSize = (layer->size.x * layer->size.y * layer->size.z + 7) / 8; - - reallocMB2(cells, cellsSize, dataSize, "cells"); - reallocMB2(futureCells, cellsSize, dataSize, "futureCells"); - reallocMB2(cellColors, cellsSize, layer->size.x * layer->size.y * layer->size.z, "cellColors"); + reallocMB2(cells, cellsSize, (layer->size.x * layer->size.y * layer->size.z + 7) / 8, "cells"); + reallocMB2(futureCells, cellsSize, cellsSize, "futureCells"); // take the cellsSize of cells (can be smaller then asked for if not enough memory) + reallocMB2(cellColors, cellColorsSize, layer->size.x * layer->size.y * layer->size.z, "cellColors"); if (!cells || !futureCells || !cellColors) { EXT_LOGE(ML_TAG, "allocation of cells || !futureCells || !cellColors failed"); // freeMB will be done when GOL is deleted return; } - EXT_LOGD(ML_TAG, "allocation of cells futureCells cellColors successful %d %d", dataSize, layer->nrOfLights); + EXT_LOGD(ML_TAG, "allocation of cells futureCells cellColors successful %d %d", cellsSize, layer->nrOfLights); startNewGameOfLife(); } @@ -220,30 +218,34 @@ class GameOfLifeEffect : public Node { bool blurDead = step > millis() && !fadedBackground; // Redraw Loop if (generation <= 1 || blurDead) { // Readd overlay support when implemented - for (int x = 0; x < layer->size.x; x++) - for (int y = 0; y < layer->size.y; y++) + for (int x = 0; x < layer->size.x; x++) { + for (int y = 0; y < layer->size.y; y++) { for (int z = 0; z < layer->size.z; z++) { uint16_t cIndex = layer->XYZUnModified(Coord3D(x, y, z)); // Current cell index (bit grid lookup) - Coord3D cLocPos = Coord3D(x, y, z); - uint16_t cLoc = layer->XYZ(cLocPos); // Current cell location (led index) - if (!layer->isMapped(cIndex)) continue; - bool alive = getBitValue(cells, cIndex); - bool recolor = (alive && generation == 1 && cellColors[cIndex] == 0 && !random(16)); // Palette change or Initial Color - // Redraw alive if palette changed, spawn initial colors randomly, age alive cells while paused - if (alive && recolor) { - cellColors[cIndex] = random8(1, 255); - layer->setRGB(cLoc, colorByAge ? CRGB::Green : ColorFromPalette(layerP.palette, cellColors[cIndex])); - } else if (alive && colorByAge && !generation) - layer->blendColor(cLoc, CRGB::Red, 248); // Age alive cells while paused - else if (alive && cellColors[cIndex] != 0) - layer->setRGB(cLoc, colorByAge ? CRGB::Green : ColorFromPalette(layerP.palette, cellColors[cIndex])); - // Redraw dead if palette changed, blur paused game, fade on newgame - // if (!alive && (paletteChanged || disablePause)) layer->setRGB(cLoc, bgColor); // Remove blended dead cells - else if (!alive && blurDead) - layer->blendColor(cLoc, bgColor, blur); // Blend dead cells while paused - else if (!alive && generation == 1) - layer->blendColor(cLoc, bgColor, 248); // Fade dead on new game + if (cIndex < cellColorsSize) { + Coord3D cLocPos = Coord3D(x, y, z); + uint16_t cLoc = layer->XYZ(cLocPos); // Current cell location (led index) + if (!layer->isMapped(cIndex)) continue; + bool alive = getBitValue(cells, cIndex); + bool recolor = (alive && generation == 1 && cellColors[cIndex] == 0 && !random(16)); // Palette change or Initial Color + // Redraw alive if palette changed, spawn initial colors randomly, age alive cells while paused + if (alive && recolor) { + cellColors[cIndex] = random8(1, 255); + layer->setRGB(cLoc, colorByAge ? CRGB::Green : ColorFromPalette(layerP.palette, cellColors[cIndex])); + } else if (alive && colorByAge && !generation) + layer->blendColor(cLoc, CRGB::Red, 248); // Age alive cells while paused + else if (alive && cellColors[cIndex] != 0) + layer->setRGB(cLoc, colorByAge ? CRGB::Green : ColorFromPalette(layerP.palette, cellColors[cIndex])); + // Redraw dead if palette changed, blur paused game, fade on newgame + // if (!alive && (paletteChanged || disablePause)) layer->setRGB(cLoc, bgColor); // Remove blended dead cells + else if (!alive && blurDead) + layer->blendColor(cLoc, bgColor, blur); // Blend dead cells while paused + else if (!alive && generation == 1) + layer->blendColor(cLoc, bgColor, 248); // Fade dead on new game + } } + } + } } // if (!speed || step > millis() || millis() - step < 1000 / speed) return; // Check if enough time has passed for updating @@ -323,14 +325,14 @@ class GameOfLifeEffect : public Node { soloGlider = true; else soloGlider = false; - memcpy(cells, futureCells, dataSize); - uint16_t crc = crc16((const unsigned char*)cells, dataSize); + memcpy(cells, futureCells, cellsSize); + uint16_t crc = crc16((const unsigned char*)cells, cellsSize); bool repetition = false; if (!aliveCount || crc == oscillatorCRC || crc == spaceshipCRC || crc == cubeGliderCRC) repetition = true; if ((repetition && infinite) || (infinite && !random8(50)) || (infinite && float(aliveCount) / (aliveCount + deadCount) < 0.05)) { placePentomino(futureCells, colorByAge); // Place R-pentomino/Glider if infinite mode is enabled - memcpy(cells, futureCells, dataSize); + memcpy(cells, futureCells, cellsSize); repetition = false; } if (repetition) { diff --git a/src/MoonLight/Nodes/Modifiers/M_MoonLight.h b/src/MoonLight/Nodes/Modifiers/M_MoonLight.h index 8817339ac..b155d033d 100644 --- a/src/MoonLight/Nodes/Modifiers/M_MoonLight.h +++ b/src/MoonLight/Nodes/Modifiers/M_MoonLight.h @@ -236,9 +236,9 @@ class RippleXZModifier : public Node { // 1D->2D: ripple effect propagates along the X-axis if (towardsX) { if (layer->effectDimension == _1D && layer->layerDimension > _1D) { - for (int y = layer->size.y - 1; y >= 1; y--) { - for (int x = 0; x < layer->size.x; x++) { - layer->setRGB(Coord3D(x, y, 0), layer->getRGB(Coord3D(x, y-1, 0))); + for (int x = layer->size.x - 1; x >= 1; x--) { + for (int y = 0; y < layer->size.y; y++) { + layer->setRGB(Coord3D(x, y, 0), layer->getRGB(Coord3D(x - 1, y, 0))); } } } From ffc76fd663deb0a39a329145190e87ce689f5122 Mon Sep 17 00:00:00 2001 From: ewowi Date: Fri, 16 Jan 2026 11:46:27 +0100 Subject: [PATCH 6/6] use nrOfLights_t, GOL mem checks --- src/MoonLight/Layers/VirtualLayer.h | 2 +- src/MoonLight/Nodes/Effects/E_MoonModules.h | 169 +++++++++++--------- src/MoonLight/Nodes/Effects/E_WLED.h | 8 +- 3 files changed, 99 insertions(+), 80 deletions(-) diff --git a/src/MoonLight/Layers/VirtualLayer.h b/src/MoonLight/Layers/VirtualLayer.h index 0048438ff..57be12db6 100644 --- a/src/MoonLight/Layers/VirtualLayer.h +++ b/src/MoonLight/Layers/VirtualLayer.h @@ -227,7 +227,7 @@ class VirtualLayer { void addLight(Coord3D position); // checks if a virtual light is mapped to a physical light (use with XY() or XYZ() to get the indexV) - bool isMapped(int indexV) const { return oneToOneMapping || indexV < mappingTableSize && (mappingTable[indexV].mapType == m_oneLight || mappingTable[indexV].mapType == m_moreLights); } + bool isMapped(nrOfLights_t indexV) const { return oneToOneMapping || indexV < mappingTableSize && (mappingTable[indexV].mapType == m_oneLight || mappingTable[indexV].mapType == m_moreLights); } void blur1d(fract8 blur_amount, uint16_t x = 0) { // todo: check updated in wled-MM diff --git a/src/MoonLight/Nodes/Effects/E_MoonModules.h b/src/MoonLight/Nodes/Effects/E_MoonModules.h index 9fea467a6..71a3789fd 100644 --- a/src/MoonLight/Nodes/Effects/E_MoonModules.h +++ b/src/MoonLight/Nodes/Effects/E_MoonModules.h @@ -32,16 +32,22 @@ class GameOfLifeEffect : public Node { for (int i = 0; i < 5; i++) { int nx = x + pattern[i][0]; int ny = y + pattern[i][1]; - if (getBitValue(futureCells, layer->XYZUnModified(Coord3D(nx, ny, z)))) { - canPlace = false; - break; + nrOfLights_t index = layer->XYZUnModified(Coord3D(nx, ny, z)); + if (index < dataSize * 8) { + if (getBitValue(futureCells, index)) { + canPlace = false; + break; + } } } if (canPlace || attempts == 99) { for (int i = 0; i < 5; i++) { int nx = x + pattern[i][0]; int ny = y + pattern[i][1]; - setBitValue(futureCells, layer->XYZUnModified(Coord3D(nx, ny, z)), true); + nrOfLights_t index = layer->XYZUnModified(Coord3D(nx, ny, z)); + if (index < dataSize * 8) { + setBitValue(futureCells, index, true); + } layer->setRGB(Coord3D(nx, ny, z), colorByAge ? CRGB::Green : color); } return; @@ -147,32 +153,36 @@ class GameOfLifeEffect : public Node { if (!cells || !futureCells || !cellColors) return; // Setup Grid - memset(cells, 0, cellsSize); - memset(cellColors, 0, layer->size.x * layer->size.y * layer->size.z); + memset(cells, 0, dataSize); + memset(cellColors, 0, cellColorsSize); for (int x = 0; x < layer->size.x; x++) for (int y = 0; y < layer->size.y; y++) for (int z = 0; z < layer->size.z; z++) { if (layer->layerDimension == _3D && !layer->isMapped(layer->XYZUnModified(Coord3D(x, y, z)))) continue; if (random8(100) < lifeChance) { - int index = layer->XYZUnModified(Coord3D(x, y, z)); - setBitValue(cells, index, true); - cellColors[index] = random8(1, 255); - layer->setRGB(Coord3D(x, y, z), colorByAge ? CRGB::Green : ColorFromPalette(layerP.palette, cellColors[index])); - // layer->setRGB(Coord3D(x,y,z), bgColor); // Color set in redraw loop + nrOfLights_t index = layer->XYZUnModified(Coord3D(x, y, z)); + if (index < dataSize * 8) { + setBitValue(cells, index, true); + } + if (index < cellColorsSize) { + cellColors[index] = random8(1, 255); + layer->setRGB(Coord3D(x, y, z), colorByAge ? CRGB::Green : ColorFromPalette(layerP.palette, cellColors[index])); + // layer->setRGB(Coord3D(x,y,z), bgColor); // Color set in redraw loop + } } } - memcpy(futureCells, cells, cellsSize); + memcpy(futureCells, cells, dataSize); soloGlider = false; // Change CRCs - uint16_t crc = crc16((const unsigned char*)cells, cellsSize); + uint16_t crc = crc16((const unsigned char*)cells, dataSize); oscillatorCRC = crc, spaceshipCRC = crc, cubeGliderCRC = crc; gliderLength = lcm(layer->size.y, layer->size.x) * 4; cubeGliderLength = gliderLength * 6; // Change later for rectangular cuboid } - size_t cellsSize = 0; + size_t dataSize = 0; size_t cellColorsSize = 0; ~GameOfLifeEffect() override { @@ -184,16 +194,16 @@ class GameOfLifeEffect : public Node { void onSizeChanged(const Coord3D& prevSize) override { // EXT_LOGW(ML_TAG, "GameOfLife onSizeChanged %d,%d,%d -> %d,%d,%d", prevSize.x, prevSize.y, prevSize.z, layer->size.x, layer->size.y, layer->size.z); - reallocMB2(cells, cellsSize, (layer->size.x * layer->size.y * layer->size.z + 7) / 8, "cells"); - reallocMB2(futureCells, cellsSize, cellsSize, "futureCells"); // take the cellsSize of cells (can be smaller then asked for if not enough memory) - reallocMB2(cellColors, cellColorsSize, layer->size.x * layer->size.y * layer->size.z, "cellColors"); + reallocMB2(cells, dataSize, (layer->size.x * layer->size.y * layer->size.z + 7) / 8, "cells"); + reallocMB2(futureCells, dataSize, dataSize, "futureCells"); // take the dataSize of cells (can be smaller then asked for if not enough memory) + reallocMB2(cellColors, cellColorsSize, layer->size.x * layer->size.y * layer->size.z, "cellColors"); // dataSize * 8 > cellColorsSize if alloc okay if (!cells || !futureCells || !cellColors) { EXT_LOGE(ML_TAG, "allocation of cells || !futureCells || !cellColors failed"); // freeMB will be done when GOL is deleted return; } - EXT_LOGD(ML_TAG, "allocation of cells futureCells cellColors successful %d %d", cellsSize, layer->nrOfLights); + EXT_LOGD(ML_TAG, "allocation of cells futureCells cellColors successful d:%d c:%d #ol:%d", dataSize, cellColorsSize, layer->nrOfLights); startNewGameOfLife(); } @@ -221,10 +231,10 @@ class GameOfLifeEffect : public Node { for (int x = 0; x < layer->size.x; x++) { for (int y = 0; y < layer->size.y; y++) { for (int z = 0; z < layer->size.z; z++) { - uint16_t cIndex = layer->XYZUnModified(Coord3D(x, y, z)); // Current cell index (bit grid lookup) + nrOfLights_t cIndex = layer->XYZUnModified(Coord3D(x, y, z)); // Current cell index (bit grid lookup) if (cIndex < cellColorsSize) { Coord3D cLocPos = Coord3D(x, y, z); - uint16_t cLoc = layer->XYZ(cLocPos); // Current cell location (led index) + nrOfLights_t cLoc = layer->XYZ(cLocPos); // Current cell location (led index) if (!layer->isMapped(cIndex)) continue; bool alive = getBitValue(cells, cIndex); bool recolor = (alive && generation == 1 && cellColors[cIndex] == 0 && !random(16)); // Palette change or Initial Color @@ -260,61 +270,70 @@ class GameOfLifeEffect : public Node { for (int y = 0; y < layer->size.y; y++) { for (int z = 0; z < layer->size.z; z++) { Coord3D cPos = Coord3D(x, y, z); - uint16_t cIndex = layer->XYZUnModified(cPos); - bool cellValue = getBitValue(cells, cIndex); - if (cellValue) - aliveCount++; - else - deadCount++; - if (zAxis && !layer->isMapped(cIndex)) continue; // Skip if not physical led on 3D fixtures - uint8_t neighbors = 0, colorCount = 0; - uint8_t nColors[9]; - - for (int i = -1; i <= 1; i++) - for (int j = -1; j <= 1; j++) - for (int k = -zAxis; k <= zAxis; k++) { - if (i == 0 && j == 0 && k == 0) continue; // Ignore itself - Coord3D nPos = Coord3D(x + i, y + j, z + k); - if (nPos.isOutofBounds(layer->size)) { - // Wrap is disabled when unchecked, for 3D fixtures, every 1500 generations, and solo gliders - if (disableWrap) continue; - nPos = (nPos + layer->size) % layer->size; // Wrap around 3D + nrOfLights_t cIndex = layer->XYZUnModified(cPos); + if (cIndex < dataSize * 8) { + bool cellValue = getBitValue(cells, cIndex); + if (cellValue) + aliveCount++; + else + deadCount++; + if (zAxis && !layer->isMapped(cIndex)) continue; // Skip if not physical led on 3D fixtures + uint8_t neighbors = 0, colorCount = 0; + uint8_t nColors[9]; + + for (int i = -1; i <= 1; i++) + for (int j = -1; j <= 1; j++) + for (int k = -zAxis; k <= zAxis; k++) { + if (i == 0 && j == 0 && k == 0) continue; // Ignore itself + Coord3D nPos = Coord3D(x + i, y + j, z + k); + if (nPos.isOutofBounds(layer->size)) { + // Wrap is disabled when unchecked, for 3D fixtures, every 1500 generations, and solo gliders + if (disableWrap) continue; + nPos = (nPos + layer->size) % layer->size; // Wrap around 3D + } + nrOfLights_t nIndex = layer->XYZUnModified(nPos); + if (nIndex < dataSize * 8) { + // Count neighbors and store up to 9 neighbor colors + if (getBitValue(cells, nIndex)) { + neighbors++; + if (cellValue || colorByAge) continue; // Skip if cell is alive (color already set) or color by age (colors are not used) + if (cellColors[nIndex] == 0) continue; // Skip if neighbor color is 0 (dead cell) + nColors[colorCount % 9] = cellColors[nIndex]; + colorCount++; + } + } } - uint16_t nIndex = layer->XYZUnModified(nPos); - // Count neighbors and store up to 9 neighbor colors - if (getBitValue(cells, nIndex)) { - neighbors++; - if (cellValue || colorByAge) continue; // Skip if cell is alive (color already set) or color by age (colors are not used) - if (cellColors[nIndex] == 0) continue; // Skip if neighbor color is 0 (dead cell) - nColors[colorCount % 9] = cellColors[nIndex]; - colorCount++; + // Rules of Life + if (cellValue && !surviveNumbers[neighbors]) { + // Loneliness or Overpopulation + setBitValue(futureCells, cIndex, false); + layer->blendColor(cPos, bgColor, blur); + } else if (!cellValue && birthNumbers[neighbors]) { + // Reproduction + setBitValue(futureCells, cIndex, true); + uint8_t colorIndex = (colorCount > 0) ? nColors[random8(colorCount)] : random8(); + if (random8(100) < mutation) colorIndex = random8(); + if (cIndex < cellColorsSize) { + cellColors[cIndex] = colorIndex; + } + layer->setRGB(cPos, colorByAge ? CRGB::Green : ColorFromPalette(layerP.palette, colorIndex)); + } else { + // Blending, fade dead cells further causing blurring effect to moving cells + if (!cellValue) { + if (fadedBackground) { + CRGB val = layer->getRGB(cPos); + if (fadedBackground < val.r + val.g + val.b) layer->blendColor(cPos, bgColor, blur); + } else + layer->blendColor(cPos, bgColor, blur); + } else { // alive + if (colorByAge) + layer->blendColor(cPos, CRGB::Red, 248); + else { + if (cIndex < cellColorsSize) { + layer->setRGB(cPos, ColorFromPalette(layerP.palette, cellColors[cIndex])); + } } } - // Rules of Life - if (cellValue && !surviveNumbers[neighbors]) { - // Loneliness or Overpopulation - setBitValue(futureCells, cIndex, false); - layer->blendColor(cPos, bgColor, blur); - } else if (!cellValue && birthNumbers[neighbors]) { - // Reproduction - setBitValue(futureCells, cIndex, true); - uint8_t colorIndex = (colorCount > 0) ? nColors[random8(colorCount)] : random8(); - if (random8(100) < mutation) colorIndex = random8(); - cellColors[cIndex] = colorIndex; - layer->setRGB(cPos, colorByAge ? CRGB::Green : ColorFromPalette(layerP.palette, colorIndex)); - } else { - // Blending, fade dead cells further causing blurring effect to moving cells - if (!cellValue) { - if (fadedBackground) { - CRGB val = layer->getRGB(cPos); - if (fadedBackground < val.r + val.g + val.b) layer->blendColor(cPos, bgColor, blur); - } else - layer->blendColor(cPos, bgColor, blur); - } else { // alive - if (colorByAge) - layer->blendColor(cPos, CRGB::Red, 248); - else - layer->setRGB(cPos, ColorFromPalette(layerP.palette, cellColors[cIndex])); } } } @@ -325,14 +344,14 @@ class GameOfLifeEffect : public Node { soloGlider = true; else soloGlider = false; - memcpy(cells, futureCells, cellsSize); - uint16_t crc = crc16((const unsigned char*)cells, cellsSize); + memcpy(cells, futureCells, dataSize); + uint16_t crc = crc16((const unsigned char*)cells, dataSize); bool repetition = false; if (!aliveCount || crc == oscillatorCRC || crc == spaceshipCRC || crc == cubeGliderCRC) repetition = true; if ((repetition && infinite) || (infinite && !random8(50)) || (infinite && float(aliveCount) / (aliveCount + deadCount) < 0.05)) { placePentomino(futureCells, colorByAge); // Place R-pentomino/Glider if infinite mode is enabled - memcpy(cells, futureCells, cellsSize); + memcpy(cells, futureCells, dataSize); repetition = false; } if (repetition) { diff --git a/src/MoonLight/Nodes/Effects/E_WLED.h b/src/MoonLight/Nodes/Effects/E_WLED.h index cca5b1e08..9777ec414 100644 --- a/src/MoonLight/Nodes/Effects/E_WLED.h +++ b/src/MoonLight/Nodes/Effects/E_WLED.h @@ -132,7 +132,7 @@ class BlurzEffect : public Node { if ((aux1 < layer->size.x * layer->size.y * layer->size.z) && (sharedData.volume > 1.0f)) layer->setRGB(aux1, step); // "repaint" last pixel after blur unsigned freqBand = aux0 % 16; - uint16_t segLoc = random16(layer->size.x * layer->size.y * layer->size.z); + nrOfLights_t segLoc = random16(layer->size.x * layer->size.y * layer->size.z); if (freqMap) { // FreqMap mode : blob location by major frequency int freqLocn; @@ -145,7 +145,7 @@ class BlurzEffect : public Node { int bandStart = roundf(bandWidth * freqBand); segLoc = bandStart + random16(max(1, int(bandWidth))); } - segLoc = MAX(uint16_t(0), MIN(uint16_t(layer->size.x * layer->size.y * layer->size.z - 1), segLoc)); // fix overflows + segLoc = MAX(nrOfLights_t(0), MIN(nrOfLights_t(layer->size.x * layer->size.y * layer->size.z - 1), segLoc)); // fix overflows if (layer->size.x * layer->size.y * layer->size.z < 2) segLoc = 0; // WLEDMM just to be sure unsigned pixColor = (2 * sharedData.bands[freqBand] * 240) / max(1, layer->size.x * layer->size.y * layer->size.z - 1); // WLEDMM avoid uint8 overflow, and preserve pixel parameters for redraw @@ -1326,7 +1326,7 @@ class OctopusEffect : public Node { const uint8_t mapp = 180 / max(layer->size.x, layer->size.y); for (pos.x = 0; pos.x < layer->size.x; pos.x++) { for (pos.y = 0; pos.y < layer->size.y; pos.y++) { - uint16_t indexV = layer->XYZUnModified(pos); + nrOfLights_t indexV = layer->XYZUnModified(pos); if (indexV < layer->size.x * layer->size.y) { // excluding UINT16_MAX from XY if out of bounds due to projection rMap[indexV].angle = 40.7436f * atan2f(pos.y - C_Y, pos.x - C_X); // avoid 128*atan2()/PI rMap[indexV].radius = hypotf(pos.x - C_X, pos.y - C_Y) * mapp; // thanks Sutaburosu @@ -1349,7 +1349,7 @@ class OctopusEffect : public Node { Coord3D pos = {0, 0, 0}; for (pos.x = 0; pos.x < layer->size.x; pos.x++) { for (pos.y = 0; pos.y < layer->size.y; pos.y++) { - uint16_t indexV = layer->XYZUnModified(pos); + nrOfLights_t indexV = layer->XYZUnModified(pos); if (indexV < rMapSize) { // excluding UINT16_MAX from XY if out of bounds due to projection byte angle = rMap[indexV].angle; byte radius = rMap[indexV].radius;