Merge pull request #4859 from Liliputech/udp_name_sync_rework

new usermod hooks "onUdpPacket"
This commit is contained in:
Will Miles
2025-09-04 22:29:21 -04:00
committed by GitHub
5 changed files with 165 additions and 78 deletions

View File

@@ -0,0 +1,5 @@
{
"name": "udp_name_sync",
"build": { "libArchive": false },
"dependencies": {}
}

View File

@@ -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);

View File

@@ -315,6 +315,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;}
@@ -354,6 +355,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);

View File

@@ -565,93 +565,82 @@ void handleNotifications()
return;
}
if (!receiveDirect) 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
//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;
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 (tpmType != 0xda) return; //return if notTPM2.NET data
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
return;
} else {
realtimeLock(udpIn[1]*1000 +1, REALTIME_MODE_UDP);
}
if (realtimeOverride) return;
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;
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;
}
//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);
}
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;
}
// API over UDP
@@ -669,6 +658,8 @@ void handleNotifications()
}
releaseJSONBufferLock();
}
UsermodManager::onUdpPacket(udpIn, packetSize);
}

View File

@@ -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