From 220ab3541371033bfd97287cece9e5d4e53fd6b3 Mon Sep 17 00:00:00 2001 From: Arthur Suzuki Date: Mon, 28 Sep 2026 11:13:35 +0200 Subject: [PATCH 1/2] Add UDP packet retry mechanism to udp_name_sync usermod - Add retry tracking variables (_lastNameSentTime, _nameSendCount, _nameNeedsSync) - Implement retry logic matching core WLED UDP sync behavior - Refactor packet sending into sendNamePacket() helper function - Use global udpNumRetries configuration (Settings -> Sync Interfaces) - Add missing static member definitions for UI integration - Retry packets every 250ms up to udpNumRetries times for reliability - Check for name changes BEFORE retry branch to avoid retrying old names - Clear _nameNeedsSync flag when all retries are complete - Update early-return check to not block retries when _nameNeedsSync is set - Preserve early return for cleared names that have never been sent - Allow pending empty-name packets to reach the retry branch Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe --- usermods/udp_name_sync/udp_name_sync.cpp | 80 ++++++++++++++++++------ 1 file changed, 60 insertions(+), 20 deletions(-) diff --git a/usermods/udp_name_sync/udp_name_sync.cpp b/usermods/udp_name_sync/udp_name_sync.cpp index 34197bb345..a6676c4239 100644 --- a/usermods/udp_name_sync/udp_name_sync.cpp +++ b/usermods/udp_name_sync/udp_name_sync.cpp @@ -9,6 +9,11 @@ class UdpNameSync : public Usermod { static constexpr uint8_t kPacketType = 200; // custom usermod packet type static const char _name[]; static const char _enabled[]; + + // Retry mechanism variables (similar to core UDP sync) + unsigned long _lastNameSentTime = 0; + uint8_t _nameSendCount = 0; + bool _nameNeedsSync = false; public: /** @@ -30,37 +35,68 @@ class UdpNameSync : public Usermod { 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 + // Early return only if name was never set and no retry is pending + // This allows empty-name packets to reach the retry branch when _nameNeedsSync is set + if (segmentName[0] == '\0' && !mainseg.name && !_nameNeedsSync) 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 - + + // Check for name change first - this takes priority over retries + if (strncmp(curName, segmentName, sizeof(segmentName)) != 0) { + // Name changed, send new name (initial send, reset retry counter) + sendNamePacket(false); + return; + } + + // Name hasn't changed - check if we need to retry + if (_nameNeedsSync && udpConnected && (_nameSendCount < udpNumRetries) && ((millis() - _lastNameSentTime) > 250)) { + sendNamePacket(true); // retry + return; + } + + // If we were waiting for retries but they're now complete, clear the flag + if (_nameNeedsSync && (_nameSendCount >= udpNumRetries || !udpConnected)) { + _nameNeedsSync = false; + } + + // Name is in sync, no action needed + return; + } + + void sendNamePacket(bool isRetry) { IPAddress broadcastIp = uint32_t(WLEDNetwork.localIP()) | ~uint32_t(WLEDNetwork.subnetMask()); byte udpOut[WLED_MAX_SEGNAME_LEN + 2]; udpOut[0] = kPacketType; // custom usermod packet type (avoid 0..5 used by core protocols) - + + Segment& mainseg = strip.getMainSegment(); + const char* curName = mainseg.name ? mainseg.name : ""; + if (segmentName[0] != '\0' && !mainseg.name) { // name cleared - notifierUdp.beginPacket(broadcastIp, udpPort); - segmentName[0] = '\0'; - DEBUG_PRINTLN(F("UdpNameSync: sending empty name")); + strlcpy(segmentName, "", sizeof(segmentName)); + if (!isRetry) DEBUG_PRINTLN(F("UdpNameSync: sending empty name")); udpOut[1] = 0; // explicit empty string + notifierUdp.beginPacket(broadcastIp, udpPort); notifierUdp.write(udpOut, 2); notifierUdp.endPacket(); - return; + } else { + 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.beginPacket(broadcastIp, udpPort); + notifierUdp.write(udpOut, 2 + nameLen); + notifierUdp.endPacket(); + if (!isRetry) { + DEBUG_PRINT(F("UdpNameSync: Sent segment name : ")); + DEBUG_PRINTLN(segmentName); + } } - - 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; + + // Update retry tracking (match core behavior) + _lastNameSentTime = millis(); + _nameSendCount = isRetry ? _nameSendCount + 1 : 0; + _nameNeedsSync = true; } bool onUdpPacket(uint8_t * payload, size_t len) override { @@ -81,5 +117,9 @@ class UdpNameSync : public Usermod { } }; +// Static member definitions +const char UdpNameSync::_name[] PROGMEM = "UDP Name Sync"; +const char UdpNameSync::_enabled[] PROGMEM = "enabled"; + static UdpNameSync udp_name_sync; REGISTER_USERMOD(udp_name_sync); From 945555ce4f7b68921e5418cfc1b1959cb5e0c641 Mon Sep 17 00:00:00 2001 From: Arthur Suzuki Date: Mon, 5 Oct 2026 23:05:16 +0200 Subject: [PATCH 2/2] fix segment name truncation in udp_name_sync usermod --- usermods/udp_name_sync/udp_name_sync.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/udp_name_sync/udp_name_sync.cpp b/usermods/udp_name_sync/udp_name_sync.cpp index a6676c4239..f4d1a824dd 100644 --- a/usermods/udp_name_sync/udp_name_sync.cpp +++ b/usermods/udp_name_sync/udp_name_sync.cpp @@ -5,7 +5,7 @@ class UdpNameSync : public Usermod { private: bool enabled = false; - char segmentName[WLED_MAX_SEGNAME_LEN] = {0}; + char segmentName[WLED_MAX_SEGNAME_LEN + 1] = {0}; // Segment::setName() accepts up to WLED_MAX_SEGNAME_LEN chars, so keep room for the terminator static constexpr uint8_t kPacketType = 200; // custom usermod packet type static const char _name[]; static const char _enabled[];