Files
led2/lightsync/protocol/drgb.py
Claude 5e13da3967 feat(03-01): implement WLED protocol encoders and animation command packet
- 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
2026-04-06 21:40:52 +00:00

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