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.