From 4de6656bc4e9fbcfe9d35b63e7d8f9e70f3d0404 Mon Sep 17 00:00:00 2001 From: Arthur Suzuki Date: Thu, 21 Aug 2025 01:00:22 +0200 Subject: [PATCH 1/9] new usermod hooks "onUdpPacket" this new hooks will help you implement new and custom protocols in usermods. I've provided an example (see usermods/udp_name_sync). The example will help you share the main segment name across different WLED instances. The segment name can be useful to sync with some effects like GIF image or scrolling text. If you define new packet format in your usermod, make sure it will either not collide with already used version of wled udp packet : - 0 is for udp sync - 1 is for AudioReactive data - 2 is for udp_name_sync :) Also, the onUdpPacket will override "parseNotification" if it returns "true". Have fun! --- usermods/udp_name_sync/library.json | 5 ++ usermods/udp_name_sync/udp_name_sync.cpp | 79 ++++++++++++++++++++++++ wled00/fcn_declare.h | 2 + wled00/udp.cpp | 3 + wled00/um_manager.cpp | 4 ++ 5 files changed, 93 insertions(+) create mode 100644 usermods/udp_name_sync/library.json create mode 100644 usermods/udp_name_sync/udp_name_sync.cpp 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..d926e1b678 --- /dev/null +++ b/usermods/udp_name_sync/udp_name_sync.cpp @@ -0,0 +1,79 @@ +#include "wled.h" + +class UdpNameSync : public Usermod { + + private: + + bool enabled = false; + bool initDone = false; + unsigned long lastTime = 0; + char segmentName[WLED_MAX_SEGNAME_LEN] = {0}; + static const char _name[]; + static const char _enabled[]; + + public: + /** + * Enable/Disable the usermod + */ + inline void enable(bool enable) { enabled = enable; } + + /** + * Get usermod enabled/disabled state + */ + inline bool isEnabled() { return enabled; } + + void setup() override { + initDone = true; + } + + void loop() override { + if (!WLED_CONNECTED) return; + if (!udpConnected) return; + Segment& mainseg = strip.getMainSegment(); + if (!strlen(segmentName) && !mainseg.name) return; //name was never set, do nothing + + IPAddress broadcastIp = ~uint32_t(Network.subnetMask()) | uint32_t(Network.gatewayIP()); + byte udpOut[WLED_MAX_SEGNAME_LEN + 2]; + udpOut[0] = 2; // 0: wled notifier protocol, 1: warls protocol, 2 is free + + if (strlen(segmentName) && !mainseg.name) { //name is back to null + notifierUdp.beginPacket(broadcastIp, udpPort); + strcpy(segmentName,""); + DEBUG_PRINTLN(F("UdpNameSync: sending Null name")); + notifierUdp.write( udpOut , 2); + notifierUdp.endPacket(); + return; + } + + if (0 == strcmp(mainseg.name, segmentName)) return; //same name, do nothing + + notifierUdp.beginPacket(broadcastIp, udpPort); + DEBUG_PRINT(F("UdpNameSync: saving segment name ")); + DEBUG_PRINTLN(mainseg.name); + byte length = strlen(mainseg.name); + strlcpy(segmentName, mainseg.name, length+1); + strlcpy((char *)&udpOut[1], segmentName, length+1); + notifierUdp.write(udpOut, length + 2); + notifierUdp.endPacket(); + DEBUG_PRINT(F("UdpNameSync: Sent segment name : ")); + DEBUG_PRINTLN(segmentName); + } + + bool onUdpPacket(uint8_t * payload, uint8_t len) override { + DEBUG_PRINT(F("UdpNameSync: Received packet")); + if (payload[0] != 2) return false; + //else + Segment& mainseg = strip.getMainSegment(); + mainseg.setName((char *)&payload[1]); + DEBUG_PRINT(F("UdpNameSync: set segment name")); + return true; + } +}; + + +// add more strings here to reduce flash memory usage +const char UdpNameSync::_name[] PROGMEM = "UdpNameSync"; +const char UdpNameSync::_enabled[] PROGMEM = "enabled"; + +static UdpNameSync udp_name_sync; +REGISTER_USERMOD(udp_name_sync); diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 0cd28a31a0..326ae12287 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, uint8_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, uint8_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..7b9e53bcd2 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -557,6 +557,9 @@ void handleNotifications() return; } + // usermods hook can override processing + if (UsermodManager::onUdpPacket(udpIn, packetSize)) return; + //wled notifier, ignore if realtime packets active if (udpIn[0] == 0 && !realtimeMode && receiveGroups) { diff --git a/wled00/um_manager.cpp b/wled00/um_manager.cpp index 1a7cc22694..483fdc6876 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, uint8_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 From 2082b01a3cb98f322d4a756b8bd710bb1d03fa75 Mon Sep 17 00:00:00 2001 From: netmindz Date: Thu, 21 Aug 2025 08:53:11 +0100 Subject: [PATCH 2/9] Apply suggestions from code review Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- usermods/udp_name_sync/udp_name_sync.cpp | 11 ++++++----- wled00/um_manager.cpp | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/usermods/udp_name_sync/udp_name_sync.cpp b/usermods/udp_name_sync/udp_name_sync.cpp index d926e1b678..cd279386aa 100644 --- a/usermods/udp_name_sync/udp_name_sync.cpp +++ b/usermods/udp_name_sync/udp_name_sync.cpp @@ -45,15 +45,16 @@ class UdpNameSync : public Usermod { return; } - if (0 == strcmp(mainseg.name, segmentName)) return; //same name, do nothing + const char* curName = mainseg.name ? mainseg.name : ""; + if (strcmp(curName, segmentName) == 0) return; // same name, do nothing notifierUdp.beginPacket(broadcastIp, udpPort); DEBUG_PRINT(F("UdpNameSync: saving segment name ")); DEBUG_PRINTLN(mainseg.name); - byte length = strlen(mainseg.name); - strlcpy(segmentName, mainseg.name, length+1); - strlcpy((char *)&udpOut[1], segmentName, length+1); - notifierUdp.write(udpOut, length + 2); + size_t length = strlen(mainseg.name); + strlcpy(segmentName, mainseg.name, sizeof(segmentName)); + strlcpy((char *)&udpOut[1], segmentName, sizeof(udpOut) - 1); // leave room for header byte + notifierUdp.write(udpOut, 2 + strnlen((char *)&udpOut[1], sizeof(udpOut) - 1)); notifierUdp.endPacket(); DEBUG_PRINT(F("UdpNameSync: Sent segment name : ")); DEBUG_PRINTLN(segmentName); diff --git a/wled00/um_manager.cpp b/wled00/um_manager.cpp index 483fdc6876..647757ad6f 100644 --- a/wled00/um_manager.cpp +++ b/wled00/um_manager.cpp @@ -68,7 +68,7 @@ bool UsermodManager::onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t return false; } #endif -bool UsermodManager::onUdpPacket(uint8_t* payload, uint8_t len) { +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; } From f3e3f585dfc25e731f08dd71f957aad830e72f12 Mon Sep 17 00:00:00 2001 From: Liliputech Date: Sun, 24 Aug 2025 13:52:39 +0200 Subject: [PATCH 3/9] usermod udp_name_sync : properly initialize packet if segment name is empty Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- usermods/udp_name_sync/udp_name_sync.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/usermods/udp_name_sync/udp_name_sync.cpp b/usermods/udp_name_sync/udp_name_sync.cpp index cd279386aa..31a271fe12 100644 --- a/usermods/udp_name_sync/udp_name_sync.cpp +++ b/usermods/udp_name_sync/udp_name_sync.cpp @@ -36,13 +36,14 @@ class UdpNameSync : public Usermod { byte udpOut[WLED_MAX_SEGNAME_LEN + 2]; udpOut[0] = 2; // 0: wled notifier protocol, 1: warls protocol, 2 is free - if (strlen(segmentName) && !mainseg.name) { //name is back to null - notifierUdp.beginPacket(broadcastIp, udpPort); - strcpy(segmentName,""); - DEBUG_PRINTLN(F("UdpNameSync: sending Null name")); - notifierUdp.write( udpOut , 2); - notifierUdp.endPacket(); - return; + if (strlen(segmentName) && !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; } const char* curName = mainseg.name ? mainseg.name : ""; From 550b4d9deaa8f7f7060c1b00842d14455ea38d61 Mon Sep 17 00:00:00 2001 From: Arthur Suzuki Date: Sun, 24 Aug 2025 15:54:45 +0200 Subject: [PATCH 4/9] fix comments for usermod hooks "onUdpPacket" --- usermods/udp_name_sync/udp_name_sync.cpp | 16 +++++++++++----- wled00/fcn_declare.h | 4 ++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/usermods/udp_name_sync/udp_name_sync.cpp b/usermods/udp_name_sync/udp_name_sync.cpp index 31a271fe12..361dd7a576 100644 --- a/usermods/udp_name_sync/udp_name_sync.cpp +++ b/usermods/udp_name_sync/udp_name_sync.cpp @@ -34,7 +34,7 @@ class UdpNameSync : public Usermod { IPAddress broadcastIp = ~uint32_t(Network.subnetMask()) | uint32_t(Network.gatewayIP()); byte udpOut[WLED_MAX_SEGNAME_LEN + 2]; - udpOut[0] = 2; // 0: wled notifier protocol, 1: warls protocol, 2 is free + udpOut[0] = 200; // 0: wled notifier protocol, 1: warls protocol, 2 is free if (strlen(segmentName) && !mainseg.name) { // name cleared notifierUdp.beginPacket(broadcastIp, udpPort); @@ -46,8 +46,13 @@ class UdpNameSync : public Usermod { return; } - const char* curName = mainseg.name ? mainseg.name : ""; - if (strcmp(curName, segmentName) == 0) return; // same name, do nothing + char checksumSegName = 0; + char checksumCurName = 0; + for(int i=0; i++; mainseg.name[i]==0 || segmentName[i]==0) { + checksumSegName+=segmentName[i]; + checksumCurName+=mainseg.name[i]; + } + if (checksumCurName == checksumSegName) return; // same name, do nothing notifierUdp.beginPacket(broadcastIp, udpPort); DEBUG_PRINT(F("UdpNameSync: saving segment name ")); @@ -61,9 +66,10 @@ class UdpNameSync : public Usermod { DEBUG_PRINTLN(segmentName); } - bool onUdpPacket(uint8_t * payload, uint8_t len) override { + bool onUdpPacket(uint8_t * payload, size_t len) override { DEBUG_PRINT(F("UdpNameSync: Received packet")); - if (payload[0] != 2) return false; + if (receiveDirect) return false; + if (payload[0] != 200) return false; //else Segment& mainseg = strip.getMainSegment(); mainseg.setName((char *)&payload[1]); diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 326ae12287..f83f4ae68b 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -442,7 +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, uint8_t len) { return false; } //fired upon UDP packet 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;} @@ -482,7 +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, uint8_t len); + bool onUdpPacket(uint8_t* payload, size_t len); void onUpdateBegin(bool); void onStateChange(uint8_t); Usermod* lookup(uint16_t mod_id); From 4b5c3a396d87ca1d1d6187d334a8f3974c0ae5d5 Mon Sep 17 00:00:00 2001 From: Arthur Suzuki Date: Mon, 25 Aug 2025 22:22:51 +0200 Subject: [PATCH 5/9] applied suggestions from review --- usermods/udp_name_sync/udp_name_sync.cpp | 9 +- wled00/udp.cpp | 150 +++++++++++------------ 2 files changed, 71 insertions(+), 88 deletions(-) diff --git a/usermods/udp_name_sync/udp_name_sync.cpp b/usermods/udp_name_sync/udp_name_sync.cpp index 361dd7a576..d7f67117e9 100644 --- a/usermods/udp_name_sync/udp_name_sync.cpp +++ b/usermods/udp_name_sync/udp_name_sync.cpp @@ -46,13 +46,8 @@ class UdpNameSync : public Usermod { return; } - char checksumSegName = 0; - char checksumCurName = 0; - for(int i=0; i++; mainseg.name[i]==0 || segmentName[i]==0) { - checksumSegName+=segmentName[i]; - checksumCurName+=mainseg.name[i]; - } - if (checksumCurName == checksumSegName) return; // same name, do nothing + const char* curName = mainseg.name ? mainseg.name : ""; + if (strncmp(curName, segmentName, sizeof(segmentName)) == 0) return; // same name, do nothing notifierUdp.beginPacket(broadcastIp, udpPort); DEBUG_PRINT(F("UdpNameSync: saving segment name ")); diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 7b9e53bcd2..bd9b43951c 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -557,9 +557,6 @@ void handleNotifications() return; } - // usermods hook can override processing - if (UsermodManager::onUdpPacket(udpIn, packetSize)) return; - //wled notifier, ignore if realtime packets active if (udpIn[0] == 0 && !realtimeMode && receiveGroups) { @@ -568,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]; + 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 - 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; - } + realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP(); + realtimeLock(realtimeTimeoutMs, REALTIME_MODE_TPM2NET); + if (realtimeOverride) 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; + 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]; - if (udpIn[1] == 0) { - realtimeTimeout = 0; // cancel realtime mode immediately + 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; - } else { - realtimeLock(udpIn[1]*1000 +1, REALTIME_MODE_UDP); } - 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); + //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 @@ -672,6 +658,8 @@ void handleNotifications() } releaseJSONBufferLock(); } + + UsermodManager::onUdpPacket(udpIn, packetSize); } From f8ce5980a17cee530c2e13dc5164131c83deb46c Mon Sep 17 00:00:00 2001 From: Arthur Suzuki Date: Mon, 25 Aug 2025 22:45:01 +0200 Subject: [PATCH 6/9] removed tabs and replace by space --- wled00/udp.cpp | 52 +++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/wled00/udp.cpp b/wled00/udp.cpp index bd9b43951c..5600b6739a 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -572,7 +572,7 @@ void handleNotifications() //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; + sendTPM2Ack(); return; } if (tpmType != 0xda) return; //return if notTPM2.NET data @@ -588,12 +588,12 @@ void handleNotifications() 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); + 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(); + tpmPacketCount = 0; + if (useMainSegmentOnly) strip.trigger(); + else strip.show(); } return; } @@ -605,37 +605,37 @@ void handleNotifications() if (packetSize < 2) return; if (udpIn[1] == 0) { - realtimeTimeout = 0; // cancel realtime mode immediately - return; + realtimeTimeout = 0; // cancel realtime mode immediately + return; } else { - realtimeLock(udpIn[1]*1000 +1, REALTIME_MODE_UDP); + realtimeLock(udpIn[1]*1000 +1, REALTIME_MODE_UDP); } 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); - } + 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); - } + 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]); - } + 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); - } + 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]); - } + 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(); From a60be251d237b20f86ee8c413eba52cca9509473 Mon Sep 17 00:00:00 2001 From: Arthur Suzuki Date: Thu, 28 Aug 2025 10:49:27 +0200 Subject: [PATCH 7/9] fix nitpicks from coderabbit --- usermods/udp_name_sync/udp_name_sync.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/usermods/udp_name_sync/udp_name_sync.cpp b/usermods/udp_name_sync/udp_name_sync.cpp index d7f67117e9..fdeda78a95 100644 --- a/usermods/udp_name_sync/udp_name_sync.cpp +++ b/usermods/udp_name_sync/udp_name_sync.cpp @@ -5,8 +5,6 @@ class UdpNameSync : public Usermod { private: bool enabled = false; - bool initDone = false; - unsigned long lastTime = 0; char segmentName[WLED_MAX_SEGNAME_LEN] = {0}; static const char _name[]; static const char _enabled[]; @@ -15,15 +13,14 @@ class UdpNameSync : public Usermod { /** * Enable/Disable the usermod */ - inline void enable(bool enable) { enabled = enable; } + inline void enable(bool value) { enabled = value; } /** * Get usermod enabled/disabled state */ - inline bool isEnabled() { return enabled; } + inline bool isEnabled() const { return enabled; } void setup() override { - initDone = true; } void loop() override { @@ -32,10 +29,9 @@ class UdpNameSync : public Usermod { Segment& mainseg = strip.getMainSegment(); if (!strlen(segmentName) && !mainseg.name) return; //name was never set, do nothing - IPAddress broadcastIp = ~uint32_t(Network.subnetMask()) | uint32_t(Network.gatewayIP()); + IPAddress broadcastIp = uint32_t(Network.localIP()) | ~uint32_t(Network.subnetMask()); byte udpOut[WLED_MAX_SEGNAME_LEN + 2]; - udpOut[0] = 200; // 0: wled notifier protocol, 1: warls protocol, 2 is free - + udpOut[0] = 200; // custom usermod packet type (avoid 0..5 used by core protocols) if (strlen(segmentName) && !mainseg.name) { // name cleared notifierUdp.beginPacket(broadcastIp, udpPort); segmentName[0] = '\0'; @@ -52,7 +48,6 @@ class UdpNameSync : public Usermod { notifierUdp.beginPacket(broadcastIp, udpPort); DEBUG_PRINT(F("UdpNameSync: saving segment name ")); DEBUG_PRINTLN(mainseg.name); - size_t length = strlen(mainseg.name); strlcpy(segmentName, mainseg.name, sizeof(segmentName)); strlcpy((char *)&udpOut[1], segmentName, sizeof(udpOut) - 1); // leave room for header byte notifierUdp.write(udpOut, 2 + strnlen((char *)&udpOut[1], sizeof(udpOut) - 1)); From 62fad4dcdfbf8f30b5b3e5bf1ad4dd56c27b36f7 Mon Sep 17 00:00:00 2001 From: Arthur Suzuki Date: Sat, 30 Aug 2025 01:52:36 +0200 Subject: [PATCH 8/9] applied coderabbit suggestions --- usermods/udp_name_sync/udp_name_sync.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/usermods/udp_name_sync/udp_name_sync.cpp b/usermods/udp_name_sync/udp_name_sync.cpp index fdeda78a95..7b78a624e6 100644 --- a/usermods/udp_name_sync/udp_name_sync.cpp +++ b/usermods/udp_name_sync/udp_name_sync.cpp @@ -6,6 +6,7 @@ class UdpNameSync : public Usermod { 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[]; @@ -21,9 +22,11 @@ class UdpNameSync : public Usermod { inline bool isEnabled() const { return enabled; } void setup() override { + enable(true); } void loop() override { + if (!enabled) return; if (!WLED_CONNECTED) return; if (!udpConnected) return; Segment& mainseg = strip.getMainSegment(); @@ -31,7 +34,7 @@ class UdpNameSync : public Usermod { IPAddress broadcastIp = uint32_t(Network.localIP()) | ~uint32_t(Network.subnetMask()); byte udpOut[WLED_MAX_SEGNAME_LEN + 2]; - udpOut[0] = 200; // custom usermod packet type (avoid 0..5 used by core protocols) + udpOut[0] = kPacketType; // custom usermod packet type (avoid 0..5 used by core protocols) if (strlen(segmentName) && !mainseg.name) { // name cleared notifierUdp.beginPacket(broadcastIp, udpPort); segmentName[0] = '\0'; @@ -48,6 +51,7 @@ class UdpNameSync : public Usermod { notifierUdp.beginPacket(broadcastIp, udpPort); DEBUG_PRINT(F("UdpNameSync: saving segment name ")); DEBUG_PRINTLN(mainseg.name); + DEBUG_PRINTLN(curName); strlcpy(segmentName, mainseg.name, sizeof(segmentName)); strlcpy((char *)&udpOut[1], segmentName, sizeof(udpOut) - 1); // leave room for header byte notifierUdp.write(udpOut, 2 + strnlen((char *)&udpOut[1], sizeof(udpOut) - 1)); @@ -58,14 +62,20 @@ class UdpNameSync : public Usermod { bool onUdpPacket(uint8_t * payload, size_t len) override { DEBUG_PRINT(F("UdpNameSync: Received packet")); + if (!enabled) return false; if (receiveDirect) return false; - if (payload[0] != 200) return false; - //else + 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(); - mainseg.setName((char *)&payload[1]); + 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; - } + } }; From c8757d45c8fb8c58f48312296bc9a90a1a622305 Mon Sep 17 00:00:00 2001 From: Arthur Suzuki Date: Sat, 30 Aug 2025 04:12:08 +0200 Subject: [PATCH 9/9] fix more nitpicks comments --- usermods/udp_name_sync/udp_name_sync.cpp | 26 +++++++++++------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/usermods/udp_name_sync/udp_name_sync.cpp b/usermods/udp_name_sync/udp_name_sync.cpp index 7b78a624e6..b31b856983 100644 --- a/usermods/udp_name_sync/udp_name_sync.cpp +++ b/usermods/udp_name_sync/udp_name_sync.cpp @@ -22,6 +22,7 @@ class UdpNameSync : public Usermod { 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); } @@ -30,12 +31,16 @@ class UdpNameSync : public Usermod { if (!WLED_CONNECTED) return; if (!udpConnected) return; Segment& mainseg = strip.getMainSegment(); - if (!strlen(segmentName) && !mainseg.name) return; //name was never set, do nothing + 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 (strlen(segmentName) && !mainseg.name) { // name cleared + 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")); @@ -45,19 +50,17 @@ class UdpNameSync : public Usermod { return; } - const char* curName = mainseg.name ? mainseg.name : ""; - if (strncmp(curName, segmentName, sizeof(segmentName)) == 0) return; // same name, do nothing - notifierUdp.beginPacket(broadcastIp, udpPort); DEBUG_PRINT(F("UdpNameSync: saving segment name ")); - DEBUG_PRINTLN(mainseg.name); DEBUG_PRINTLN(curName); - strlcpy(segmentName, mainseg.name, sizeof(segmentName)); + strlcpy(segmentName, curName, sizeof(segmentName)); strlcpy((char *)&udpOut[1], segmentName, sizeof(udpOut) - 1); // leave room for header byte - notifierUdp.write(udpOut, 2 + strnlen((char *)&udpOut[1], sizeof(udpOut) - 1)); + 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 { @@ -78,10 +81,5 @@ class UdpNameSync : public Usermod { } }; - -// add more strings here to reduce flash memory usage -const char UdpNameSync::_name[] PROGMEM = "UdpNameSync"; -const char UdpNameSync::_enabled[] PROGMEM = "enabled"; - static UdpNameSync udp_name_sync; REGISTER_USERMOD(udp_name_sync);