2020-03-31 02:38:08 +02:00
|
|
|
|
#include "wled.h"
|
|
|
|
|
|
2020-12-01 14:40:00 +01:00
|
|
|
|
#define MAX_3_CH_LEDS_PER_UNIVERSE 170
|
|
|
|
|
#define MAX_4_CH_LEDS_PER_UNIVERSE 128
|
2020-08-03 02:48:47 +02:00
|
|
|
|
#define MAX_CHANNELS_PER_UNIVERSE 512
|
|
|
|
|
|
2020-03-31 02:38:08 +02:00
|
|
|
|
/*
|
|
|
|
|
* E1.31 handler
|
|
|
|
|
*/
|
|
|
|
|
|
2020-09-28 16:29:01 +02:00
|
|
|
|
//DDP protocol support, called by handleE131Packet
|
|
|
|
|
//handles RGB data only
|
|
|
|
|
void handleDDPPacket(e131_packet_t* p) {
|
|
|
|
|
int lastPushSeq = e131LastSequenceNumber[0];
|
2023-01-06 09:24:29 +01:00
|
|
|
|
|
2020-09-28 16:29:01 +02:00
|
|
|
|
//reject late packets belonging to previous frame (assuming 4 packets max. before push)
|
|
|
|
|
if (e131SkipOutOfSequence && lastPushSeq) {
|
|
|
|
|
int sn = p->sequenceNum & 0xF;
|
|
|
|
|
if (sn) {
|
|
|
|
|
if (lastPushSeq > 5) {
|
|
|
|
|
if (sn > (lastPushSeq -5) && sn < lastPushSeq) return;
|
|
|
|
|
} else {
|
|
|
|
|
if (sn > (10 + lastPushSeq) || sn < lastPushSeq) return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-02 03:12:03 +02:00
|
|
|
|
uint8_t ddpChannelsPerLed = (p->dataType == DDP_TYPE_RGBW32) ? 4 : 3; // data type 0x1A is RGBW (type 3, 8 bit/channel)
|
|
|
|
|
|
|
|
|
|
uint32_t start = htonl(p->channelOffset) / ddpChannelsPerLed;
|
|
|
|
|
start += DMXAddress / ddpChannelsPerLed;
|
|
|
|
|
uint16_t stop = start + htons(p->dataLen) / ddpChannelsPerLed;
|
2020-09-28 16:29:01 +02:00
|
|
|
|
uint8_t* data = p->data;
|
|
|
|
|
uint16_t c = 0;
|
|
|
|
|
if (p->flags & DDP_TIMECODE_FLAG) c = 4; //packet has timecode flag, we do not support it, but data starts 4 bytes later
|
|
|
|
|
|
|
|
|
|
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP);
|
2023-01-06 09:24:29 +01:00
|
|
|
|
|
2022-03-25 16:36:05 +01:00
|
|
|
|
if (!realtimeOverride || (realtimeMode && useMainSegmentOnly)) {
|
2021-11-03 16:14:01 +01:00
|
|
|
|
for (uint16_t i = start; i < stop; i++) {
|
2022-09-02 03:12:03 +02:00
|
|
|
|
setRealtimePixel(i, data[c], data[c+1], data[c+2], ddpChannelsPerLed >3 ? data[c+3] : 0);
|
|
|
|
|
c += ddpChannelsPerLed;
|
2021-11-03 16:14:01 +01:00
|
|
|
|
}
|
2020-09-28 16:29:01 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool push = p->flags & DDP_PUSH_FLAG;
|
|
|
|
|
if (push) {
|
|
|
|
|
e131NewData = true;
|
|
|
|
|
byte sn = p->sequenceNum & 0xF;
|
|
|
|
|
if (sn) e131LastSequenceNumber[0] = sn;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//E1.31 and Art-Net protocol support
|
|
|
|
|
void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
|
2020-03-31 02:38:08 +02:00
|
|
|
|
|
2020-04-13 00:42:27 +02:00
|
|
|
|
uint16_t uni = 0, dmxChannels = 0;
|
|
|
|
|
uint8_t* e131_data = nullptr;
|
|
|
|
|
uint8_t seq = 0, mde = REALTIME_MODE_E131;
|
|
|
|
|
|
2020-09-28 16:29:01 +02:00
|
|
|
|
if (protocol == P_ARTNET)
|
2020-04-13 00:42:27 +02:00
|
|
|
|
{
|
2022-09-22 20:34:46 +02:00
|
|
|
|
if (p->art_opcode == ARTNET_OPCODE_OPPOLL) {
|
|
|
|
|
handleArtnetPollReply(clientIP);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-04-13 00:42:27 +02:00
|
|
|
|
uni = p->art_universe;
|
|
|
|
|
dmxChannels = htons(p->art_length);
|
|
|
|
|
e131_data = p->art_data;
|
|
|
|
|
seq = p->art_sequence_number;
|
|
|
|
|
mde = REALTIME_MODE_ARTNET;
|
2020-09-28 16:29:01 +02:00
|
|
|
|
} else if (protocol == P_E131) {
|
2023-02-21 17:13:15 +01:00
|
|
|
|
// Ignore PREVIEW data (E1.31: 6.2.6)
|
|
|
|
|
if ((p->options & 0x80) != 0) return;
|
|
|
|
|
dmxChannels = htons(p->property_value_count) - 1;
|
|
|
|
|
// DMX level data is zero start code. Ignore everything else. (E1.11: 8.5)
|
|
|
|
|
if (dmxChannels == 0 || p->property_values[0] != 0) return;
|
2020-04-13 00:42:27 +02:00
|
|
|
|
uni = htons(p->universe);
|
|
|
|
|
e131_data = p->property_values;
|
|
|
|
|
seq = p->sequence_number;
|
2023-02-21 17:13:15 +01:00
|
|
|
|
if (e131Priority != 0) {
|
|
|
|
|
if (p->priority < e131Priority ) return;
|
|
|
|
|
// track highest priority & skip all lower priorities
|
|
|
|
|
if (p->priority >= highPriority.get()) highPriority.set(p->priority);
|
|
|
|
|
if (p->priority < highPriority.get()) return;
|
|
|
|
|
}
|
2020-09-28 16:29:01 +02:00
|
|
|
|
} else { //DDP
|
|
|
|
|
realtimeIP = clientIP;
|
|
|
|
|
handleDDPPacket(p);
|
|
|
|
|
return;
|
2020-04-13 00:42:27 +02:00
|
|
|
|
}
|
2020-03-31 02:38:08 +02:00
|
|
|
|
|
2020-04-21 22:56:14 +02:00
|
|
|
|
#ifdef WLED_ENABLE_DMX
|
|
|
|
|
// does not act on out-of-order packets yet
|
|
|
|
|
if (e131ProxyUniverse > 0 && uni == e131ProxyUniverse) {
|
|
|
|
|
for (uint16_t i = 1; i <= dmxChannels; i++)
|
|
|
|
|
dmx.write(i, e131_data[i]);
|
|
|
|
|
dmx.update();
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2020-03-31 02:38:08 +02:00
|
|
|
|
// only listen for universes we're handling & allocated memory
|
2022-09-23 01:02:49 +02:00
|
|
|
|
if (uni < e131Universe || uni >= (e131Universe + E131_MAX_UNIVERSE_COUNT)) return;
|
2020-03-31 02:38:08 +02:00
|
|
|
|
|
2020-04-13 00:42:27 +02:00
|
|
|
|
uint8_t previousUniverses = uni - e131Universe;
|
|
|
|
|
|
2020-03-31 02:38:08 +02:00
|
|
|
|
if (e131SkipOutOfSequence)
|
2022-09-23 01:02:49 +02:00
|
|
|
|
if (seq < e131LastSequenceNumber[previousUniverses] && seq > 20 && e131LastSequenceNumber[previousUniverses] < 250){
|
2022-12-26 10:20:45 +01:00
|
|
|
|
DEBUG_PRINT(F("skipping E1.31 frame (last seq="));
|
2022-09-23 01:02:49 +02:00
|
|
|
|
DEBUG_PRINT(e131LastSequenceNumber[previousUniverses]);
|
2022-12-26 10:20:45 +01:00
|
|
|
|
DEBUG_PRINT(F(", current seq="));
|
2020-04-13 00:42:27 +02:00
|
|
|
|
DEBUG_PRINT(seq);
|
2022-12-26 10:20:45 +01:00
|
|
|
|
DEBUG_PRINT(F(", universe="));
|
2020-03-31 02:38:08 +02:00
|
|
|
|
DEBUG_PRINT(uni);
|
|
|
|
|
DEBUG_PRINTLN(")");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-09-23 01:02:49 +02:00
|
|
|
|
e131LastSequenceNumber[previousUniverses] = seq;
|
2020-03-31 02:38:08 +02:00
|
|
|
|
|
|
|
|
|
// update status info
|
|
|
|
|
realtimeIP = clientIP;
|
2020-05-03 20:57:53 +02:00
|
|
|
|
byte wChannel = 0;
|
2021-10-31 11:57:41 +01:00
|
|
|
|
uint16_t totalLen = strip.getLengthTotal();
|
2022-09-23 01:02:49 +02:00
|
|
|
|
uint16_t availDMXLen = 0;
|
2022-04-03 17:52:36 +02:00
|
|
|
|
uint16_t dataOffset = DMXAddress;
|
|
|
|
|
|
2022-09-23 01:02:49 +02:00
|
|
|
|
// For legacy DMX start address 0 the available DMX length offset is 0
|
|
|
|
|
const uint16_t dmxLenOffset = (DMXAddress == 0) ? 0 : 1;
|
|
|
|
|
|
|
|
|
|
// Check if DMX start address fits in available channels
|
|
|
|
|
if (dmxChannels >= DMXAddress) {
|
|
|
|
|
availDMXLen = (dmxChannels - DMXAddress) + dmxLenOffset;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-03 17:52:36 +02:00
|
|
|
|
// DMX data in Art-Net packet starts at index 0, for E1.31 at index 1
|
|
|
|
|
if (protocol == P_ARTNET && dataOffset > 0) {
|
|
|
|
|
dataOffset--;
|
|
|
|
|
}
|
2020-08-03 02:48:47 +02:00
|
|
|
|
|
2020-03-31 02:38:08 +02:00
|
|
|
|
switch (DMXMode) {
|
|
|
|
|
case DMX_MODE_DISABLED:
|
|
|
|
|
return; // nothing to do
|
|
|
|
|
break;
|
|
|
|
|
|
2022-11-19 14:10:40 +01:00
|
|
|
|
case DMX_MODE_SINGLE_RGB: // 3 channel: [R,G,B]
|
2020-03-31 02:38:08 +02:00
|
|
|
|
if (uni != e131Universe) return;
|
2022-04-03 17:52:36 +02:00
|
|
|
|
if (availDMXLen < 3) return;
|
2022-09-23 01:02:49 +02:00
|
|
|
|
|
2020-04-13 00:42:27 +02:00
|
|
|
|
realtimeLock(realtimeTimeoutMs, mde);
|
2022-09-23 01:02:49 +02:00
|
|
|
|
|
2022-03-25 16:36:05 +01:00
|
|
|
|
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return;
|
2022-09-23 01:02:49 +02:00
|
|
|
|
|
2022-04-03 17:52:36 +02:00
|
|
|
|
wChannel = (availDMXLen > 3) ? e131_data[dataOffset+3] : 0;
|
2021-10-31 11:57:41 +01:00
|
|
|
|
for (uint16_t i = 0; i < totalLen; i++)
|
2022-04-03 17:52:36 +02:00
|
|
|
|
setRealtimePixel(i, e131_data[dataOffset+0], e131_data[dataOffset+1], e131_data[dataOffset+2], wChannel);
|
2020-03-31 02:38:08 +02:00
|
|
|
|
break;
|
|
|
|
|
|
2022-11-19 14:10:40 +01:00
|
|
|
|
case DMX_MODE_SINGLE_DRGB: // 4 channel: [Dimmer,R,G,B]
|
2020-03-31 02:38:08 +02:00
|
|
|
|
if (uni != e131Universe) return;
|
2022-04-03 17:52:36 +02:00
|
|
|
|
if (availDMXLen < 4) return;
|
2022-09-23 01:02:49 +02:00
|
|
|
|
|
2020-04-13 00:42:27 +02:00
|
|
|
|
realtimeLock(realtimeTimeoutMs, mde);
|
2022-03-25 16:36:05 +01:00
|
|
|
|
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return;
|
2022-04-03 17:52:36 +02:00
|
|
|
|
wChannel = (availDMXLen > 4) ? e131_data[dataOffset+4] : 0;
|
2022-09-23 01:02:49 +02:00
|
|
|
|
|
|
|
|
|
if (bri != e131_data[dataOffset+0]) {
|
2022-04-03 17:52:36 +02:00
|
|
|
|
bri = e131_data[dataOffset+0];
|
2022-03-10 20:40:48 +01:00
|
|
|
|
strip.setBrightness(bri, true);
|
2020-03-31 02:38:08 +02:00
|
|
|
|
}
|
2022-09-23 01:02:49 +02:00
|
|
|
|
|
2021-10-31 11:57:41 +01:00
|
|
|
|
for (uint16_t i = 0; i < totalLen; i++)
|
2022-04-03 17:52:36 +02:00
|
|
|
|
setRealtimePixel(i, e131_data[dataOffset+1], e131_data[dataOffset+2], e131_data[dataOffset+3], wChannel);
|
2020-03-31 02:38:08 +02:00
|
|
|
|
break;
|
|
|
|
|
|
2022-11-23 00:38:42 +01:00
|
|
|
|
case DMX_MODE_PRESET: // 2 channel: [Dimmer,Preset]
|
2023-03-17 13:40:21 +01:00
|
|
|
|
{
|
|
|
|
|
if (uni != e131Universe || availDMXLen < 2) return;
|
|
|
|
|
|
|
|
|
|
// limit max. selectable preset to 250, even though DMX max. val is 255
|
|
|
|
|
uint8_t dmxValPreset = (e131_data[dataOffset+1] > 250 ? 250 : e131_data[dataOffset+1]);
|
|
|
|
|
|
|
|
|
|
// only apply preset if value changed
|
|
|
|
|
if (dmxValPreset != 0 && dmxValPreset != currentPreset &&
|
|
|
|
|
// only apply preset if not in playlist, or playlist changed
|
|
|
|
|
(currentPlaylist < 0 || dmxValPreset != currentPlaylist)) {
|
|
|
|
|
presetCycCurr = dmxValPreset;
|
|
|
|
|
unloadPlaylist(); // applying a preset unloads the playlist
|
|
|
|
|
applyPreset(dmxValPreset, CALL_MODE_NOTIFICATION);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// only change brightness if value changed
|
|
|
|
|
if (bri != e131_data[dataOffset]) {
|
|
|
|
|
bri = e131_data[dataOffset];
|
|
|
|
|
strip.setBrightness(scaledBri(bri), false);
|
|
|
|
|
stateUpdated(CALL_MODE_WS_SEND);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
break;
|
2022-11-23 00:38:42 +01:00
|
|
|
|
}
|
2022-09-23 01:02:49 +02:00
|
|
|
|
|
2022-11-26 14:41:59 +01:00
|
|
|
|
case DMX_MODE_EFFECT: // 15 channels [bri,effectCurrent,effectSpeed,effectIntensity,effectPalette,effectOption,R,G,B,R2,G2,B2,R3,G3,B3]
|
|
|
|
|
case DMX_MODE_EFFECT_W: // 18 channels, same as above but with extra +3 white channels [..,W,W2,W3]
|
2022-12-14 22:55:42 +01:00
|
|
|
|
case DMX_MODE_EFFECT_SEGMENT: // 15 channels per segment;
|
|
|
|
|
case DMX_MODE_EFFECT_SEGMENT_W: // 18 Channels per segment;
|
2020-03-31 02:38:08 +02:00
|
|
|
|
{
|
2022-11-19 14:10:40 +01:00
|
|
|
|
if (uni != e131Universe) return;
|
|
|
|
|
bool isSegmentMode = DMXMode == DMX_MODE_EFFECT_SEGMENT || DMXMode == DMX_MODE_EFFECT_SEGMENT_W;
|
2022-11-26 14:41:59 +01:00
|
|
|
|
uint8_t dmxEffectChannels = (DMXMode == DMX_MODE_EFFECT || DMXMode == DMX_MODE_EFFECT_SEGMENT) ? 15 : 18;
|
2022-11-23 23:19:53 +01:00
|
|
|
|
for (uint8_t id = 0; id < strip.getSegmentsNum(); id++) {
|
|
|
|
|
Segment& seg = strip.getSegment(id);
|
2022-11-19 14:10:40 +01:00
|
|
|
|
if (isSegmentMode)
|
2022-11-23 23:19:53 +01:00
|
|
|
|
dataOffset = DMXAddress + id * (dmxEffectChannels + DMXSegmentSpacing);
|
2022-11-19 14:10:40 +01:00
|
|
|
|
else
|
|
|
|
|
dataOffset = DMXAddress;
|
|
|
|
|
// Modify address for Art-Net data
|
|
|
|
|
if (protocol == P_ARTNET && dataOffset > 0)
|
|
|
|
|
dataOffset--;
|
|
|
|
|
// Skip out of universe addresses
|
2022-12-09 02:10:39 +01:00
|
|
|
|
if (dataOffset > dmxChannels - dmxEffectChannels + 1)
|
2022-11-19 14:10:40 +01:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (e131_data[dataOffset+1] < strip.getModeCount())
|
2022-11-26 14:41:59 +01:00
|
|
|
|
if (e131_data[dataOffset+1] != seg.mode) seg.setMode( e131_data[dataOffset+1]);
|
|
|
|
|
if (e131_data[dataOffset+2] != seg.speed) seg.speed = e131_data[dataOffset+2];
|
|
|
|
|
if (e131_data[dataOffset+3] != seg.intensity) seg.intensity = e131_data[dataOffset+3];
|
|
|
|
|
if (e131_data[dataOffset+4] != seg.palette) seg.setPalette(e131_data[dataOffset+4]);
|
|
|
|
|
|
|
|
|
|
uint8_t segOption = (uint8_t)floor(e131_data[dataOffset+5]/64.0);
|
|
|
|
|
if (segOption == 0 && (seg.mirror || seg.reverse )) {seg.setOption(SEG_OPTION_MIRROR, false); seg.setOption(SEG_OPTION_REVERSED, false);}
|
|
|
|
|
if (segOption == 1 && (seg.mirror || !seg.reverse)) {seg.setOption(SEG_OPTION_MIRROR, false); seg.setOption(SEG_OPTION_REVERSED, true);}
|
|
|
|
|
if (segOption == 2 && (!seg.mirror || seg.reverse )) {seg.setOption(SEG_OPTION_MIRROR, true); seg.setOption(SEG_OPTION_REVERSED, false);}
|
|
|
|
|
if (segOption == 3 && (!seg.mirror || !seg.reverse)) {seg.setOption(SEG_OPTION_MIRROR, true); seg.setOption(SEG_OPTION_REVERSED, true);}
|
|
|
|
|
|
|
|
|
|
uint32_t colors[3];
|
|
|
|
|
byte whites[3] = {0,0,0};
|
|
|
|
|
if (dmxEffectChannels == 18) {
|
|
|
|
|
whites[0] = e131_data[dataOffset+15];
|
|
|
|
|
whites[1] = e131_data[dataOffset+16];
|
|
|
|
|
whites[2] = e131_data[dataOffset+17];
|
2022-11-19 14:10:40 +01:00
|
|
|
|
}
|
2022-11-26 14:41:59 +01:00
|
|
|
|
colors[0] = RGBW32(e131_data[dataOffset+ 6], e131_data[dataOffset+ 7], e131_data[dataOffset+ 8], whites[0]);
|
|
|
|
|
colors[1] = RGBW32(e131_data[dataOffset+ 9], e131_data[dataOffset+10], e131_data[dataOffset+11], whites[1]);
|
|
|
|
|
colors[2] = RGBW32(e131_data[dataOffset+12], e131_data[dataOffset+13], e131_data[dataOffset+14], whites[2]);
|
|
|
|
|
if (colors[0] != seg.colors[0]) seg.setColor(0, colors[0]);
|
|
|
|
|
if (colors[1] != seg.colors[1]) seg.setColor(1, colors[1]);
|
|
|
|
|
if (colors[2] != seg.colors[2]) seg.setColor(2, colors[2]);
|
2022-11-23 23:19:53 +01:00
|
|
|
|
|
|
|
|
|
// Set segment opacity or global brightness
|
2022-11-19 14:10:40 +01:00
|
|
|
|
if (isSegmentMode) {
|
2022-11-26 14:41:59 +01:00
|
|
|
|
if (e131_data[dataOffset] != seg.opacity) seg.setOpacity(e131_data[dataOffset]);
|
2022-11-23 23:19:53 +01:00
|
|
|
|
} else if ( id == strip.getSegmentsNum()-1 ) {
|
|
|
|
|
if (bri != e131_data[dataOffset]) {
|
|
|
|
|
bri = e131_data[dataOffset];
|
|
|
|
|
strip.setBrightness(bri, true);
|
|
|
|
|
}
|
2022-11-19 14:10:40 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-11-26 14:41:59 +01:00
|
|
|
|
return;
|
2022-11-19 14:10:40 +01:00
|
|
|
|
break;
|
2020-03-31 02:38:08 +02:00
|
|
|
|
}
|
2022-11-19 14:10:40 +01:00
|
|
|
|
|
2020-03-31 02:38:08 +02:00
|
|
|
|
case DMX_MODE_MULTIPLE_DRGB:
|
2020-08-03 02:48:47 +02:00
|
|
|
|
case DMX_MODE_MULTIPLE_RGB:
|
2020-12-01 14:40:00 +01:00
|
|
|
|
case DMX_MODE_MULTIPLE_RGBW:
|
2020-08-03 02:48:47 +02:00
|
|
|
|
{
|
2020-12-01 14:40:00 +01:00
|
|
|
|
bool is4Chan = (DMXMode == DMX_MODE_MULTIPLE_RGBW);
|
|
|
|
|
const uint16_t dmxChannelsPerLed = is4Chan ? 4 : 3;
|
|
|
|
|
const uint16_t ledsPerUniverse = is4Chan ? MAX_4_CH_LEDS_PER_UNIVERSE : MAX_3_CH_LEDS_PER_UNIVERSE;
|
2022-09-23 01:02:49 +02:00
|
|
|
|
uint8_t stripBrightness = bri;
|
2022-04-03 17:52:36 +02:00
|
|
|
|
uint16_t previousLeds, dmxOffset, ledsTotal;
|
2022-09-23 01:02:49 +02:00
|
|
|
|
|
2020-08-03 02:48:47 +02:00
|
|
|
|
if (previousUniverses == 0) {
|
2022-04-03 17:52:36 +02:00
|
|
|
|
if (availDMXLen < 1) return;
|
|
|
|
|
dmxOffset = dataOffset;
|
2020-08-03 12:27:16 +02:00
|
|
|
|
previousLeds = 0;
|
2020-08-03 02:48:47 +02:00
|
|
|
|
// First DMX address is dimmer in DMX_MODE_MULTIPLE_DRGB mode.
|
|
|
|
|
if (DMXMode == DMX_MODE_MULTIPLE_DRGB) {
|
2022-09-23 01:02:49 +02:00
|
|
|
|
stripBrightness = e131_data[dmxOffset++];
|
2022-04-03 17:52:36 +02:00
|
|
|
|
ledsTotal = (availDMXLen - 1) / dmxChannelsPerLed;
|
|
|
|
|
} else {
|
|
|
|
|
ledsTotal = availDMXLen / dmxChannelsPerLed;
|
2020-08-03 02:48:47 +02:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// All subsequent universes start at the first channel.
|
2020-09-28 16:29:01 +02:00
|
|
|
|
dmxOffset = (protocol == P_ARTNET) ? 0 : 1;
|
2022-09-23 01:02:49 +02:00
|
|
|
|
const uint16_t dimmerOffset = (DMXMode == DMX_MODE_MULTIPLE_DRGB) ? 1 : 0;
|
|
|
|
|
uint16_t ledsInFirstUniverse = (((MAX_CHANNELS_PER_UNIVERSE - DMXAddress) + dmxLenOffset) - dimmerOffset) / dmxChannelsPerLed;
|
2020-12-01 14:40:00 +01:00
|
|
|
|
previousLeds = ledsInFirstUniverse + (previousUniverses - 1) * ledsPerUniverse;
|
2022-04-03 17:52:36 +02:00
|
|
|
|
ledsTotal = previousLeds + (dmxChannels / dmxChannelsPerLed);
|
2020-03-31 02:38:08 +02:00
|
|
|
|
}
|
2022-09-23 01:02:49 +02:00
|
|
|
|
|
|
|
|
|
// All LEDs already have values
|
|
|
|
|
if (previousLeds >= totalLen) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
realtimeLock(realtimeTimeoutMs, mde);
|
|
|
|
|
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return;
|
|
|
|
|
|
|
|
|
|
if (ledsTotal > totalLen) {
|
|
|
|
|
ledsTotal = totalLen;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (DMXMode == DMX_MODE_MULTIPLE_DRGB && previousUniverses == 0) {
|
|
|
|
|
if (bri != stripBrightness) {
|
|
|
|
|
bri = stripBrightness;
|
|
|
|
|
strip.setBrightness(bri, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-06 09:24:29 +01:00
|
|
|
|
|
2020-12-01 14:40:00 +01:00
|
|
|
|
if (!is4Chan) {
|
|
|
|
|
for (uint16_t i = previousLeds; i < ledsTotal; i++) {
|
2021-02-27 00:20:31 +01:00
|
|
|
|
setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], 0);
|
|
|
|
|
dmxOffset+=3;
|
2020-12-01 14:40:00 +01:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for (uint16_t i = previousLeds; i < ledsTotal; i++) {
|
2021-02-27 00:20:31 +01:00
|
|
|
|
setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], e131_data[dmxOffset+3]);
|
|
|
|
|
dmxOffset+=4;
|
2020-12-01 14:40:00 +01:00
|
|
|
|
}
|
2020-03-31 02:38:08 +02:00
|
|
|
|
}
|
2020-08-03 02:48:47 +02:00
|
|
|
|
break;
|
2020-03-31 02:38:08 +02:00
|
|
|
|
}
|
|
|
|
|
default:
|
2020-09-20 01:18:31 +02:00
|
|
|
|
DEBUG_PRINTLN(F("unknown E1.31 DMX mode"));
|
2020-03-31 02:38:08 +02:00
|
|
|
|
return; // nothing to do
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e131NewData = true;
|
|
|
|
|
}
|
2022-09-22 20:34:46 +02:00
|
|
|
|
|
|
|
|
|
void handleArtnetPollReply(IPAddress ipAddress) {
|
|
|
|
|
ArtPollReply artnetPollReply;
|
|
|
|
|
prepareArtnetPollReply(&artnetPollReply);
|
|
|
|
|
|
|
|
|
|
uint16_t startUniverse = e131Universe;
|
|
|
|
|
uint16_t endUniverse = e131Universe;
|
|
|
|
|
|
|
|
|
|
switch (DMXMode) {
|
|
|
|
|
case DMX_MODE_DISABLED:
|
|
|
|
|
return; // nothing to do
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case DMX_MODE_SINGLE_RGB:
|
|
|
|
|
case DMX_MODE_SINGLE_DRGB:
|
2022-11-19 14:10:40 +01:00
|
|
|
|
case DMX_MODE_PRESET:
|
2022-09-22 20:34:46 +02:00
|
|
|
|
case DMX_MODE_EFFECT:
|
2022-11-19 14:10:40 +01:00
|
|
|
|
case DMX_MODE_EFFECT_W:
|
|
|
|
|
case DMX_MODE_EFFECT_SEGMENT:
|
|
|
|
|
case DMX_MODE_EFFECT_SEGMENT_W:
|
2022-09-22 20:34:46 +02:00
|
|
|
|
break; // 1 universe is enough
|
|
|
|
|
|
|
|
|
|
case DMX_MODE_MULTIPLE_DRGB:
|
|
|
|
|
case DMX_MODE_MULTIPLE_RGB:
|
|
|
|
|
case DMX_MODE_MULTIPLE_RGBW:
|
|
|
|
|
{
|
|
|
|
|
bool is4Chan = (DMXMode == DMX_MODE_MULTIPLE_RGBW);
|
|
|
|
|
const uint16_t dmxChannelsPerLed = is4Chan ? 4 : 3;
|
|
|
|
|
const uint16_t dimmerOffset = (DMXMode == DMX_MODE_MULTIPLE_DRGB) ? 1 : 0;
|
|
|
|
|
const uint16_t dmxLenOffset = (DMXAddress == 0) ? 0 : 1; // For legacy DMX start address 0
|
|
|
|
|
const uint16_t ledsInFirstUniverse = (((MAX_CHANNELS_PER_UNIVERSE - DMXAddress) + dmxLenOffset) - dimmerOffset) / dmxChannelsPerLed;
|
|
|
|
|
const uint16_t totalLen = strip.getLengthTotal();
|
|
|
|
|
|
|
|
|
|
if (totalLen > ledsInFirstUniverse) {
|
|
|
|
|
const uint16_t ledsPerUniverse = is4Chan ? MAX_4_CH_LEDS_PER_UNIVERSE : MAX_3_CH_LEDS_PER_UNIVERSE;
|
|
|
|
|
const uint16_t remainLED = totalLen - ledsInFirstUniverse;
|
|
|
|
|
|
|
|
|
|
endUniverse += (remainLED / ledsPerUniverse);
|
|
|
|
|
|
|
|
|
|
if ((remainLED % ledsPerUniverse) > 0) {
|
|
|
|
|
endUniverse++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((endUniverse - startUniverse) > E131_MAX_UNIVERSE_COUNT) {
|
|
|
|
|
endUniverse = startUniverse + E131_MAX_UNIVERSE_COUNT - 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
DEBUG_PRINTLN(F("unknown E1.31 DMX mode"));
|
|
|
|
|
return; // nothing to do
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (uint16_t i = startUniverse; i <= endUniverse; ++i) {
|
|
|
|
|
sendArtnetPollReply(&artnetPollReply, ipAddress, i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void prepareArtnetPollReply(ArtPollReply *reply) {
|
|
|
|
|
// Art-Net
|
|
|
|
|
reply->reply_id[0] = 0x41;
|
|
|
|
|
reply->reply_id[1] = 0x72;
|
|
|
|
|
reply->reply_id[2] = 0x74;
|
|
|
|
|
reply->reply_id[3] = 0x2d;
|
|
|
|
|
reply->reply_id[4] = 0x4e;
|
|
|
|
|
reply->reply_id[5] = 0x65;
|
|
|
|
|
reply->reply_id[6] = 0x74;
|
|
|
|
|
reply->reply_id[7] = 0x00;
|
|
|
|
|
|
|
|
|
|
reply->reply_opcode = ARTNET_OPCODE_OPPOLLREPLY;
|
|
|
|
|
|
|
|
|
|
IPAddress localIP = Network.localIP();
|
|
|
|
|
for (uint8_t i = 0; i < 4; i++) {
|
|
|
|
|
reply->reply_ip[i] = localIP[i];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reply->reply_port = ARTNET_DEFAULT_PORT;
|
|
|
|
|
|
|
|
|
|
char * numberEnd = versionString;
|
|
|
|
|
reply->reply_version_h = (uint8_t)strtol(numberEnd, &numberEnd, 10);
|
|
|
|
|
numberEnd++;
|
|
|
|
|
reply->reply_version_l = (uint8_t)strtol(numberEnd, &numberEnd, 10);
|
|
|
|
|
|
|
|
|
|
// Switch values depend on universe, set before sending
|
|
|
|
|
reply->reply_net_sw = 0x00;
|
|
|
|
|
reply->reply_sub_sw = 0x00;
|
|
|
|
|
|
|
|
|
|
reply->reply_oem_h = 0x00; // TODO add assigned oem code
|
|
|
|
|
reply->reply_oem_l = 0x00;
|
|
|
|
|
|
|
|
|
|
reply->reply_ubea_ver = 0x00;
|
|
|
|
|
|
|
|
|
|
// Indicators in Normal Mode
|
|
|
|
|
// All or part of Port-Address programmed by network or Web browser
|
|
|
|
|
reply->reply_status_1 = 0xE0;
|
|
|
|
|
|
|
|
|
|
reply->reply_esta_man = 0x0000;
|
|
|
|
|
|
|
|
|
|
strlcpy((char *)(reply->reply_short_name), serverDescription, 18);
|
|
|
|
|
strlcpy((char *)(reply->reply_long_name), serverDescription, 64);
|
|
|
|
|
|
|
|
|
|
reply->reply_node_report[0] = '\0';
|
|
|
|
|
|
|
|
|
|
reply->reply_num_ports_h = 0x00;
|
|
|
|
|
reply->reply_num_ports_l = 0x01; // One output port
|
|
|
|
|
|
|
|
|
|
reply->reply_port_types[0] = 0x80; // Output DMX data
|
|
|
|
|
reply->reply_port_types[1] = 0x00;
|
|
|
|
|
reply->reply_port_types[2] = 0x00;
|
|
|
|
|
reply->reply_port_types[3] = 0x00;
|
|
|
|
|
|
|
|
|
|
// No inputs
|
|
|
|
|
reply->reply_good_input[0] = 0x00;
|
|
|
|
|
reply->reply_good_input[1] = 0x00;
|
|
|
|
|
reply->reply_good_input[2] = 0x00;
|
|
|
|
|
reply->reply_good_input[3] = 0x00;
|
|
|
|
|
|
|
|
|
|
// One output
|
|
|
|
|
reply->reply_good_output_a[0] = 0x80; // Data is being transmitted
|
|
|
|
|
reply->reply_good_output_a[1] = 0x00;
|
|
|
|
|
reply->reply_good_output_a[2] = 0x00;
|
|
|
|
|
reply->reply_good_output_a[3] = 0x00;
|
|
|
|
|
|
|
|
|
|
// Values depend on universe, set before sending
|
|
|
|
|
reply->reply_sw_in[0] = 0x00;
|
|
|
|
|
reply->reply_sw_in[1] = 0x00;
|
|
|
|
|
reply->reply_sw_in[2] = 0x00;
|
|
|
|
|
reply->reply_sw_in[3] = 0x00;
|
|
|
|
|
|
|
|
|
|
// Values depend on universe, set before sending
|
|
|
|
|
reply->reply_sw_out[0] = 0x00;
|
|
|
|
|
reply->reply_sw_out[1] = 0x00;
|
|
|
|
|
reply->reply_sw_out[2] = 0x00;
|
|
|
|
|
reply->reply_sw_out[3] = 0x00;
|
|
|
|
|
|
|
|
|
|
reply->reply_sw_video = 0x00;
|
|
|
|
|
reply->reply_sw_macro = 0x00;
|
|
|
|
|
reply->reply_sw_remote = 0x00;
|
|
|
|
|
|
|
|
|
|
reply->reply_spare[0] = 0x00;
|
|
|
|
|
reply->reply_spare[1] = 0x00;
|
|
|
|
|
reply->reply_spare[2] = 0x00;
|
|
|
|
|
|
|
|
|
|
// A DMX to / from Art-Net device
|
|
|
|
|
reply->reply_style = 0x00;
|
|
|
|
|
|
|
|
|
|
Network.localMAC(reply->reply_mac);
|
|
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < 4; i++) {
|
|
|
|
|
reply->reply_bind_ip[i] = localIP[i];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reply->reply_bind_index = 1;
|
|
|
|
|
|
|
|
|
|
// Product supports web browser configuration
|
|
|
|
|
// Node’s IP is DHCP or manually configured
|
|
|
|
|
// Node is DHCP capable
|
|
|
|
|
// Node supports 15 bit Port-Address (Art-Net 3 or 4)
|
|
|
|
|
// Node is able to switch between ArtNet and sACN
|
|
|
|
|
reply->reply_status_2 = (staticIP[0] == 0) ? 0x1F : 0x1D;
|
|
|
|
|
|
|
|
|
|
// RDM is disabled
|
|
|
|
|
// Output style is continuous
|
|
|
|
|
reply->reply_good_output_b[0] = 0xC0;
|
|
|
|
|
reply->reply_good_output_b[1] = 0xC0;
|
|
|
|
|
reply->reply_good_output_b[2] = 0xC0;
|
|
|
|
|
reply->reply_good_output_b[3] = 0xC0;
|
|
|
|
|
|
|
|
|
|
// Fail-over state: Hold last state
|
|
|
|
|
// Node does not support fail-over
|
|
|
|
|
reply->reply_status_3 = 0x00;
|
|
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < 21; i++) {
|
|
|
|
|
reply->reply_filler[i] = 0x00;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sendArtnetPollReply(ArtPollReply *reply, IPAddress ipAddress, uint16_t portAddress) {
|
|
|
|
|
reply->reply_net_sw = (uint8_t)((portAddress >> 8) & 0x007F);
|
|
|
|
|
reply->reply_sub_sw = (uint8_t)((portAddress >> 4) & 0x000F);
|
|
|
|
|
reply->reply_sw_out[0] = (uint8_t)(portAddress & 0x000F);
|
|
|
|
|
|
2022-12-26 10:20:45 +01:00
|
|
|
|
snprintf_P((char *)reply->reply_node_report, sizeof(reply->reply_node_report)-1, PSTR("#0001 [%04u] OK - WLED v" TOSTRING(WLED_VERSION)), pollReplyCount);
|
2023-01-06 09:24:29 +01:00
|
|
|
|
|
2022-09-22 20:34:46 +02:00
|
|
|
|
if (pollReplyCount < 9999) {
|
|
|
|
|
pollReplyCount++;
|
|
|
|
|
} else {
|
|
|
|
|
pollReplyCount = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
notifierUdp.beginPacket(ipAddress, ARTNET_DEFAULT_PORT);
|
|
|
|
|
notifierUdp.write(reply->raw, sizeof(ArtPollReply));
|
|
|
|
|
notifierUdp.endPacket();
|
|
|
|
|
|
|
|
|
|
reply->reply_bind_index++;
|
|
|
|
|
}
|