Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c0d662a
Swap DMX output to also use esp_dmx
netmindz Jan 10, 2026
37732ca
Temp enable DMX Output in all builds
netmindz Jan 10, 2026
96d4489
WLED_GLOBAL needed for dmx field to prevent linker errors
netmindz Jan 10, 2026
84ce66f
ass ifdef for 8266
netmindz Jan 10, 2026
d40f0f6
Set output pin during init
netmindz Jan 10, 2026
7d3a673
Remove lazy init
netmindz Jan 10, 2026
d336b97
set output pin
netmindz Jan 10, 2026
576b628
Allow runtime config of DMX output pin
netmindz Jan 10, 2026
2f874cf
remove redundant code
netmindz Jan 10, 2026
034e4f5
register pin with PinManager
netmindz Jan 10, 2026
834c285
default output pin to -1
netmindz Jan 10, 2026
22aff94
move dmx definition back to original location
netmindz Jan 10, 2026
585d174
minor cleanup
netmindz Jan 10, 2026
7a910c8
minor cleanup, code style
netmindz Jan 10, 2026
5cdca58
Fix naming
netmindz Jan 11, 2026
48168ed
write data
netmindz Jan 11, 2026
93ef454
Swap DMX output to also use esp_dmx
netmindz Jan 10, 2026
cd08d6f
Temp enable DMX Output in all builds
netmindz Jan 10, 2026
c8ff292
WLED_GLOBAL needed for dmx field to prevent linker errors
netmindz Jan 10, 2026
b11c7f7
ass ifdef for 8266
netmindz Jan 10, 2026
d5061ed
Set output pin during init
netmindz Jan 10, 2026
1ccef16
Remove lazy init
netmindz Jan 10, 2026
4a60207
set output pin
netmindz Jan 10, 2026
3b92b2d
Allow runtime config of DMX output pin
netmindz Jan 10, 2026
ec64355
remove redundant code
netmindz Jan 10, 2026
02274e1
register pin with PinManager
netmindz Jan 10, 2026
b8fcb1c
default output pin to -1
netmindz Jan 10, 2026
306ba83
move dmx definition back to original location
netmindz Jan 10, 2026
f5522e6
minor cleanup
netmindz Jan 10, 2026
38330b5
minor cleanup, code style
netmindz Jan 10, 2026
daa2223
Fix naming
netmindz Jan 11, 2026
e28774d
write data
netmindz Jan 11, 2026
c20f59d
DMXInput: try to assign pins before UART port and default back to 1 i…
netmindz Mar 20, 2026
ea3aba9
DMXInput: No need to swap default pin for LED when DMX not hard-coded
netmindz Mar 20, 2026
ab632e9
DMXInput: Move allocation of pin to initDMXOutput
netmindz Mar 20, 2026
cf1f236
Merge remote-tracking branch 'netmindz/dmx-output-esp_dmx' into dmx-o…
netmindz Mar 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ build_flags =
-D DECODE_LG=true
-DWLED_USE_MY_CONFIG
-D WLED_PS_DONT_REPLACE_FX ; PS replacement FX are purely a flash memory saving feature, do not replace classic FX until we run out of flash
-D WLED_ENABLE_DMX
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo: remove before merge


build_unflags =

Expand Down
6 changes: 6 additions & 0 deletions wled00/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
tdd = if_live[F("timeout")] | -1;
if (tdd >= 0) realtimeTimeoutMs = tdd * 100;

