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,
*,
wraps: bool = False,
)
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
Prefer device.animator, which every light owns. Constructing an
Animator directly gives a second writer for the same device, with its
own ack gate.
| 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:
|
wraps
|
True if the canvas is a ring whose last pixel sits next to its first (default: False)
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If |
| METHOD | DESCRIPTION |
|---|---|
prepare |
Query the device for its geometry, once, and get ready to draw. |
for_matrix |
Return the device's Animator, prepared for a MatrixLight. |
for_multizone |
Return the device's Animator, prepared for a MultiZoneLight. |
for_light |
Return the device's Animator 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¶
duration_ms
property
writable
¶
duration_ms: int
Transition duration, in milliseconds, of frames sent by send_frame().
When non-zero, the device smoothly interpolates between frames.
pixel_count
property
¶
pixel_count: int
Get total number of input pixels (canvas size for multi-tile).
Methods:¶
prepare
async
¶
Query the device for its geometry, once, and get ready to draw.
A light's Animator learns its tile layout or zone count from the device the first time it is prepared; later calls make no query. Every call also tells the device that frames are about to bypass its own colour methods, so a Ceiling or Mirror component call reads the device instead of undoing the animation. A single light's Animator is ready without this, but preparing it is harmless.
A light evidenced as Thread, by its own replies or an mDNS record, is
refused unless the caller passes enable_thread=True: a Thread mesh
is not built for a steady stream of frames. A light not yet heard
from is not refused.
| PARAMETER | DESCRIPTION |
|---|---|
enable_thread
|
Stream to a light evidenced as Thread anyway. Off by default.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Animator
|
This Animator, so |
Animator
|
reads naturally. |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If a matrix light reports no tiles, or a multizone light does not support the extended multizone protocol. |
LifxUnsupportedCommandError
|
If the light is evidenced as Thread
and |
Example
Source code in src/lifx/animation/animator.py
for_matrix
async
classmethod
¶
for_matrix(
device: MatrixLight, duration_ms: int = 0, *, enable_thread: bool = False
) -> Animator
Return the device's Animator, prepared for a MatrixLight.
Deprecated: use device.animator and await device.animator.prepare().
Every call returns the same Animator, the one the device owns, so a
library effect and this caller share one writer and one ack gate.
| PARAMETER | DESCRIPTION |
|---|---|
device
|
MatrixLight device (must be connected)
TYPE:
|
duration_ms
|
Transition duration in milliseconds (default 0 for
instant), applied to frames sent through
TYPE:
|
enable_thread
|
Stream to a light evidenced as Thread anyway;
see
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Animator
|
The device's Animator |
| RAISES | DESCRIPTION |
|---|---|
LifxUnsupportedCommandError
|
If the device is evidenced as Thread
and |
ValueError
|
If the device reports no tiles |
Source code in src/lifx/animation/animator.py
for_multizone
async
classmethod
¶
for_multizone(
device: MultiZoneLight, duration_ms: int = 0, *, enable_thread: bool = False
) -> Animator
Return the device's Animator, prepared for a MultiZoneLight.
Deprecated: use device.animator and await device.animator.prepare().
Every call returns the same Animator, the one the device owns. Only
devices with extended multizone capability are supported.
| PARAMETER | DESCRIPTION |
|---|---|
device
|
MultiZoneLight device (must be connected and support extended multizone protocol)
TYPE:
|
duration_ms
|
Transition duration in milliseconds (default 0 for
instant), applied to frames sent through
TYPE:
|
enable_thread
|
Stream to a light evidenced as Thread anyway;
see
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Animator
|
The device's Animator |
| RAISES | DESCRIPTION |
|---|---|
LifxUnsupportedCommandError
|
If the device is evidenced as Thread
and |
ValueError
|
If device doesn't support extended multizone |
Source code in src/lifx/animation/animator.py
for_light
classmethod
¶
Return the device's Animator for a single Light device.
Deprecated: use device.animator. Every call returns the same
Animator, the one the device owns. A single light's Animator needs no
device query, so this stays synchronous.
| PARAMETER | DESCRIPTION |
|---|---|
device
|
Light device (must have ip and serial set)
TYPE:
|
duration_ms
|
Transition duration in milliseconds (default 0 for
instant), applied to frames sent through
TYPE:
|
enable_thread
|
Stream to a light evidenced as Thread anyway;
see
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Animator
|
The device's Animator |
| RAISES | DESCRIPTION |
|---|---|
LifxUnsupportedCommandError
|
If the device is evidenced as Thread
and |
RuntimeError
|
If the device is a matrix or multizone light whose
Animator has not been prepared yet; await
|
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. |
LifxNetworkError
|
If the destination is invalid or the UDP socket cannot be created, or a frame datagram cannot be sent. |
RuntimeError
|
If the Animator has not been prepared yet. |
Source code in src/lifx/animation/animator.py
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 |
|---|---|
duration_ms |
Transition duration, in milliseconds, baked into new templates.
TYPE:
|
probe_template_index |
Template index that carries the ack-required flow-control probe.
TYPE:
|
Attributes¶
duration_ms
property
writable
¶
duration_ms: int
Transition duration, in milliseconds, baked into new templates.
Changing it affects only templates created afterwards; the Animator recreates its templates when a frame needs a different duration.
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, and a final batch of fewer rows covers any rows left over:
- Ceiling 16x8 (128 pixels; the "Ceiling 13x26" products, 201 and
202, report this tile): 2 Set64 packets of 64 colours each (rows
0-3, 4-7) + 1 CopyFrameBuffer = 3 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