Skip to content

DMX Output - Runtime Config & replace SparkFunDMX with esp_dmx#5433

Draft
netmindz wants to merge 36 commits intowled:mainfrom
netmindz:dmx-output-esp_dmx
Draft

DMX Output - Runtime Config & replace SparkFunDMX with esp_dmx#5433
netmindz wants to merge 36 commits intowled:mainfrom
netmindz:dmx-output-esp_dmx

Conversation

@netmindz
Copy link
Member

@netmindz netmindz commented Mar 20, 2026

Allow the DMX Output pin to be set at runtime, not need the user to hack around in the source code
Also swap the DMX Output to the same driver as DMX Input, leaving only 8266 to use ESPDMX

Summary by CodeRabbit

  • New Features
    • DMX output pin is now user-configurable instead of hardcoded; select your preferred GPIO pin for DMX output
    • New "DMX Output Pin" setting added to Sync configuration page
    • DMX output pin configuration is persisted across device restarts

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 20, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 70faa969-e409-4500-98b2-c6a1fa0f436c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

Migrating from UI to YAML configuration.

Use the @coderabbitai configuration command in a PR comment to get a dump of all your UI settings in YAML format. You can then edit this YAML file and upload it to the root of your repository to configure CodeRabbit programmatically.

@netmindz netmindz added this to the 16.0.0 beta milestone Mar 20, 2026
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

-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

@netmindz netmindz changed the title Dmx output esp dmx DMX Output - Runtime Config & replace SparkFunDMX with esp_dmx Mar 20, 2026
@netmindz netmindz modified the milestones: 16.0.0 beta, 16.1 Mar 20, 2026
@netmindz
Copy link
Member Author

Replaces corrupt #5287

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wled00/src/dependencies/dmx/ESPDMX.cpp (1)

54-56: ⚠️ Potential issue | 🟠 Major

Send the start code plus all configured slots.

dmxDataStore[0] is the DMX start code, so writing only channelSize bytes drops the highest channel every frame. With the current default of 32, slot 32 never gets transmitted.

💡 Suggested change
-  Serial1.write(dmxDataStore, channelSize);
+  Serial1.write(dmxDataStore, channelSize + 1);
🤖 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 54 - 56, The code is
sending only channelSize bytes from dmxDataStore which omits dmxDataStore[0]
(the DMX start code) plus the last slot; update the send call so Serial1.write
transmits the start code plus all configured slots (send dmxDataStore with
length channelSize + 1) — locate the Serial1.write(dmxDataStore, channelSize)
call in ESPDMX.cpp and change it to send channelSize + 1 bytes so all slots
(including the highest) are transmitted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/dmx_input.cpp`:
- Around line 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.

In `@wled00/dmx_output.cpp`:
- Around line 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).
- Around line 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).

In `@wled00/dmx_output.h`:
- Around line 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.

In `@wled00/src/dependencies/dmx/ESPDMX.cpp`:
- Around line 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.
- Around line 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.

---

Outside diff comments:
In `@wled00/src/dependencies/dmx/ESPDMX.cpp`:
- Around line 54-56: The code is sending only channelSize bytes from
dmxDataStore which omits dmxDataStore[0] (the DMX start code) plus the last
slot; update the send call so Serial1.write transmits the start code plus all
configured slots (send dmxDataStore with length channelSize + 1) — locate the
Serial1.write(dmxDataStore, channelSize) call in ESPDMX.cpp and change it to
send channelSize + 1 bytes so all slots (including the highest) are transmitted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6e5b0125-2ad6-4e6e-8d7c-4748bb3b6968

📥 Commits

Reviewing files that changed from the base of the PR and between a721264 and cf1f236.

📒 Files selected for processing (17)
  • platformio.ini
  • wled00/cfg.cpp
  • wled00/const.h
  • wled00/data/settings_sync.htm
  • wled00/dmx_input.cpp
  • wled00/dmx_output.cpp
  • wled00/dmx_output.h
  • wled00/fcn_declare.h
  • wled00/pin_manager.h
  • wled00/set.cpp
  • wled00/src/dependencies/dmx/ESPDMX.cpp
  • wled00/src/dependencies/dmx/ESPDMX.h
  • wled00/src/dependencies/dmx/SparkFunDMX.cpp
  • wled00/src/dependencies/dmx/SparkFunDMX.h
  • wled00/wled.cpp
  • wled00/wled.h
  • wled00/xml.cpp
💤 Files with no reviewable changes (2)
  • wled00/src/dependencies/dmx/SparkFunDMX.cpp
  • wled00/src/dependencies/dmx/SparkFunDMX.h

Comment on lines +151 to +158
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;
}
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.

Comment on lines 1 to +2
#include "wled.h"

#include "dmx_output.h"
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).

Comment on lines +69 to +93
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;
}
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).

Comment on lines +20 to +24
void init(uint8_t outputPin);
void write(uint8_t channel, uint8_t value);
void update();
private:
byte dmxdata[DMX_PACKET_SIZE];
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.

Comment on lines +476 to +478
#ifdef WLED_ENABLE_DMX
dmxOutputPin = request->arg(F("IDMO")).toInt();
#endif
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`

Comment on lines +21 to 23
void DMXESPSerial::init(int sendPin) {
this->sendPin = sendPin;
channelSize = defaultMax;
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.

Comment on lines 25 to 27
Serial1.begin(DMXSPEED);
pinMode(sendPin, OUTPUT);
dmxStarted = true;
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant