- encode_drgb/encode_drgbw: 2-byte header + RGB/RGBW body, truncate at MTU max - encode_dnrgb_packets: splits large frames into 489-LED chunks with big-endian start index - encode_animation_cmd: 16-byte 0xAC packet with speed (float32), color, flags, density - decode_animation_cmd: full inverse round-trip for all 7 animation types - All 27 tests pass
72 lines
2.4 KiB
Python
72 lines
2.4 KiB
Python
"""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
|