# Architecture Overview

lifx-async is built as a layered architecture with clear separation of concerns.

## System Architecture

```mermaid
graph TB
    subgraph "Layer 8: High-Level API"
        API[api.py<br/>discover, find_by_label, etc.]
        DeviceGroup[DeviceGroup<br/>Batch operations]
    end

    subgraph "Layer 7: Theme Layer"
        Theme[Theme<br/>Named color palettes]
        ThemeGen[Generators<br/>Theme-based colors]
    end

    subgraph "Layer 6: Effects Layer"
        Effects[30+ Effects<br/>aurora, flame, plasma, etc.]
        Registry[Registry<br/>Effect discovery]
    end

    subgraph "Layer 5: Animation Layer"
        Animator[Animator<br/>Direct UDP sending]
        FrameBuffer[FrameBuffer<br/>Multi-tile canvas]
    end

    subgraph "Layer 4: Device Layer"
        Device[Device<br/>Base class]
        Light[Light<br/>Color control]
        Hev[HevLight<br />HEV support]
        Infrared[InfraredLight<br />Infrared support]
        MultiZone[MultiZoneLight<br/>Linear/1D zones]
        Matrix[MatrixLight<br/>Matrix/2D zones]
        Ceiling[CeilingLight<br/>Up/downlight control]
    end

    subgraph "Layer 3: Network Layer"
        Discovery[Discovery<br/>UDP broadcast + mDNS]
        Connection[Connection<br/>Lazy opening]
        Transport[Transport<br/>UDP sockets]
    end

    subgraph "Layer 2: Protocol Layer"
        Generator[Generator<br/>YAML → Python]
        Types[Protocol Types<br/>Enums, HSBK, etc.]
        Packets[Packets<br/>Message classes]
    end

    subgraph "Layer 1: Utilities"
        Color[HSBK / Colors<br/>Color conversion]
        Products[Products Registry<br/>Device capabilities]
    end

    subgraph "External"
        YAML[protocol.yml<br/>LIFX specification]
        Network[UDP Network<br/>Port 56700]
    end

    API --> DeviceGroup
    DeviceGroup --> Light
    DeviceGroup --> Matrix
    DeviceGroup --> MultiZone
    API --> Discovery
    Theme --> ThemeGen
    ThemeGen --> Color
    Effects --> Animator
    Effects --> Theme
    Animator --> FrameBuffer
    Animator --> Transport
    Device --> Connection
    Light --> Device
    Hev --> Light
    Infrared --> Light
    MultiZone --> Light
    Matrix --> Light
    Ceiling --> Matrix
    Connection --> Transport
    Connection --> Packets
    Discovery --> Transport
    Packets --> Types
    Transport --> Network
    Generator --> YAML
    Generator -.generates.-> Types
    Generator -.generates.-> Packets

    style API fill:#e1f5e1
    style Device fill:#e1f0ff
    style Connection fill:#fff4e1
    style Generator fill:#ffe1f0
    style Animator fill:#f0e1ff
    style Effects fill:#ffe1e1
    style Theme fill:#e1fff0
```

## Layer Responsibilities

### Layer 1: Utilities

**Purpose**: Provide shared colour and product-capability helpers used by every layer above

- **User-Facing Colour**: `HSBK` class with float values and RGB conversion, plus `Colors` presets
- **Product Registry**: Auto-generated database mapping product IDs to capabilities
- **Zero Runtime Dependencies**: Pure standard-library code, like the rest of the library

**Key Files**:

- `color.py` - HSBK class and Colors presets
- `products/registry.py` - Auto-generated product capability database

### Layer 2: Protocol Layer

**Purpose**: Handle LIFX binary protocol

- **Auto-Generated**: All code generated from `protocol.yml`
- **Type-Safe**: Full type hints for all structures
- **Binary Serialisation**: Pack/unpack protocol messages
- **No Business Logic**: Pure data structures

**Key Files**:

- `protocol_types.py` - Enums, HSBK, field structures
- `packets.py` - Packet class definitions
- `generator.py` - Code generation from YAML

**Example**:

