DDP Support

This commit is contained in:
cschwinne 2020-09-28 16:29:01 +02:00
parent 8c513c2aad
commit 52df963be9
9 changed files with 178 additions and 94 deletions

View File

@ -57,6 +57,7 @@
#define REALTIME_MODE_ADALIGHT 5 #define REALTIME_MODE_ADALIGHT 5
#define REALTIME_MODE_ARTNET 6 #define REALTIME_MODE_ARTNET 6
#define REALTIME_MODE_TPM2NET 7 #define REALTIME_MODE_TPM2NET 7
#define REALTIME_MODE_DDP 8
//realtime override modes //realtime override modes
#define REALTIME_OVERRIDE_NONE 0 #define REALTIME_OVERRIDE_NONE 0

View File

@ -47,6 +47,7 @@ Type:
<select name=DI onchange="SP(); adj();"> <select name=DI onchange="SP(); adj();">
<option value=5568>E1.31 (sACN)</option> <option value=5568>E1.31 (sACN)</option>
<option value=6454>Art-Net</option> <option value=6454>Art-Net</option>
<option value=4048>DDP</option>
<option value=0 selected>Custom port</option> <option value=0 selected>Custom port</option>
</select><br> </select><br>
<div id=xp>Port: <input name="EP" type="number" min="1" max="65535" value="5568" class="d5" required><br></div> <div id=xp>Port: <input name="EP" type="number" min="1" max="65535" value="5568" class="d5" required><br></div>

View File

@ -7,25 +7,66 @@
* E1.31 handler * E1.31 handler
*/ */
void handleE131Packet(e131_packet_t* p, IPAddress clientIP, bool isArtnet){ //DDP protocol support, called by handleE131Packet
//E1.31 protocol support //handles RGB data only
void handleDDPPacket(e131_packet_t* p) {
int lastPushSeq = e131LastSequenceNumber[0];
//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;
}
}
}
uint16_t offsetLeds = p->channelOffset /3;
uint16_t packetLeds = p->dataLen /3;
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);
for (uint16_t i = offsetLeds; i < offsetLeds + packetLeds; i++) {
setRealtimePixel(i, data[c++], data[c++], data[c++], 0);
}
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){
uint16_t uni = 0, dmxChannels = 0; uint16_t uni = 0, dmxChannels = 0;
uint8_t* e131_data = nullptr; uint8_t* e131_data = nullptr;
uint8_t seq = 0, mde = REALTIME_MODE_E131; uint8_t seq = 0, mde = REALTIME_MODE_E131;
if (isArtnet) if (protocol == P_ARTNET)
{ {
uni = p->art_universe; uni = p->art_universe;
dmxChannels = htons(p->art_length); dmxChannels = htons(p->art_length);
e131_data = p->art_data; e131_data = p->art_data;
seq = p->art_sequence_number; seq = p->art_sequence_number;
mde = REALTIME_MODE_ARTNET; mde = REALTIME_MODE_ARTNET;
} else { } else if (protocol == P_E131) {
uni = htons(p->universe); uni = htons(p->universe);
dmxChannels = htons(p->property_value_count) -1; dmxChannels = htons(p->property_value_count) -1;
e131_data = p->property_values; e131_data = p->property_values;
seq = p->sequence_number; seq = p->sequence_number;
} else { //DDP
realtimeIP = clientIP;
handleDDPPacket(p);
return;
} }
#ifdef WLED_ENABLE_DMX #ifdef WLED_ENABLE_DMX
@ -133,7 +174,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, bool isArtnet){
} }
} else { } else {
// All subsequent universes start at the first channel. // All subsequent universes start at the first channel.
dmxOffset = isArtnet ? 0 : 1; dmxOffset = (protocol == P_ARTNET) ? 0 : 1;
uint16_t ledsInFirstUniverse = (MAX_CHANNELS_PER_UNIVERSE - DMXAddress) / 3; uint16_t ledsInFirstUniverse = (MAX_CHANNELS_PER_UNIVERSE - DMXAddress) / 3;
previousLeds = ledsInFirstUniverse + (previousUniverses - 1) * MAX_LEDS_PER_UNIVERSE; previousLeds = ledsInFirstUniverse + (previousUniverses - 1) * MAX_LEDS_PER_UNIVERSE;
} }

View File

@ -44,7 +44,7 @@ void initDMX();
void handleDMX(); void handleDMX();
//e131.cpp //e131.cpp
void handleE131Packet(e131_packet_t* p, IPAddress clientIP, bool isArtnet); void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol);
//file.cpp //file.cpp
bool handleFileRead(AsyncWebServerRequest*, String path); bool handleFileRead(AsyncWebServerRequest*, String path);

View File

@ -383,18 +383,18 @@ Send notifications twice: <input type="checkbox" name="S2"><br><i>
Reboot required to apply changes.</i><h3>Realtime</h3>Receive UDP realtime: Reboot required to apply changes.</i><h3>Realtime</h3>Receive UDP realtime:
<input type="checkbox" name="RD"><br><br><i>Network DMX input</i><br>Type: <input type="checkbox" name="RD"><br><br><i>Network DMX input</i><br>Type:
<select name="DI" onchange="SP(),adj()"><option value="5568">E1.31 (sACN) <select name="DI" onchange="SP(),adj()"><option value="5568">E1.31 (sACN)
</option><option value="6454">Art-Net</option><option value="0" </option><option value="6454">Art-Net</option><option value="4048">DDP</option>
selected="selected">Custom port</option></select><br><div id="xp">Port: <input <option value="0" selected="selected">Custom port</option></select><br><div
name="EP" type="number" min="1" max="65535" value="5568" class="d5" required> id="xp">Port: <input name="EP" type="number" min="1" max="65535" value="5568"
<br></div>Multicast: <input type="checkbox" name="EM"><br>Start universe: <input class="d5" required><br></div>Multicast: <input type="checkbox" name="EM"><br>
name="EU" type="number" min="0" max="63999" required><br><i>Reboot required. Start universe: <input name="EU" type="number" min="0" max="63999" required><br>
</i> Check out <a href="https://github.com/ahodges9/LedFx" target="_blank">LedFx <i>Reboot required.</i> Check out <a href="https://github.com/ahodges9/LedFx"
</a>!<br>Skip out-of-sequence packets: <input type="checkbox" name="ES"><br> target="_blank">LedFx</a>!<br>Skip out-of-sequence packets: <input
DMX start address: <input name="DA" type="number" min="0" max="510" required> type="checkbox" name="ES"><br>DMX start address: <input name="DA" type="number"
<br>DMX mode: <select name="DM"><option value="0">Disabled</option><option min="0" max="510" required><br>DMX mode: <select name="DM"><option value="0">
value="1">Single RGB</option><option value="2">Single DRGB</option><option Disabled</option><option value="1">Single RGB</option><option value="2">
value="3">Effect</option><option value="4">Multi RGB</option><option value="5"> Single DRGB</option><option value="3">Effect</option><option value="4">Multi RGB
Dimmer + Multi RGB</option></select><br><a </option><option value="5">Dimmer + Multi RGB</option></select><br><a
href="https://github.com/Aircoookie/WLED/wiki/E1.31-DMX" target="_blank"> href="https://github.com/Aircoookie/WLED/wiki/E1.31-DMX" target="_blank">
E1.31 info</a><br>Timeout: <input name="ET" type="number" min="1" max="65000" E1.31 info</a><br>Timeout: <input name="ET" type="number" min="1" max="65000"
required> ms<br>Force max brightness: <input type="checkbox" name="FB"><br> required> ms<br>Force max brightness: <input type="checkbox" name="FB"><br>

View File

@ -392,6 +392,7 @@ void serializeInfo(JsonObject root)
case REALTIME_MODE_ADALIGHT: root["lm"] = F("USB Adalight/TPM2"); break; case REALTIME_MODE_ADALIGHT: root["lm"] = F("USB Adalight/TPM2"); break;
case REALTIME_MODE_ARTNET: root["lm"] = F("Art-Net"); break; case REALTIME_MODE_ARTNET: root["lm"] = F("Art-Net"); break;
case REALTIME_MODE_TPM2NET: root["lm"] = F("tpm2.net"); break; case REALTIME_MODE_TPM2NET: root["lm"] = F("tpm2.net"); break;
case REALTIME_MODE_DDP: root["lm"] = F("DDP"); break;
} }
if (realtimeIP[0] == 0) if (realtimeIP[0] == 0)

View File

@ -38,15 +38,15 @@ ESPAsyncE131::ESPAsyncE131(e131_packet_callback_function callback) {
///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////
bool ESPAsyncE131::begin(bool multicast, uint16_t port, uint16_t universe, uint8_t n) { bool ESPAsyncE131::begin(bool multicast, uint16_t port, uint16_t universe, uint8_t n) {
bool success = false; bool success = false;
if (multicast) { if (multicast) {
success = initMulticast(port, universe, n); success = initMulticast(port, universe, n);
} else { } else {
success = initUnicast(port); success = initUnicast(port);
} }
return success; return success;
} }
///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////
@ -56,40 +56,38 @@ bool ESPAsyncE131::begin(bool multicast, uint16_t port, uint16_t universe, uint8
///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////
bool ESPAsyncE131::initUnicast(uint16_t port) { bool ESPAsyncE131::initUnicast(uint16_t port) {
bool success = false; bool success = false;
if (udp.listen(port)) { if (udp.listen(port)) {
udp.onPacket(std::bind(&ESPAsyncE131::parsePacket, this, udp.onPacket(std::bind(&ESPAsyncE131::parsePacket, this, std::placeholders::_1));
std::placeholders::_1)); success = true;
success = true; }
} return success;
return success;
} }
bool ESPAsyncE131::initMulticast(uint16_t port, uint16_t universe, uint8_t n) { bool ESPAsyncE131::initMulticast(uint16_t port, uint16_t universe, uint8_t n) {
bool success = false; bool success = false;
IPAddress address = IPAddress(239, 255, ((universe >> 8) & 0xff), IPAddress address = IPAddress(239, 255, ((universe >> 8) & 0xff),
((universe >> 0) & 0xff)); ((universe >> 0) & 0xff));
if (udp.listenMulticast(address, port)) { if (udp.listenMulticast(address, port)) {
ip4_addr_t ifaddr; ip4_addr_t ifaddr;
ip4_addr_t multicast_addr; ip4_addr_t multicast_addr;
ifaddr.addr = static_cast<uint32_t>(WiFi.localIP()); ifaddr.addr = static_cast<uint32_t>(WiFi.localIP());
for (uint8_t i = 1; i < n; i++) { for (uint8_t i = 1; i < n; i++) {
multicast_addr.addr = static_cast<uint32_t>(IPAddress(239, 255, multicast_addr.addr = static_cast<uint32_t>(IPAddress(239, 255,
(((universe + i) >> 8) & 0xff), (((universe + i) >> 0) (((universe + i) >> 8) & 0xff), (((universe + i) >> 0)
& 0xff))); & 0xff)));
igmp_joingroup(&ifaddr, &multicast_addr); igmp_joingroup(&ifaddr, &multicast_addr);
}
udp.onPacket(std::bind(&ESPAsyncE131::parsePacket, this,
std::placeholders::_1));
success = true;
} }
return success;
udp.onPacket(std::bind(&ESPAsyncE131::parsePacket, this, std::placeholders::_1));
success = true;
}
return success;
} }
///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////
@ -99,15 +97,16 @@ bool ESPAsyncE131::initMulticast(uint16_t port, uint16_t universe, uint8_t n) {
///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////
void ESPAsyncE131::parsePacket(AsyncUDPPacket _packet) { void ESPAsyncE131::parsePacket(AsyncUDPPacket _packet) {
bool error = false, isArtnet = false; bool error = false;
uint8_t protocol = P_E131;
sbuff = reinterpret_cast<e131_packet_t *>(_packet.data()); sbuff = reinterpret_cast<e131_packet_t *>(_packet.data());
//E1.31 packet identifier ("ACS-E1.17") //E1.31 packet identifier ("ACS-E1.17")
if (memcmp(sbuff->acn_id, ESPAsyncE131::ACN_ID, sizeof(sbuff->acn_id))) if (memcmp(sbuff->acn_id, ESPAsyncE131::ACN_ID, sizeof(sbuff->acn_id)))
isArtnet = true; //not E1.31 protocol = P_ARTNET;
if (isArtnet) { if (protocol = P_ARTNET) {
if (memcmp(sbuff->art_id, ESPAsyncE131::ART_ID, sizeof(sbuff->art_id))) if (memcmp(sbuff->art_id, ESPAsyncE131::ART_ID, sizeof(sbuff->art_id)))
error = true; //not "Art-Net" error = true; //not "Art-Net"
if (sbuff->art_opcode != ARTNET_OPCODE_OPDMX) if (sbuff->art_opcode != ARTNET_OPCODE_OPDMX)
@ -121,9 +120,18 @@ void ESPAsyncE131::parsePacket(AsyncUDPPacket _packet) {
error = true; error = true;
if (sbuff->property_values[0] != 0) if (sbuff->property_values[0] != 0)
error = true; error = true;
} }
if (error && _packet.localPort() == DDP_DEFAULT_PORT) { //DDP packet
int pushSeq = 0;
error = false;
protocol = P_DDP;
//bool push = sbuff->flags & DDP_PUSH_FLAG;
//if (push) pushSeq = sbuff->sequenceNum;
//else if (sbuff->sequenceNum > pushSeq -5) error = true;
}
if (!error) { if (!error) {
_callback(sbuff, _packet.remoteIP(), isArtnet); _callback(sbuff, _packet.remoteIP(), protocol);
} }
} }

View File

@ -5,6 +5,9 @@
* Copyright (c) 2019 Shelby Merrick * Copyright (c) 2019 Shelby Merrick
* http://www.forkineye.com * http://www.forkineye.com
* *
* Project: ESPAsyncDDP - Asynchronous DDP library for Arduino ESP8266 and ESP32
* Copyright (c) 2019 Daniel Kulp
*
* This program is provided free for you to use in any way that you wish, * This program is provided free for you to use in any way that you wish,
* subject to the laws and regulations where you are using it. Due diligence * subject to the laws and regulations where you are using it. Due diligence
* is strongly suggested before using this code. Please give credit where due. * is strongly suggested before using this code. Please give credit where due.
@ -45,9 +48,17 @@ typedef struct ip_addr ip4_addr_t;
// Defaults // Defaults
#define E131_DEFAULT_PORT 5568 #define E131_DEFAULT_PORT 5568
#define ARTNET_DEFAULT_PORT 6454 #define ARTNET_DEFAULT_PORT 6454
#define DDP_DEFAULT_PORT 4048
#define DDP_PUSH_FLAG 0x01
#define DDP_TIMECODE_FLAG 0x10
#define ARTNET_OPCODE_OPDMX 0x5000 #define ARTNET_OPCODE_OPDMX 0x5000
#define P_E131 0
#define P_ARTNET 1
#define P_DDP 2
// E1.31 Packet Offsets // E1.31 Packet Offsets
#define E131_ROOT_PREAMBLE_SIZE 0 #define E131_ROOT_PREAMBLE_SIZE 0
#define E131_ROOT_POSTAMBLE_SIZE 2 #define E131_ROOT_POSTAMBLE_SIZE 2
@ -76,57 +87,78 @@ typedef struct ip_addr ip4_addr_t;
// E1.31 Packet Structure // E1.31 Packet Structure
typedef union { typedef union {
struct { //E1.31 packet struct { //E1.31 packet
// Root Layer // Root Layer
uint16_t preamble_size; uint16_t preamble_size;
uint16_t postamble_size; uint16_t postamble_size;
uint8_t acn_id[12]; uint8_t acn_id[12];
uint16_t root_flength; uint16_t root_flength;
uint32_t root_vector; uint32_t root_vector;
uint8_t cid[16]; uint8_t cid[16];
// Frame Layer // Frame Layer
uint16_t frame_flength; uint16_t frame_flength;
uint32_t frame_vector; uint32_t frame_vector;
uint8_t source_name[64]; uint8_t source_name[64];
uint8_t priority; uint8_t priority;
uint16_t reserved; uint16_t reserved;
uint8_t sequence_number; uint8_t sequence_number;
uint8_t options; uint8_t options;
uint16_t universe; uint16_t universe;
// DMP Layer // DMP Layer
uint16_t dmp_flength; uint16_t dmp_flength;
uint8_t dmp_vector; uint8_t dmp_vector;
uint8_t type; uint8_t type;
uint16_t first_address; uint16_t first_address;
uint16_t address_increment; uint16_t address_increment;
uint16_t property_value_count; uint16_t property_value_count;
uint8_t property_values[513]; uint8_t property_values[513];
} __attribute__((packed)); } __attribute__((packed));
struct { //Art-Net packet struct { //Art-Net packet
uint8_t art_id[8]; uint8_t art_id[8];
uint16_t art_opcode; uint16_t art_opcode;
uint16_t art_protocol_ver; uint16_t art_protocol_ver;
uint8_t art_sequence_number; uint8_t art_sequence_number;
uint8_t art_physical; uint8_t art_physical;
uint16_t art_universe; uint16_t art_universe;
uint16_t art_length; uint16_t art_length;
uint8_t art_data[512]; uint8_t art_data[512];
} __attribute__((packed)); } __attribute__((packed));
uint8_t raw[638]; struct { //DDP Header
uint8_t flags;
uint8_t sequenceNum;
uint8_t dataType;
uint8_t destination;
uint32_t channelOffset;
uint16_t dataLen;
uint8_t data[1];
} __attribute__((packed));
/*struct { //DDP Time code Header (unsupported)
uint8_t flags;
uint8_t sequenceNum;
uint8_t dataType;
uint8_t destination;
uint32_t channelOffset;
uint16_t dataLen;
uint32_t timeCode;
uint8_t data[1];
} __attribute__((packed));*/
uint8_t raw[1458];
} e131_packet_t; } e131_packet_t;
// new packet callback // new packet callback
typedef void (*e131_packet_callback_function) (e131_packet_t* p, IPAddress clientIP, bool isArtnet); typedef void (*e131_packet_callback_function) (e131_packet_t* p, IPAddress clientIP, byte protocol);
class ESPAsyncE131 { class ESPAsyncE131 {
private: private:
// Constants for packet validation // Constants for packet validation
static const uint8_t ACN_ID[]; static const uint8_t ACN_ID[];
static const uint8_t ART_ID[]; static const uint8_t ART_ID[];
static const uint32_t VECTOR_ROOT = 4; static const uint32_t VECTOR_ROOT = 4;
static const uint32_t VECTOR_FRAME = 2; static const uint32_t VECTOR_FRAME = 2;
static const uint8_t VECTOR_DMP = 2; static const uint8_t VECTOR_DMP = 2;

View File

@ -8,7 +8,7 @@
*/ */
// version code in format yymmddb (b = daily build) // version code in format yymmddb (b = daily build)
#define VERSION 2009260 #define VERSION 2009280
// ESP8266-01 (blue) got too little storage space to work with all features of WLED. To use it, you must use ESP8266 Arduino Core v2.4.2 and the setting 512K(No SPIFFS). // ESP8266-01 (blue) got too little storage space to work with all features of WLED. To use it, you must use ESP8266 Arduino Core v2.4.2 and the setting 512K(No SPIFFS).