"""WLED-compatible UDP packet encoders — DRGB, DRGBW, DNRGB. Source: https://kno.wled.ge/interfaces/udp-realtime/ """ import struct WLED_PORT = 21324 PROTOCOL_DRGB = 2 PROTOCOL_DRGBW = 3 PROTOCOL_DNRGB = 4 TIMEOUT_BYTE = 2 # seconds before device returns to normal mode # Max pixels per packet (stays under 1472-byte UDP payload limit) # Standard Ethernet MTU = 1500; IP header = 20; UDP header = 8 → max payload = 1472 DRGB_MAX_PX = 490 # (1472 - 2) // 3 DRGBW_MAX_PX = 367 # (1472 - 2) // 4 DNRGB_MAX_PX = 489 # (1472 - 4) // 3 def encode_drgb(pixels: list[tuple[int, int, int]]) -> bytes: """Encode up to 490 RGB pixels into a DRGB UDP packet. Format: [0x02][timeout][R0][G0][B0]...[Rn][Gn][Bn] Truncates silently to DRGB_MAX_PX if more pixels are provided. """ header = bytes([PROTOCOL_DRGB, TIMEOUT_BYTE]) body = bytearray() for r, g, b in pixels[:DRGB_MAX_PX]: body.extend([r & 0xFF, g & 0xFF, b & 0xFF]) return header + bytes(body) def encode_drgbw(pixels: list[tuple[int, int, int, int]]) -> bytes: """Encode up to 367 RGBW pixels into a DRGBW UDP packet. Format: [0x03][timeout][R0][G0][B0][W0]...[Rn][Gn][Bn][Wn] Truncates silently to DRGBW_MAX_PX if more pixels are provided. """ header = bytes([PROTOCOL_DRGBW, TIMEOUT_BYTE]) body = bytearray() for r, g, b, w in pixels[:DRGBW_MAX_PX]: body.extend([r & 0xFF, g & 0xFF, b & 0xFF, w & 0xFF]) return header + bytes(body) def encode_dnrgb_packets( pixels: list[tuple[int, int, int]], start_index: int = 0, ) -> list[bytes]: """Split a large RGB pixel array into DNRGB UDP packets, each under MTU. Format per packet: [0x04][timeout][start_hi][start_lo][R0][G0][B0]...[Rn][Gn][Bn] Start index is 16-bit big-endian per WLED spec. Args: pixels: Full list of (R, G, B) pixel tuples. start_index: LED strip offset for the first pixel (default 0). Returns: List of byte strings, one per chunk of up to DNRGB_MAX_PX pixels. """ packets = [] for offset in range(0, len(pixels), DNRGB_MAX_PX): chunk = pixels[offset:offset + DNRGB_MAX_PX] idx = start_index + offset header = struct.pack(">BBH", PROTOCOL_DNRGB, TIMEOUT_BYTE, idx) body = bytearray() for r, g, b in chunk: body.extend([r & 0xFF, g & 0xFF, b & 0xFF]) packets.append(header + bytes(body)) return packets