```python
from lifx.protocol.packets import Light
from lifx.protocol.protocol_types import LightHsbk

# Create a packet
packet = Light.SetColor(
    color=LightHsbk(hue=32768, saturation=65535, brightness=52428, kelvin=3500),
    duration=1000,  # milliseconds (uint32)
)

# Serialise to bytes
data = packet.pack()
```

### Layer 3: Network Layer

**Purpose**: Handle network communication

- **UDP Transport**: Async socket operations
- **Discovery**: Broadcast-based device discovery
- **Lazy Connections**: Auto-open on first request
- **Retry Logic**: Automatic retransmits on an escalating schedule within each request's timeout

**Key Files**:

- `transport.py` - UDP socket wrapper
- `discovery.py` - Device discovery
- `connection.py` - Connection management
- `message.py` - Message building

**Example**:

```python
from lifx.network.connection import DeviceConnection

conn = DeviceConnection(serial="d073d5123456", ip="192.168.1.100")
# Connection opens lazily on first request
response = await conn.request(packet)
```

### Layer 4: Device Layer

**Purpose**: Device abstractions with high-level operations

- **Device Types**: Base, Light, HevLight, InfraredLight, MultiZoneLight, MatrixLight, CeilingLight
- **State Caching**: Cached state properties for efficient access
- **Type Detection**: Automatic capability detection
- **Async Context Managers**: Automatic resource cleanup

**Key Files**:

- `base.py` - Base Device class
- `light.py` - Light class
- `hev.py` - HevLight class
- `infrared.py` - InfraredLight class
- `multizone.py` - MultiZoneLight class
- `matrix.py` - MatrixLight class
- `ceiling.py` - CeilingLight class

**Example**:

```python
from lifx import Light

async with Light(serial, ip) as light:
    # High-level operations
    await light.set_color(Colors.BLUE)
    await light.pulse(Colors.RED, period=1.0, cycles=5)
```

### Layer 5: Animation Layer

**Purpose**: High-frequency frame delivery for real-time effects (up to ~20 FPS over WiFi)

- **Direct UDP**: Bypasses request/response for low-latency frame delivery
- **Ack-Gated Pacing**: Frame delivery is paced against device acknowledgements with latest-frame-wins semantics — internal behaviour, no consumer configuration required
- **Multi-Tile Canvas**: Maps tiles using `user_x`/`user_y` positions
- **Orientation Correction**: LRU-cached tile rotation lookup tables
- **Protocol-Ready Values**: Uses uint16 HSBK directly (no conversion overhead)

**Key Files**:

- `animation/animator.py` - High-level `Animator` class
- `animation/flow.py` - Ack-gated flow control
- `animation/framebuffer.py` - Multi-tile canvas mapping
- `animation/packets.py` - Prebaked packet templates
- `animation/orientation.py` - Tile orientation remapping

### Layer 6: Effects Layer

**Purpose**: 30+ built-in visual effects

- **Effect Library**: aurora, flame, plasma, rainbow, twinkle, etc.
- **Registry**: Discover effects by name
- **State Management**: Run effects on devices
- **Frame Generation**: Effects produce HSBK frames consumed by the Animation Layer

**Key Files**:

- `effects/base.py` - Base effect class
- `effects/registry.py` - Effect discovery
- `effects/state_manager.py` - Effect lifecycle

### Layer 7: Theme Layer

**Purpose**: Named color palettes for effects

- **Theme Definitions**: Named color palettes
- **Theme Library**: Built-in themes
- **Color Generators**: Theme-based color generation for effects
- **Canvas Abstraction**: Apply themes to device layouts

**Key Files**:

- `theme/theme.py` - Theme definitions
- `theme/library.py` - Built-in themes
- `theme/generators.py` - Theme-based color generators

### Layer 8: High-Level API

**Purpose**: Simple, batteries-included API

- **Simplified Discovery**: One-line device discovery via UDP or mDNS
- **Batch Operations**: Control multiple devices with `DeviceGroup`
- **Direct Connection**: Connect by serial and IP with no discovery, or by IP alone via a one-off unicast discovery round-trip
- **Filtered Discovery**: Find devices by label, serial, or IP

**Key Files**:

