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