From 87f2be7e10382d3e9c543206de3e18326b323fe1 Mon Sep 17 00:00:00 2001 From: ewowi Date: Mon, 8 Dec 2025 18:13:04 +0100 Subject: [PATCH 1/7] Add Dig2Go, relay lightson, infrared 10 presets, layer nroflights fix Back-end ======== - ModuleIO: Octo -> Octa, Add Dig2Go, set index to 1 - Light control: rename pinRelayBrightness and pinToggleOnOff to pinRelayLightsOn and pinButtonLightsOn. readPins()/onUpdate: digitalWrite relay - Audio Sync: refactor no connection, set to 0 - Infrared: increase to 10 presets - Virtual layer: nrOfLights is max mapped --- docs/moonlight/drivers.md | 2 +- src/MoonBase/Modules/ModuleIO.h | 23 +++++++++++--- src/MoonLight/Layers/VirtualLayer.cpp | 2 +- src/MoonLight/Modules/ModuleDrivers.h | 1 - src/MoonLight/Modules/ModuleEffects.h | 1 - src/MoonLight/Modules/ModuleLightsControl.h | 34 ++++++++++++--------- src/MoonLight/Nodes/Drivers/D_AudioSync.h | 12 ++++++-- src/MoonLight/Nodes/Drivers/D_Infrared.h | 2 +- 8 files changed, 50 insertions(+), 27 deletions(-) diff --git a/docs/moonlight/drivers.md b/docs/moonlight/drivers.md index 5688351c8..169a94326 100644 --- a/docs/moonlight/drivers.md +++ b/docs/moonlight/drivers.md @@ -33,7 +33,7 @@ Want to add a Driver to MoonLight, see [develop](https://moonmodules.org/MoonLig | Parallel LED Driver | | Parallel | Drive multiple LED types, all devices including ESP32-P4(-nano) supported
Max Power and Light preset: See below
DMA buffer: set higher when LEDs flicker
Virtual LED Driver will be part of the Parallel LED driver.| | FastLED Driver | | FastLed | Most used LED driver. Drive most common LEDs (WS2812).
Max Power: See below | | Art-Net | | Art-Net | Drive LEDS and DMX lights over the network. See below | -| AudioSync | | No controls | Listens to audio sent over the local network by WLED-AC or WLED-MM and allows audio reactive effects (β™ͺ & β™«) to use audio data (volume and bands (FFT)) | +| Audio Sync | | No controls | Listens to audio sent over the local network by WLED-AC or WLED-MM and allows audio reactive effects (β™ͺ & β™«) to use audio data (volume and bands (FFT)) | | HUB75 Driver | | | Drive HUB75 panels
Not implemented yet | | IR Driver πŸ†•πŸš§ | | | Receive IR commands and [Lights Control](https://moonmodules.org/MoonLight/moonlight/lightscontrol/) | diff --git a/src/MoonBase/Modules/ModuleIO.h b/src/MoonBase/Modules/ModuleIO.h index e0e031f17..f361cf77d 100644 --- a/src/MoonBase/Modules/ModuleIO.h +++ b/src/MoonBase/Modules/ModuleIO.h @@ -66,7 +66,8 @@ enum IO_Boards { board_none, // board_QuinLEDDigUnoV3, board_QuinLEDDigQuadV3, - board_QuinLEDDigOctoV2, + board_QuinLEDDigOctaV2, + board_QuinLEDDig2Go, board_QuinLEDPenta, board_QuinLEDPentaPlus, board_SergUniShieldV5, @@ -107,6 +108,7 @@ class ModuleIO : public Module { addControlValue(control, "QuinLED Dig Uno v3"); addControlValue(control, "QuinLED Dig Quad v3"); addControlValue(control, "QuinLED Dig Octa v2"); + addControlValue(control, "QuinLED Dig 2Go"); addControlValue(control, "QuinLED Penta"); addControlValue(control, "QuinLED Penta Plus"); addControlValue(control, "Serg Universal Shield v5"); @@ -230,7 +232,7 @@ class ModuleIO : public Module { JsonObject pin = pins.add(); pin["GPIO"] = gpio_num; pin["usage"] = 0; - pin["index"] = 0; + pin["index"] = 1; // Check if GPIO is valid bool is_valid = GPIO_IS_VALID_GPIO(gpio_num); @@ -319,13 +321,26 @@ class ModuleIO : public Module { // pinAssigner.assignPin(13, pin_Temperature; // pinAssigner.assignPin(15, pin_I2S_SCK; // pinAssigner.assignPin(32, pin_Exposed; - } else if (boardID == board_QuinLEDDigOctoV2) { + } else if (boardID == board_QuinLEDDigOctaV2) { // Dig-Octa-32-8L uint8_t ledPins[8] = {0, 1, 2, 3, 4, 5, 12, 13}; // LED_PINS for (int i = 0; i < sizeof(ledPins); i++) pinAssigner.assignPin(ledPins[i], pin_LED); pinAssigner.assignPin(33, pin_Relay); pinAssigner.assignPin(34, pin_ButtonPush); - + } else if (boardID == board_QuinLEDDig2Go) { + // dig2go + pinAssigner.assignPin(0, pin_Button_LightsOn); + pinAssigner.assignPin(5, pin_Infrared); + pinAssigner.assignPin(16, pin_LED); + pinAssigner.assignPin(12, pin_Relay_LightsOn); + pinAssigner.assignPin(19, pin_I2S_SD); + pinAssigner.assignPin(4, pin_I2S_WS); + pinAssigner.assignPin(18, pin_I2S_SCK); + pinAssigner.assignPin(21, pin_I2C_SDA); + pinAssigner.assignPin(22, pin_I2C_SCL); + pinAssigner.assignPin(23, pin_Exposed); + pinAssigner.assignPin(25, pin_Exposed); + // pinAssigner.assignPin(xx, pin_I2S_MCLK); } else if (boardID == board_QuinLEDPenta) { uint8_t ledPins[5] = {14, 13, 12, 4, 2}; // LED_PINS for (int i = 0; i < sizeof(ledPins); i++) pinAssigner.assignPin(ledPins[i], pin_LED); diff --git a/src/MoonLight/Layers/VirtualLayer.cpp b/src/MoonLight/Layers/VirtualLayer.cpp index 1f717fe37..6d355b806 100644 --- a/src/MoonLight/Layers/VirtualLayer.cpp +++ b/src/MoonLight/Layers/VirtualLayer.cpp @@ -336,7 +336,7 @@ void VirtualLayer::addLight(Coord3D position) { if (position.x != UINT16_MAX) { // can be set to UINT16_MAX by modifier todo: check multiple modifiers uint16_t indexV = XYZUnModified(position); if (indexV < mappingTableSize) { - nrOfLights = indexV + 1; + nrOfLights = MAX(nrOfLights, indexV + 1); addIndexP(mappingTable[indexV], layerP->indexP); } } else { diff --git a/src/MoonLight/Modules/ModuleDrivers.h b/src/MoonLight/Modules/ModuleDrivers.h index 754f5a1c0..bf103e7f0 100644 --- a/src/MoonLight/Modules/ModuleDrivers.h +++ b/src/MoonLight/Modules/ModuleDrivers.h @@ -69,7 +69,6 @@ class ModuleDrivers : public NodeManager { void begin() override { defaultNodeName = getNameAndTags(); - nodes = &layerP.nodes; NodeManager::begin(); diff --git a/src/MoonLight/Modules/ModuleEffects.h b/src/MoonLight/Modules/ModuleEffects.h index c6aaf3056..c715a827a 100644 --- a/src/MoonLight/Modules/ModuleEffects.h +++ b/src/MoonLight/Modules/ModuleEffects.h @@ -27,7 +27,6 @@ class ModuleEffects : public NodeManager { void begin() override { defaultNodeName = getNameAndTags(); nodes = &(layerP.layers[0]->nodes); // to do add nodes from all layers... - NodeManager::begin(); #if FT_ENABLED(FT_MONITOR) diff --git a/src/MoonLight/Modules/ModuleLightsControl.h b/src/MoonLight/Modules/ModuleLightsControl.h index 1b55f5476..e082d1cfe 100644 --- a/src/MoonLight/Modules/ModuleLightsControl.h +++ b/src/MoonLight/Modules/ModuleLightsControl.h @@ -25,8 +25,8 @@ class ModuleLightsControl : public Module { PsychicHttpServer* _server; FileManager* _fileManager; ModuleIO* _moduleIO; - uint8_t pinRelayBrightness = UINT8_MAX; - uint8_t pinToggleOnOff = UINT8_MAX; + uint8_t pinRelayLightsOn = UINT8_MAX; + uint8_t pinButtonLightsOn = UINT8_MAX; ModuleLightsControl(PsychicHttpServer* server, ESP32SvelteKit* sveltekit, FileManager* fileManager, ModuleIO* moduleIO) : Module("lightscontrol", server, sveltekit) { EXT_LOGV(ML_TAG, "constructor"); @@ -71,17 +71,20 @@ class ModuleLightsControl : public Module { void readPins() { moduleIO.read([&](ModuleState& state) { - pinRelayBrightness = UINT8_MAX; - pinToggleOnOff = UINT8_MAX; + pinRelayLightsOn = UINT8_MAX; + pinButtonLightsOn = UINT8_MAX; for (JsonObject pinObject : state.data["pins"].as()) { uint8_t usage = pinObject["usage"]; if (usage == pin_Relay_LightsOn) { - pinRelayBrightness = pinObject["GPIO"]; - EXT_LOGD(ML_TAG, "pinRelayBrightness found %d", pinRelayBrightness); + pinRelayLightsOn = pinObject["GPIO"]; + pinMode(pinRelayLightsOn, OUTPUT); + uint8_t newBri = _state.data["lightsOn"] ? _state.data["brightness"] : 0; + digitalWrite(pinRelayLightsOn, newBri>0?HIGH:LOW); + EXT_LOGD(ML_TAG, "pinRelayLightsOn found %d", pinRelayLightsOn); } else if (usage == pin_Button_LightsOn) { - pinToggleOnOff = pinObject["GPIO"]; - pinMode(pinToggleOnOff, INPUT_PULLUP); - EXT_LOGD(ML_TAG, "pinToggleOnOff found %d", pinToggleOnOff); + pinButtonLightsOn = pinObject["GPIO"]; + pinMode(pinButtonLightsOn, INPUT_PULLUP); + EXT_LOGD(ML_TAG, "pinButtonLightsOn found %d", pinButtonLightsOn); } } // for (int i = 0; i < sizeof(pins); i++) EXT_LOGD(ML_TAG, "pin %d = %d", i, pins[i]); @@ -151,8 +154,9 @@ class ModuleLightsControl : public Module { layerP.lights.header.blue = _state.data["blue"]; } else if (updatedItem.name == "lightsOn" || updatedItem.name == "brightness") { uint8_t newBri = _state.data["lightsOn"] ? _state.data["brightness"] : 0; - if (!!layerP.lights.header.brightness != !!newBri && pinRelayBrightness != UINT8_MAX) { - EXT_LOGD(ML_TAG, "pinRelayBrightness %s", !!newBri ? "On" : "Off"); + if (!!layerP.lights.header.brightness != !!newBri && pinRelayLightsOn != UINT8_MAX) { + EXT_LOGD(ML_TAG, "pinRelayLightsOn %s", !!newBri ? "On" : "Off"); + digitalWrite(pinRelayLightsOn, newBri>0?HIGH:LOW); }; layerP.lights.header.brightness = newBri; } else if (updatedItem.name == "palette") { @@ -251,7 +255,7 @@ class ModuleLightsControl : public Module { } unsigned long lastPresetTime = 0; - // see pinToggleOnOff + // see pinButtonLightsOn unsigned long lastDebounceTime = 0; static constexpr unsigned long debounceDelay = 50; // 50ms debounce int lastState = HIGH; @@ -303,8 +307,8 @@ class ModuleLightsControl : public Module { } } - if (pinToggleOnOff != UINT8_MAX) { - int state = digitalRead(pinToggleOnOff); + if (pinButtonLightsOn != UINT8_MAX) { + int state = digitalRead(pinButtonLightsOn); if (state != lastState && (millis() - lastDebounceTime) > debounceDelay) { lastDebounceTime = millis(); // Trigger only on button press (HIGH to LOW transition for INPUT_PULLUP) @@ -326,7 +330,7 @@ class ModuleLightsControl : public Module { _socket->emitEvent("monitor", (char*)layerP.lights.channels, MIN(layerP.lights.header.nrOfLights * 3, layerP.lights.maxChannels)); //*3 is for 3 bytes position } memset(layerP.lights.channels, 0, layerP.lights.maxChannels); // set all the channels to 0 //cleaning the positions - EXT_LOGD(ML_TAG, "positions sent to monitor (2 -> 3, noL:%d noC:%d)", layerP.lights.header.nrOfLights, layerP.lights.maxChannels); + EXT_LOGD(ML_TAG, "positions sent to monitor (2 -> 3, #L:%d maxC:%d)", layerP.lights.header.nrOfLights, layerP.lights.maxChannels); layerP.lights.header.isPositions = 3; }); } else if (layerP.lights.header.isPositions == 0 && layerP.lights.header.nrOfLights) { // send to UI diff --git a/src/MoonLight/Nodes/Drivers/D_AudioSync.h b/src/MoonLight/Nodes/Drivers/D_AudioSync.h index cb65e30a3..0291c3507 100644 --- a/src/MoonLight/Nodes/Drivers/D_AudioSync.h +++ b/src/MoonLight/Nodes/Drivers/D_AudioSync.h @@ -16,7 +16,7 @@ class AudioSyncDriver : public Node { public: - static const char* name() { return "AudioSync"; } + static const char* name() { return "Audio Sync"; } static uint8_t dim() { return _NoD; } static const char* tags() { return "β˜ΈοΈβ™«"; } @@ -26,6 +26,12 @@ class AudioSyncDriver : public Node { void loop() override { if (!WiFi.isConnected() && !ETH.connected()) { // make WLED Audio Sync network failure resilient - WIP + if (init) EXT_LOGI(ML_TAG, "Audio Sync: stopped"); + //set all data to 0 + memset(sharedData.bands, 0, sizeof(sharedData.bands)); + sharedData.volume = 0; + sharedData.volumeRaw = 0; + sharedData.majorPeak = 0; init = false; return; } @@ -33,7 +39,7 @@ class AudioSyncDriver : public Node { if (!init) { sync.begin(); init = true; - EXT_LOGI(ML_TAG, "AudioSync: Initialized"); + EXT_LOGI(ML_TAG, "Audio Sync: Initialized"); } if (sync.read()) { @@ -42,7 +48,7 @@ class AudioSyncDriver : public Node { sharedData.volumeRaw = sync.volumeRaw; sharedData.majorPeak = sync.FFT_MajorPeak; // if (audio.bands[0] > 0) { - // EXT_LOGV(ML_TAG, "AudioSync: %d %f", audio.bands[0], audio.volume); + // EXT_LOGV(ML_TAG, "Audio Sync: %d %f", audio.bands[0], audio.volume); // } } } diff --git a/src/MoonLight/Nodes/Drivers/D_Infrared.h b/src/MoonLight/Nodes/Drivers/D_Infrared.h index 46b9c96f9..9cc542704 100644 --- a/src/MoonLight/Nodes/Drivers/D_Infrared.h +++ b/src/MoonLight/Nodes/Drivers/D_Infrared.h @@ -353,7 +353,7 @@ class IRDriver : public Node { if (combined_code == codeOff || combined_code == codeOn) { // Lights on/off newState["lightsOn"] = state.data["lightsOn"].as() ? false : true; } else if (combined_code == codePaletteInc) { // palette increase - newState["palette"] = min(state.data["palette"].as() + 1, 8); // to do: replace 8 with max palette count + newState["palette"] = min(state.data["palette"].as() + 1, 10); // to do: replace 8 with max palette count } else if (combined_code == codePaletteDec) { // palette decrease newState["palette"] = max(state.data["palette"].as() - 1, 0); } else if (combined_code == codePresetDec) { // next button - go to previous preset From e54963eb10a10d9b5f88fe4295c714f218afe7f6 Mon Sep 17 00:00:00 2001 From: ewowi Date: Tue, 9 Dec 2025 11:17:54 +0100 Subject: [PATCH 2/7] Layers start with one, Art-net In tweaks Installer: add Dig2Go Back-end ======== - Lights control: store nrOfPalettes - (virtual) Layers starting with 1 - Info: add layer number - Artnet in: layer start with 1, include universe - Infrared: use nrOfPalettes --- docs/gettingstarted/installer.md | 2 +- src/MoonLight/Modules/ModuleChannels.h | 2 +- src/MoonLight/Modules/ModuleEffects.h | 4 ++-- src/MoonLight/Modules/ModuleLightsControl.h | 6 ++++-- src/MoonLight/Modules/ModuleMoonLightInfo.h | 4 +++- src/MoonLight/Nodes/Drivers/D_ArtnetIn.h | 21 ++++++++++++--------- src/MoonLight/Nodes/Drivers/D_Infrared.h | 9 +++++---- 7 files changed, 28 insertions(+), 20 deletions(-) diff --git a/docs/gettingstarted/installer.md b/docs/gettingstarted/installer.md index ee7e13473..8645072b3 100644 --- a/docs/gettingstarted/installer.md +++ b/docs/gettingstarted/installer.md @@ -12,7 +12,7 @@ MoonLight v0.6.0, 7 November 2025 | 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"} | +| 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-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-s3-devkitc-1-n8r8v | ![esp32-s3-devkitc-1-n8r8v](../firmware/installer/images/esp32-s3-devkitc-1-n8r8v.jpg){: style="width:100px"} | | SE-16p
![SE-16p](../firmware/installer/images/esp32-s3-stephanelec-16p.jpg){: style="width:100px"} | | esp32-s3-devkitc-1-n16r8v | ![esp32-s3-devkitc-1-n16r8v](../firmware/installer/images/esp32-s3-devkitc-1-n8r8v.jpg){: style="width:100px"} | | [Ali*](https://s.click.aliexpress.com/e/_DBAtJ2H){:target="_blank"} | diff --git a/src/MoonLight/Modules/ModuleChannels.h b/src/MoonLight/Modules/ModuleChannels.h index dbd72b24b..ebc1007ef 100644 --- a/src/MoonLight/Modules/ModuleChannels.h +++ b/src/MoonLight/Modules/ModuleChannels.h @@ -29,7 +29,7 @@ class ModuleChannels : public Module { control = addControl(controls, "view", "select"); control["default"] = 0; addControlValue(control, "Physical layer"); - uint8_t i = 0; + uint8_t i = 1; //start with 1 for (VirtualLayer* layer : layerP.layers) { Char<32> layerName; layerName.format("Layer %d", i); diff --git a/src/MoonLight/Modules/ModuleEffects.h b/src/MoonLight/Modules/ModuleEffects.h index c715a827a..9e5bbef9c 100644 --- a/src/MoonLight/Modules/ModuleEffects.h +++ b/src/MoonLight/Modules/ModuleEffects.h @@ -60,8 +60,8 @@ class ModuleEffects : public NodeManager { EXT_LOGV(ML_TAG, ""); JsonObject control; // state.data has one or more properties control = addControl(controls, "layer", "select"); - control["default"] = 0; - uint8_t i = 0; + control["default"] = 1; + uint8_t i = 1; //start with 1 for (VirtualLayer* layer : layerP.layers) { addControlValue(control, i); i++; diff --git a/src/MoonLight/Modules/ModuleLightsControl.h b/src/MoonLight/Modules/ModuleLightsControl.h index e082d1cfe..e03bccf44 100644 --- a/src/MoonLight/Modules/ModuleLightsControl.h +++ b/src/MoonLight/Modules/ModuleLightsControl.h @@ -79,7 +79,7 @@ class ModuleLightsControl : public Module { pinRelayLightsOn = pinObject["GPIO"]; pinMode(pinRelayLightsOn, OUTPUT); uint8_t newBri = _state.data["lightsOn"] ? _state.data["brightness"] : 0; - digitalWrite(pinRelayLightsOn, newBri>0?HIGH:LOW); + digitalWrite(pinRelayLightsOn, newBri > 0 ? HIGH : LOW); EXT_LOGD(ML_TAG, "pinRelayLightsOn found %d", pinRelayLightsOn); } else if (usage == pin_Button_LightsOn) { pinButtonLightsOn = pinObject["GPIO"]; @@ -91,6 +91,7 @@ class ModuleLightsControl : public Module { }); } + uint8_t nrOfPalettes = 0; // define the data model void setupDefinition(const JsonArray& controls) override { EXT_LOGV(ML_TAG, ""); @@ -122,6 +123,7 @@ class ModuleLightsControl : public Module { addControlValue(control, "Random"); addControlValue(control, "Quin"); addControlValue(control, "Orange"); + nrOfPalettes = control["values"].as().size(); control = addControl(controls, "preset", "pad"); control["width"] = 8; @@ -156,7 +158,7 @@ class ModuleLightsControl : public Module { uint8_t newBri = _state.data["lightsOn"] ? _state.data["brightness"] : 0; if (!!layerP.lights.header.brightness != !!newBri && pinRelayLightsOn != UINT8_MAX) { EXT_LOGD(ML_TAG, "pinRelayLightsOn %s", !!newBri ? "On" : "Off"); - digitalWrite(pinRelayLightsOn, newBri>0?HIGH:LOW); + digitalWrite(pinRelayLightsOn, newBri > 0 ? HIGH : LOW); }; layerP.lights.header.brightness = newBri; } else if (updatedItem.name == "palette") { diff --git a/src/MoonLight/Modules/ModuleMoonLightInfo.h b/src/MoonLight/Modules/ModuleMoonLightInfo.h index 5a19b168b..c9bad6fc6 100644 --- a/src/MoonLight/Modules/ModuleMoonLightInfo.h +++ b/src/MoonLight/Modules/ModuleMoonLightInfo.h @@ -22,7 +22,7 @@ class ModuleMoonLightInfo : public Module { void setupDefinition(const JsonArray& controls) override { EXT_LOGV(ML_TAG, ""); JsonObject control; // state.data has one or more properties - JsonArray rows; // if a control is an array, this is the rows of the array + JsonArray rows; // if a control is an array, this is the rows of the array addControl(controls, "nrOfLights", "number", 0, 65535, true); addControl(controls, "channelsPerLight", "number", 0, 65535, true); @@ -34,6 +34,7 @@ class ModuleMoonLightInfo : public Module { control["crud"] = "r"; rows = control["n"].to(); { + addControl(rows, "layer", "number", 0, 255, true); addControl(rows, "nrOfLights", "number", 0, 65535, true); addControl(rows, "size", "coord3D", 0, UINT16_MAX, true); addControl(rows, "mappingTable#", "number", 0, 65535, true); @@ -80,6 +81,7 @@ class ModuleMoonLightInfo : public Module { } } + data["layers"][index]["layer"] = index + 1; // start with one data["layers"][index]["nrOfLights"] = layer->nrOfLights; data["layers"][index]["size"]["x"] = layer->size.x; data["layers"][index]["size"]["y"] = layer->size.y; diff --git a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h index c4cff2abc..4b34b01a9 100644 --- a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h +++ b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h @@ -27,7 +27,7 @@ class ArtNetInDriver : public Node { uint8_t packetBuffer[1500]; bool ddp = false; - uint8_t view = 0; + uint8_t view = 0; // physical layer uint16_t port = 6454; void setup() override { @@ -35,7 +35,7 @@ class ArtNetInDriver : public Node { addControl(port, "port", "number", 0, 65538); addControl(view, "view", "select"); addControlValue("Physical layer"); - uint8_t i = 0; + uint8_t i = 1; // start with one for (VirtualLayer* layer : layerP.layers) { Char<32> layerName; layerName.format("Layer %d", i); @@ -95,8 +95,6 @@ class ArtNetInDriver : public Node { }; void handleArtNet() { - // LightsHeader* header = &layerP.lights.header; - int packetSize = artnetUdp.parsePacket(); if (packetSize >= sizeof(ArtNetHeader)) { @@ -107,7 +105,7 @@ class ArtNetInDriver : public Node { ArtNetHeader* header = (ArtNetHeader*)packetBuffer; uint16_t opcode = header->opcode; // - // EXT_LOGD(ML_TAG, "%d", header->universe); + EXT_LOGD(ML_TAG, "size:%d universe:%d", packetSize, header->universe); // Check if it's a DMX packet (opcode 0x5000) if (opcode == 0x5000) { @@ -120,10 +118,17 @@ class ArtNetInDriver : public Node { uint8_t* dmxData = packetBuffer + sizeof(ArtNetHeader); // Map DMX channels to LEDs (3 channels per LED: RGB) - int numPixels = min((uint16_t)(dataLength / layerP.lights.header.channelsPerLight), (uint16_t)(layerP.lights.header.nrOfLights)); + // Calculate starting LED position based on universe + // Each Art-Net universe supports up to 512 DMX channels + int startPixel = universe * (512 / layerP.lights.header.channelsPerLight); + int numPixels = min((uint16_t)(dataLength / layerP.lights.header.channelsPerLight), (uint16_t)(layerP.lights.header.nrOfLights - startPixel)); + // Write to the correct offset for (int i = 0; i < numPixels; i++) { - memcpy(&layerP.lights.channels[i * layerP.lights.header.channelsPerLight], &dmxData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); + int ledIndex = startPixel + i; + if (ledIndex < layerP.lights.header.nrOfLights) { + memcpy(&layerP.lights.channels[ledIndex * layerP.lights.header.channelsPerLight], &dmxData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); + } } // FastLED.show(); @@ -177,5 +182,3 @@ class ArtNetInDriver : public Node { }; #endif - -// #include diff --git a/src/MoonLight/Nodes/Drivers/D_Infrared.h b/src/MoonLight/Nodes/Drivers/D_Infrared.h index 9cc542704..a78000504 100644 --- a/src/MoonLight/Nodes/Drivers/D_Infrared.h +++ b/src/MoonLight/Nodes/Drivers/D_Infrared.h @@ -350,12 +350,13 @@ class IRDriver : public Node { // do not process longpress if (nec_repeat == false) { + ModuleLightsControl* lc = (ModuleLightsControl*)moduleControl; if (combined_code == codeOff || combined_code == codeOn) { // Lights on/off newState["lightsOn"] = state.data["lightsOn"].as() ? false : true; - } else if (combined_code == codePaletteInc) { // palette increase - newState["palette"] = min(state.data["palette"].as() + 1, 10); // to do: replace 8 with max palette count - } else if (combined_code == codePaletteDec) { // palette decrease - newState["palette"] = max(state.data["palette"].as() - 1, 0); + } else if (combined_code == codePaletteInc) { // palette increase + newState["palette"] = MIN(state.data["palette"].as() + 1, lc->nrOfPalettes); // to do: replace 8 with max palette count + } else if (combined_code == codePaletteDec) { // palette decrease + newState["palette"] = MAX(state.data["palette"].as() - 1, 0); } else if (combined_code == codePresetDec) { // next button - go to previous preset newState["preset"] = state.data["preset"]; newState["preset"]["action"] = "click"; From 1c6bd4db89e5303b4cf1cb55fd38be25e18e3697 Mon Sep 17 00:00:00 2001 From: ewowi Date: Tue, 9 Dec 2025 12:23:29 +0100 Subject: [PATCH 3/7] Art-Net in tweaks Back-end ======== - Module IO: jumper1 triggers redefine - Art-Net in: process all packets, store in physical layer or virtual --- src/MoonBase/Modules/ModuleIO.h | 6 +- src/MoonLight/Modules/ModuleLightsControl.h | 2 - src/MoonLight/Nodes/Drivers/D_ArtnetIn.h | 121 +++++++++----------- src/MoonLight/Nodes/Drivers/D_Infrared.h | 3 +- 4 files changed, 62 insertions(+), 70 deletions(-) diff --git a/src/MoonBase/Modules/ModuleIO.h b/src/MoonBase/Modules/ModuleIO.h index f361cf77d..f31aa4cdb 100644 --- a/src/MoonBase/Modules/ModuleIO.h +++ b/src/MoonBase/Modules/ModuleIO.h @@ -451,7 +451,11 @@ class ModuleIO : public Module { EXT_LOGD(MB_TAG, "%s[%d]%s[%d].%s = %s -> %s", updatedItem.parent[0].c_str(), updatedItem.index[0], updatedItem.parent[1].c_str(), updatedItem.index[1], updatedItem.name.c_str(), updatedItem.oldValue.c_str(), updatedItem.value.as().c_str()); newBoardID = _state.data["boardPreset"]; // run in sveltekit task } - } else if (updatedItem.name == "usage" && !_state.updateOriginId.contains("server")) { // not done by this module: done by UI + } else if (updatedItem.name == "jumper1" && !_state.updateOriginId.contains("server")) { // not done by this module: done by UI + // rebuild with new jumper setting + _state.data["modded"] = false; + newBoardID = _state.data["boardPreset"]; // run in sveltekit task + } else if (updatedItem.name == "usage" && !_state.updateOriginId.contains("server")) { // not done by this module: done by UI // EXT_LOGD(MB_TAG, "%s[%d]%s[%d].%s = %s -> %s", updatedItem.parent[0].c_str(), updatedItem.index[0], updatedItem.parent[1].c_str(), updatedItem.index[1], updatedItem.name.c_str(), updatedItem.oldValue.c_str(), updatedItem.value.as().c_str()); // set pins to default if modded is turned off JsonDocument doc; diff --git a/src/MoonLight/Modules/ModuleLightsControl.h b/src/MoonLight/Modules/ModuleLightsControl.h index e03bccf44..3a38d3a87 100644 --- a/src/MoonLight/Modules/ModuleLightsControl.h +++ b/src/MoonLight/Modules/ModuleLightsControl.h @@ -91,7 +91,6 @@ class ModuleLightsControl : public Module { }); } - uint8_t nrOfPalettes = 0; // define the data model void setupDefinition(const JsonArray& controls) override { EXT_LOGV(ML_TAG, ""); @@ -123,7 +122,6 @@ class ModuleLightsControl : public Module { addControlValue(control, "Random"); addControlValue(control, "Quin"); addControlValue(control, "Orange"); - nrOfPalettes = control["values"].as().size(); control = addControl(controls, "preset", "pad"); control["width"] = 8; diff --git a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h index 4b34b01a9..eac2ee9c9 100644 --- a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h +++ b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h @@ -95,87 +95,78 @@ class ArtNetInDriver : public Node { }; void handleArtNet() { - int packetSize = artnetUdp.parsePacket(); - - if (packetSize >= sizeof(ArtNetHeader)) { - artnetUdp.read(packetBuffer, packetSize); - - // Verify Art-Net packet - if (memcmp(packetBuffer, "Art-Net", 7) == 0) { - ArtNetHeader* header = (ArtNetHeader*)packetBuffer; - uint16_t opcode = header->opcode; - // - EXT_LOGD(ML_TAG, "size:%d universe:%d", packetSize, header->universe); - - // Check if it's a DMX packet (opcode 0x5000) - if (opcode == 0x5000) { - uint16_t universe = header->universe; - uint16_t dataLength = (header->length >> 8) | (header->length << 8); // Swap bytes - - // Process if it's our universe - - // if (universe == artnetUniverse) { // all universes welcome - uint8_t* dmxData = packetBuffer + sizeof(ArtNetHeader); - - // Map DMX channels to LEDs (3 channels per LED: RGB) - // Calculate starting LED position based on universe - // Each Art-Net universe supports up to 512 DMX channels - int startPixel = universe * (512 / layerP.lights.header.channelsPerLight); - int numPixels = min((uint16_t)(dataLength / layerP.lights.header.channelsPerLight), (uint16_t)(layerP.lights.header.nrOfLights - startPixel)); - - // Write to the correct offset - for (int i = 0; i < numPixels; i++) { - int ledIndex = startPixel + i; - if (ledIndex < layerP.lights.header.nrOfLights) { - memcpy(&layerP.lights.channels[ledIndex * layerP.lights.header.channelsPerLight], &dmxData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); + // Process ALL available packets in the buffer + while (int packetSize = artnetUdp.parsePacket()) { + if (packetSize >= sizeof(ArtNetHeader)) { + artnetUdp.read(packetBuffer, min(packetSize, (int)sizeof(packetBuffer))); + + EXT_LOGD(ML_TAG, "size:%d", packetSize); + + // Verify Art-Net packet + if (memcmp(packetBuffer, "Art-Net", 7) == 0) { + ArtNetHeader* header = (ArtNetHeader*)packetBuffer; + uint16_t opcode = header->opcode; + + EXT_LOGD(ML_TAG, "size:%d universe:%d", packetSize, header->universe); + + // Check if it's a DMX packet (opcode 0x5000) + if (opcode == 0x5000) { + uint16_t universe = header->universe; + uint16_t dataLength = (header->length >> 8) | (header->length << 8); + + uint8_t* dmxData = packetBuffer + sizeof(ArtNetHeader); + + int startPixel = universe * (512 / layerP.lights.header.channelsPerLight); + int numPixels = min((uint16_t)(dataLength / layerP.lights.header.channelsPerLight), (uint16_t)(layerP.lights.header.nrOfLights - startPixel)); + + for (int i = 0; i < numPixels; i++) { + int ledIndex = startPixel + i; + if (ledIndex < layerP.lights.header.nrOfLights) { + if (view == 0) { + memcpy(&layerP.lights.channels[ledIndex * layerP.lights.header.channelsPerLight], &dmxData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); + } else { + layerP.layers[view - 1]->setLight(ledIndex, &dmxData[i * layerP.lights.header.channelsPerLight], 0, layerP.lights.header.channelsPerLight); + } + } } } - - // FastLED.show(); - // Serial.println("Art-Net: " + String(numPixels) + " pixels updated"); - // } } } } } void handleDDP() { - int packetSize = ddpUdp.parsePacket(); - - if (packetSize >= sizeof(DDPHeader)) { - ddpUdp.read(packetBuffer, packetSize); + // drain all packets + while (int packetSize = ddpUdp.parsePacket()) { + if (packetSize >= sizeof(DDPHeader)) { + ddpUdp.read(packetBuffer, min(packetSize, (int)sizeof(packetBuffer))); - DDPHeader* header = (DDPHeader*)packetBuffer; + DDPHeader* header = (DDPHeader*)packetBuffer; - // Extract header fields - bool pushFlag = (header->flags & 0x80) != 0; // Bit 7 - uint8_t dataType = header->dataType; + bool pushFlag = (header->flags & 0x80) != 0; + uint8_t dataType = header->dataType; - // Convert big-endian offset and length - uint32_t offset = (header->offset >> 24) | ((header->offset >> 8) & 0xFF00) | ((header->offset << 8) & 0xFF0000) | (header->offset << 24); + uint32_t offset = (header->offset >> 24) | ((header->offset >> 8) & 0xFF00) | ((header->offset << 8) & 0xFF0000) | (header->offset << 24); + uint16_t dataLen = (header->dataLen >> 8) | (header->dataLen << 8); - uint16_t dataLen = (header->dataLen >> 8) | (header->dataLen << 8); + if (dataType == 0x01) { + uint8_t* pixelData = packetBuffer + sizeof(DDPHeader); - // Validate data type (0x01 = RGB) - if (dataType == 0x01) { - uint8_t* pixelData = packetBuffer + sizeof(DDPHeader); + int startPixel = offset / layerP.lights.header.channelsPerLight; + int numPixels = min((uint16_t)(dataLen / layerP.lights.header.channelsPerLight), (uint16_t)(layerP.lights.header.nrOfLights - startPixel)); - // Calculate starting pixel from byte offset (3 bytes per pixel) - int startPixel = offset / layerP.lights.header.channelsPerLight; - int numPixels = min((uint16_t)(dataLen / layerP.lights.header.channelsPerLight), (uint16_t)(layerP.lights.header.nrOfLights - startPixel)); - - // Update LEDs - for (int i = 0; i < numPixels; i++) { - int ledIndex = startPixel + i; - if (ledIndex < layerP.lights.header.nrOfLights) { - memcpy(&layerP.lights.channels[ledIndex * layerP.lights.header.channelsPerLight], &pixelData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); + for (int i = 0; i < numPixels; i++) { + int ledIndex = startPixel + i; + if (ledIndex < layerP.lights.header.nrOfLights) { + memcpy(&layerP.lights.channels[ledIndex * layerP.lights.header.channelsPerLight], &pixelData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); + if (view == 0) { + memcpy(&layerP.lights.channels[ledIndex * layerP.lights.header.channelsPerLight], &pixelData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); + } else { + layerP.layers[view - 1]->setLight(ledIndex, &pixelData[i * layerP.lights.header.channelsPerLight], 0, layerP.lights.header.channelsPerLight); + } + } } } - // Only update display if push flag is set - if (pushFlag) { - // FastLED.show(); - // Serial.println("DDP: " + String(numPixels) + " pixels updated (offset: " + String(startPixel) + ")"); - } } } } diff --git a/src/MoonLight/Nodes/Drivers/D_Infrared.h b/src/MoonLight/Nodes/Drivers/D_Infrared.h index a78000504..45fb10600 100644 --- a/src/MoonLight/Nodes/Drivers/D_Infrared.h +++ b/src/MoonLight/Nodes/Drivers/D_Infrared.h @@ -350,11 +350,10 @@ class IRDriver : public Node { // do not process longpress if (nec_repeat == false) { - ModuleLightsControl* lc = (ModuleLightsControl*)moduleControl; if (combined_code == codeOff || combined_code == codeOn) { // Lights on/off newState["lightsOn"] = state.data["lightsOn"].as() ? false : true; } else if (combined_code == codePaletteInc) { // palette increase - newState["palette"] = MIN(state.data["palette"].as() + 1, lc->nrOfPalettes); // to do: replace 8 with max palette count + newState["palette"] = MIN(state.data["palette"].as() + 1, 11); // to do: replace 8 with max palette count } else if (combined_code == codePaletteDec) { // palette decrease newState["palette"] = MAX(state.data["palette"].as() - 1, 0); } else if (combined_code == codePresetDec) { // next button - go to previous preset From 966b2b589acac2d461febe0eb8b187f7b24d3a7d Mon Sep 17 00:00:00 2001 From: ewowi Date: Tue, 9 Dec 2025 13:53:08 +0100 Subject: [PATCH 4/7] readPins pin check and Art-Net in clear buffer bugfix Back end ======== - IO (Ethernet), LightsControl(button and relay lightson), : Add pincheck in readPins - Artnet in: refactor and add artnetUDP.clear (fixed the bug) --- src/MoonBase/Modules/ModuleIO.h | 39 +++++- src/MoonLight/Modules/ModuleLightsControl.h | 18 ++- src/MoonLight/Nodes/Drivers/D_ArtnetIn.h | 145 ++++++++++---------- src/MoonLight/Nodes/Drivers/D_AudioSync.h | 16 ++- src/MoonLight/Nodes/Drivers/D_Infrared.h | 9 +- 5 files changed, 131 insertions(+), 96 deletions(-) diff --git a/src/MoonBase/Modules/ModuleIO.h b/src/MoonBase/Modules/ModuleIO.h index f31aa4cdb..2065213f3 100644 --- a/src/MoonBase/Modules/ModuleIO.h +++ b/src/MoonBase/Modules/ModuleIO.h @@ -454,8 +454,8 @@ class ModuleIO : public Module { } else if (updatedItem.name == "jumper1" && !_state.updateOriginId.contains("server")) { // not done by this module: done by UI // rebuild with new jumper setting _state.data["modded"] = false; - newBoardID = _state.data["boardPreset"]; // run in sveltekit task - } else if (updatedItem.name == "usage" && !_state.updateOriginId.contains("server")) { // not done by this module: done by UI + newBoardID = _state.data["boardPreset"]; // run in sveltekit task + } else if (updatedItem.name == "usage" && !_state.updateOriginId.contains("server")) { // not done by this module: done by UI // EXT_LOGD(MB_TAG, "%s[%d]%s[%d].%s = %s -> %s", updatedItem.parent[0].c_str(), updatedItem.index[0], updatedItem.parent[1].c_str(), updatedItem.index[1], updatedItem.name.c_str(), updatedItem.oldValue.c_str(), updatedItem.value.as().c_str()); // set pins to default if modded is turned off JsonDocument doc; @@ -506,11 +506,36 @@ class ModuleIO : public Module { for (JsonObject pinObject : _state.data["pins"].as()) { uint8_t usage = pinObject["usage"]; uint8_t gpio = pinObject["GPIO"]; - if (usage == pin_SPI_SCK) ess->v_ETH_SPI_SCK = gpio; - if (usage == pin_SPI_MISO) ess->v_ETH_SPI_MISO = gpio; - if (usage == pin_SPI_MOSI) ess->v_ETH_SPI_MOSI = gpio; - if (usage == pin_PHY_CS) ess->v_ETH_PHY_CS = gpio; - if (usage == pin_PHY_IRQ) ess->v_ETH_PHY_IRQ = gpio; + if (usage == pin_SPI_SCK) { + if (GPIO_IS_VALID_GPIO(gpio)) + ess->v_ETH_SPI_SCK = gpio; + else + EXT_LOGE(MB_TAG, "%d: gpio %d not valid", usage, gpio); + } + if (usage == pin_SPI_MISO) { + if (GPIO_IS_VALID_GPIO(gpio)) + ess->v_ETH_SPI_MISO = gpio; + else + EXT_LOGE(MB_TAG, "%d: gpio %d not valid", usage, gpio); + } + if (usage == pin_SPI_MOSI) { + if (GPIO_IS_VALID_GPIO(gpio)) + ess->v_ETH_SPI_MOSI = gpio; + else + EXT_LOGE(MB_TAG, "%d: gpio %d not valid", usage, gpio); + } + if (usage == pin_PHY_CS) { + if (GPIO_IS_VALID_GPIO(gpio)) + ess->v_ETH_PHY_CS = gpio; + else + EXT_LOGE(MB_TAG, "%d: gpio %d not valid", usage, gpio); + } + if (usage == pin_PHY_IRQ) { + if (GPIO_IS_VALID_GPIO(gpio)) + ess->v_ETH_PHY_IRQ = gpio; + else + EXT_LOGE(MB_TAG, "%d: gpio %d not valid", usage, gpio); + } } // allocate the pins found diff --git a/src/MoonLight/Modules/ModuleLightsControl.h b/src/MoonLight/Modules/ModuleLightsControl.h index 3a38d3a87..2328bf892 100644 --- a/src/MoonLight/Modules/ModuleLightsControl.h +++ b/src/MoonLight/Modules/ModuleLightsControl.h @@ -77,14 +77,20 @@ class ModuleLightsControl : public Module { uint8_t usage = pinObject["usage"]; if (usage == pin_Relay_LightsOn) { pinRelayLightsOn = pinObject["GPIO"]; - pinMode(pinRelayLightsOn, OUTPUT); - uint8_t newBri = _state.data["lightsOn"] ? _state.data["brightness"] : 0; - digitalWrite(pinRelayLightsOn, newBri > 0 ? HIGH : LOW); - EXT_LOGD(ML_TAG, "pinRelayLightsOn found %d", pinRelayLightsOn); + if (GPIO_IS_VALID_OUTPUT_GPIO(pinRelayLightsOn)) { + pinMode(pinRelayLightsOn, OUTPUT); + uint8_t newBri = _state.data["lightsOn"] ? _state.data["brightness"] : 0; + digitalWrite(pinRelayLightsOn, newBri > 0 ? HIGH : LOW); + EXT_LOGD(ML_TAG, "pinRelayLightsOn found %d", pinRelayLightsOn); + } else + EXT_LOGE(MB_TAG, "gpio %d not valid", pinRelayLightsOn); } else if (usage == pin_Button_LightsOn) { pinButtonLightsOn = pinObject["GPIO"]; - pinMode(pinButtonLightsOn, INPUT_PULLUP); - EXT_LOGD(ML_TAG, "pinButtonLightsOn found %d", pinButtonLightsOn); + if (GPIO_IS_VALID_GPIO(pinButtonLightsOn)) { + pinMode(pinButtonLightsOn, INPUT_PULLUP); + EXT_LOGD(ML_TAG, "pinButtonLightsOn found %d", pinButtonLightsOn); + } else + EXT_LOGE(MB_TAG, "gpio %d not valid", pinButtonLightsOn); } } // for (int i = 0; i < sizeof(pins); i++) EXT_LOGD(ML_TAG, "pin %d = %d", i, pins[i]); diff --git a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h index eac2ee9c9..7712cd5d6 100644 --- a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h +++ b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h @@ -23,7 +23,6 @@ class ArtNetInDriver : public Node { // uint16_t ARTNET_PORT = 6454; // uint16_t DDP_PORT = 4048; WiFiUDP artnetUdp; - WiFiUDP ddpUdp; uint8_t packetBuffer[1500]; bool ddp = false; @@ -55,19 +54,36 @@ class ArtNetInDriver : public Node { bool init = false; void loop() override { - if (!WiFi.localIP() && !ETH.localIP()) return; + if (!WiFi.localIP() && !ETH.localIP()) { + if (init) { + EXT_LOGI(ML_TAG, "Stop Listening for %s on port %d", ddp ? "DDP" : "Art-Net", port); + artnetUdp.stop(); + init = false; + } + return; + } + if (!init) { - if (ddp) - ddpUdp.begin(port); - else - artnetUdp.begin(port); - EXT_LOGD(ML_TAG, "Listening for %s on port %d", ddp ? "DDP" : "Art-Net", port); + artnetUdp.begin(port); + EXT_LOGI(ML_TAG, "Listening for %s on port %d", ddp ? "DDP" : "Art-Net", port); init = true; } - if (ddp) - handleDDP(); - else - handleArtNet(); + + while (int packetSize = artnetUdp.parsePacket()) { + if (packetSize < sizeof(ArtNetHeader) || packetSize > sizeof(packetBuffer)) { + artnetUdp.clear(); + continue; + } + + if (packetSize >= sizeof(ArtNetHeader)) { + artnetUdp.read(packetBuffer, min(packetSize, (int)sizeof(packetBuffer))); + + if (ddp) + handleDDP(); + else + handleArtNet(); + } + } } // Art-Net Configuration @@ -95,39 +111,30 @@ class ArtNetInDriver : public Node { }; void handleArtNet() { - // Process ALL available packets in the buffer - while (int packetSize = artnetUdp.parsePacket()) { - if (packetSize >= sizeof(ArtNetHeader)) { - artnetUdp.read(packetBuffer, min(packetSize, (int)sizeof(packetBuffer))); - - EXT_LOGD(ML_TAG, "size:%d", packetSize); - - // Verify Art-Net packet - if (memcmp(packetBuffer, "Art-Net", 7) == 0) { - ArtNetHeader* header = (ArtNetHeader*)packetBuffer; - uint16_t opcode = header->opcode; - - EXT_LOGD(ML_TAG, "size:%d universe:%d", packetSize, header->universe); - - // Check if it's a DMX packet (opcode 0x5000) - if (opcode == 0x5000) { - uint16_t universe = header->universe; - uint16_t dataLength = (header->length >> 8) | (header->length << 8); - - uint8_t* dmxData = packetBuffer + sizeof(ArtNetHeader); - - int startPixel = universe * (512 / layerP.lights.header.channelsPerLight); - int numPixels = min((uint16_t)(dataLength / layerP.lights.header.channelsPerLight), (uint16_t)(layerP.lights.header.nrOfLights - startPixel)); - - for (int i = 0; i < numPixels; i++) { - int ledIndex = startPixel + i; - if (ledIndex < layerP.lights.header.nrOfLights) { - if (view == 0) { - memcpy(&layerP.lights.channels[ledIndex * layerP.lights.header.channelsPerLight], &dmxData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); - } else { - layerP.layers[view - 1]->setLight(ledIndex, &dmxData[i * layerP.lights.header.channelsPerLight], 0, layerP.lights.header.channelsPerLight); - } - } + // Verify Art-Net packet + if (memcmp(packetBuffer, "Art-Net", 7) == 0) { + ArtNetHeader* header = (ArtNetHeader*)packetBuffer; + uint16_t opcode = header->opcode; + + // EXT_LOGD(ML_TAG, "size:%d universe:%d", packetSize, header->universe); + + // Check if it's a DMX packet (opcode 0x5000) + if (opcode == 0x5000) { + uint16_t universe = header->universe; + uint16_t dataLength = (header->length >> 8) | (header->length << 8); + + uint8_t* dmxData = packetBuffer + sizeof(ArtNetHeader); + + int startPixel = universe * (512 / layerP.lights.header.channelsPerLight); + int numPixels = min((uint16_t)(dataLength / layerP.lights.header.channelsPerLight), (uint16_t)(layerP.lights.header.nrOfLights - startPixel)); + + for (int i = 0; i < numPixels; i++) { + int ledIndex = startPixel + i; + if (ledIndex < layerP.lights.header.nrOfLights) { + if (view == 0) { + memcpy(&layerP.lights.channels[ledIndex * layerP.lights.header.channelsPerLight], &dmxData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); + } else { + layerP.layers[view - 1]->setLight(ledIndex, &dmxData[i * layerP.lights.header.channelsPerLight], 0, layerP.lights.header.channelsPerLight); } } } @@ -136,35 +143,27 @@ class ArtNetInDriver : public Node { } void handleDDP() { - // drain all packets - while (int packetSize = ddpUdp.parsePacket()) { - if (packetSize >= sizeof(DDPHeader)) { - ddpUdp.read(packetBuffer, min(packetSize, (int)sizeof(packetBuffer))); - - DDPHeader* header = (DDPHeader*)packetBuffer; - - bool pushFlag = (header->flags & 0x80) != 0; - uint8_t dataType = header->dataType; - - uint32_t offset = (header->offset >> 24) | ((header->offset >> 8) & 0xFF00) | ((header->offset << 8) & 0xFF0000) | (header->offset << 24); - uint16_t dataLen = (header->dataLen >> 8) | (header->dataLen << 8); - - if (dataType == 0x01) { - uint8_t* pixelData = packetBuffer + sizeof(DDPHeader); - - int startPixel = offset / layerP.lights.header.channelsPerLight; - int numPixels = min((uint16_t)(dataLen / layerP.lights.header.channelsPerLight), (uint16_t)(layerP.lights.header.nrOfLights - startPixel)); - - for (int i = 0; i < numPixels; i++) { - int ledIndex = startPixel + i; - if (ledIndex < layerP.lights.header.nrOfLights) { - memcpy(&layerP.lights.channels[ledIndex * layerP.lights.header.channelsPerLight], &pixelData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); - if (view == 0) { - memcpy(&layerP.lights.channels[ledIndex * layerP.lights.header.channelsPerLight], &pixelData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); - } else { - layerP.layers[view - 1]->setLight(ledIndex, &pixelData[i * layerP.lights.header.channelsPerLight], 0, layerP.lights.header.channelsPerLight); - } - } + DDPHeader* header = (DDPHeader*)packetBuffer; + + bool pushFlag = (header->flags & 0x80) != 0; + uint8_t dataType = header->dataType; + + uint32_t offset = (header->offset >> 24) | ((header->offset >> 8) & 0xFF00) | ((header->offset << 8) & 0xFF0000) | (header->offset << 24); + uint16_t dataLen = (header->dataLen >> 8) | (header->dataLen << 8); + + if (dataType == 0x01) { + uint8_t* pixelData = packetBuffer + sizeof(DDPHeader); + + int startPixel = offset / layerP.lights.header.channelsPerLight; + int numPixels = min((uint16_t)(dataLen / layerP.lights.header.channelsPerLight), (uint16_t)(layerP.lights.header.nrOfLights - startPixel)); + + for (int i = 0; i < numPixels; i++) { + int ledIndex = startPixel + i; + if (ledIndex < layerP.lights.header.nrOfLights) { + if (view == 0) { + memcpy(&layerP.lights.channels[ledIndex * layerP.lights.header.channelsPerLight], &pixelData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); + } else { + layerP.layers[view - 1]->setLight(ledIndex, &pixelData[i * layerP.lights.header.channelsPerLight], 0, layerP.lights.header.channelsPerLight); } } } diff --git a/src/MoonLight/Nodes/Drivers/D_AudioSync.h b/src/MoonLight/Nodes/Drivers/D_AudioSync.h index 0291c3507..a26baaa84 100644 --- a/src/MoonLight/Nodes/Drivers/D_AudioSync.h +++ b/src/MoonLight/Nodes/Drivers/D_AudioSync.h @@ -26,13 +26,15 @@ class AudioSyncDriver : public Node { void loop() override { if (!WiFi.isConnected() && !ETH.connected()) { // make WLED Audio Sync network failure resilient - WIP - if (init) EXT_LOGI(ML_TAG, "Audio Sync: stopped"); - //set all data to 0 - memset(sharedData.bands, 0, sizeof(sharedData.bands)); - sharedData.volume = 0; - sharedData.volumeRaw = 0; - sharedData.majorPeak = 0; - init = false; + if (init) { + // set all data to 0 + memset(sharedData.bands, 0, sizeof(sharedData.bands)); + sharedData.volume = 0; + sharedData.volumeRaw = 0; + sharedData.majorPeak = 0; + init = false; + EXT_LOGI(ML_TAG, "Audio Sync: stopped"); + } return; } diff --git a/src/MoonLight/Nodes/Drivers/D_Infrared.h b/src/MoonLight/Nodes/Drivers/D_Infrared.h index 45fb10600..c8da5d718 100644 --- a/src/MoonLight/Nodes/Drivers/D_Infrared.h +++ b/src/MoonLight/Nodes/Drivers/D_Infrared.h @@ -44,7 +44,10 @@ class IRDriver : public Node { uint8_t usage = pinObject["usage"]; if (usage == pin_Infrared) { pinInfrared = pinObject["GPIO"]; - EXT_LOGD(ML_TAG, "pin_Infrared found %d", pinInfrared); + if (GPIO_IS_VALID_GPIO(pinInfrared)) { + EXT_LOGD(ML_TAG, "pin_Infrared found %d", pinInfrared); + } else + EXT_LOGE(MB_TAG, "gpio %d not valid", pinInfrared); } } if (pinInfrared != UINT8_MAX) { @@ -352,9 +355,9 @@ class IRDriver : public Node { if (nec_repeat == false) { if (combined_code == codeOff || combined_code == codeOn) { // Lights on/off newState["lightsOn"] = state.data["lightsOn"].as() ? false : true; - } else if (combined_code == codePaletteInc) { // palette increase + } else if (combined_code == codePaletteInc) { // palette increase newState["palette"] = MIN(state.data["palette"].as() + 1, 11); // to do: replace 8 with max palette count - } else if (combined_code == codePaletteDec) { // palette decrease + } else if (combined_code == codePaletteDec) { // palette decrease newState["palette"] = MAX(state.data["palette"].as() - 1, 0); } else if (combined_code == codePresetDec) { // next button - go to previous preset newState["preset"] = state.data["preset"]; From 40743fd472e84cf0fb0a9eb9a852d92741b8b33d Mon Sep 17 00:00:00 2001 From: ewowi Date: Wed, 10 Dec 2025 15:13:21 +0100 Subject: [PATCH 5/7] Board Presets and Art-Net-In updates and readPin checks and doc updates Docs ==== - Add ESPConnect in installation - Add board presets in IO - Add Art-Net In in drivers Back-end ======== - IO: add maxPower for DigOcta and Dig2Go - readPins, only assign if valid(gpio) - Art-Net In: add universe Min/Max --- docs/develop/development.md | 4 +- docs/gettingstarted/installation.md | 15 +++++++ docs/moonbase/inputoutput.md | 21 ++++++--- docs/moonlight/drivers.md | 27 +++++++++--- src/MoonBase/Modules/ModuleIO.h | 2 + src/MoonLight/Modules/ModuleDrivers.h | 5 ++- src/MoonLight/Modules/ModuleLightsControl.h | 10 +++-- src/MoonLight/Nodes/Drivers/D_ArtnetIn.h | 48 +++++++++++---------- src/MoonLight/Nodes/Drivers/D_Infrared.h | 9 ++-- 9 files changed, 97 insertions(+), 44 deletions(-) diff --git a/docs/develop/development.md b/docs/develop/development.md index 89ebd5874..ed7ebfd1c 100644 --- a/docs/develop/development.md +++ b/docs/develop/development.md @@ -110,9 +110,11 @@ Firmware binaries come in 2 flavours: including boot and partition (merged) and !!! tip "flash firmware using esptool" * [>_] in the statusbar of vscode ``` - esptool --port /dev/cu.usbmodem11201 write-flash 0x0 ./build/merged/MoonLight_esp32-s3-devkitc-1-n16r8v_0-6-0_webflash.bin + esptool --port /dev/cu.usbserial-1130 write-flash -b 2000000 0x0 ./build/merged/MoonLight_esp32-s3-devkitc-1-n16r8v_0-6-1_webflash.bin ``` + * replace port and file to match your setup * optionally add erase-flash before write-flash + * -b 2000000: Baud rate: lower if too high for your device * use ./build/release/MoonLight_esp32-s3-devkitc-1-n16r8v_0-6-0.bin and address 0x10000 to flash only the MoonLight partition ### Adding an ESP32 device Definition diff --git a/docs/gettingstarted/installation.md b/docs/gettingstarted/installation.md index d8ed40c20..3131867eb 100644 --- a/docs/gettingstarted/installation.md +++ b/docs/gettingstarted/installation.md @@ -165,3 +165,18 @@ Keep this page visible until installation complete. * To install the latest release, you can also use the [MoonLight Installer](https://moonmodules.org/MoonLight/gettingstarted/installer/) (no need to erase the device if updating) * Upload directly from VSCode, see [Develop / Installation](https://moonmodules.org/MoonLight/develop/installation/) + +## Update firmware using ESPConnect + +ESPConnect is a browser-based control center for ESP32- and ESP8266-class boards. It runs entirely inside a modern Chromium browser so you can inspect hardware details, manage SPIFFS files, back up flash, and deploy firmware without installing desktop software. It is based on [Jason2866](https://github.com/Jason2866)'s [WebSerial ESPTool](https://github.com/Jason2866/WebSerial_ESPTool/tree/development). + +[ESPConnect on GitHub](https://github.com/thelastoutpostworkshop/ESPConnect) + +[ESPConnect](https://thelastoutpostworkshop.github.io/microcontroller_devkit/espconnect/) + +* Click Connect and choose your device when the browser asks for permission. +* Select Flash Tools, go to Flash Firmware +* Open a firmware.bin file. + * Files ending with _webflash.bin (e.g. MoonLight_esp32-d0_0-6-1_webflash.bin) also formats the partition: choose Flash offset 0x0 or recommended offsets Bootloader. webflash files can be found [here](https://github.com/MoonModules/MoonLight/tree/main/firmware/installer) and are used by the [MoonLight Installer](https://moonmodules.org/MoonLight/gettingstarted/installer/) as well. + * Files ending with .bin, without _webflash (e.g. MoonLight_esp32-d0_0-6-1.bin). Choose recommended offsets App0 (Or App1?). Only use if you previously had MoonLight installed on the device, so the partitions has already been set. These files can be found in [published releases](https://github.com/MoonModules/MoonLight/releases). + * Nightly builds if shared on [discord](https://discord.gg/MTn9mVUG5n). \ No newline at end of file diff --git a/docs/moonbase/inputoutput.md b/docs/moonbase/inputoutput.md index f8ebcf9b5..e75729c3e 100644 --- a/docs/moonbase/inputoutput.md +++ b/docs/moonbase/inputoutput.md @@ -11,7 +11,7 @@ Currently the following boards are defined. Not all are supported yet 🚧 For each board the following presets are defined: * Modded: if any change to the default preset is made. -* Max Power: adjust the brightness to approach this max power, depending on the number of LEDs used. +* Max Power in Watts: adjust the brightness to approach this max power, depending on the number of LEDs used. Default 10: 5V * 2A = 10W (so it runs fine on USB). * Jumper1: If the board contains a jumper, it can define pin behaviour. Eg. select between Infrared and Ethernet. * Pins: This module is the central place to assign functionality to gpio pins. Other modules and nodes use the pin assignments made here. @@ -36,9 +36,10 @@ For each board the following presets are defined: * Current * Infrared * Button LightsOn: sets on/off in [Light Control](https://moonmodules.org/MoonLight/moonlight/lightscontrol/) + * Relay LightsOn: sets on/off in [Light Control](https://moonmodules.org/MoonLight/moonlight/lightscontrol/) + * SPI_SCK, SPI_MISO, SPI_MOSI, PHY_CS, PHY_IRQ: S3 Ethernet * Planned soon * Battery - * Relay brightness * DMX (in) * Planned later * I2S for microphone and line in @@ -55,14 +56,22 @@ For each board the following presets are defined: ### QuinLed boards -* Choose the esp32-d0 (4MB) board in the [MoonLight Installer](https://moonmodules.org/MoonLight/gettingstarted/installer/) -* On first install, erase flash first as MoonLight uses a partition scheme with 3MB of flash (no ota at the moment). +![Dig2Go](https://shop.allnetchina.cn/cdn/shop/products/Led_4.jpg?v=1680836018&width=1600){: 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"} + +* Dig 2Go, Dig Uno, Dig Quad: Choose the esp32-d0 (4MB) board in the [MoonLight Installer](https://moonmodules.org/MoonLight/gettingstarted/installer/) +* Dig Octa: Choose the esp32-d0-16mb board in the [MoonLight Installer](https://moonmodules.org/MoonLight/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 (no ota at the moment). * You might need to reset your router if you first run WLED on the same MCU and no new IP is assigned. -!!! Tip - Dig Uno: Remove fuse to connect USB cable to flash the board. +!!! Tip "Dig Uno USB" + Remove fuse to connect USB cable to flash the board. ### SE16 v1 +![SE-16p](../firmware/installer/images/esp32-s3-stephanelec-16p.jpg){: style="width:100px"} + * Choose the esp32-s3-devkitc-1-n8r8v board in the [MoonLight Installer](https://moonmodules.org/MoonLight/gettingstarted/installer/) * Set jumper1 the same as you set it on the board: on: Infrared, off: Ethernet \ No newline at end of file diff --git a/docs/moonlight/drivers.md b/docs/moonlight/drivers.md index 169a94326..000c01aa2 100644 --- a/docs/moonlight/drivers.md +++ b/docs/moonlight/drivers.md @@ -30,12 +30,13 @@ Want to add a Driver to MoonLight, see [develop](https://moonmodules.org/MoonLig | Name | Preview | Controls | Remarks | ---- | ----- | ---- | ---- | -| Parallel LED Driver | | Parallel | Drive multiple LED types, all devices including ESP32-P4(-nano) supported
Max Power and Light preset: See below
DMA buffer: set higher when LEDs flicker
Virtual LED Driver will be part of the Parallel LED driver.| -| FastLED Driver | | FastLed | Most used LED driver. Drive most common LEDs (WS2812).
Max Power: See below | -| Art-Net | | Art-Net | Drive LEDS and DMX lights over the network. See below | +| Parallel LED Driver | | Parallel | Drive multiple LED types, all devices including ESP32-P4(-nano) supported
Light preset: See below
DMA buffer: set higher when LEDs flicker
Virtual LED Driver will be part of the Parallel LED driver.| +| FastLED Driver | | FastLed | Most used LED driver. Drive most common LEDs (WS2812). | +| Art-Net In πŸ†• | | DDP: Yes/No
Port
Universe Min-Max
View: Layers | Receive Art-Net (or DDP) packages e.g. from Touch Designer. See [below](#art-net-in) | +| Art-Net Out| | Art-Net | Send Art-Net to Drive LEDS and DMX lights over the network. See [below](#art-net-out) | | Audio Sync | | No controls | Listens to audio sent over the local network by WLED-AC or WLED-MM and allows audio reactive effects (β™ͺ & β™«) to use audio data (volume and bands (FFT)) | | HUB75 Driver | | | Drive HUB75 panels
Not implemented yet | -| IR Driver πŸ†•πŸš§ | | | Receive IR commands and [Lights Control](https://moonmodules.org/MoonLight/moonlight/lightscontrol/) | +| IR Driver | | | Receive IR commands and [Lights Control](https://moonmodules.org/MoonLight/moonlight/lightscontrol/) | * The Parallel LED driver uses different hardware peripherals depending on the MCU type: ESP32-D0: I2S, ESP32-S3: LCD_CAM, ESP32-P4: Parallel IO (ParLIO). * Virtual LED Driver: Driving max 120! outputs (E.g. 48 panels of 256 LEDs each run at 50-100 FPS) using shift registers. Integrated within the Parallel LED Driver architecture. Not implemented yet @@ -43,7 +44,7 @@ Want to add a Driver to MoonLight, see [develop](https://moonmodules.org/MoonLig ### Max Power and Light Preset -* **Max Power**: max amount of power in watts to send to LEDs. Default 10: 5V * 2A = 10W (so it runs fine on USB). πŸ†•: Moved to board presets in [Module IO](https://moonmodules.org/MoonLight/moonbase/inputoutput/). +* **Max Power**: πŸ†• moved to [IO Module](https://moonmodules.org/MoonLight/moonbase/inputoutput/) board presets. * **Light preset**: Defines the channels per light and color order @@ -65,7 +66,21 @@ Want to add a Driver to MoonLight, see [develop](https://moonmodules.org/MoonLig !!! info "Custom setup" These are predefined presets. In a future release custom presets will be possible. -### Art-Net ☸️ +### Art-Net In ☸️ + +Receives Art-Net data from the network. + +* DDP: If unchecked, processes data in Art-Net format, if checked, process data in DDP format +* Port: The port listening to for Art-Net +* Universe Min-Max: Filters Universes (Art-Net only). +* View: + * Select physical layer to directly store the received channels into the physical layer + * Select one of the (virtual layers) to take mapping into account (using layout specification and modifiers specified , see [Modifiers](https://moonmodules.org/MoonLight/moonlight/modifiers/), part of the [Effects Module](https://moonmodules.org/MoonLight/moonlight/effects/)) + +!!! tip "Running effects and Art-Net In" + Effects can run at the same time, disable or delete them if you only want to run Art-Net In. + +### Art-Net Out ☸️ Sends Lights in Art-Net compatible packages to an Art-Net controller specified by the IP address provided. diff --git a/src/MoonBase/Modules/ModuleIO.h b/src/MoonBase/Modules/ModuleIO.h index 2065213f3..20d5c19b5 100644 --- a/src/MoonBase/Modules/ModuleIO.h +++ b/src/MoonBase/Modules/ModuleIO.h @@ -323,12 +323,14 @@ class ModuleIO : public Module { // 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[8] = {0, 1, 2, 3, 4, 5, 12, 13}; // LED_PINS for (int i = 0; i < sizeof(ledPins); i++) pinAssigner.assignPin(ledPins[i], 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 pinAssigner.assignPin(0, pin_Button_LightsOn); pinAssigner.assignPin(5, pin_Infrared); pinAssigner.assignPin(16, pin_LED); diff --git a/src/MoonLight/Modules/ModuleDrivers.h b/src/MoonLight/Modules/ModuleDrivers.h index bf103e7f0..319efe1d8 100644 --- a/src/MoonLight/Modules/ModuleDrivers.h +++ b/src/MoonLight/Modules/ModuleDrivers.h @@ -44,8 +44,9 @@ class ModuleDrivers : public NodeManager { for (JsonObject pinObject : state.data["pins"].as()) { uint8_t usage = pinObject["usage"]; uint8_t index = pinObject["index"]; - if (usage == pin_LED && index >=1 && index <= 20 && GPIO_IS_VALID_OUTPUT_GPIO(pinObject["GPIO"].as())) { - layerP.ledPins[index-1] = pinObject["GPIO"]; + uint8_t gpio = pinObject["GPIO"]; + if (usage == pin_LED && index >=1 && index <= 20 && GPIO_IS_VALID_OUTPUT_GPIO(gpio)) { + layerP.ledPins[index-1] = gpio; } } diff --git a/src/MoonLight/Modules/ModuleLightsControl.h b/src/MoonLight/Modules/ModuleLightsControl.h index 2328bf892..38797f1d2 100644 --- a/src/MoonLight/Modules/ModuleLightsControl.h +++ b/src/MoonLight/Modules/ModuleLightsControl.h @@ -75,9 +75,11 @@ class ModuleLightsControl : public Module { pinButtonLightsOn = UINT8_MAX; for (JsonObject pinObject : state.data["pins"].as()) { uint8_t usage = pinObject["usage"]; + uint8_t gpio = pinObject["GPIO"]; + if (usage == pin_Relay_LightsOn) { - pinRelayLightsOn = pinObject["GPIO"]; - if (GPIO_IS_VALID_OUTPUT_GPIO(pinRelayLightsOn)) { + if (GPIO_IS_VALID_OUTPUT_GPIO(gpio)) { + pinRelayLightsOn = gpio; pinMode(pinRelayLightsOn, OUTPUT); uint8_t newBri = _state.data["lightsOn"] ? _state.data["brightness"] : 0; digitalWrite(pinRelayLightsOn, newBri > 0 ? HIGH : LOW); @@ -85,8 +87,8 @@ class ModuleLightsControl : public Module { } else EXT_LOGE(MB_TAG, "gpio %d not valid", pinRelayLightsOn); } else if (usage == pin_Button_LightsOn) { - pinButtonLightsOn = pinObject["GPIO"]; - if (GPIO_IS_VALID_GPIO(pinButtonLightsOn)) { + if (GPIO_IS_VALID_GPIO(gpio)) { + pinButtonLightsOn = gpio; pinMode(pinButtonLightsOn, INPUT_PULLUP); EXT_LOGD(ML_TAG, "pinButtonLightsOn found %d", pinButtonLightsOn); } else diff --git a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h index 7712cd5d6..8bdeab70b 100644 --- a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h +++ b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h @@ -28,10 +28,14 @@ class ArtNetInDriver : public Node { bool ddp = false; uint8_t view = 0; // physical layer uint16_t port = 6454; + uint8_t universeMin = 0; + uint8_t universeMax = 255; void setup() override { addControl(ddp, "DDP", "checkbox"); addControl(port, "port", "number", 0, 65538); + addControl(universeMin, "universeMin", "number", 0, 65538); + addControl(universeMax, "universeMax", "number", 0, 65538); addControl(view, "view", "select"); addControlValue("Physical layer"); uint8_t i = 1; // start with one @@ -71,18 +75,16 @@ class ArtNetInDriver : public Node { while (int packetSize = artnetUdp.parsePacket()) { if (packetSize < sizeof(ArtNetHeader) || packetSize > sizeof(packetBuffer)) { - artnetUdp.clear(); + artnetUdp.clear(); // drains all available packets continue; } - if (packetSize >= sizeof(ArtNetHeader)) { - artnetUdp.read(packetBuffer, min(packetSize, (int)sizeof(packetBuffer))); + artnetUdp.read(packetBuffer, min(packetSize, (int)sizeof(packetBuffer))); - if (ddp) - handleDDP(); - else - handleArtNet(); - } + if (ddp) + handleDDP(); + else + handleArtNet(); } } @@ -121,20 +123,22 @@ class ArtNetInDriver : public Node { // Check if it's a DMX packet (opcode 0x5000) if (opcode == 0x5000) { uint16_t universe = header->universe; - uint16_t dataLength = (header->length >> 8) | (header->length << 8); - - uint8_t* dmxData = packetBuffer + sizeof(ArtNetHeader); - - int startPixel = universe * (512 / layerP.lights.header.channelsPerLight); - int numPixels = min((uint16_t)(dataLength / layerP.lights.header.channelsPerLight), (uint16_t)(layerP.lights.header.nrOfLights - startPixel)); - - for (int i = 0; i < numPixels; i++) { - int ledIndex = startPixel + i; - if (ledIndex < layerP.lights.header.nrOfLights) { - if (view == 0) { - memcpy(&layerP.lights.channels[ledIndex * layerP.lights.header.channelsPerLight], &dmxData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); - } else { - layerP.layers[view - 1]->setLight(ledIndex, &dmxData[i * layerP.lights.header.channelsPerLight], 0, layerP.lights.header.channelsPerLight); + if (universe >= universeMin && universe <= universeMax) { + uint16_t dataLength = (header->length >> 8) | (header->length << 8); + + uint8_t* dmxData = packetBuffer + sizeof(ArtNetHeader); + + int startPixel = (universe-universeMin) * (512 / layerP.lights.header.channelsPerLight); + int numPixels = min((uint16_t)(dataLength / layerP.lights.header.channelsPerLight), (uint16_t)(layerP.lights.header.nrOfLights - startPixel)); + + for (int i = 0; i < numPixels; i++) { + int ledIndex = startPixel + i; + if (ledIndex < layerP.lights.header.nrOfLights) { + if (view == 0) { + memcpy(&layerP.lights.channels[ledIndex * layerP.lights.header.channelsPerLight], &dmxData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); + } else { + layerP.layers[view - 1]->setLight(ledIndex, &dmxData[i * layerP.lights.header.channelsPerLight], 0, layerP.lights.header.channelsPerLight); + } } } } diff --git a/src/MoonLight/Nodes/Drivers/D_Infrared.h b/src/MoonLight/Nodes/Drivers/D_Infrared.h index c8da5d718..16a694a6d 100644 --- a/src/MoonLight/Nodes/Drivers/D_Infrared.h +++ b/src/MoonLight/Nodes/Drivers/D_Infrared.h @@ -42,14 +42,17 @@ class IRDriver : public Node { pinInfrared = UINT8_MAX; for (JsonObject pinObject : state.data["pins"].as()) { uint8_t usage = pinObject["usage"]; + uint8_t gpio = pinObject["GPIO"]; if (usage == pin_Infrared) { - pinInfrared = pinObject["GPIO"]; - if (GPIO_IS_VALID_GPIO(pinInfrared)) { + if (GPIO_IS_VALID_GPIO(gpio)) { + pinInfrared = gpio; EXT_LOGD(ML_TAG, "pin_Infrared found %d", pinInfrared); - } else + } else { EXT_LOGE(MB_TAG, "gpio %d not valid", pinInfrared); + } } } + if (pinInfrared != UINT8_MAX) { EXT_LOGI(IR_DRIVER_TAG, "Changing to pin #%d", pinInfrared); From 5fc1d69d92f3cf6e2ee56490aae510dcf4d1efdf Mon Sep 17 00:00:00 2001 From: ewowi Date: Wed, 10 Dec 2025 16:37:31 +0100 Subject: [PATCH 6/7] Doc updates + Ethernet / Art-Net-In small changes Back-End ======== - Ethernet: set pins to int8_t (to allow for -1) - Module - Art-Net-In: universe Min/Max to uint16_t, max 32K --- docs/develop/development.md | 2 +- docs/gettingstarted/installation.md | 2 +- docs/moonbase/inputoutput.md | 2 +- lib/framework/EthernetSettingsService.h | 12 +++---- src/MoonBase/Modules/ModuleIO.h | 45 ++++++++++-------------- src/MoonLight/Nodes/Drivers/D_ArtnetIn.h | 10 +++--- 6 files changed, 33 insertions(+), 40 deletions(-) diff --git a/docs/develop/development.md b/docs/develop/development.md index ed7ebfd1c..8a60a10c3 100644 --- a/docs/develop/development.md +++ b/docs/develop/development.md @@ -115,7 +115,7 @@ Firmware binaries come in 2 flavours: including boot and partition (merged) and * replace port and file to match your setup * optionally add erase-flash before write-flash * -b 2000000: Baud rate: lower if too high for your device - * use ./build/release/MoonLight_esp32-s3-devkitc-1-n16r8v_0-6-0.bin and address 0x10000 to flash only the MoonLight partition + * use ./build/release/MoonLight_esp32-s3-devkitc-1-n16r8v_0-6-1.bin and address 0x10000 to flash only the MoonLight partition ### Adding an ESP32 device Definition diff --git a/docs/gettingstarted/installation.md b/docs/gettingstarted/installation.md index 3131867eb..a4a8dbea0 100644 --- a/docs/gettingstarted/installation.md +++ b/docs/gettingstarted/installation.md @@ -178,5 +178,5 @@ ESPConnect is a browser-based control center for ESP32- and ESP8266-class boards * Select Flash Tools, go to Flash Firmware * Open a firmware.bin file. * Files ending with _webflash.bin (e.g. MoonLight_esp32-d0_0-6-1_webflash.bin) also formats the partition: choose Flash offset 0x0 or recommended offsets Bootloader. webflash files can be found [here](https://github.com/MoonModules/MoonLight/tree/main/firmware/installer) and are used by the [MoonLight Installer](https://moonmodules.org/MoonLight/gettingstarted/installer/) as well. - * Files ending with .bin, without _webflash (e.g. MoonLight_esp32-d0_0-6-1.bin). Choose recommended offsets App0 (Or App1?). Only use if you previously had MoonLight installed on the device, so the partitions has already been set. These files can be found in [published releases](https://github.com/MoonModules/MoonLight/releases). + * Files ending with .bin, without _webflash (e.g. MoonLight_esp32-d0_0-6-1.bin). Choose recommended offsets App0. Only use if you previously had MoonLight installed on the device, so the partitions has already been set. These files can be found in [published releases](https://github.com/MoonModules/MoonLight/releases). * Nightly builds if shared on [discord](https://discord.gg/MTn9mVUG5n). \ No newline at end of file diff --git a/docs/moonbase/inputoutput.md b/docs/moonbase/inputoutput.md index e75729c3e..11ace531a 100644 --- a/docs/moonbase/inputoutput.md +++ b/docs/moonbase/inputoutput.md @@ -63,7 +63,7 @@ For each board the following presets are defined: * Dig 2Go, Dig Uno, Dig Quad: Choose the esp32-d0 (4MB) board in the [MoonLight Installer](https://moonmodules.org/MoonLight/gettingstarted/installer/) * Dig Octa: Choose the esp32-d0-16mb board in the [MoonLight Installer](https://moonmodules.org/MoonLight/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 (no ota at the moment). +* 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). * You might need to reset your router if you first run WLED on the same MCU and no new IP is assigned. !!! Tip "Dig Uno USB" diff --git a/lib/framework/EthernetSettingsService.h b/lib/framework/EthernetSettingsService.h index 69093f101..286731a66 100644 --- a/lib/framework/EthernetSettingsService.h +++ b/lib/framework/EthernetSettingsService.h @@ -112,15 +112,15 @@ class EthernetSettingsService : public StatefulService // πŸŒ™ compiler directives to variables #ifdef CONFIG_IDF_TARGET_ESP32S3 - uint8_t v_ETH_SPI_SCK = UINT8_MAX; //42; v_ETH_SPI_SCK is check if configured, see configureNetwork and ModuleIO - uint8_t v_ETH_SPI_MISO = 44; - uint8_t v_ETH_SPI_MOSI = 43; + int8_t v_ETH_SPI_SCK = INT8_MAX; //42; v_ETH_SPI_SCK is check if configured, see configureNetwork and ModuleIO + int8_t v_ETH_SPI_MISO = 44; + int8_t v_ETH_SPI_MOSI = 43; eth_phy_type_t v_ETH_PHY_TYPE = ETH_PHY_W5500; //currently only one supported for S3 ... int32_t v_ETH_PHY_ADDR = 1; - int v_ETH_PHY_CS = 41; - int v_ETH_PHY_IRQ = 2; // -1 if you won't wire - int v_ETH_PHY_RST = 1; // -1 if you won't wire + int8_t v_ETH_PHY_CS = 41; + int8_t v_ETH_PHY_IRQ = 2; // -1 if you won't wire + int8_t v_ETH_PHY_RST = 1; // -1 if you won't wire #endif private: diff --git a/src/MoonBase/Modules/ModuleIO.h b/src/MoonBase/Modules/ModuleIO.h index 20d5c19b5..bef81c5e2 100644 --- a/src/MoonBase/Modules/ModuleIO.h +++ b/src/MoonBase/Modules/ModuleIO.h @@ -498,50 +498,43 @@ class ModuleIO : public Module { EXT_LOGD(MB_TAG, "Try to configure ethernet"); EthernetSettingsService* ess = _sveltekit->getEthernetSettingsService(); #ifdef CONFIG_IDF_TARGET_ESP32S3 - ess->v_ETH_SPI_SCK = UINT8_MAX; - ess->v_ETH_SPI_MISO = UINT8_MAX; - ess->v_ETH_SPI_MOSI = UINT8_MAX; - ess->v_ETH_PHY_CS = UINT8_MAX; - ess->v_ETH_PHY_IRQ = UINT8_MAX; + ess->v_ETH_SPI_SCK = INT8_MAX; + ess->v_ETH_SPI_MISO = INT8_MAX; + ess->v_ETH_SPI_MOSI = INT8_MAX; + ess->v_ETH_PHY_CS = INT8_MAX; + ess->v_ETH_PHY_IRQ = INT8_MAX; + + auto assignIfValid = [](uint8_t gpio, uint8_t usage, int8_t& target) { + if (GPIO_IS_VALID_GPIO(gpio)) + target = gpio; + else + EXT_LOGE(MB_TAG, "%d: gpio %d not valid", usage, gpio); + }; + // if ethernet pins change // find the pins needed for (JsonObject pinObject : _state.data["pins"].as()) { uint8_t usage = pinObject["usage"]; uint8_t gpio = pinObject["GPIO"]; if (usage == pin_SPI_SCK) { - if (GPIO_IS_VALID_GPIO(gpio)) - ess->v_ETH_SPI_SCK = gpio; - else - EXT_LOGE(MB_TAG, "%d: gpio %d not valid", usage, gpio); + assignIfValid(gpio, usage, ess->v_ETH_SPI_SCK); } if (usage == pin_SPI_MISO) { - if (GPIO_IS_VALID_GPIO(gpio)) - ess->v_ETH_SPI_MISO = gpio; - else - EXT_LOGE(MB_TAG, "%d: gpio %d not valid", usage, gpio); + assignIfValid(gpio, usage, ess->v_ETH_SPI_MISO); } if (usage == pin_SPI_MOSI) { - if (GPIO_IS_VALID_GPIO(gpio)) - ess->v_ETH_SPI_MOSI = gpio; - else - EXT_LOGE(MB_TAG, "%d: gpio %d not valid", usage, gpio); + assignIfValid(gpio, usage, ess->v_ETH_SPI_MOSI); } if (usage == pin_PHY_CS) { - if (GPIO_IS_VALID_GPIO(gpio)) - ess->v_ETH_PHY_CS = gpio; - else - EXT_LOGE(MB_TAG, "%d: gpio %d not valid", usage, gpio); + assignIfValid(gpio, usage, ess->v_ETH_PHY_CS); } if (usage == pin_PHY_IRQ) { - if (GPIO_IS_VALID_GPIO(gpio)) - ess->v_ETH_PHY_IRQ = gpio; - else - EXT_LOGE(MB_TAG, "%d: gpio %d not valid", usage, gpio); + assignIfValid(gpio, usage, ess->v_ETH_PHY_IRQ); } } // allocate the pins found - if (ess->v_ETH_SPI_SCK != UINT8_MAX && ess->v_ETH_SPI_MISO != UINT8_MAX && ess->v_ETH_SPI_MOSI != UINT8_MAX && ess->v_ETH_PHY_CS != UINT8_MAX && ess->v_ETH_PHY_IRQ != UINT8_MAX) { + if (ess->v_ETH_SPI_SCK != INT8_MAX && ess->v_ETH_SPI_MISO != INT8_MAX && ess->v_ETH_SPI_MOSI != INT8_MAX && ess->v_ETH_PHY_CS != INT8_MAX && ess->v_ETH_PHY_IRQ != INT8_MAX) { // ess->v_ETH_PHY_TYPE = ETH_PHY_W5500; // ess->v_ETH_PHY_ADDR = 1; ess->v_ETH_PHY_RST = -1; // not wired diff --git a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h index 8bdeab70b..ed10f2234 100644 --- a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h +++ b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h @@ -28,14 +28,14 @@ class ArtNetInDriver : public Node { bool ddp = false; uint8_t view = 0; // physical layer uint16_t port = 6454; - uint8_t universeMin = 0; - uint8_t universeMax = 255; + uint16_t universeMin = 0; + uint16_t universeMax = 255; void setup() override { addControl(ddp, "DDP", "checkbox"); addControl(port, "port", "number", 0, 65538); - addControl(universeMin, "universeMin", "number", 0, 65538); - addControl(universeMax, "universeMax", "number", 0, 65538); + addControl(universeMin, "universeMin", "number", 0, 32767); + addControl(universeMax, "universeMax", "number", 0, 32767); addControl(view, "view", "select"); addControlValue("Physical layer"); uint8_t i = 1; // start with one @@ -149,7 +149,7 @@ class ArtNetInDriver : public Node { void handleDDP() { DDPHeader* header = (DDPHeader*)packetBuffer; - bool pushFlag = (header->flags & 0x80) != 0; + // bool pushFlag = (header->flags & 0x80) != 0; uint8_t dataType = header->dataType; uint32_t offset = (header->offset >> 24) | ((header->offset >> 8) & 0xFF00) | ((header->offset << 8) & 0xFF0000) | (header->offset << 24); From 60bd9e0f57eaa6d7e720c8a36626108827266c6d Mon Sep 17 00:00:00 2001 From: ewowi Date: Wed, 10 Dec 2025 17:18:35 +0100 Subject: [PATCH 7/7] Final changes before merge --- lib/framework/EthernetSettingsService.h | 2 +- src/MoonBase/Modules/ModuleIO.h | 12 ++++++------ src/MoonLight/Nodes/Drivers/D_ArtnetIn.h | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/framework/EthernetSettingsService.h b/lib/framework/EthernetSettingsService.h index 286731a66..666ab55fa 100644 --- a/lib/framework/EthernetSettingsService.h +++ b/lib/framework/EthernetSettingsService.h @@ -112,7 +112,7 @@ class EthernetSettingsService : public StatefulService // πŸŒ™ compiler directives to variables #ifdef CONFIG_IDF_TARGET_ESP32S3 - int8_t v_ETH_SPI_SCK = INT8_MAX; //42; v_ETH_SPI_SCK is check if configured, see configureNetwork and ModuleIO + int8_t v_ETH_SPI_SCK = -1; //42; v_ETH_SPI_SCK is check if configured, see configureNetwork and ModuleIO int8_t v_ETH_SPI_MISO = 44; int8_t v_ETH_SPI_MOSI = 43; diff --git a/src/MoonBase/Modules/ModuleIO.h b/src/MoonBase/Modules/ModuleIO.h index bef81c5e2..12b6530d0 100644 --- a/src/MoonBase/Modules/ModuleIO.h +++ b/src/MoonBase/Modules/ModuleIO.h @@ -498,11 +498,11 @@ class ModuleIO : public Module { EXT_LOGD(MB_TAG, "Try to configure ethernet"); EthernetSettingsService* ess = _sveltekit->getEthernetSettingsService(); #ifdef CONFIG_IDF_TARGET_ESP32S3 - ess->v_ETH_SPI_SCK = INT8_MAX; - ess->v_ETH_SPI_MISO = INT8_MAX; - ess->v_ETH_SPI_MOSI = INT8_MAX; - ess->v_ETH_PHY_CS = INT8_MAX; - ess->v_ETH_PHY_IRQ = INT8_MAX; + ess->v_ETH_SPI_SCK = -1; + ess->v_ETH_SPI_MISO = -1; + ess->v_ETH_SPI_MOSI = -1; + ess->v_ETH_PHY_CS = -1; + ess->v_ETH_PHY_IRQ = -1; auto assignIfValid = [](uint8_t gpio, uint8_t usage, int8_t& target) { if (GPIO_IS_VALID_GPIO(gpio)) @@ -534,7 +534,7 @@ class ModuleIO : public Module { } // allocate the pins found - if (ess->v_ETH_SPI_SCK != INT8_MAX && ess->v_ETH_SPI_MISO != INT8_MAX && ess->v_ETH_SPI_MOSI != INT8_MAX && ess->v_ETH_PHY_CS != INT8_MAX && ess->v_ETH_PHY_IRQ != INT8_MAX) { + if (ess->v_ETH_SPI_SCK != -1 && ess->v_ETH_SPI_MISO != -1 && ess->v_ETH_SPI_MOSI != -1 && ess->v_ETH_PHY_CS != -1 && ess->v_ETH_PHY_IRQ != -1) { // ess->v_ETH_PHY_TYPE = ETH_PHY_W5500; // ess->v_ETH_PHY_ADDR = 1; ess->v_ETH_PHY_RST = -1; // not wired diff --git a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h index ed10f2234..5f1c7614d 100644 --- a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h +++ b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h @@ -29,7 +29,7 @@ class ArtNetInDriver : public Node { uint8_t view = 0; // physical layer uint16_t port = 6454; uint16_t universeMin = 0; - uint16_t universeMax = 255; + uint16_t universeMax = 32767; void setup() override { addControl(ddp, "DDP", "checkbox");