diff --git a/usermods/udp_name_sync/library.json b/usermods/udp_name_sync/library.json new file mode 100644 index 0000000000..4c5bb44814 --- /dev/null +++ b/usermods/udp_name_sync/library.json @@ -0,0 +1,5 @@ +{ + "name": "udp_name_sync", + "build": { "libArchive": false }, + "dependencies": {} +} diff --git a/usermods/udp_name_sync/udp_name_sync.cpp b/usermods/udp_name_sync/udp_name_sync.cpp new file mode 100644 index 0000000000..b31b856983 --- /dev/null +++ b/usermods/udp_name_sync/udp_name_sync.cpp @@ -0,0 +1,85 @@ +#include "wled.h" + +class UdpNameSync : public Usermod { + + private: + + bool enabled = false; + char segmentName[WLED_MAX_SEGNAME_LEN] = {0}; + static constexpr uint8_t kPacketType = 200; // custom usermod packet type + static const char _name[]; + static const char _enabled[]; + + public: + /** + * Enable/Disable the usermod + */ + inline void enable(bool value) { enabled = value; } + + /** + * Get usermod enabled/disabled state + */ + inline bool isEnabled() const { return enabled; } + + void setup() override { + // Enabled when this usermod is compiled, set to false if you prefer runtime opt-in + enable(true); + } + + void loop() override { + if (!enabled) return; + if (!WLED_CONNECTED) return; + if (!udpConnected) return; + Segment& mainseg = strip.getMainSegment(); + if (segmentName[0] == '\0' && !mainseg.name) return; //name was never set, do nothing + + const char* curName = mainseg.name ? mainseg.name : ""; + if (strncmp(curName, segmentName, sizeof(segmentName)) == 0) return; // same name, do nothing + + IPAddress broadcastIp = uint32_t(Network.localIP()) | ~uint32_t(Network.subnetMask()); + byte udpOut[WLED_MAX_SEGNAME_LEN + 2]; + udpOut[0] = kPacketType; // custom usermod packet type (avoid 0..5 used by core protocols) + + if (segmentName[0] != '\0' && !mainseg.name) { // name cleared + notifierUdp.beginPacket(broadcastIp, udpPort); + segmentName[0] = '\0'; + DEBUG_PRINTLN(F("UdpNameSync: sending empty name")); + udpOut[1] = 0; // explicit empty string + notifierUdp.write(udpOut, 2); + notifierUdp.endPacket(); + return; + } + + notifierUdp.beginPacket(broadcastIp, udpPort); + DEBUG_PRINT(F("UdpNameSync: saving segment name ")); + DEBUG_PRINTLN(curName); + strlcpy(segmentName, curName, sizeof(segmentName)); + strlcpy((char *)&udpOut[1], segmentName, sizeof(udpOut) - 1); // leave room for header byte + size_t nameLen = strnlen((char *)&udpOut[1], sizeof(udpOut) - 1); + notifierUdp.write(udpOut, 2 + nameLen); + notifierUdp.endPacket(); + DEBUG_PRINT(F("UdpNameSync: Sent segment name : ")); + DEBUG_PRINTLN(segmentName); + return; + } + + bool onUdpPacket(uint8_t * payload, size_t len) override { + DEBUG_PRINT(F("UdpNameSync: Received packet")); + if (!enabled) return false; + if (receiveDirect) return false; + if (len < 2) return false; // need type + at least 1 byte for name (can be 0) + if (payload[0] != kPacketType) return false; + Segment& mainseg = strip.getMainSegment(); + char tmp[WLED_MAX_SEGNAME_LEN] = {0}; + size_t copyLen = len - 1; + if (copyLen > sizeof(tmp) - 1) copyLen = sizeof(tmp) - 1; + memcpy(tmp, &payload[1], copyLen); + tmp[copyLen] = '\0'; + mainseg.setName(tmp); + DEBUG_PRINT(F("UdpNameSync: set segment name")); + return true; + } +}; + +static UdpNameSync udp_name_sync; +REGISTER_USERMOD(udp_name_sync); diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 0cd28a31a0..f83f4ae68b 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -442,6 +442,7 @@ class Usermod { virtual void onMqttConnect(bool sessionPresent) {} // fired when MQTT connection is established (so usermod can subscribe) virtual bool onMqttMessage(char* topic, char* payload) { return false; } // fired upon MQTT message received (wled topic) virtual bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len) { return false; } // fired upon ESP-NOW message received + virtual bool onUdpPacket(uint8_t* payload, size_t len) { return false; } //fired upon UDP packet received virtual void onUpdateBegin(bool) {} // fired prior to and after unsuccessful firmware update virtual void onStateChange(uint8_t mode) {} // fired upon WLED state change virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;} @@ -481,6 +482,7 @@ namespace UsermodManager { #ifndef WLED_DISABLE_ESPNOW bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len); #endif + bool onUdpPacket(uint8_t* payload, size_t len); void onUpdateBegin(bool); void onStateChange(uint8_t); Usermod* lookup(uint16_t mod_id); diff --git a/wled00/udp.cpp b/wled00/udp.cpp index bdb60c363a..5600b6739a 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -565,93 +565,82 @@ void handleNotifications() return; } - if (!receiveDirect) return; - - //TPM2.NET - if (udpIn[0] == 0x9c) - { - //WARNING: this code assumes that the final TMP2.NET payload is evenly distributed if using multiple packets (ie. frame size is constant) - //if the number of LEDs in your installation doesn't allow that, please include padding bytes at the end of the last packet - byte tpmType = udpIn[1]; - if (tpmType == 0xaa) { //TPM2.NET polling, expect answer - sendTPM2Ack(); return; - } - if (tpmType != 0xda) return; //return if notTPM2.NET data - - realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP(); - realtimeLock(realtimeTimeoutMs, REALTIME_MODE_TPM2NET); - if (realtimeOverride) return; - - tpmPacketCount++; //increment the packet count - if (tpmPacketCount == 1) tpmPayloadFrameSize = (udpIn[2] << 8) + udpIn[3]; //save frame size for the whole payload if this is the first packet - byte packetNum = udpIn[4]; //starts with 1! - byte numPackets = udpIn[5]; - - unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED - unsigned totalLen = strip.getLengthTotal(); - for (size_t i = 6; i < tpmPayloadFrameSize + 4U && id < totalLen; i += 3, id++) { - setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); - } - if (tpmPacketCount == numPackets) { //reset packet count and show if all packets were received - tpmPacketCount = 0; - if (useMainSegmentOnly) strip.trigger(); - else strip.show(); - } - return; - } + if (receiveDirect) { + //TPM2.NET + if (udpIn[0] == 0x9c) { + //WARNING: this code assumes that the final TMP2.NET payload is evenly distributed if using multiple packets (ie. frame size is constant) + //if the number of LEDs in your installation doesn't allow that, please include padding bytes at the end of the last packet + byte tpmType = udpIn[1]; + if (tpmType == 0xaa) { //TPM2.NET polling, expect answer + sendTPM2Ack(); return; + } + if (tpmType != 0xda) return; //return if notTPM2.NET data - //UDP realtime: 1 warls 2 drgb 3 drgbw 4 dnrgb 5 dnrgbw - if (udpIn[0] > 0 && udpIn[0] < 6) - { - realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP(); - DEBUG_PRINTLN(realtimeIP); - if (packetSize < 2) return; + realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP(); + realtimeLock(realtimeTimeoutMs, REALTIME_MODE_TPM2NET); + if (realtimeOverride) return; - if (udpIn[1] == 0) { - realtimeTimeout = 0; // cancel realtime mode immediately - return; - } else { - realtimeLock(udpIn[1]*1000 +1, REALTIME_MODE_UDP); - } - if (realtimeOverride) return; + tpmPacketCount++; //increment the packet count + if (tpmPacketCount == 1) tpmPayloadFrameSize = (udpIn[2] << 8) + udpIn[3]; //save frame size for the whole payload if this is the first packet + byte packetNum = udpIn[4]; //starts with 1! + byte numPackets = udpIn[5]; - unsigned totalLen = strip.getLengthTotal(); - if (udpIn[0] == 1 && packetSize > 5) //warls - { - for (size_t i = 2; i < packetSize -3; i += 4) - { - setRealtimePixel(udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3], 0); - } - } else if (udpIn[0] == 2 && packetSize > 4) //drgb - { - for (size_t i = 2, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++) - { + unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED + unsigned totalLen = strip.getLengthTotal(); + for (size_t i = 6; i < tpmPayloadFrameSize + 4U && id < totalLen; i += 3, id++) { setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); } - } else if (udpIn[0] == 3 && packetSize > 6) //drgbw - { - for (size_t i = 2, id = 0; i < packetSize -3 && id < totalLen; i += 4, id++) - { - setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]); + if (tpmPacketCount == numPackets) { //reset packet count and show if all packets were received + tpmPacketCount = 0; + if (useMainSegmentOnly) strip.trigger(); + else strip.show(); } - } else if (udpIn[0] == 4 && packetSize > 7) //dnrgb - { - unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00); - for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 3, id++) - { - setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); + return; + } + + //UDP realtime: 1 warls 2 drgb 3 drgbw 4 dnrgb 5 dnrgbw + if (udpIn[0] > 0 && udpIn[0] < 6) { + realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP(); + DEBUG_PRINTLN(realtimeIP); + if (packetSize < 2) return; + + if (udpIn[1] == 0) { + realtimeTimeout = 0; // cancel realtime mode immediately + return; + } else { + realtimeLock(udpIn[1]*1000 +1, REALTIME_MODE_UDP); } - } else if (udpIn[0] == 5 && packetSize > 8) //dnrgbw - { - unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00); - for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 4, id++) - { - setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]); + if (realtimeOverride) return; + + unsigned totalLen = strip.getLengthTotal(); + if (udpIn[0] == 1 && packetSize > 5) { //warls + for (size_t i = 2; i < packetSize -3; i += 4) { + setRealtimePixel(udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3], 0); + } + } else if (udpIn[0] == 2 && packetSize > 4) { //drgb + for (size_t i = 2, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++) + { + setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); + } + } else if (udpIn[0] == 3 && packetSize > 6) { //drgbw + for (size_t i = 2, id = 0; i < packetSize -3 && id < totalLen; i += 4, id++) { + setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]); + } + } else if (udpIn[0] == 4 && packetSize > 7) { //dnrgb + unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00); + for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 3, id++) { + setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); + } + } else if (udpIn[0] == 5 && packetSize > 8) { //dnrgbw + unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00); + for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 4, id++) { + setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]); + } } + if (useMainSegmentOnly) strip.trigger(); + else strip.show(); + return; } - if (useMainSegmentOnly) strip.trigger(); - else strip.show(); - return; } // API over UDP @@ -669,6 +658,8 @@ void handleNotifications() } releaseJSONBufferLock(); } + + UsermodManager::onUdpPacket(udpIn, packetSize); } diff --git a/wled00/um_manager.cpp b/wled00/um_manager.cpp index 1a7cc22694..647757ad6f 100644 --- a/wled00/um_manager.cpp +++ b/wled00/um_manager.cpp @@ -68,6 +68,10 @@ bool UsermodManager::onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t return false; } #endif +bool UsermodManager::onUdpPacket(uint8_t* payload, size_t len) { + for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) if ((*mod)->onUdpPacket(payload, len)) return true; + return false; +} void UsermodManager::onUpdateBegin(bool init) { for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) (*mod)->onUpdateBegin(init); } // notify usermods that update is to begin void UsermodManager::onStateChange(uint8_t mode) { for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) (*mod)->onStateChange(mode); } // notify usermods that WLED state changed