- `api.py` - High-level functions
- `color.py` - Color utilities

**Example**:

```python
from lifx import discover, DeviceGroup, Colors

devices = []
async for device in discover():
    devices.append(device)
group = DeviceGroup(devices)

await group.set_color(Colors.BLUE)
```

## Data Flow

### Sending a Command

```mermaid
sequenceDiagram
    participant User
    participant Light
    participant Connection
    participant Transport
    participant Device

    User->>Light: set_color(Colors.BLUE)
    Light->>Light: Convert to HSBK
    Light->>Connection: send_packet(SetColor)
    Connection->>Connection: Serialise packet
    Connection->>Transport: send_message(bytes)
    Transport->>Device: UDP packet
    Device-->>Transport: UDP acknowledgement
    Transport-->>Connection: Response
    Connection-->>Connection: Deserialise packet
    Connection-->>Light: Reply
    Light-->>User: Success
```

### Discovery Process

```mermaid
sequenceDiagram
    participant User
    participant Discovery
    participant Transport
    participant Network
    participant Devices

    User->>Discovery: discover_devices(timeout=3.0)
    Discovery->>Transport: Open UDP socket
    Discovery->>Transport: Send broadcast (GetService)
    Transport->>Network: Broadcast on 255.255.255.255
    Network->>Devices: Broadcast packet
    Devices-->>Network: StateService responses
    Network-->>Transport: Multiple responses
    Transport-->>Discovery: Parse responses
    Discovery->>Discovery: Collect device info
    Discovery-->>User: List[DiscoveredDevice]
```

## Key Design Decisions

### Async-First

**Why**: LIFX operations involve network I/O which benefits from async

```python
# Multiple devices controlled concurrently
await asyncio.gather(
    light1.set_color(Colors.RED),
    light2.set_color(Colors.BLUE),
    light3.set_color(Colors.GREEN),
)
```

### Lazy Connections

**Why**: Simple lifecycle management with automatic cleanup

```python
# Connection opens lazily on first request
async with await Device.connect("192.168.1.100") as light:
    await light.set_color(Colors.RED)  # Opens connection here
    await light.set_brightness(0.5)  # Reuses same connection
    await light.get_label()  # Reuses same connection
# Connection automatically closed on exit
```

### State Caching

**Why**: Reduces network traffic and provides fast access to semi-static device state

```python
# Semi-static properties return cached values:
label = light.label
if label:
    # Use cached label
    print(f"Label: {label}")
else:
    # No cached data yet, fetch from device
    label = await light.get_label()
    print(f"Fetched label: {label}")

# Volatile state (power, color) always requires fresh fetch
# get_color() will cache the label value only
color, power, label = await light.get_color()
print(f"Current state of {light.label} - Power: {power}, Color: {color}")
```

### Code Generation

**Why**: Protocol updates are automatic, reduces errors

```bash
# Regenerate code
uv run python -m lifx.protocol.generator
```

## Performance Characteristics

### Connection Lifecycle

- **Lazy Opening**: Opens on first request, not on creation
- **Explicit Cleanup**: Closes via `close()` or context manager exit
- **Low Overhead**: One UDP socket per device

### State Caching as Properties

- **Format**: Cached values or `None` if not yet fetched
- **Properties**: All device state available as properties
- **Getting Fresh Data**: Use `get_*()` methods to always fetch from device

## Concurrency Model

Each connection uses a background receiver task that routes responses to the
correct pending request:

```python
conn = DeviceConnection(serial="d073d5123456", ip="192.168.1.100")
# Multiple concurrent requests are supported
result1 = await conn.request(packet1)
result2 = await conn.request(packet2)
await conn.close()
```

**How it works**:

- A background receiver task continuously reads from the UDP socket
- Each request registers a per-request `asyncio.Queue` keyed by `(source, sequence, serial)`
- The receiver routes incoming packets to the matching queue
- Sequence numbers (0-255, uint8) are atomically allocated for response correlation
- Different devices have different connections, so multi-device operations run in parallel
- Single UDP socket per connection

## Next Steps

- [API Reference](../api/index.md) - Detailed API documentation
- [FAQ](../faq.md) - Frequently asked questions
