Skip to content

Commit 48f5099

Browse files
authored
Merge pull request #4495 from netmindz/DMX-Input-AC
Add Wired DMX Input support
2 parents b9aeb19 + a582786 commit 48f5099

15 files changed

+460
-23
lines changed

platformio.ini

+2
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,10 @@ build_flags = -g
282282
-DARDUINO_ARCH_ESP32 -DESP32
283283
-D CONFIG_ASYNC_TCP_USE_WDT=0
284284
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
285+
-D WLED_ENABLE_DMX_INPUT
285286
lib_deps =
286287
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
288+
https://github.com/someweisguy/esp_dmx.git#47db25d
287289
${env.lib_deps}
288290
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
289291

wled00/cfg.cpp

+14
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,14 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
522522

523523
tdd = if_live[F("timeout")] | -1;
524524
if (tdd >= 0) realtimeTimeoutMs = tdd * 100;
525+
526+
#ifdef WLED_ENABLE_DMX_INPUT
527+
CJSON(dmxInputTransmitPin, if_live_dmx[F("inputRxPin")]);
528+
CJSON(dmxInputReceivePin, if_live_dmx[F("inputTxPin")]);
529+
CJSON(dmxInputEnablePin, if_live_dmx[F("inputEnablePin")]);
530+
CJSON(dmxInputPort, if_live_dmx[F("dmxInputPort")]);
531+
#endif
532+
525533
CJSON(arlsForceMaxBri, if_live[F("maxbri")]);
526534
CJSON(arlsDisableGammaCorrection, if_live[F("no-gc")]); // false
527535
CJSON(arlsOffset, if_live[F("offset")]); // 0
@@ -1001,6 +1009,12 @@ void serializeConfig() {
10011009
if_live_dmx[F("addr")] = DMXAddress;
10021010
if_live_dmx[F("dss")] = DMXSegmentSpacing;
10031011
if_live_dmx["mode"] = DMXMode;
1012+
#ifdef WLED_ENABLE_DMX_INPUT
1013+
if_live_dmx[F("inputRxPin")] = dmxInputTransmitPin;
1014+
if_live_dmx[F("inputTxPin")] = dmxInputReceivePin;
1015+
if_live_dmx[F("inputEnablePin")] = dmxInputEnablePin;
1016+
if_live_dmx[F("dmxInputPort")] = dmxInputPort;
1017+
#endif
10041018

10051019
if_live[F("timeout")] = realtimeTimeoutMs / 100;
10061020
if_live[F("maxbri")] = arlsForceMaxBri;

wled00/const.h

+1
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@
250250
#define REALTIME_MODE_ARTNET 6
251251
#define REALTIME_MODE_TPM2NET 7
252252
#define REALTIME_MODE_DDP 8
253+
#define REALTIME_MODE_DMX 9
253254

254255
//realtime override modes
255256
#define REALTIME_OVERRIDE_NONE 0

wled00/data/settings_sync.htm

+13
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,19 @@ <h3>Realtime</h3>
151151
Force max brightness: <input type="checkbox" name="FB"><br>
152152
Disable realtime gamma correction: <input type="checkbox" name="RG"><br>
153153
Realtime LED offset: <input name="WO" type="number" min="-255" max="255" required>
154+
<div id="dmxInput">
155+
<h4>Wired DMX Input Pins</h4>
156+
DMX RX: <input name="IDMR" type="number" min="-1" max="99">RO<br/>
157+
DMX TX: <input name="IDMT" type="number" min="-1" max="99">DI<br/>
158+
DMX Enable: <input name="IDME" type="number" min="-1" max="99">RE+DE<br/>
159+
DMX Port: <input name="IDMP" type="number" min="1" max="2"><br/>
160+
</div>
161+
<div id="dmxInputOff">
162+
<br><em style="color:darkorange">This firmware build does not include DMX Input support. <br></em>
163+
</div>
164+
<div id="dmxOnOff2">
165+
<br><em style="color:darkorange">This firmware build does not include DMX output support. <br></em>
166+
</div>
154167
<hr class="sml">
155168
<h3>Alexa Voice Assistant</h3>
156169
<div id="NoAlexa" class="hide">

wled00/dmx_input.cpp

+280
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
#include "wled.h"
2+
3+
#ifdef WLED_ENABLE_DMX_INPUT
4+
5+
#ifdef ESP8266
6+
#error DMX input is only supported on ESP32
7+
#endif
8+
9+
#include "dmx_input.h"
10+
#include <rdm/responder.h>
11+
12+
void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
13+
void *context)
14+
{
15+
DMXInput *dmx = static_cast<DMXInput *>(context);
16+
17+
if (!dmx) {
18+
DEBUG_PRINTLN("DMX: Error: no context in rdmPersonalityChangedCb");
19+
return;
20+
}
21+
22+
if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) {
23+
const uint8_t personality = dmx_get_current_personality(dmx->inputPortNum);
24+
DMXMode = std::min(DMX_MODE_PRESET, std::max(DMX_MODE_SINGLE_RGB, int(personality)));
25+
doSerializeConfig = true;
26+
DEBUG_PRINTF("DMX personality changed to to: %d\n", DMXMode);
27+
}
28+
}
29+
30+
void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
31+
void *context)
32+
{
33+
DMXInput *dmx = static_cast<DMXInput *>(context);
34+
35+
if (!dmx) {
36+
DEBUG_PRINTLN("DMX: Error: no context in rdmAddressChangedCb");
37+
return;
38+
}
39+
40+
if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) {
41+
const uint16_t addr = dmx_get_start_address(dmx->inputPortNum);
42+
DMXAddress = std::min(512, int(addr));
43+
doSerializeConfig = true;
44+
DEBUG_PRINTF("DMX start addr changed to: %d\n", DMXAddress);
45+
}
46+
}
47+
48+
static dmx_config_t createConfig()
49+
{
50+
dmx_config_t config;
51+
config.pd_size = 255;
52+
config.dmx_start_address = DMXAddress;
53+
config.model_id = 0;
54+
config.product_category = RDM_PRODUCT_CATEGORY_FIXTURE;
55+
config.software_version_id = VERSION;
56+
strcpy(config.device_label, "WLED_MM");
57+
58+
const std::string versionString = "WLED_V" + std::to_string(VERSION);
59+
strncpy(config.software_version_label, versionString.c_str(), 32);
60+
config.software_version_label[32] = '\0'; // zero termination in case versionString string was longer than 32 chars
61+
62+
config.personalities[0].description = "SINGLE_RGB";
63+
config.personalities[0].footprint = 3;
64+
config.personalities[1].description = "SINGLE_DRGB";
65+
config.personalities[1].footprint = 4;
66+
config.personalities[2].description = "EFFECT";
67+
config.personalities[2].footprint = 15;
68+
config.personalities[3].description = "MULTIPLE_RGB";
69+
config.personalities[3].footprint = std::min(512, int(strip.getLengthTotal()) * 3);
70+
config.personalities[4].description = "MULTIPLE_DRGB";
71+
config.personalities[4].footprint = std::min(512, int(strip.getLengthTotal()) * 3 + 1);
72+
config.personalities[5].description = "MULTIPLE_RGBW";
73+
config.personalities[5].footprint = std::min(512, int(strip.getLengthTotal()) * 4);
74+
config.personalities[6].description = "EFFECT_W";
75+
config.personalities[6].footprint = 18;
76+
config.personalities[7].description = "EFFECT_SEGMENT";
77+
config.personalities[7].footprint = std::min(512, strip.getSegmentsNum() * 15);
78+
config.personalities[8].description = "EFFECT_SEGMENT_W";
79+
config.personalities[8].footprint = std::min(512, strip.getSegmentsNum() * 18);
80+
config.personalities[9].description = "PRESET";
81+
config.personalities[9].footprint = 1;
82+
83+
config.personality_count = 10;
84+
// rdm personalities are numbered from 1, thus we can just set the DMXMode directly.
85+
config.current_personality = DMXMode;
86+
87+
return config;
88+
}
89+
90+
void dmxReceiverTask(void *context)
91+
{
92+
DMXInput *instance = static_cast<DMXInput *>(context);
93+
if (instance == nullptr) {
94+
return;
95+
}
96+
97+
if (instance->installDriver()) {
98+
while (true) {
99+
instance->updateInternal();
100+
}
101+
}
102+
}
103+
104+
bool DMXInput::installDriver()
105+
{
106+
107+
const auto config = createConfig();
108+
DEBUG_PRINTF("DMX port: %u\n", inputPortNum);
109+
if (!dmx_driver_install(inputPortNum, &config, DMX_INTR_FLAGS_DEFAULT)) {
110+
DEBUG_PRINTF("Error: Failed to install dmx driver\n");
111+
return false;
112+
}
113+
114+
DEBUG_PRINTF("Listening for DMX on pin %u\n", rxPin);
115+
DEBUG_PRINTF("Sending DMX on pin %u\n", txPin);
116+
DEBUG_PRINTF("DMX enable pin is: %u\n", enPin);
117+
dmx_set_pin(inputPortNum, txPin, rxPin, enPin);
118+
119+
rdm_register_dmx_start_address(inputPortNum, rdmAddressChangedCb, this);
120+
rdm_register_dmx_personality(inputPortNum, rdmPersonalityChangedCb, this);
121+
initialized = true;
122+
return true;
123+
}
124+
125+
void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPortNum)
126+
{
127+
128+
#ifdef WLED_ENABLE_DMX_OUTPUT
129+
//TODO add again once dmx output has been merged
130+
// if(inputPortNum == dmxOutputPort)
131+
// {
132+
// DEBUG_PRINTF("DMXInput: Error: Input port == output port");
133+
// return;
134+
// }
135+
#endif
136+
137+
if (inputPortNum <= (SOC_UART_NUM - 1) && inputPortNum > 0) {
138+
this->inputPortNum = inputPortNum;
139+
}
140+
else {
141+
DEBUG_PRINTF("DMXInput: Error: invalid inputPortNum: %d\n", inputPortNum);
142+
return;
143+
}
144+
145+
if (rxPin > 0 && enPin > 0 && txPin > 0) {
146+
147+
const managed_pin_type pins[] = {
148+
{(int8_t)txPin, false}, // these are not used as gpio pins, thus isOutput is always false.
149+
{(int8_t)rxPin, false},
150+
{(int8_t)enPin, false}};
151+
const bool pinsAllocated = PinManager::allocateMultiplePins(pins, 3, PinOwner::DMX_INPUT);
152+
if (!pinsAllocated) {
153+
DEBUG_PRINTF("DMXInput: Error: Failed to allocate pins for DMX_INPUT. Pins already in use:\n");
154+
DEBUG_PRINTF("rx in use by: %s\n", pinManager.getPinOwnerText(rxPin).c_str());
155+
DEBUG_PRINTF("tx in use by: %s\n", pinManager.getPinOwnerText(txPin).c_str());
156+
DEBUG_PRINTF("en in use by: %s\n", pinManager.getPinOwnerText(enPin).c_str());
157+
return;
158+
}
159+
160+
this->rxPin = rxPin;
161+
this->txPin = txPin;
162+
this->enPin = enPin;
163+
164+
// put dmx receiver into seperate task because it should not be blocked
165+
// pin to core 0 because wled is running on core 1
166+
xTaskCreatePinnedToCore(dmxReceiverTask, "DMX_RCV_TASK", 10240, this, 2, &task, 0);
167+
if (!task) {
168+
DEBUG_PRINTF("Error: Failed to create dmx rcv task");
169+
}
170+
}
171+
else {
172+
DEBUG_PRINTLN("DMX input disabled due to rxPin, enPin or txPin not set");
173+
return;
174+
}
175+
}
176+
177+
void DMXInput::updateInternal()
178+
{
179+
if (!initialized) {
180+
return;
181+
}
182+
183+
checkAndUpdateConfig();
184+
185+
dmx_packet_t packet;
186+
unsigned long now = millis();
187+
if (dmx_receive(inputPortNum, &packet, DMX_TIMEOUT_TICK)) {
188+
if (!packet.err) {
189+
if(!connected) {
190+
DEBUG_PRINTLN("DMX Input - connected");
191+
}
192+
connected = true;
193+
identify = isIdentifyOn();
194+
if (!packet.is_rdm) {
195+
const std::lock_guard<std::mutex> lock(dmxDataLock);
196+
dmx_read(inputPortNum, dmxdata, packet.size);
197+
}
198+
}
199+
else {
200+
connected = false;
201+
}
202+
}
203+
else {
204+
if(connected) {
205+
DEBUG_PRINTLN("DMX Input - disconnected");
206+
}
207+
connected = false;
208+
}
209+
}
210+
211+
212+
void DMXInput::update()
213+
{
214+
if (identify) {
215+
turnOnAllLeds();
216+
}
217+
else if (connected) {
218+
const std::lock_guard<std::mutex> lock(dmxDataLock);
219+
handleDMXData(1, 512, dmxdata, REALTIME_MODE_DMX, 0);
220+
}
221+
}
222+
223+
void DMXInput::turnOnAllLeds()
224+
{
225+
// TODO not sure if this is the correct way?
226+
const uint16_t numPixels = strip.getLengthTotal();
227+
for (uint16_t i = 0; i < numPixels; ++i)
228+
{
229+
strip.setPixelColor(i, 255, 255, 255, 255);
230+
}
231+
strip.setBrightness(255, true);
232+
strip.show();
233+
}
234+
235+
void DMXInput::disable()
236+
{
237+
if (initialized) {
238+
dmx_driver_disable(inputPortNum);
239+
}
240+
}
241+
void DMXInput::enable()
242+
{
243+
if (initialized) {
244+
dmx_driver_enable(inputPortNum);
245+
}
246+
}
247+
248+
bool DMXInput::isIdentifyOn() const
249+
{
250+
251+
uint8_t identify = 0;
252+
const bool gotIdentify = rdm_get_identify_device(inputPortNum, &identify);
253+
// gotIdentify should never be false because it is a default parameter in rdm
254+
// but just in case we check for it anyway
255+
return bool(identify) && gotIdentify;
256+
}
257+
258+
void DMXInput::checkAndUpdateConfig()
259+
{
260+
261+
/**
262+
* The global configuration variables are modified by the web interface.
263+
* If they differ from the driver configuration, we have to update the driver
264+
* configuration.
265+
*/
266+
267+
const uint8_t currentPersonality = dmx_get_current_personality(inputPortNum);
268+
if (currentPersonality != DMXMode) {
269+
DEBUG_PRINTF("DMX personality has changed from %d to %d\n", currentPersonality, DMXMode);
270+
dmx_set_current_personality(inputPortNum, DMXMode);
271+
}
272+
273+
const uint16_t currentAddr = dmx_get_start_address(inputPortNum);
274+
if (currentAddr != DMXAddress) {
275+
DEBUG_PRINTF("DMX address has changed from %d to %d\n", currentAddr, DMXAddress);
276+
dmx_set_start_address(inputPortNum, DMXAddress);
277+
}
278+
}
279+
280+
#endif

0 commit comments

Comments
 (0)