Animation API Reference¶
Looking for usage examples? See the Animation Guide for tutorials, multi-tile canvas usage, HSBK format details, and troubleshooting. This page covers the API surface only.
The animation module provides efficient high-frequency frame delivery for LIFX devices, optimised for real-time effects at up to ~20 FPS.
Animator¶
High-level class integrating all animation components.
Animator
¶
Animator(
ip: str,
serial: Serial,
framebuffer: FrameBuffer,
packet_generator: PacketGenerator,
port: int = LIFX_UDP_PORT,
)
High-level animator for LIFX devices.
Sends animation frames directly via UDP for maximum throughput. No
connection layer overhead -- frames are paced internally by ack-gated
flow control rather than fired blind: delivery is paced against device
acknowledgements, and when a device falls behind, new frames are
dropped, never queued (latest-frame-wins). If a device stops
acknowledging entirely, throughput degrades to a slow floor rather
than stalling forever. This is entirely internal behaviour -- there is
no flow-control toggle; consumers keep calling send_frame() exactly
as before.
All packets are prebaked at initialization time. Per-frame, only
color data, the sequence number, and (for the probe template) the
AckGate's tracked-probe bookkeeping are updated -- the hot path
remains a non-blocking recvfrom_into sweep on the animator's own
socket plus one dict write, so send_frame() stays synchronous with no
event-loop coupling.
| ATTRIBUTE | DESCRIPTION |
|---|---|
pixel_count |
Total number of pixels/zones
TYPE:
|
Example
Use the for_matrix() or for_multizone() class methods for
automatic configuration from a device.
| PARAMETER | DESCRIPTION |
|---|---|
ip
|
Device IP address
TYPE:
|
serial
|
Device serial number
TYPE:
|
framebuffer
|
Configured FrameBuffer for orientation mapping
TYPE:
|
packet_generator
|
Configured PacketGenerator for the device
TYPE:
|
port
|
UDP port (default: 56700)
TYPE:
|
| METHOD | DESCRIPTION |
|---|---|
for_matrix |
Create an Animator configured for a MatrixLight device. |
for_multizone |
Create an Animator configured for a MultiZoneLight device. |
for_light |
Create an Animator configured for a single Light device. |
send_frame |
Send a frame to the device via direct UDP. |
close |
Close the UDP socket. |
Source code in src/lifx/animation/animator.py
Attributes¶
pixel_count
property
¶
pixel_count: int
Get total number of input pixels (canvas size for multi-tile).
Methods:¶
for_matrix
async
classmethod
¶
for_matrix(device: MatrixLight, duration_ms: int = 0) -> Animator
Create an Animator configured for a MatrixLight device.
Queries the device for tile information, then returns an animator that sends frames via direct UDP (no device connection needed after creation).
| PARAMETER | DESCRIPTION |
|---|---|
device
|
MatrixLight device (must be connected)
TYPE:
|
duration_ms
|
Transition duration in milliseconds (default 0 for instant). When non-zero, device smoothly interpolates between frames.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Animator
|
Configured Animator instance |
Example
Source code in src/lifx/animation/animator.py
for_multizone
async
classmethod
¶
for_multizone(device: MultiZoneLight, duration_ms: int = 0) -> Animator
Create an Animator configured for a MultiZoneLight device.
Only devices with extended multizone capability are supported. Queries the device for zone count, then returns an animator that sends frames via direct UDP.
| PARAMETER | DESCRIPTION |
|---|---|
device
|
MultiZoneLight device (must be connected and support extended multizone protocol)
TYPE:
|
duration_ms
|
Transition duration in milliseconds (default 0 for instant). When non-zero, device smoothly interpolates between frames.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Animator
|
Configured Animator instance |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If device doesn't support extended multizone |
Example
Source code in src/lifx/animation/animator.py
for_light
classmethod
¶
Create an Animator configured for a single Light device.
Unlike the matrix/multizone factories, this does not need to be async because single lights don't require any device queries for configuration.
| PARAMETER | DESCRIPTION |
|---|---|
device
|
Light device (must have ip and serial set)
TYPE:
|
duration_ms
|
Transition duration in milliseconds (default 0 for instant). When non-zero, device smoothly interpolates between frames.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Animator
|
Configured Animator instance |
Example
Source code in src/lifx/animation/animator.py
send_frame
¶
Send a frame to the device via direct UDP.
Applies orientation mapping (for matrix devices), updates colors in prebaked packets, and sends them directly via UDP -- unless ack-gated flow control drops the frame first. Each call first sweeps the animator's own socket for arrived acks; if the device is still behind on acknowledgements after the sweep, the frame is dropped entirely (no framebuffer work, no packets sent, no sequence numbers consumed) and the caller should simply send its next frame rather than retry or queue this one (latest-frame-wins).
This is a synchronous method for minimum overhead. UDP sendto()
is non-blocking for datagrams, and the ack sweep is a non-blocking
recvfrom_into loop on the same socket -- no event loop is
required.
| PARAMETER | DESCRIPTION |
|---|---|
hsbk
|
Protocol-ready HSBK data for all pixels. Each tuple is (hue, sat, brightness, kelvin) where H/S/B are 0-65535 and K is 1500-9000. |
| RETURNS | DESCRIPTION |
|---|---|
AnimatorStats
|
AnimatorStats with operation statistics. |
AnimatorStats
|
frame was dropped by flow control. |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If hsbk length doesn't match pixel_count. This validation always runs, even when the gate is saturated -- a full gate must never suppress input validation. |
Source code in src/lifx/animation/animator.py
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 | |
close
¶
Close the UDP socket.
Call this when done with the animator to free resources. Also resets the ack gate, so a fresh animator session (new socket, new gate) starts ungated.
Source code in src/lifx/animation/animator.py
AnimatorStats¶
Statistics returned by Animator.send_frame().
AnimatorStats
dataclass
¶
AnimatorStats(
packets_sent: int,
total_time_ms: float,
gated: bool = False,
acks_outstanding: int = 0,
)
Statistics about a frame send operation.
| ATTRIBUTE | DESCRIPTION |
|---|---|
packets_sent |
Number of packets sent
TYPE:
|
total_time_ms |
Total time for the operation in milliseconds
TYPE:
|
gated |
Whether this frame was dropped by ack-gated flow control. A gated frame sends nothing, consumes no sequence numbers, and skips framebuffer work entirely -- latest-frame-wins means the caller should simply send its next frame rather than queue or retry this one.
TYPE:
|
acks_outstanding |
The number of probe acks outstanding immediately after this call (for a gated frame, the count that caused the gate; for a sent frame, the count including this frame's own probe). Purely for observability -- consumers cannot configure flow control.
TYPE:
|
FrameBuffer¶
Canvas mapping and orientation handling for matrix devices.
FrameBuffer
¶
FrameBuffer(
pixel_count: int,
canvas_width: int = 0,
canvas_height: int = 0,
tile_regions: list[TileRegion] | None = None,
)
Orientation mapping for matrix device animations.
For matrix devices with tile orientation (like the original LIFX Tile), this class remaps pixel coordinates from user-space (logical layout) to device-space (physical tile order accounting for rotation).
For multi-tile devices, the FrameBuffer creates a unified canvas where each tile's position (user_x, user_y) determines which region of the canvas it displays. This allows animations to span across all tiles instead of being mirrored.
For multizone devices and matrix devices without orientation, this is essentially a passthrough.
| ATTRIBUTE | DESCRIPTION |
|---|---|
pixel_count |
Total number of device pixels
TYPE:
|
canvas_width |
Width of the logical canvas in pixels
TYPE:
|
canvas_height |
Height of the logical canvas in pixels
TYPE:
|
tile_regions |
List of tile regions with positions and orientations
TYPE:
|
Example
| PARAMETER | DESCRIPTION |
|---|---|
pixel_count
|
Total number of device pixels
TYPE:
|
canvas_width
|
Width of the logical canvas (0 = same as pixel_count)
TYPE:
|
canvas_height
|
Height of the logical canvas (0 = 1 for linear)
TYPE:
|
tile_regions
|
List of tile regions with positions and orientations. If provided, input is interpreted as a 2D canvas.
TYPE:
|
| METHOD | DESCRIPTION |
|---|---|
for_matrix |
Create a FrameBuffer configured for a MatrixLight device. |
for_multizone |
Create a FrameBuffer configured for a MultiZoneLight device. |
for_light |
Create a FrameBuffer configured for a single Light device. |
apply |
Apply orientation mapping to frame data. |
Source code in src/lifx/animation/framebuffer.py
Attributes¶
Methods:¶
for_matrix
async
classmethod
¶
for_matrix(device: MatrixLight) -> FrameBuffer
Create a FrameBuffer configured for a MatrixLight device.
Automatically determines pixel count from device chain and creates appropriate mapping for tile orientations and positions.
For multi-tile devices (has_chain capability), creates a unified canvas based on tile positions (user_x, user_y). Each tile's position determines which region of the canvas it displays, allowing animations to span across all tiles.
| PARAMETER | DESCRIPTION |
|---|---|
device
|
MatrixLight device (must be connected)
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
FrameBuffer
|
Configured FrameBuffer instance |
Example
Source code in src/lifx/animation/framebuffer.py
for_multizone
async
classmethod
¶
for_multizone(device: MultiZoneLight) -> FrameBuffer
Create a FrameBuffer configured for a MultiZoneLight device.
Automatically determines pixel count from zone count. Multizone devices don't need permutation (zones are linear).
| PARAMETER | DESCRIPTION |
|---|---|
device
|
MultiZoneLight device (must be connected)
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
FrameBuffer
|
Configured FrameBuffer instance |
Example
Source code in src/lifx/animation/framebuffer.py
for_light
classmethod
¶
for_light(_device: Light) -> FrameBuffer
Create a FrameBuffer configured for a single Light device.
Single lights have exactly 1 pixel, so this is a trivial passthrough.
| PARAMETER | DESCRIPTION |
|---|---|
_device
|
Light device (unused — single lights always have 1 pixel)
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
FrameBuffer
|
Configured FrameBuffer instance |
Source code in src/lifx/animation/framebuffer.py
apply
¶
Apply orientation mapping to frame data.
For multi-tile devices, the input is interpreted as a row-major 2D canvas of size (canvas_width x canvas_height). Each tile extracts its region from the canvas based on its position.
For single-tile or multizone devices, this is a passthrough.
| PARAMETER | DESCRIPTION |
|---|---|
hsbk
|
List of protocol-ready HSBK tuples. - For multi-tile: length must match canvas_size - For single-tile/multizone: length must match pixel_count Each tuple is (hue, sat, brightness, kelvin) where H/S/B are 0-65535 and K is 1500-9000. |
| RETURNS | DESCRIPTION |
|---|---|
list[tuple[int, int, int, int]]
|
Remapped HSBK data in device order |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If hsbk length doesn't match expected size |
Source code in src/lifx/animation/framebuffer.py
TileRegion¶
Represents a tile's region within the canvas.
TileRegion
dataclass
¶
TileRegion(
x: int,
y: int,
width: int,
height: int,
orientation_lut: tuple[int, ...] | None = None,
)
Packet Generators¶
Device-specific packet generation with prebaked templates.
PacketGenerator (Base)¶
PacketGenerator
¶
Bases: ABC
Abstract base class for packet generators.
Packet generators prebake complete packets (header + payload) at initialization time. Per-frame, only color data and sequence numbers are updated in place.
| METHOD | DESCRIPTION |
|---|---|
create_templates |
Create prebaked packet templates. |
update_colors |
Update color data in prebaked templates. |
pixel_count |
Get the total pixel count this generator expects. |
| ATTRIBUTE | DESCRIPTION |
|---|---|
probe_template_index |
Template index that carries the ack-required flow-control probe.
TYPE:
|
Attributes¶
probe_template_index
property
¶
probe_template_index: int
Template index that carries the ack-required flow-control probe.
The Animator bakes the ack_required flag into exactly one
prebaked template at initialisation time -- this property names
which one. Default is the first packet of the frame. Flow control
applies uniformly to all generator families with no per-family
carve-outs; MatrixPacketGenerator overrides this for large-tile
mode.
Methods:¶
create_templates
abstractmethod
¶
create_templates(source: int, target: bytes) -> list[PacketTemplate]
Create prebaked packet templates.
| PARAMETER | DESCRIPTION |
|---|---|
source
|
Client source ID for header
TYPE:
|
target
|
6-byte device serial for header
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
list[PacketTemplate]
|
List of PacketTemplate with prebaked packets |
Source code in src/lifx/animation/packets.py
update_colors
abstractmethod
¶
Update color data in prebaked templates.
| PARAMETER | DESCRIPTION |
|---|---|
templates
|
Prebaked packet templates
TYPE:
|
hsbk
|
Protocol-ready HSBK data for all pixels |
Source code in src/lifx/animation/packets.py
PacketTemplate¶
Prebaked packet template for zero-allocation frame updates.
PacketTemplate
dataclass
¶
Prebaked packet template for zero-allocation animation.
Contains a complete packet (header + payload) as a mutable bytearray. Only the sequence byte and color data need to be updated per frame.
| ATTRIBUTE | DESCRIPTION |
|---|---|
data |
Complete packet bytes (header + payload)
TYPE:
|
color_offset |
Byte offset where color data starts
TYPE:
|
color_count |
Number of HSBK colors in this packet
TYPE:
|
hsbk_start |
Starting index in the input HSBK array
TYPE:
|
fmt |
Pre-computed struct format string for bulk color packing
TYPE:
|
MatrixPacketGenerator¶
Generates Set64 packets for MatrixLight devices.
MatrixPacketGenerator
¶
Bases: PacketGenerator
Packet generator for MatrixLight devices.
Generates Set64 packets for all tiles. Uses prebaked packet templates with complete headers for maximum performance.
For standard tiles (≤64 pixels):
- Single Set64 packet directly to display buffer (fb_index=0)
For large tiles (>64 pixels), colours are chunked row-aligned — each Set64 packet covers whole rows of the tile (rows_per_packet = 64 // tile_width), matching the device's row-major Set64 fill order from (x=0, y=y_offset). The colour slice offset (hsbk_start = y_offset * tile_width) therefore always matches the rect's y offset, even on widths that do not evenly divide 64:
- Ceiling 16x8 (128 pixels, divides evenly): 2 Set64 packets of 64
colours each (rows 0-3, 4-7) + 1 CopyFrameBuffer = 3 packets/tile.
- Ceiling 13x26 (338 pixels, does not divide evenly): 7 Set64
packets of 52 colours each for the first 6 (4 rows x 13 width)
plus a final partial batch of 26 colours (2 rows x 13 width) +
1 CopyFrameBuffer = 8 packets/tile.
- Multiple Set64 packets to temp buffer (fb_index=1)
- CopyFrameBuffer packet to copy fb_index=1 → fb_index=0
Set64 Payload Layout (522 bytes):
- Offset 0: tile_index (uint8)
- Offset 1: length (uint8, always 1)
- Offset 2-5: TileBufferRect (fb_index, x, y, width - 4 x uint8)
- Offset 6-9: duration (uint32)
- Offset 10-521: colors (64 x HSBK, each 8 bytes)
CopyFrameBuffer Payload Layout (15 bytes):
- Offset 0: tile_index (uint8)
- Offset 1: length (uint8, always 1)
- Offset 2: src_fb_index (uint8, 1 = temp buffer)
- Offset 3: dst_fb_index (uint8, 0 = display)
- Offset 4-7: src_x, src_y, dst_x, dst_y (uint8 each)
- Offset 8-9: width, height (uint8 each)
- Offset 10-13: duration (uint32)
- Offset 14: reserved (uint8)
| PARAMETER | DESCRIPTION |
|---|---|
tile_count
|
Number of tiles in the device chain
TYPE:
|
tile_width
|
Width of each tile in pixels
TYPE:
|
tile_height
|
Height of each tile in pixels
TYPE:
|
duration_ms
|
Transition duration in milliseconds (default 0 for instant)
TYPE:
|
| METHOD | DESCRIPTION |
|---|---|
pixel_count |
Get total pixel count. |
create_templates |
Create prebaked packet templates for all tiles. |
update_colors |
Update color data in prebaked templates. |
| ATTRIBUTE | DESCRIPTION |
|---|---|
is_large_tile |
Check if tiles have >64 pixels (requires multi-packet strategy).
TYPE:
|
packets_per_tile |
Get number of Set64 packets needed per tile.
TYPE:
|
probe_template_index |
Template index that carries the ack-required flow-control probe.
TYPE:
|
Source code in src/lifx/animation/packets.py
Attributes¶
is_large_tile
property
¶
is_large_tile: bool
Check if tiles have >64 pixels (requires multi-packet strategy).
probe_template_index
property
¶
probe_template_index: int
Template index that carries the ack-required flow-control probe.
In large-tile mode the probe attaches to the FINAL CopyFrameBuffer -- the frame-commit packet, since nothing is visible until the buffer swap. The CopyFB's ack RTT includes the device's drain of the preceding Set64 burst, a strictly better congestion signal than acking the first Set64 of a multi-packet frame. This property is the one-line fallback seam to index 0 (first Set64) should hardware disagree.
For standard (≤64px) tiles, the probe sits on the single Set64 packet.
Methods:¶
create_templates
¶
create_templates(source: int, target: bytes) -> list[PacketTemplate]
Create prebaked packet templates for all tiles.
| PARAMETER | DESCRIPTION |
|---|---|
source
|
Client source ID
TYPE:
|
target
|
6-byte device serial
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
list[PacketTemplate]
|
List of PacketTemplate with complete prebaked packets |
Source code in src/lifx/animation/packets.py
update_colors
¶
Update color data in prebaked templates.
| PARAMETER | DESCRIPTION |
|---|---|
templates
|
Prebaked packet templates
TYPE:
|
hsbk
|
Protocol-ready HSBK data for all pixels |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If hsbk has fewer values than the total pixel count |
Source code in src/lifx/animation/packets.py
MultiZonePacketGenerator¶
Generates SetExtendedColorZones packets for MultiZoneLight devices.
MultiZonePacketGenerator
¶
Bases: PacketGenerator
Packet generator for MultiZoneLight devices with extended multizone.
Uses SetExtendedColorZones packets (up to 82 zones each). For devices with >82 zones, multiple packets are generated.
SetExtendedColorZones Payload Layout (664 bytes):
- Offset 0-3: duration (uint32)
- Offset 4: apply (uint8, 1 = APPLY)
- Offset 5-6: zone_index (uint16)
- Offset 7: colors_count (uint8)
- Offset 8-663: colors (82 x HSBK, each 8 bytes)
| PARAMETER | DESCRIPTION |
|---|---|
zone_count
|
Total number of zones on the device
TYPE:
|
duration_ms
|
Transition duration in milliseconds (default 0 for instant)
TYPE:
|
| METHOD | DESCRIPTION |
|---|---|
pixel_count |
Get total zone count. |
create_templates |
Create prebaked packet templates for all zones. |
update_colors |
Update color data in prebaked templates. |
Source code in src/lifx/animation/packets.py
Methods:¶
create_templates
¶
create_templates(source: int, target: bytes) -> list[PacketTemplate]
Create prebaked packet templates for all zones.
| PARAMETER | DESCRIPTION |
|---|---|
source
|
Client source ID
TYPE:
|
target
|
6-byte device serial
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
list[PacketTemplate]
|
List of PacketTemplate with complete prebaked packets |
Source code in src/lifx/animation/packets.py
update_colors
¶
Update color data in prebaked templates.
| PARAMETER | DESCRIPTION |
|---|---|
templates
|
Prebaked packet templates
TYPE:
|
hsbk
|
Protocol-ready HSBK data for all zones |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If hsbk has fewer values than the total zone count |
Source code in src/lifx/animation/packets.py
Tile Orientation¶
Pixel remapping for rotated tiles.
Orientation Enum¶
Orientation
¶
Bases: IntEnum
Tile orientation based on accelerometer data.
These values match the orientation detection in TileInfo.nearest_orientation but use integer enum for efficient comparison and caching.
Physical mounting positions:
- RIGHT_SIDE_UP: Normal position, no rotation needed
- ROTATED_90: Rotated 90 degrees clockwise (RotatedRight)
- ROTATED_180: Upside down (UpsideDown)
- ROTATED_270: Rotated 90 degrees counter-clockwise (RotatedLeft)
- FACE_UP: Tile facing ceiling
- FACE_DOWN: Tile facing floor
| METHOD | DESCRIPTION |
|---|---|
from_string |
Convert TileInfo.nearest_orientation string to Orientation enum. |
Methods:¶
from_string
classmethod
¶
from_string(orientation_str: str) -> Orientation
Convert TileInfo.nearest_orientation string to Orientation enum.
| PARAMETER | DESCRIPTION |
|---|---|
orientation_str
|
String from TileInfo.nearest_orientation
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Orientation
|
Corresponding Orientation enum value |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If orientation string is not recognized |
Source code in src/lifx/animation/orientation.py
build_orientation_lut¶
build_orientation_lut
cached
¶
build_orientation_lut(
width: int, height: int, orientation: Orientation
) -> tuple[int, ...]
Build a lookup table for remapping pixels based on tile orientation.
The LUT maps physical tile positions to row-major framebuffer indices. For a pixel at physical position i, lut[i] gives the framebuffer index.
This is LRU-cached because tiles typically have standard dimensions (8x8) and there are only 6 orientations, so the cache will be highly effective.
| PARAMETER | DESCRIPTION |
|---|---|
width
|
Tile width in pixels
TYPE:
|
height
|
Tile height in pixels
TYPE:
|
orientation
|
Tile orientation
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
int
|
Tuple of indices mapping physical position to framebuffer position. |
...
|
Tuple is used instead of list for hashability in caches. |
Example
lut = build_orientation_lut(8, 8, Orientation.RIGHT_SIDE_UP) len(lut) 64 lut[0] # First pixel maps to index 0 0 lut = build_orientation_lut(8, 8, Orientation.ROTATED_180) lut[0] # First physical position maps to last framebuffer index 63
Source code in src/lifx/animation/orientation.py
Performance Characteristics¶
Direct UDP Delivery¶
The animation module uses a purpose-built network stack with the following characteristics:
- Frame packets are sent via a raw UDP socket;
send_frame()never blocks - Delivery is paced against device acknowledgements internally — when a device falls behind, new frames are dropped, never queued (latest-frame-wins)
- A dropped frame is never retried; occasional frame loss under saturation is expected (visual artefacts are brief)
Prebaked Packet Templates¶
Packets are constructed once at initialisation:
- Header and payload structure prebaked as
bytearray - Per-frame: only color data and sequence number updated
- Zero object allocation in the hot path
- Sequence number wraps at 256 (uint8)
Typical Performance¶
| Device Type | Pixels | Packets/Frame | Send Time |
|---|---|---|---|
| Single tile (8x8) | 64 | 1 | <0.5ms |
| 5-tile chain | 320 | 5 | <1ms |
| Large Ceiling (16x8) | 128 | 3 | <1ms |
| MultiZone (82 zones) | 82 | 1 | <0.5ms |
See Also¶
- Animation Guide — Usage guide with examples, multi-tile canvas, and troubleshooting