Sending Multi Segment UDP Packets to WLED

Hello! I’m trying to be able to send UDP packets at a WLED instance by way of a Python script. I can get this to work to an extent. If a segment is selected, it will be affected by the UDP packet I send, but if it’s not, I can’t figure out how to control the segment - or even change what’s selected via UDP.

Using Wireshark I’ve scoped the packets that are sent out to notify other instances, but they don’t seem to have multi segment component information.

I see how this is possible using the JSON API , I’ve looked over the udp.cpp code on GitHub and I am using WLED 0.13.1. Any pointers about the structure of the UDP packet would be super helpful, thanks!

@beatKeeper, First things first, I am no authority in WLED internals. That said:

  • A few #defines and constants to note (from udp.cpp):

    #define UDP_SEG_SIZE 28
    #define WLEDPACKETSIZE (41+(MAX_NUM_SEGMENTS*UDP_SEG_SIZE)+0)
    

    Assuming an ESP32 setup, WLEDPACKETSIZE is 937 (i.e. MAX_NUM_SEGMENTS == 32).
    Otherwise ESP8266 (MAX_NUM_SEGMENTS == 16) – see Fx.h.

  • Consider the following 2-segment preset JSON:

    {
      "n":"Cream Red","on":true,"bri":255,"transition":7,"mainseg":0,
      "seg":[
        {"id":0,"start":0,"stop":300,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":250,"cct":127,
         "col":[[255,224,160],[0,0,0],0,0,0]],"fx":0,"sx":60,"ix":220,"pal":2,"sel":false,"rev":false,"mi":false},
        {"id":1,"start":300,"stop":600,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":250,"cct":127,
         "col":[[255,0,0],[0,0,0],[0,0,0]],"fx":0,"sx":61,"ix":255,"pal":2,"sel":true,"rev":true,"mi":false},
       {"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},
       {"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},
       {"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},
       {"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}
      ]
    }	
    
  • Hook up a receiver-ESP32 to your computer’s USB-serial

  • Add the following hack in udp.cpp, compile and upload to the receiver-ESP32:

    void printUdp(byte *udpIn)
    {
       printf(
     	"udpIn[0]: %u\n""udpIn[1]: %u\n""udpIn[2]: %u\n"
     	"udpIn[3]: %u\n""udpIn[4]: %u\n""udpIn[5]: %u\n"
     	"udpIn[6]: %u\n""udpIn[7]: %u\n""udpIn[8]: %u\n"
     	"udpIn[9]: %u\n""udpIn[10]: %u\n""udpIn[11]: %u\n"
     	"udpIn[12]: %u\n""udpIn[13]: %u\n""udpIn[14]: %u\n"
     	"udpIn[15]: %u\n""udpIn[16]: %u\n""udpIn[17]: %u\n"
     	"udpIn[18]: %u\n""udpIn[19]: %u\n""udpIn[20]: %u\n"
     	"udpIn[21]: %u\n""udpIn[22]: %u\n""udpIn[23]: %u\n"
     	"udpIn[24]: %u\n""udpIn[25]: %u\n""udpIn[26]: %u\n"
     	"udpIn[27]: %u\n""udpIn[28]: %u\n""udpIn[29]: %u\n"
     	"udpIn[30]: %u\n""udpIn[31]: %u\n""udpIn[32]: %u\n"
     	"udpIn[33]: %u\n""udpIn[34]: %u\n""udpIn[35]: %u\n"
     	"udpIn[36]: %u\n""udpIn[37]: %u\n""udpIn[38]: %u\n"
     	"udpIn[39]: %u\n""udpIn[40]: %u\n",
     	udpIn[0], udpIn[1], udpIn[2], udpIn[3],
     	udpIn[4], udpIn[5], udpIn[6], udpIn[7], udpIn[8],
     	udpIn[9], udpIn[10], udpIn[11], udpIn[12], udpIn[13],
     	udpIn[14], udpIn[15], udpIn[16], udpIn[17], udpIn[18], udpIn[19],
     	udpIn[20], udpIn[21], udpIn[22], udpIn[23], udpIn[24],
     	udpIn[25], udpIn[26], udpIn[27], udpIn[28], udpIn[29],
     	udpIn[30], udpIn[31], udpIn[32], udpIn[33], udpIn[34],
     	udpIn[35], udpIn[36], udpIn[37], udpIn[38], udpIn[39],
     	udpIn[40]);
     
      for (uint8_t i = 0; i < udpIn[39]; i++) {
        uint16_t ofs = 41 + i*udpIn[40];
        printf(
          "udpIn[%u]: %u\n""udpIn[%u]: %u\n""udpIn[%u]: %u\n"
          "udpIn[%u]: %u\n""udpIn[%u]: %u\n""udpIn[%u]: %u\n"
          "udpIn[%u]: %u\n""udpIn[%u]: %u\n""udpIn[%u]: %u\n"
          "udpIn[%u]: %u\n""udpIn[%u]: %u\n""udpIn[%u]: %u\n"
          "udpIn[%u]: %u\n""udpIn[%u]: %u\n""udpIn[%u]: %u\n"
          "udpIn[%u]: %u\n""udpIn[%u]: %u\n""udpIn[%u]: %u\n"
          "udpIn[%u]: %u\n""udpIn[%u]: %u\n""udpIn[%u]: %u\n"
          "udpIn[%u]: %u\n""udpIn[%u]: %u\n""udpIn[%u]: %u\n"
          "udpIn[%u]: %u\n""udpIn[%u]: %u\n""udpIn[%u]: %u\n"
          "udpIn[%u]: %u\n",
           ofs, udpIn[0 +ofs], ofs+1, udpIn[1 +ofs],
           ofs+2, udpIn[2 +ofs], ofs+3, udpIn[3 +ofs], 
               ofs+4, udpIn[4 +ofs], ofs+5, udpIn[5 +ofs],
           ofs+6, udpIn[6 +ofs], ofs+7, udpIn[7 +ofs],
           ofs+8, udpIn[8 +ofs], ofs+9, udpIn[9 +ofs],
           ofs+10, udpIn[10+ofs], ofs+11, udpIn[11+ofs],
           ofs+12, udpIn[12+ofs], ofs+13, udpIn[13+ofs],
           ofs+14, udpIn[14+ofs], ofs+15, udpIn[15+ofs],
           ofs+16, udpIn[16+ofs], ofs+17, udpIn[17+ofs],
           ofs+18, udpIn[18+ofs], ofs+19, udpIn[19+ofs],
           ofs+20, udpIn[20+ofs], ofs+21, udpIn[21+ofs],
           ofs+22, udpIn[22+ofs], ofs+23, udpIn[23+ofs],
           ofs+24, udpIn[24+ofs], ofs+25, udpIn[25+ofs],
           ofs+26, udpIn[26+ofs], ofs+27, udpIn[27+ofs]);
         }  
       }
     	
       void handleNotifications() {
          ...
          //wled notifier, ignore if realtime packets active
          if (udpIn[0] == 0 && !realtimeMode && receiveNotifications)
          {
             //ignore notification if received within a second after sending a notification ourselves
             if (millis() - notificationSentTime < 1000) return;
             if (udpIn[1] > 199) return; //do not receive custom versions
    
             printUdp(udpIn); // <-- INSERT THIS CALL
     			
             ...
     			
    
  • Enable Sync on your sender-ESP32. Assuming you sent the 2-segment JSON preset shown above to the sender-ESP32, it should “forward” (and the receiver-ESP32 should print out) a UDP sync packet similar to this dump…I used Linux CLI screen -L -Logfile log.txt /dev/ttyUSB0 115200 to grab this serial log.

  • Comparing the UDP packet dump and the preset JSON, along with the comments in udp.cpp::notify(), things should be quite self-explanatory:

    • udpIn[0] is the WLED notifier protocol,
    • udpIn[1] is the callMode; I got 12 (CALL_MODE_BUTTON_PRESS) since I used the IR remote, but you will probably get (and should use) 1 ( CALL_MODE_DIRECT_CHANGE)
    • udpIn[2]udpIn[40] values are mainly associated with seg0 a.k.a “mainSegment”, as well as things such as timestamp info, transition info, etc

    The rest of the udpIn byte array is “per-segment” info, with each mapping being 28 bytes long:

    • udpIn[41]udpIn[68] is (once again) seg0 info
    • udpIn[69]udpIn[96] is seg1 info
    • udpIn[97]udpIn[124] corresponds to the first {"stop":0} segment object of the preset JSON
    • updIn[125]udpIn[152] corresponds to the second {"stop":0}, etc…

    The important thing to note with the {"stop":0} segment mappings is that the “segmentStart” and “segmentStop” values are 0

Hope this helps!
Gbd

@gbd This is a great strategy. Generally jives with what I was trying but I love the serial print hack for feedback. Gonna give this a whirl. Thanks!

Got this working full boat now. The main catch for me was UdpIn[36]. This has to be set to 1 for the segment stuff to actually work. Then everything works as expected.

FYI - the incoming packet is variable length. First part is 41 bytes - each add’l segment you want to take to is another 36 bytes. You tell WLED how many segments you’re talking to in UdpIn[39] and leave UdpIn[40] set to 36 (0x24). So for talking to two segments your packet length is (41 + 36 + 36) 113 bytes.

Super appreciate how you jumped in to help @gbd !

Please hit me up if anyone needs any help sorting out something similar with the UDP notify.

1 Like

@beatKeeper, Nice. I might just use this setup in the near future. Thank you too.

@beatKeeper Hello! I’m working on the exact same problem, and this is the only post I could find with relevant information. I apologize in advance if it’s bad manners to reply to a two year old topic!

I’ve read through this several times, but I must be missing something small but crucial in order to get it to work. If you could provide some more explicit information it would be very appreciated! I’m able to send UDP commands that affect all of the segments that are checked in the UI, but I’m not able to control them individually directly through UDP. I have made sure to enable segment options in the Sync Setup menu on the web UI.

My packet setup is as follows:
UDP[0 - 23] are as outlined in the 24 byte UDP packet in the notifier protocol. I was pretty easily able to get this working properly, but the rest of the packet seems to be ignored entirely.

UDP[24-35] looking at udp.cpp, this seems to be for time syncing between units. I’m not sure if I’d need to pass anything here, so I’ve left these at 0x00.

UDP[36] I’ve set to 0x01 as you noted.
UDP[39] is set to 0x02, as I’m working with two segments.
UDP[40] is set to 36 (0x24)
UDP[41-76] is info for my first segment.
UDP[77-113] is info for my second segment.

Just to be very explicit with where I’m at, here’s the structure of my segment packets. I based these again off of the ‘udp.cpp’ file.
UDP[0] - Segment number (0x00 for my first segment, 0x01 for my second segment).
UDP[1-2] - Start LED
UPD[3-4] - Stop LED
UDP[5] - Grouping
UDP[6] - Spacing
UDP[7-8] - Offset
UDP[9] - Options
UDP[10] - Opacity
UDP[11] - Mode
UDP[12] - Speed
UDP[13] - Intensity
UDP[14] - Palette
UDP[15-18] RGBW Primary
UDP[19-22] RGBW Secondary
UDP[23-26] RGBW Tertiary
UDP[27] - CCT
UDP[28-35] - All set to 0x00

Please let me know if there is anything I’m overlooking.

FYI you can always send JSON API (and even HTTP API) via UDP in the same way as HTTP.
It may be inefficient for many segments, but simpler if you are dealing with only one segment.

1 Like

Thanks @blazoncek . For my use case, I’m working with a Productivity 1000 PLC so my tools for communication protocols are very limited. There is unfortunately not an easy way to build the strings required for JSON, but HTTP might be a bit more manageable. I’ll see if I that works as an alternative, though sending the byte array (however tedious to set up) is probably still ideal.

Hey there - sorry to say I’ve been off of this project for a good long time now so it’s all a bit fuzzy for me. I know WLED has moved through some versions over that time so the first place I’d look is to see if the specifiers for segment size and quantity have moved. Then I’d make sure that segments are still expecting the same length (36). I wonder if extra options have changed any of that stuff. I’ll dig around in my IDE and see if I can find anything more helpful.

anything UDP related is in udp.cpp

and make sure you use 0_15 branch or you’ll have headache down the road

I’ve got the HTTP setup working pretty nicely! Thanks again @blazoncek for the suggestion.

Just in case anybody else from the PLC world stumbles upon this, here’s the implementation I went with for building the string in Productivity.

@beatKeeper thanks for looking into it. I’d still love to get the byte array working, as addressing more than 10 segments would be useful for future applications. As far as I can tell, the info that you and gbd provided matches up with the latest udp.cpp file.