Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ArtPoll support #2615

Merged
merged 6 commits into from
Sep 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 198 additions & 0 deletions wled00/e131.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,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;
Expand Down Expand Up @@ -214,3 +218,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++;
}
3 changes: 3 additions & 0 deletions wled00/fcn_declare.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,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);
Expand Down
4 changes: 2 additions & 2 deletions wled00/src/dependencies/e131/ESPAsyncE131.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
44 changes: 44 additions & 0 deletions wled00/src/dependencies/e131/ESPAsyncE131.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ typedef struct ip_addr ip4_addr_t;
#define DDP_TIMECODE_FLAG 0x10

#define ARTNET_OPCODE_OPDMX 0x5000
#define ARTNET_OPCODE_OPPOLL 0x2000
#define ARTNET_OPCODE_OPPOLLREPLY 0x2100

#define P_E131 0
#define P_ARTNET 1
Expand Down Expand Up @@ -151,6 +153,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);

Expand Down
28 changes: 28 additions & 0 deletions wled00/src/dependencies/network/Network.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions wled00/src/dependencies/network/Network.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class NetworkClass
IPAddress localIP();
IPAddress subnetMask();
IPAddress gatewayIP();
void localMAC(uint8_t* MAC);
bool isConnected();
bool isEthernet();
};
Expand Down
1 change: 1 addition & 0 deletions wled00/wled.h
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,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)
Expand Down