diff --git a/docs/develop/development.md b/docs/develop/development.md index 89ebd5874..8a60a10c3 100644 --- a/docs/develop/development.md +++ b/docs/develop/development.md @@ -110,10 +110,12 @@ 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 - * use ./build/release/MoonLight_esp32-s3-devkitc-1-n16r8v_0-6-0.bin and address 0x10000 to flash only the MoonLight partition + * -b 2000000: Baud rate: lower if too high for your device + * 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 d8ed40c20..a4a8dbea0 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. 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/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/docs/moonbase/inputoutput.md b/docs/moonbase/inputoutput.md index f8ebcf9b5..11ace531a 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 (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: 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 5688351c8..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 | -| 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)) | +| 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/lib/framework/EthernetSettingsService.h b/lib/framework/EthernetSettingsService.h index 69093f101..666ab55fa 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 = -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; 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 e0e031f17..12b6530d0 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,28 @@ 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 + 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); + 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); @@ -436,6 +453,10 @@ 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 == "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 @@ -477,25 +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 = -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)) + 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) 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) { + assignIfValid(gpio, usage, ess->v_ETH_SPI_SCK); + } + if (usage == pin_SPI_MISO) { + assignIfValid(gpio, usage, ess->v_ETH_SPI_MISO); + } + if (usage == pin_SPI_MOSI) { + assignIfValid(gpio, usage, ess->v_ETH_SPI_MOSI); + } + if (usage == pin_PHY_CS) { + assignIfValid(gpio, usage, ess->v_ETH_PHY_CS); + } + if (usage == pin_PHY_IRQ) { + 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 != -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/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/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/ModuleDrivers.h b/src/MoonLight/Modules/ModuleDrivers.h index 754f5a1c0..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; } } @@ -69,7 +70,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..9e5bbef9c 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) @@ -61,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 1b55f5476..38797f1d2 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,28 @@ 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"]; + uint8_t gpio = pinObject["GPIO"]; + if (usage == pin_Relay_LightsOn) { - pinRelayBrightness = pinObject["GPIO"]; - EXT_LOGD(ML_TAG, "pinRelayBrightness found %d", pinRelayBrightness); + 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); + EXT_LOGD(ML_TAG, "pinRelayLightsOn found %d", pinRelayLightsOn); + } else + EXT_LOGE(MB_TAG, "gpio %d not valid", pinRelayLightsOn); } else if (usage == pin_Button_LightsOn) { - pinToggleOnOff = pinObject["GPIO"]; - pinMode(pinToggleOnOff, INPUT_PULLUP); - EXT_LOGD(ML_TAG, "pinToggleOnOff found %d", pinToggleOnOff); + if (GPIO_IS_VALID_GPIO(gpio)) { + pinButtonLightsOn = gpio; + 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]); @@ -151,8 +162,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 +263,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 +315,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 +338,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/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..5f1c7614d 100644 --- a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h +++ b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h @@ -23,19 +23,22 @@ 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; - uint8_t view = 0; + uint8_t view = 0; // physical layer uint16_t port = 6454; + uint16_t universeMin = 0; + uint16_t universeMax = 32767; void setup() override { addControl(ddp, "DDP", "checkbox"); addControl(port, "port", "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 = 0; + uint8_t i = 1; // start with one for (VirtualLayer* layer : layerP.layers) { Char<32> layerName; layerName.format("Layer %d", i); @@ -55,19 +58,34 @@ 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) { + artnetUdp.begin(port); + EXT_LOGI(ML_TAG, "Listening for %s on port %d", ddp ? "DDP" : "Art-Net", port); + init = true; + } + + while (int packetSize = artnetUdp.parsePacket()) { + if (packetSize < sizeof(ArtNetHeader) || packetSize > sizeof(packetBuffer)) { + artnetUdp.clear(); // drains all available packets + continue; + } + + artnetUdp.read(packetBuffer, min(packetSize, (int)sizeof(packetBuffer))); + if (ddp) - ddpUdp.begin(port); + handleDDP(); else - artnetUdp.begin(port); - EXT_LOGD(ML_TAG, "Listening for %s on port %d", ddp ? "DDP" : "Art-Net", port); - init = true; + handleArtNet(); } - if (ddp) - handleDDP(); - else - handleArtNet(); } // Art-Net Configuration @@ -95,87 +113,66 @@ class ArtNetInDriver : public Node { }; void handleArtNet() { - // LightsHeader* header = &layerP.lights.header; + // Verify Art-Net packet + if (memcmp(packetBuffer, "Art-Net", 7) == 0) { + ArtNetHeader* header = (ArtNetHeader*)packetBuffer; + uint16_t opcode = header->opcode; - int packetSize = artnetUdp.parsePacket(); + // EXT_LOGD(ML_TAG, "size:%d universe:%d", packetSize, header->universe); - if (packetSize >= sizeof(ArtNetHeader)) { - artnetUdp.read(packetBuffer, packetSize); + // Check if it's a DMX packet (opcode 0x5000) + if (opcode == 0x5000) { + uint16_t universe = header->universe; + if (universe >= universeMin && universe <= universeMax) { + uint16_t dataLength = (header->length >> 8) | (header->length << 8); - // Verify Art-Net packet - if (memcmp(packetBuffer, "Art-Net", 7) == 0) { - ArtNetHeader* header = (ArtNetHeader*)packetBuffer; - uint16_t opcode = header->opcode; - // - // EXT_LOGD(ML_TAG, "%d", 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) - int numPixels = min((uint16_t)(dataLength / layerP.lights.header.channelsPerLight), (uint16_t)(layerP.lights.header.nrOfLights)); + 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++) { - 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) { + 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); + DDPHeader* header = (DDPHeader*)packetBuffer; - DDPHeader* header = (DDPHeader*)packetBuffer; + // bool pushFlag = (header->flags & 0x80) != 0; + uint8_t dataType = header->dataType; - // Extract header fields - bool pushFlag = (header->flags & 0x80) != 0; // Bit 7 - 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); - // Convert big-endian offset and length - uint32_t offset = (header->offset >> 24) | ((header->offset >> 8) & 0xFF00) | ((header->offset << 8) & 0xFF0000) | (header->offset << 24); + if (dataType == 0x01) { + uint8_t* pixelData = packetBuffer + sizeof(DDPHeader); - uint16_t dataLen = (header->dataLen >> 8) | (header->dataLen << 8); + int startPixel = offset / layerP.lights.header.channelsPerLight; + int numPixels = min((uint16_t)(dataLen / layerP.lights.header.channelsPerLight), (uint16_t)(layerP.lights.header.nrOfLights - startPixel)); - // Validate data type (0x01 = RGB) - if (dataType == 0x01) { - uint8_t* pixelData = packetBuffer + sizeof(DDPHeader); - - // 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) { + 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); } } - // Only update display if push flag is set - if (pushFlag) { - // FastLED.show(); - // Serial.println("DDP: " + String(numPixels) + " pixels updated (offset: " + String(startPixel) + ")"); - } } } } }; #endif - -// #include diff --git a/src/MoonLight/Nodes/Drivers/D_AudioSync.h b/src/MoonLight/Nodes/Drivers/D_AudioSync.h index cb65e30a3..a26baaa84 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,14 +26,22 @@ class AudioSyncDriver : public Node { void loop() override { if (!WiFi.isConnected() && !ETH.connected()) { // make WLED Audio Sync network failure resilient - WIP - 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; } if (!init) { sync.begin(); init = true; - EXT_LOGI(ML_TAG, "AudioSync: Initialized"); + EXT_LOGI(ML_TAG, "Audio Sync: Initialized"); } if (sync.read()) { @@ -42,7 +50,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..16a694a6d 100644 --- a/src/MoonLight/Nodes/Drivers/D_Infrared.h +++ b/src/MoonLight/Nodes/Drivers/D_Infrared.h @@ -42,11 +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"]; - EXT_LOGD(ML_TAG, "pin_Infrared found %d", pinInfrared); + if (GPIO_IS_VALID_GPIO(gpio)) { + pinInfrared = gpio; + EXT_LOGD(ML_TAG, "pin_Infrared found %d", pinInfrared); + } else { + EXT_LOGE(MB_TAG, "gpio %d not valid", pinInfrared); + } } } + if (pinInfrared != UINT8_MAX) { EXT_LOGI(IR_DRIVER_TAG, "Changing to pin #%d", pinInfrared); @@ -352,10 +358,10 @@ 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 - newState["palette"] = min(state.data["palette"].as() + 1, 8); // 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, 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 newState["preset"] = state.data["preset"]; newState["preset"]["action"] = "click";