Add ArtPoll support (#2615)
* Add ArtPoll support * Improved calculations * Add support for legacy DMX start address 0 * Small efficiency improvement * ESP8266 doesn't like yield * Optimized ArtPoll memory use Co-authored-by: cschwinne <dev.aircoookie@gmail.com>
This commit is contained in:
parent
de90e5b753
commit
d0189b0719
198
wled00/e131.cpp
198
wled00/e131.cpp
@ -60,6 +60,10 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
|
||||
|
||||
if (protocol == P_ARTNET)
|
||||
{
|
||||
if (p->art_opcode == ARTNET_OPCODE_OPPOLL) {
|
||||
handleArtnetPollReply(clientIP);
|
||||
return;
|
||||
}
|
||||
uni = p->art_universe;
|
||||
dmxChannels = htons(p->art_length);
|
||||
e131_data = p->art_data;
|
||||
@ -227,3 +231,197 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
|
||||
|
||||
e131NewData = true;
|
||||
}
|
||||
|
||||
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:
|
||||
case DMX_MODE_EFFECT:
|
||||
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);
|
||||
|
||||
sprintf((char *)reply->reply_node_report, "#0001 [%04u] OK - WLED v" TOSTRING(WLED_VERSION), pollReplyCount);
|
||||
|
||||
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++;
|
||||
}
|
@ -82,6 +82,9 @@ void handleDMX();
|
||||
|
||||
//e131.cpp
|
||||
void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol);
|
||||
void handleArtnetPollReply(IPAddress ipAddress);
|
||||
void prepareArtnetPollReply(ArtPollReply* reply);
|
||||
void sendArtnetPollReply(ArtPollReply* reply, IPAddress ipAddress, uint16_t portAddress);
|
||||
|
||||
//file.cpp
|
||||
bool handleFileRead(AsyncWebServerRequest*, String path);
|
||||
|
@ -110,8 +110,8 @@ void ESPAsyncE131::parsePacket(AsyncUDPPacket _packet) {
|
||||
if (protocol == P_ARTNET) {
|
||||
if (memcmp(sbuff->art_id, ESPAsyncE131::ART_ID, sizeof(sbuff->art_id)))
|
||||
error = true; //not "Art-Net"
|
||||
if (sbuff->art_opcode != ARTNET_OPCODE_OPDMX)
|
||||
error = true; //not a DMX packet
|
||||
if (sbuff->art_opcode != ARTNET_OPCODE_OPDMX && sbuff->art_opcode != ARTNET_OPCODE_OPPOLL)
|
||||
error = true; //not a DMX or poll packet
|
||||
} else { //E1.31 error handling
|
||||
if (htonl(sbuff->root_vector) != ESPAsyncE131::VECTOR_ROOT)
|
||||
error = true;
|
||||
|
@ -57,6 +57,8 @@ typedef struct ip_addr ip4_addr_t;
|
||||
#define DDP_TYPE_RGBW32 0x1A
|
||||
|
||||
#define ARTNET_OPCODE_OPDMX 0x5000
|
||||
#define ARTNET_OPCODE_OPPOLL 0x2000
|
||||
#define ARTNET_OPCODE_OPPOLLREPLY 0x2100
|
||||
|
||||
#define P_E131 0
|
||||
#define P_ARTNET 1
|
||||
@ -154,6 +156,48 @@ typedef union {
|
||||
uint8_t raw[1458];
|
||||
} e131_packet_t;
|
||||
|
||||
typedef union {
|
||||
struct {
|
||||
uint8_t reply_id[8];
|
||||
uint16_t reply_opcode;
|
||||
uint8_t reply_ip[4];
|
||||
uint16_t reply_port;
|
||||
uint8_t reply_version_h;
|
||||
uint8_t reply_version_l;
|
||||
uint8_t reply_net_sw;
|
||||
uint8_t reply_sub_sw;
|
||||
uint8_t reply_oem_h;
|
||||
uint8_t reply_oem_l;
|
||||
uint8_t reply_ubea_ver;
|
||||
uint8_t reply_status_1;
|
||||
uint16_t reply_esta_man;
|
||||
uint8_t reply_short_name[18];
|
||||
uint8_t reply_long_name[64];
|
||||
uint8_t reply_node_report[64];
|
||||
uint8_t reply_num_ports_h;
|
||||
uint8_t reply_num_ports_l;
|
||||
uint8_t reply_port_types[4];
|
||||
uint8_t reply_good_input[4];
|
||||
uint8_t reply_good_output_a[4];
|
||||
uint8_t reply_sw_in[4];
|
||||
uint8_t reply_sw_out[4];
|
||||
uint8_t reply_sw_video;
|
||||
uint8_t reply_sw_macro;
|
||||
uint8_t reply_sw_remote;
|
||||
uint8_t reply_spare[3];
|
||||
uint8_t reply_style;
|
||||
uint8_t reply_mac[6];
|
||||
uint8_t reply_bind_ip[4];
|
||||
uint8_t reply_bind_index;
|
||||
uint8_t reply_status_2;
|
||||
uint8_t reply_good_output_b[4];
|
||||
uint8_t reply_status_3;
|
||||
uint8_t reply_filler[21];
|
||||
} __attribute__((packed));
|
||||
|
||||
uint8_t raw[239];
|
||||
} ArtPollReply;
|
||||
|
||||
// new packet callback
|
||||
typedef void (*e131_packet_callback_function) (e131_packet_t* p, IPAddress clientIP, byte protocol);
|
||||
|
||||
|
@ -43,6 +43,34 @@ IPAddress NetworkClass::gatewayIP()
|
||||
return INADDR_NONE;
|
||||
}
|
||||
|
||||
void NetworkClass::localMAC(uint8_t* MAC)
|
||||
{
|
||||
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
|
||||
// ETH.macAddress(MAC); // Does not work because of missing ETHClass:: in ETH.ccp
|
||||
|
||||
// Start work around
|
||||
String macString = ETH.macAddress();
|
||||
char macChar[18];
|
||||
char * octetEnd = macChar;
|
||||
|
||||
strlcpy(macChar, macString.c_str(), 18);
|
||||
|
||||
for (uint8_t i = 0; i < 6; i++) {
|
||||
MAC[i] = (uint8_t)strtol(octetEnd, &octetEnd, 16);
|
||||
octetEnd++;
|
||||
}
|
||||
// End work around
|
||||
|
||||
for (uint8_t i = 0; i < 6; i++) {
|
||||
if (MAC[i] != 0x00) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
WiFi.macAddress(MAC);
|
||||
return;
|
||||
}
|
||||
|
||||
bool NetworkClass::isConnected()
|
||||
{
|
||||
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
|
||||
|
@ -14,6 +14,7 @@ public:
|
||||
IPAddress localIP();
|
||||
IPAddress subnetMask();
|
||||
IPAddress gatewayIP();
|
||||
void localMAC(uint8_t* MAC);
|
||||
bool isConnected();
|
||||
bool isEthernet();
|
||||
};
|
||||
|
@ -387,6 +387,7 @@ WLED_GLOBAL byte DMXOldDimmer _INIT(0); // only update
|
||||
WLED_GLOBAL byte e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; // to detect packet loss
|
||||
WLED_GLOBAL bool e131Multicast _INIT(false); // multicast or unicast
|
||||
WLED_GLOBAL bool e131SkipOutOfSequence _INIT(false); // freeze instead of flickering
|
||||
WLED_GLOBAL uint16_t pollReplyCount _INIT(0); // count number of replies for ArtPoll node report
|
||||
|
||||
WLED_GLOBAL bool mqttEnabled _INIT(false);
|
||||
WLED_GLOBAL char mqttDeviceTopic[33] _INIT(""); // main MQTT topic (individual per device, default is wled/mac)
|
||||
|
Loading…
Reference in New Issue
Block a user