Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 12 additions & 3 deletions wled00/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -751,11 +751,15 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(DMXStartLED,dmx[F("start-led")]);

JsonArray dmx_fixmap = dmx[F("fixmap")];
for (int i = 0; i < dmx_fixmap.size(); i++) {
if (i > 14) break;
for (int i = 0; i < MIN(dmx_fixmap.size(), MAX_CHANNELS_PER_FIXTURE); i++) {
CJSON(DMXFixtureMap[i],dmx_fixmap[i]);
}

JsonArray dmx_chsval = dmx[F("chsval")];
for (int i = 0; i < MIN(dmx_chsval.size(), MAX_CHANNELS_PER_FIXTURE); i++) {
CJSON(DMXChannelsValue[i],dmx_chsval[i]);
}

CJSON(e131ProxyUniverse, dmx[F("e131proxy")]);
#endif

Expand Down Expand Up @@ -1253,10 +1257,15 @@ void serializeConfig(JsonObject root) {
dmx[F("start-led")] = DMXStartLED;

JsonArray dmx_fixmap = dmx.createNestedArray(F("fixmap"));
for (unsigned i = 0; i < 15; i++) {
for (unsigned i = 0; i < MAX_CHANNELS_PER_FIXTURE; i++) {
dmx_fixmap.add(DMXFixtureMap[i]);
}

JsonArray dmx_chsval = dmx.createNestedArray(F("chsval"));
for (unsigned i = 0; i < MAX_CHANNELS_PER_FIXTURE; i++) {
dmx_chsval.add(DMXChannelsValue[i]);
}

dmx[F("e131proxy")] = e131ProxyUniverse;
#endif

Expand Down
4 changes: 4 additions & 0 deletions wled00/const.h
Original file line number Diff line number Diff line change
Expand Up @@ -655,4 +655,8 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
#define IRAM_ATTR_YN IRAM_ATTR
#endif

#ifdef WLED_ENABLE_DMX
#define MAX_CHANNELS_PER_FIXTURE 15
#endif

#endif
23 changes: 18 additions & 5 deletions wled00/data/settings_dmx.htm
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,20 @@
<script src="common.js" async type="text/javascript"></script>
<script>
function HW(){window.open("https://kno.wled.ge/interfaces/dmx-output/");}
function GCH(num) {
function GCH(num){
gId('dmxchannels').innerHTML += "";
for (i=0;i<num;i++) {
gId('dmxchannels').innerHTML += "<span id=CH" + (i+1) + "s >Channel " + (i+1) + ": <select name=CH" + (i+1) + " id=\"CH" + (i+1) + "\"><option value=0>Set to 0</option><option value=1>Red</option><option value=2>Green</option><option value=3>Blue</option><option value=4>White</option><option value=5>Shutter (Brightness)</option><option value=6>Set to 255</option></select></span><br />\n";
gId('dmxchannels').innerHTML += "<span id=CH" + (i+1) + "s >Channel " + (i+1) + ": <select name=CH" + (i+1) + " id=\"CH" + (i+1) + "\"><option value=0>Set to </option><option value=1>Red</option><option value=2>Green</option><option value=3>Blue</option><option value=4>White</option><option value=5>Shutter (Brightness)</option></select><input name=DV" + (i+1) + " id=\"DV" + (i+1) + "\" type=number min=0 max=255></span><br />\n";
}
for (i=0;i<num;i++) {
const dv = gId("DV" + (i+1));
gId("CH" + (i+1)).addEventListener("change", (event) => {
if (event.target.value == 0) {
dv.style.display = "inline";
} else {
dv.style.display = "none";
}
});
}
}
function mMap(){
Expand All @@ -22,14 +32,17 @@
gId("gapwarning").style.display="none";
}
for (i=0;i<15;i++) {
const ch = gId("CH" + (i+1));
if (i>=numCh) {
gId("CH"+(i+1) + "s").style.opacity = "0.5";
gId("CH"+(i+1)).disabled = true;

ch.disabled = true;
gId("DV"+(i+1)).disabled = true;
} else {
gId("CH"+(i+1) + "s").style.opacity = "1";
gId("CH"+(i+1)).disabled = false;
ch.disabled = false;
gId("DV"+(i+1)).disabled = false;
}
ch.dispatchEvent(new Event("change"));
}
}
function S(){
Expand Down
7 changes: 2 additions & 5 deletions wled00/dmx_output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ void handleDMXOutput()
for (int j = 0; j < DMXChannels; j++) {
int DMXAddr = DMXFixtureStart + j;
switch (DMXFixtureMap[j]) {
case 0: // Set this channel to 0. Good way to tell strobe- and fade-functions to fuck right off.
dmx.write(DMXAddr, 0);
case 0: // Set this channel to the selected value.
dmx.write(DMXAddr, DMXChannelsValue[j]);
break;
case 1: // Red
dmx.write(DMXAddr, calc_brightness ? (r * brightness) / 255 : r);
Expand All @@ -58,9 +58,6 @@ void handleDMXOutput()
case 5: // Shutter channel. Controls the brightness.
dmx.write(DMXAddr, brightness);
break;
case 6: // Sets this channel to 255. Like 0, but more wholesome.
dmx.write(DMXAddr, 255);
break;
Comment on lines -61 to -63
Copy link
Member

Choose a reason for hiding this comment

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

What happens to people's existing config that were using case 6 - can we be backwards compatible?

Copy link
Author

@mrv96 mrv96 Aug 31, 2025

Choose a reason for hiding this comment

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

Already pointed out by coderabbitai. Should I implement the solution suggested by it?

Another possibility may be to add a default behaviour in the switch-case statement which could write 255 to DMX or something else.

Let me know your favorite option.

image

Copy link

Choose a reason for hiding this comment

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

This should be the last open point (included coderabbitai actionables). Let me know how to proceed.

}
}
}
Expand Down
7 changes: 6 additions & 1 deletion wled00/set.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -640,11 +640,16 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
if (t>=0 && t < MAX_LEDS) {
DMXStartLED = t;
}
for (int i=0; i<15; i++) {
for (int i=0; i<MAX_CHANNELS_PER_FIXTURE; i++) {
String argname = "CH" + String((i+1));
t = request->arg(argname).toInt();
DMXFixtureMap[i] = t;
}
for (int i=0; i<MAX_CHANNELS_PER_FIXTURE; i++) {
String argname = "DV" + String((i+1));
t = request->arg(argname).toInt();
DMXChannelsValue[i] = constrain(t, 0, 255);
}
}
#endif

Expand Down
3 changes: 2 additions & 1 deletion wled00/wled.h
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,8 @@ WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to f
WLED_GLOBAL uint16_t e131ProxyUniverse _INIT(0); // output this E1.31 (sACN) / ArtNet universe via MAX485 (0 = disabled)
// dmx CONFIG
WLED_GLOBAL byte DMXChannels _INIT(7); // number of channels per fixture
WLED_GLOBAL byte DMXFixtureMap[15] _INIT_N(({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }));
WLED_GLOBAL byte DMXFixtureMap[MAX_CHANNELS_PER_FIXTURE] _INIT_N(({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }));
WLED_GLOBAL byte DMXChannelsValue[MAX_CHANNELS_PER_FIXTURE] _INIT_N(({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }));
// assigns the different channels to different functions. See wled21_dmx.ino for more information.
WLED_GLOBAL uint16_t DMXGap _INIT(10); // gap between the fixtures. makes addressing easier because you don't have to memorize odd numbers when climbing up onto a rig.
WLED_GLOBAL uint16_t DMXStart _INIT(10); // start address of the first fixture
Expand Down
15 changes: 9 additions & 6 deletions wled00/wled_eeprom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

//eeprom Version code, enables default settings instead of 0 init on update
#define EEPVER 22
#define EEPSIZE 2560 //Maximum is 4096
#define EEPSIZE 2570 //Maximum is 4096
//0 -> old version, default
//1 -> 0.4p 1711272 and up
//2 -> 0.4p 1711302 and up
Expand Down Expand Up @@ -343,20 +343,23 @@ void loadSettingsFromEEPROM()
//1024-2047 reserved

#ifdef WLED_ENABLE_DMX
// DMX (2530 - 2549)2535
// DMX (2530 - 2566)2535,2552
DMXChannels = EEPROM.read(2530);
DMXGap = EEPROM.read(2531) + ((EEPROM.read(2532) << 8) & 0xFF00);
DMXStart = EEPROM.read(2533) + ((EEPROM.read(2534) << 8) & 0xFF00);

for (int i=0;i<15;i++) {
for (int i=0;i<MAX_CHANNELS_PER_FIXTURE;i++) {
DMXFixtureMap[i] = EEPROM.read(2535+i);
} //last used: 2549
DMXStartLED = EEPROM.read(2550);
DMXStartLED = EEPROM.read(2550) + ((EEPROM.read(2551) << 8) & 0xFF00);
for (int i=0;i<MAX_CHANNELS_PER_FIXTURE;i++) {
DMXChannelsValue[i] = EEPROM.read(2552+i);
} //last used: 2566
Comment on lines +351 to +357
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Fix legacy DMX EEPROM overlap and DV back-compat (clamp to 15; don’t read DV from EEPROM).

Reading DMXFixtureMap with MAX_CHANNELS_PER_FIXTURE can overrun the legacy 15-byte block (2535–2549) and hit DMXStartLED at 2550 when MAX_CHANNELS_PER_FIXTURE > 15. Also, legacy EEPROM has no DV bytes at 2552+, so reading DMXChannelsValue from 2552… yields garbage. Migrate code-6 (“Set to 255”) to DV=255 and init the rest; keep reading map only up to 15. Additionally, guard 16-bit DMXStartLED for older versions and clamp to LED count.

Apply:

-  for (int i=0;i<MAX_CHANNELS_PER_FIXTURE;i++) {
-    DMXFixtureMap[i] = EEPROM.read(2535+i);
-  } //last used: 2549
-  DMXStartLED = EEPROM.read(2550) + ((EEPROM.read(2551) << 8) & 0xFF00);
-  for (int i=0;i<MAX_CHANNELS_PER_FIXTURE;i++) {
-    DMXChannelsValue[i] = EEPROM.read(2552+i);
-  } //last used: 2566
+  // Legacy EEPROM stores exactly 15 fixture-map bytes at 2535..2549
+  constexpr uint8_t LEGACY_DMX_CHANS = 15;
+  const uint8_t n = (MAX_CHANNELS_PER_FIXTURE < LEGACY_DMX_CHANS) ? MAX_CHANNELS_PER_FIXTURE : LEGACY_DMX_CHANS;
+  for (uint8_t i = 0; i < n; i++) {
+    DMXFixtureMap[i] = EEPROM.read(2535 + i);
+  } // last used: 2549
+  for (uint8_t i = n; i < MAX_CHANNELS_PER_FIXTURE; i++) {
+    DMXFixtureMap[i] = 0; // init extras
+  }
+
+  // DMXStartLED: legacy is 8-bit at 2550; optional high byte at 2551 for newer layouts
+  uint16_t dmxStartLo = EEPROM.read(2550);
+  uint16_t dmxStartHi = (lastEEPROMversion > 21) ? EEPROM.read(2551) : 0;
+  DMXStartLED = (dmxStartHi << 8) | dmxStartLo;
+  if (DMXStartLED >= MAX_LEDS) DMXStartLED = (MAX_LEDS ? (MAX_LEDS - 1) : 0);
+
+  // Legacy has no DV bytes. Initialize and migrate legacy "Set to 255" (code 6) -> DV=255
+  for (uint8_t i = 0; i < MAX_CHANNELS_PER_FIXTURE; i++) {
+    if (DMXFixtureMap[i] == 6) {
+      DMXFixtureMap[i]   = 0;   // "Set to"
+      DMXChannelsValue[i]= 255; // DV
+    } else {
+      DMXChannelsValue[i]= 0;   // default DV
+    }
+  }

To verify clamping and no overlap:


🏁 Script executed:

#!/bin/bash
# MAX_CHANNELS_PER_FIXTURE actual value
rg -nP 'MAX_CHANNELS_PER_FIXTURE' -C2 wled00/const.h

# Ensure no other code assumes DV bytes exist in legacy EEPROM space
rg -nP 'EEPROM\.read\((255[0-9]|256[0-9])\)' -C2

Length of output: 563


🏁 Script executed:

head -n 100 wled00/wled_eeprom.cpp

Length of output: 2696


🏁 Script executed:

#!/bin/bash
sed -n '340,430p' wled00/wled_eeprom.cpp

Length of output: 3156


Refactor DMXStartLED back-compat and bounds checking

  • Read the high byte at EEPROM 2551 only for EEPROM versions ≥ 17 (DMX support) and treat it as zero otherwise.
  • After computing DMXStartLED, clamp it to MAX_LEDS – 1.
  • Optional: zero-initialize any extra DMXFixtureMap slots and skip reading DMXChannelsValue for EEPROM versions before DMX was introduced (EEPVER < 17) to avoid stale data.
🤖 Prompt for AI Agents
In wled00/wled_eeprom.cpp around lines 351-357, update the DMX EEPROM reads to
be backward-compatible and safe: only read the high byte at address 2551 when
the stored EEPROM version (EEPVER or equivalent eepromVersion variable) is >=
17, otherwise treat the high byte as zero; after combining low and high bytes
compute DMXStartLED and clamp it to the valid range (0 .. MAX_LEDS-1); for
DMXFixtureMap zero-initialize any slots beyond previously-used count to avoid
stale data; and if EEPROM version < 17 skip reading DMXChannelsValue entries
(leave them zero) to prevent loading pre-DMX garbage.

#endif

//Usermod memory
//2551 - 2559 reserved for Usermods, usable by default
//2560 - 2943 usable, NOT reserved (need to increase EEPSIZE accordingly, new WLED core features may override this section)
//2571 - 2579 reserved for Usermods, usable by default
//2580 - 2943 usable, NOT reserved (need to increase EEPSIZE accordingly, new WLED core features may override this section)
//2944 - 3071 reserved for Usermods (need to increase EEPSIZE to 3072 in const.h)
}

Expand Down
7 changes: 6 additions & 1 deletion wled00/wled_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,15 @@ static String dmxProcessor(const String& var)
mapJS += F(";\nLC=");
mapJS += String(strip.getLengthTotal());
mapJS += F(";\nvar CH=[");
for (int i=0; i<15; i++) {
for (int i=0; i<MAX_CHANNELS_PER_FIXTURE; i++) {
mapJS += String(DMXFixtureMap[i]) + ',';
}
mapJS += F("0];");
mapJS += F(";\nvar DV=[");
for (int i=0; i<MAX_CHANNELS_PER_FIXTURE; i++) {
mapJS += String(DMXChannelsValue[i]) + ',';
}
mapJS += F("0];");
}
return mapJS;
}
Expand Down
26 changes: 11 additions & 15 deletions wled00/xml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -634,21 +634,17 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormValue(settingsScript,PSTR("CS"),DMXStart);
printSetFormValue(settingsScript,PSTR("SL"),DMXStartLED);

printSetFormIndex(settingsScript,PSTR("CH1"),DMXFixtureMap[0]);
printSetFormIndex(settingsScript,PSTR("CH2"),DMXFixtureMap[1]);
printSetFormIndex(settingsScript,PSTR("CH3"),DMXFixtureMap[2]);
printSetFormIndex(settingsScript,PSTR("CH4"),DMXFixtureMap[3]);
printSetFormIndex(settingsScript,PSTR("CH5"),DMXFixtureMap[4]);
printSetFormIndex(settingsScript,PSTR("CH6"),DMXFixtureMap[5]);
printSetFormIndex(settingsScript,PSTR("CH7"),DMXFixtureMap[6]);
printSetFormIndex(settingsScript,PSTR("CH8"),DMXFixtureMap[7]);
printSetFormIndex(settingsScript,PSTR("CH9"),DMXFixtureMap[8]);
printSetFormIndex(settingsScript,PSTR("CH10"),DMXFixtureMap[9]);
printSetFormIndex(settingsScript,PSTR("CH11"),DMXFixtureMap[10]);
printSetFormIndex(settingsScript,PSTR("CH12"),DMXFixtureMap[11]);
printSetFormIndex(settingsScript,PSTR("CH13"),DMXFixtureMap[12]);
printSetFormIndex(settingsScript,PSTR("CH14"),DMXFixtureMap[13]);
printSetFormIndex(settingsScript,PSTR("CH15"),DMXFixtureMap[14]);
for (int i = 0; i < MAX_CHANNELS_PER_FIXTURE; i++) {
char buf[5];
snprintf_P(buf, sizeof(buf), PSTR("CH%d"), i+1);
printSetFormIndex(settingsScript,buf,DMXFixtureMap[i]);
}

for (int i = 0; i < MAX_CHANNELS_PER_FIXTURE; i++) {
char buf[5];
snprintf_P(buf, sizeof(buf), PSTR("DV%d"), i+1);
printSetFormValue(settingsScript,buf,DMXChannelsValue[i]);
}
}
#endif

Expand Down