#ifdef WLED_ENABLE_DMX
CJSON(dmxOutputPin, if_live_dmx[F("dmxOutputPin")]);
#endif
#ifdef WLED_ENABLE_DMX_INPUT
CJSON(dmxInputTransmitPin, if_live_dmx[F("inputRxPin")]);
CJSON(dmxInputReceivePin, if_live_dmx[F("inputTxPin")]);
Expand Down Expand Up @@ -1133,6 +1136,9 @@ void serializeConfig(JsonObject root) {
if_live_dmx[F("addr")] = DMXAddress;
if_live_dmx[F("dss")] = DMXSegmentSpacing;
if_live_dmx["mode"] = DMXMode;
#ifdef WLED_ENABLE_DMX
if_live_dmx[F("dmxOutputPin")] = dmxOutputPin;
#endif
#ifdef WLED_ENABLE_DMX_INPUT
if_live_dmx[F("inputRxPin")] = dmxInputTransmitPin;
if_live_dmx[F("inputTxPin")] = dmxInputReceivePin;
Expand Down
7 changes: 1 addition & 6 deletions wled00/const.h
Original file line number Diff line number Diff line change
Expand Up @@ -650,13 +650,8 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");

// Defaults pins, type and counts to configure LED output
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3)
#ifdef WLED_ENABLE_DMX
#define DEFAULT_LED_PIN 1
#warning "Compiling with DMX. The default LED pin has been changed to pin 1."
#else
#define DEFAULT_LED_PIN 2 // GPIO2 (D4) on Wemos D1 mini compatible boards, safe to use on any board
#endif
#else
#else
#if defined(WLED_USE_ETHERNET)
#define DEFAULT_LED_PIN 4 // GPIO4 seems to be a "safe bet" for all known ethernet boards (issue #5155)
//#warning "Compiling with Ethernet support. The default LED pin has been changed to pin 4."
Expand Down
11 changes: 9 additions & 2 deletions wled00/data/settings_sync.htm
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@
}
function hideDMXInput(){gId("dmxInput").style.display="none";}
function hideNoDMXInput(){gId("dmxInputOff").style.display="none";}
function hideNoDMXOutput(){gId("dmxOnOffOutput").style.display="none";}
function hideNoDMXOutput(){
gId("dmxOnOffOutput").style.display="none";
gId("dmxOutput").style.display="inline";
}
</script>
</head>
<body>
Expand Down Expand Up @@ -179,7 +182,11 @@ <h4>Wired DMX Input Pins</h4>
DMX TX: <input name="IDMT" type="number" min="-1" max="99">DI<br/>
DMX Enable: <input name="IDME" type="number" min="-1" max="99">RE+DE<br/>
DMX Port: <input name="IDMP" type="number" min="1" max="2"><br/>
</div>
</div>
<div id="dmxOutput" style="display: none">
<h4>Wired DMX Output Pin</h4>
DMX TX: <input name="IDMO" type="number" min="-1" max="99"><br/>
</div>
<div id="dmxInputOff">
<br><i class="warn">This firmware build does not include DMX Input support. <br></i>
</div>
Expand Down
17 changes: 9 additions & 8 deletions wled00/dmx_input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,6 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo
// }
#endif

if (inputPortNum <= (SOC_UART_NUM - 1) && inputPortNum > 0) {
this->inputPortNum = inputPortNum;
}
else {
DEBUG_PRINTF("DMXInput: Error: invalid inputPortNum: %d\n", inputPortNum);
return;
}

if (rxPin > 0 && enPin > 0 && txPin > 0) {

const managed_pin_type pins[] = {
Expand All @@ -156,6 +148,15 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo
DEBUG_PRINTF("en in use by: %s\n", PinManager::getPinOwner(enPin));
return;
}
if (inputPortNum <= (SOC_UART_NUM - 1) && inputPortNum > 0) {
this->inputPortNum = inputPortNum;
}
else {
DEBUG_PRINTF("DMXInput: Error: invalid inputPortNum: %d, default to 1\n", inputPortNum);
this->inputPortNum = 1;
return;
}
Comment on lines +151 to +158
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Invalid-port fallback exits too early and leaves allocated pins reserved.

At Line 157, init returns after setting fallback port 1, so DMX input never starts on this path, and the previously allocated pins are not released.

Suggested fix
-    if (inputPortNum <= (SOC_UART_NUM - 1) && inputPortNum > 0) {
-      this->inputPortNum = inputPortNum;
-    }
-    else {
-      DEBUG_PRINTF("DMXInput: Error: invalid inputPortNum: %d, default to 1\n", inputPortNum);
-      this->inputPortNum = 1;
-      return;
-    }
+    if (inputPortNum <= (SOC_UART_NUM - 1) && inputPortNum > 0) {
+      this->inputPortNum = inputPortNum;
+    } else {
+      DEBUG_PRINTF("DMXInput: Error: invalid inputPortNum: %d, default to 1\n", inputPortNum);
+      this->inputPortNum = 1;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (inputPortNum <= (SOC_UART_NUM - 1) && inputPortNum > 0) {
this->inputPortNum = inputPortNum;
}
else {
DEBUG_PRINTF("DMXInput: Error: invalid inputPortNum: %d, default to 1\n", inputPortNum);
this->inputPortNum = 1;
return;
}
if (inputPortNum <= (SOC_UART_NUM - 1) && inputPortNum > 0) {
this->inputPortNum = inputPortNum;
} else {
DEBUG_PRINTF("DMXInput: Error: invalid inputPortNum: %d, default to 1\n", inputPortNum);
this->inputPortNum = 1;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/dmx_input.cpp` around lines 151 - 158, The code sets a fallback
this->inputPortNum = 1 then returns, which leaves previously allocated pins
reserved and prevents DMX input from starting; fix by either (A) removing the
early return so execution continues after setting inputPortNum to 1 (allowing
DMX input to start on port 1) or (B) if you must abort, release the allocated
pins/resources before returning (call the cleanup routine, e.g.,
releasePins()/deinit()/freePins() or the destructor-equivalent) so there is no
resource leak; locate the logic around inputPortNum in dmx_input.cpp (the
init/constructor where inputPortNum is validated) and apply one of these two
fixes to ensure pins are not left reserved.



this->rxPin = rxPin;
this->txPin = txPin;
Expand Down
49 changes: 38 additions & 11 deletions wled00/dmx_output.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
#include "wled.h"

#include "dmx_output.h"
Comment on lines 1 to +2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't pull in dmx_output.h before the feature guard.

The #ifdef WLED_ENABLE_DMX doesn't start until Line 11, so Line 2 makes every non-ESP8266 build require esp_dmx.h even when DMX output is disabled.

💡 Suggested change
 `#include` "wled.h"
-#include "dmx_output.h"
+#ifdef WLED_ENABLE_DMX
+#include "dmx_output.h"
+#endif
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#include "wled.h"
#include "dmx_output.h"
`#include` "wled.h"
`#ifdef` WLED_ENABLE_DMX
`#include` "dmx_output.h"
`#endif`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/dmx_output.cpp` around lines 1 - 2, dmx_output.cpp currently includes
dmx_output.h before the WLED_ENABLE_DMX guard, forcing esp_dmx.h dependencies
even when DMX is disabled; move the `#include` "dmx_output.h" (and any includes
that pull in esp_dmx.h) inside the existing `#ifdef` WLED_ENABLE_DMX / `#endif`
block so that dmx_output.h and esp_dmx.h are only included when WLED_ENABLE_DMX
is defined, preserving the current feature guard around the DMX-related
functions (references: dmx_output.h, WLED_ENABLE_DMX, esp_dmx.h).

/*
* Support for DMX output via serial (e.g. MAX485).
* Change the output pin in src/dependencies/ESPDMX.cpp, if needed (ESP8266)
* Change the output pin in src/dependencies/SparkFunDMX.cpp, if needed (ESP32)
* Support for DMX output via serial (e.g. MAX485).
* ESP8266 Library from:
* https://github.com/Rickgg/ESP-Dmx
* ESP32 Library from:
* https://github.com/sparkfun/SparkFunDMX
* https://github.com/someweisguy/esp_dmx
*/

#ifdef WLED_ENABLE_DMX
Expand Down Expand Up @@ -68,13 +66,42 @@ void handleDMXOutput()
dmx.update(); // update the DMX bus
}

void initDMXOutput() {
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2)
dmx.init(512); // initialize with bus length
#else
dmx.initWrite(512); // initialize with bus length
#endif
void initDMXOutput(int outputPin) {
if (outputPin < 1) return;
const bool pinAllocated = PinManager::allocatePin(outputPin, true, PinOwner::DMX);
if (!pinAllocated) {
DEBUG_PRINTF("DMXOutput: Error: Failed to allocate pin for DMX_OUTPUT. Pin already in use:\n");
DEBUG_PRINTF("In use by: %s\n", PinManager::getPinOwner(outputPin));
return;
}
DEBUG_PRINTF("DMXOutput: init: pin %d\n", outputPin);
dmx.init(outputPin); // set output pin and initialize DMX output
}

#if !defined(ESP8266)
void DMXOutput::init(uint8_t outputPin) {
dmx_config_t config = DMX_CONFIG_DEFAULT;
const bool installOk = dmx_driver_install(dmxPort, &config, DMX_INTR_FLAGS_DEFAULT);
if (!installOk) {
DEBUG_PRINTF("DMXOutput: Error: Failed to install dmx driver\n");
return;
}
const bool setPin = dmx_set_pin(dmxPort, outputPin, -1, -1);
if (!setPin) {
DEBUG_PRINTF("DMXOutput: Error: Failed to set DMX output pin\n");
return;
}
Comment on lines +69 to +93
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find the DMX output related files
find . -name "*dmx*" -type f | head -20

Repository: wled/WLED

Length of output: 198


🏁 Script executed:

# Look at the full dmx_output.cpp file
wc -l wled00/dmx_output.cpp

Repository: wled/WLED

Length of output: 78


🏁 Script executed:

# Check if there's a dmx_output.h header file
find . -name "dmx_output.h" -type f

Repository: wled/WLED

Length of output: 74


🏁 Script executed:

cat -n wled00/dmx_output.h

Repository: wled/WLED

Length of output: 1031


🏁 Script executed:

cat -n wled00/dmx_output.cpp

Repository: wled/WLED

Length of output: 4164


🏁 Script executed:

# Search for esp_dmx library references or documentation in the repo
rg -i "esp_dmx" --type h --type cpp | head -20

Repository: wled/WLED

Length of output: 320


🏁 Script executed:

# Check if esp_dmx is included as a dependency
find . -name "esp_dmx*" -type f | head -20

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

# Search for dmx_driver_install or dmx_set_pin usage/definition
rg "dmx_driver_install|dmx_set_pin" --type h --type cpp

Repository: wled/WLED

Length of output: 414


🌐 Web query:

someweisguy esp_dmx API dmx_driver_install dmx_set_pin return type

💡 Result:

esp_dmx is an open-source ESP32 library by someweisguy (Mitch Weisbrod) for ANSI-ESTA E1.11 DMX-512A and E1.20 RDM over UART ports. Include "esp_dmx.h". dmx_driver_install(dmx_port_t dmx_num, const dmx_config_t *config, const dmx_personality_t *personalities, int personality_count) installs/configures the DMX driver for a UART port. No explicit return type shown in examples or code snippets; called without checking return value, suggesting void. dmx_set_pin(dmx_port_t dmx_num, int tx_pin, int rx_pin, int rts_pin) configures GPIO pins for TX/RX/RTS. Similarly, no explicit return type; used without return check, suggesting void. Usage example from README: const dmx_port_t dmx_num = DMX_NUM_1; dmx_config_t config = DMX_CONFIG_DEFAULT; dmx_personality_t personalities[] = {{1, "Default"}}; int personality_count = 1; dmx_driver_install(dmx_num, &config, personalities, personality_count); dmx_set_pin(dmx_num, 17, 16, -1);

Citations:


🌐 Web query:

esp_dmx dmx_driver_install return value esp_err_t

💡 Result:

The function dmx_driver_install from the esp_dmx library (https://github.com/someweisguy/esp_dmx) returns esp_err_t, Espressif's standard error type used across ESP-IDF and Arduino-ESP32 APIs. This is evidenced by its usage in code examples where it is assigned to esp_err_t variables (e.g., esp_err_t dmxError = dmx_driver_install(...);) and consistent with ESP hardware driver patterns like uart_driver_install. Official documentation does not explicitly state the return type in READMEs, but source code patterns and error handling confirm it returns ESP_OK on success or an error code (e.g., ESP_ERR_INVALID_ARG) on failure. The library is compatible with ESP-IDF v4.4.1+ and Arduino-ESP32 v2.0.3+, supporting DMX-512A and RDM over ESP32 UARTs.

Citations:


Fix pin allocation leak and correct error handling return types.

Line 71 allocates the pin but DMXOutput::init() returns void with no mechanism to signal failure to the caller. If dmx_driver_install() or dmx_set_pin() fail, the pin allocation is never unwound, preventing retries on the same pin without a reboot.

Additionally, lines 84 and 89 incorrectly assign esp_err_t return values to bool variables. The esp_dmx library functions return esp_err_t (success/error codes), not bool. Assigning to bool loses the error information and creates invalid checks—the code will proceed regardless of actual failure. Both functions should be assigned to esp_err_t variables and checked against ESP_OK.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/dmx_output.cpp` around lines 69 - 93, initDMXOutput currently
allocates a pin via PinManager::allocatePin but DMXOutput::init returns void and
on failures (dmx_driver_install, dmx_set_pin) the allocation is never undone;
also dmx_driver_install and dmx_set_pin return esp_err_t but are stored in bools
losing error info. Change DMXOutput::init to return a bool (or esp_err_t) to
signal success/failure, modify initDMXOutput to check that result and call
PinManager::freePin(outputPin) on failure, and in DMXOutput::init assign the
results of dmx_driver_install and dmx_set_pin to esp_err_t variables and compare
against ESP_OK (use dmx_driver_install, dmx_set_pin, dmx.init,
PinManager::allocatePin, PinManager::freePin, PinManager::getPinOwner to locate
and fix the spots).

}
void DMXOutput::write(uint8_t channel, uint8_t value) {
dmxdata[channel] = value;
}
void DMXOutput::update() {
dmx_write(dmxPort, dmxdata, DMX_PACKET_SIZE);
dmx_send(dmxPort, DMX_PACKET_SIZE);
}
#endif


#else
void initDMXOutput(){}
void handleDMXOutput() {}
Expand Down
33 changes: 33 additions & 0 deletions wled00/dmx_output.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// Created by will on 1/10/26.
//

#ifndef DMX_OUTPUT_H
#define DMX_OUTPUT_H

#if defined(ESP8266)
#include "src/dependencies/dmx/ESPDMX.h"
#else
#include <esp_dmx.h>
/**
* Support for DMX Output via serial (e.g. max485) on ESP32
* ESP32 Library from:
* https://github.com/someweisguy/esp_dmx
*/
class DMXOutput
{
public:
void init(uint8_t outputPin);
void write(uint8_t channel, uint8_t value);
void update();
private:
byte dmxdata[DMX_PACKET_SIZE];
Comment on lines +20 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Use a 16-bit channel index for DMX writes.

DMXStart is uint16_t in wled00/wled.h, and handleDMXOutput() in wled00/dmx_output.cpp builds full DMX addresses from it. Exposing write() as uint8_t guarantees wraparound above channel 255, and channel 0 can currently overwrite the DMX start code.

💡 Suggested change
-    void write(uint8_t channel, uint8_t value);
+    void write(uint16_t channel, uint8_t value);

And in wled00/dmx_output.cpp:

-void DMXOutput::write(uint8_t channel, uint8_t value) {
-  dmxdata[channel] = value;
+void DMXOutput::write(uint16_t channel, uint8_t value) {
+  if (channel == 0 || channel >= DMX_PACKET_SIZE) return;
+  dmxdata[channel] = value;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void init(uint8_t outputPin);
void write(uint8_t channel, uint8_t value);
void update();
private:
byte dmxdata[DMX_PACKET_SIZE];
void init(uint8_t outputPin);
void write(uint16_t channel, uint8_t value);
void update();
private:
byte dmxdata[DMX_PACKET_SIZE];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/dmx_output.h` around lines 20 - 24, Change the write() API to accept a
16-bit DMX channel index (replace uint8_t with uint16_t in the write()
declaration) so addresses >255 do not wrap; in the implementation (write() in
dmx_output.cpp) treat the incoming channel as a full DMX address used by
handleDMXOutput()/DMXStart, validate it against DMX_PACKET_SIZE and DMXStart,
and prevent writing to index 0 (the DMX start code) by either rejecting
channel==0 or offsetting checks so index 0 is never overwritten.

/* Next, lets decide which DMX port to use. The ESP32 has either 2 or 3 ports.
Port 0 is typically used to transmit serial data back to your Serial Monitor,
so we shouldn't use that port. Lets use port 1! */
dmx_port_t dmxPort = 1;
};
#endif


#endif //DMX_OUTPUT_H
2 changes: 1 addition & 1 deletion wled00/fcn_declare.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ typedef struct WiFiConfig {
} wifi_config;

//dmx_output.cpp
void initDMXOutput();
void initDMXOutput(int outputPin);
void handleDMXOutput();

//dmx_input.cpp
Expand Down
3 changes: 2 additions & 1 deletion wled00/pin_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,12 @@ enum struct PinOwner : uint8_t {
Relay = 0x87, // 'Rly' == Relay pin from configuration
SPI_RAM = 0x88, // 'SpiR' == SPI RAM
DebugOut = 0x89, // 'Dbg' == debug output always IO1
DMX = 0x8A, // 'DMX' == hard-coded to IO2
DMX = 0x8A, // 'DMX' == DMX output via serial
HW_I2C = 0x8B, // 'I2C' == hardware I2C pins (4&5 on ESP8266, 21&22 on ESP32)
HW_SPI = 0x8C, // 'SPI' == hardware (V)SPI pins (13,14&15 on ESP8266, 5,18&23 on ESP32)
DMX_INPUT = 0x8D, // 'DMX_INPUT' == DMX input via serial
HUB75 = 0x8E, // 'Hub75' == Hub75 driver
DMX_OUTPUT = 0x8F, // 'DMX_OUTPUT' == DMX output via serial
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo: clean up

// Use UserMod IDs from const.h here
UM_Unspecified = USERMOD_ID_UNSPECIFIED, // 0x01
UM_Example = USERMOD_ID_EXAMPLE, // 0x02 // Usermod "usermod_v2_example.h"
Expand Down
4 changes: 3 additions & 1 deletion wled00/set.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
arlsDisableGammaCorrection = request->hasArg(F("RG"));
t = request->arg(F("WO")).toInt();
if (t >= -255 && t <= 255) arlsOffset = t;

#ifdef WLED_ENABLE_DMX
dmxOutputPin = request->arg(F("IDMO")).toInt();
#endif
Comment on lines +476 to +478
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add server-side bounds validation for IDMO.

Line 477 trusts client input directly. Please clamp/validate the pin range before persisting to avoid invalid DMX output pin state from crafted requests.

Suggested fix
 `#ifdef` WLED_ENABLE_DMX
-    dmxOutputPin = request->arg(F("IDMO")).toInt();
+    t = request->arg(F("IDMO")).toInt();
+    if (t >= -1 && t < WLED_NUM_PINS) {
+      dmxOutputPin = t;
+    } else {
+      dmxOutputPin = -1;
+    }
 `#endif`

#ifdef WLED_ENABLE_DMX_INPUT
dmxInputTransmitPin = request->arg(F("IDMT")).toInt();
dmxInputReceivePin = request->arg(F("IDMR")).toInt();
Expand Down
49 changes: 2 additions & 47 deletions wled00/src/dependencies/dmx/ESPDMX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,62 +18,19 @@
#include "ESPDMX.h"



#define dmxMaxChannel 512
#define defaultMax 32

#define DMXSPEED 250000
#define DMXFORMAT SERIAL_8N2
#define BREAKSPEED 83333
#define BREAKFORMAT SERIAL_8N1

bool dmxStarted = false;
int sendPin = 2; //default on ESP8266

//DMX value array and size. Entry 0 will hold startbyte, so we need 512+1 elements
uint8_t dmxDataStore[dmxMaxChannel+1] = {};
int channelSize;


void DMXESPSerial::init() {
void DMXESPSerial::init(int sendPin) {
this->sendPin = sendPin;
channelSize = defaultMax;
Comment on lines +21 to 23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

This caps the ESP8266 backend at 32 DMX channels.

Line 23 hardcodes channelSize to defaultMax, and Lines 32-33 clamp every higher address down to that ceiling. Any fixture mapping beyond slot 32 will be aliased onto channel 32 instead of reaching its real DMX address.

💡 Suggested change
-  channelSize = defaultMax;
+  channelSize = dmxMaxChannel;

If you want shorter frames, plumb the actual highest-used slot into init() instead of falling back to 32.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/src/dependencies/dmx/ESPDMX.cpp` around lines 21 - 23, The ESP8266 DMX
backend currently hardcodes channelSize to defaultMax inside DMXESPSerial::init
(and clamps addresses later), which forces a 32-channel ceiling and aliases
higher fixture addresses; change DMXESPSerial::init to accept and set the actual
highest-used slot (or a maxChannels parameter) instead of always assigning
defaultMax to channelSize, and update any address-clamping logic that references
channelSize (the clamp at lines that reduce higher addresses to the ceiling) to
use this passed-in max so frames are sized to the real highest-used slot rather
than being truncated to 32.


Serial1.begin(DMXSPEED);
pinMode(sendPin, OUTPUT);
dmxStarted = true;
Comment on lines 25 to 27
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n wled00/src/dependencies/dmx/ESPDMX.cpp | head -60

Repository: wled/WLED

Length of output: 1894


🏁 Script executed:

cat -n wled00/src/dependencies/dmx/ESPDMX.cpp

Repository: wled/WLED

Length of output: 1965


🏁 Script executed:

fd ESPDMX.h

Repository: wled/WLED

Length of output: 89


🏁 Script executed:

cat -n wled00/src/dependencies/dmx/ESPDMX.h

Repository: wled/WLED

Length of output: 1434


🏁 Script executed:

rg "DMXESPSerial" -t cpp -t h -A 3 -B 3

Repository: wled/WLED

Length of output: 2234


🏁 Script executed:

rg "dmx|DMX" -t cpp wled00/pin_manager.cpp -i -A 2 -B 2

Repository: wled/WLED

Length of output: 914


🏁 Script executed:

rg "dmx\.init|DMX.*init" -t cpp -A 2 -B 2

Repository: wled/WLED

Length of output: 1111


🏁 Script executed:

rg "allocatePin|PinOwner::DMX" -t cpp -A 2 -B 2

Repository: wled/WLED

Length of output: 32042


🏁 Script executed:

rg "Serial1|SERIAL.*TX|GPIO.*2" -t cpp wled00/ -i -B 2 -A 2 | head -50

Repository: wled/WLED

Length of output: 3077


🏁 Script executed:

rg "GPIO.?2|TX.*pin|Serial1.*pin" -t h -t cpp -i -B 2 -A 2 | head -80

Repository: wled/WLED

Length of output: 5090


🏁 Script executed:

fd "pins.h|config.h|defaults.h" | head -20

Repository: wled/WLED

Length of output: 141


🏁 Script executed:

rg "GPIO.?2|Serial1" wled00/ -t cpp -t h | grep -i "esp8266\|serial\|pin"

Repository: wled/WLED

Length of output: 1809


🏁 Script executed:

cat -n wled00/src/dependencies/dmx/ESPDMX.cpp | sed -n '44,60p'

Repository: wled/WLED

Length of output: 534


🏁 Script executed:

cat -n wled00/dmx_output.cpp

Repository: wled/WLED

Length of output: 4164


🏁 Script executed:

rg "Serial1|UART.*TX|GPIO.*2" wled00/ -t cpp -i -B 1 -A 1 | head -100

Repository: wled/WLED

Length of output: 7314


🏁 Script executed:

rg "ESP8266.*Serial|Serial1.*pin" -t cpp -i

Repository: wled/WLED

Length of output: 152


🏁 Script executed:

web_search
ESP8266 Serial1 TX pin hardware fixed GPIO

Repository: wled/WLED

Length of output: 149


🌐 Web query:

ESP8266 Serial1 TX pin GPIO fixed hardware

💡 Result:

The ESP8266 Serial1 (UART1) TX pin is fixed to GPIO2 by hardware. This is a dedicated hardware connection where UART1 TXD is hard-wired to GPIO2 pin. UART1 RX is not practically usable as its RXD pin is internally connected to the flash chip. In Arduino ESP8266 core, Serial1.begin uses this fixed GPIO2 for TX. Unlike UART0 (Serial), which defaults to GPIO1 (TX) and GPIO3 (RX) but can be swapped to other pins via software (e.g., Serial.swap to GPIO15 TX / GPIO13 RX), UART1 TX remains on GPIO2 and cannot be remapped to other GPIOs due to the fixed hardware mapping. To use Serial1 TX: Serial1.begin(115200); // TX on GPIO2 automatically Sources confirm this across official and technical documentation: - Arduino ESP8266 core reference: "Serial1 uses UART1, TX pin is GPIO2." - ESP8266EX datasheet: GPIO2 listed with UART TX function during flash programming, consistent with UART1 TX. - ESP8266 community wiki: Pin functions map SPECIAL to TX1 on pin 2 (GPIO2). - Espressif FAQ: UART1 TXD for logs/printing, RX occupied by flash. GPIO2 can be used as general GPIO when not using Serial1, but activating Serial1 configures it as TX.

Citations:


sendPin is misleading on the ESP8266 Serial1 path.

On ESP8266, Serial1 TX is hard-wired to GPIO2 and cannot be remapped. The sendPin parameter at line 26 is allocated in PinManager but is only used for RS485 direction control (digitalWrite), not actual DMX transmission. The actual TX pin (GPIO2) is never registered with PinManager, creating a mismatch where a runtime-configurable pin is reserved while the true TX pin remains untracked and could conflict with other GPIO2 allocations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/src/dependencies/dmx/ESPDMX.cpp` around lines 25 - 27, The code treats
sendPin as if it were the DMX TX, but on ESP8266 Serial1 TX is always GPIO2 and
sendPin is only the RS485 direction pin; update ESPDMX.cpp to stop treating
sendPin as the UART TX and instead reserve/register GPIO2 with the PinManager
when using Serial1 on ESP8266 (while keeping sendPin configured as the RS485
direction pin via pinMode/digitalWrite). Specifically, adjust the initialization
around Serial1.begin, pinMode(sendPin, OUTPUT) and dmxStarted so that PinManager
is used to mark GPIO2 as the serial TX (not sendPin) and ensure sendPin remains
only for direction control to avoid GPIO2 allocation conflicts.

}

// Set up the DMX-Protocol
void DMXESPSerial::init(int chanQuant) {

if (chanQuant > dmxMaxChannel || chanQuant <= 0) {
chanQuant = defaultMax;
}

channelSize = chanQuant;

Serial1.begin(DMXSPEED);
pinMode(sendPin, OUTPUT);
dmxStarted = true;
}

// Function to read DMX data
uint8_t DMXESPSerial::read(int Channel) {
if (dmxStarted == false) init();

if (Channel < 1) Channel = 1;
if (Channel > dmxMaxChannel) Channel = dmxMaxChannel;
return(dmxDataStore[Channel]);
}

// Function to send DMX data
void DMXESPSerial::write(int Channel, uint8_t value) {
if (dmxStarted == false) init();

if (Channel < 1) Channel = 1;
if (Channel > channelSize) Channel = channelSize;
if (value < 0) value = 0;
if (value > 255) value = 255;

dmxDataStore[Channel] = value;
}
Expand All @@ -85,8 +42,6 @@ void DMXESPSerial::end() {
}

void DMXESPSerial::update() {
if (dmxStarted == false) init();

//Send break
digitalWrite(sendPin, HIGH);
Serial1.begin(BREAKSPEED, BREAKFORMAT);
Expand Down
20 changes: 18 additions & 2 deletions wled00/src/dependencies/dmx/ESPDMX.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,32 @@
#ifndef ESPDMX_h
#define ESPDMX_h


#define dmxMaxChannel 512
#define defaultMax 32

#define DMXSPEED 250000
#define DMXFORMAT SERIAL_8N2
#define BREAKSPEED 83333
#define BREAKFORMAT SERIAL_8N1

// ---- Methods ----

class DMXESPSerial {
public:
void init();
void init(int MaxChan);
void init(int sendPin);
uint8_t read(int Channel);
void write(int channel, uint8_t value);
void update();
void end();
private:
int sendPin;
bool dmxStarted = false;

//DMX value array and size. Entry 0 will hold startbyte, so we need 512+1 elements
uint8_t dmxDataStore[dmxMaxChannel+1] = {};
int channelSize;

};

#endif
Loading
Loading