Skip to main content

No project description provided

Project description

libpixelair

A Python async client library for controlling PixelAir LED devices (Fluora, Monos, etc.) on the local network.

Features

  • Async/await API - Built on asyncio for non-blocking operation
  • Device Discovery - Find devices via UDP broadcast
  • Bulletproof Identification - MAC + serial number for reliable device tracking
  • Full Control - Power, brightness, hue, saturation, and effect selection
  • Home Assistant Ready - Designed for integration with Home Assistant
  • FlatBuffer Protocol - Efficient binary state serialization
  • Fragmented Packets - Automatic reassembly of large state payloads

Installation

pip install libpixelair

Or with Poetry:

poetry add libpixelair

Dependencies

  • Python 3.12+
  • python-osc - OSC message encoding
  • flatbuffers - State serialization
  • netifaces - Network interface discovery

Quick Start

Discover Devices

import asyncio
from libpixelair import UDPListener, DiscoveryService

async def main():
    async with UDPListener() as listener:
        discovery = DiscoveryService(listener)

        # Discover all devices with full info (model, MAC, firmware)
        devices = await discovery.discover_with_info(timeout=5.0)

        for device in devices:
            print(f"Found: {device.display_name}")
            print(f"  Serial: {device.serial_number}")
            print(f"  MAC: {device.mac_address}")
            print(f"  IP: {device.ip_address}")
            print(f"  Model: {device.model}")
            print(f"  Firmware: {device.firmware_version}")

asyncio.run(main())

Control a Device

import asyncio
from libpixelair import UDPListener, PixelAirDevice

async def main():
    async with UDPListener() as listener:
        # Connect using MAC + serial (recommended for persistent identification)
        device = await PixelAirDevice.from_identifiers(
            mac_address="aa:bb:cc:dd:ee:ff",
            serial_number="abc123",
            listener=listener,
        )

        if device:
            async with device:
                # Get current state
                state = await device.get_state()
                print(f"Power: {'ON' if state.is_on else 'OFF'}")
                print(f"Brightness: {state.brightness * 100:.0f}%")
                print(f"Effect: {state.current_effect}")

                # Control the device
                await device.turn_on()
                await device.set_brightness(0.75)
                await device.set_hue(0.5)
                await device.set_saturation(0.8)

                # Set effect by ID
                await device.set_effect("auto")  # Auto mode
                await device.set_effect("scene:0")  # First scene
                await device.set_effect("manual:5")  # Manual animation at index 5

asyncio.run(main())

API Reference

Core Components

UDPListener

Shared UDP listener for receiving device packets on port 12345.

async with UDPListener() as listener:
    # listener.interfaces - list of network interfaces
    # listener.is_running - True if active
    pass

DiscoveryService

Device discovery via UDP broadcast.

discovery = DiscoveryService(listener)

# One-shot discovery
devices = await discovery.discover(timeout=5.0)

# Discovery with full device info (model, MAC, firmware)
devices = await discovery.discover_with_info(timeout=5.0)

# Find device by MAC address
device = await discovery.find_device_by_mac("aa:bb:cc:dd:ee:ff")

# Find device by serial number
device = await discovery.find_device_by_serial("abc123")

# Continuous discovery
async def on_found(device):
    print(f"Found: {device.serial_number}")

await discovery.start_continuous(on_found, interval=30.0)
await discovery.stop_continuous()

PixelAirDevice

Individual device representation and control.

# Create from discovery result
device = PixelAirDevice.from_discovered(discovered_device, listener)

# Create from stored identifiers (Home Assistant pattern)
device = await PixelAirDevice.from_identifiers(
    mac_address="aa:bb:cc:dd:ee:ff",
    serial_number="abc123",
    listener=listener,
)

async with device:
    # State
    state = await device.get_state()

    # Power control
    await device.turn_on()
    await device.turn_off()

    # Brightness (0.0 - 1.0)
    await device.set_brightness(0.75)

    # Hue and Saturation (0.0 - 1.0)
    # Routes to the current mode's palette (Auto/Scene/Manual)
    await device.set_hue(0.5)
    await device.set_saturation(0.8)

    # Effects
    await device.set_effect("auto")
    await device.set_effect("scene:0")
    await device.set_effect("manual:3")

    # Mode
    await device.set_mode(DeviceMode.AUTO)
    await device.set_mode(DeviceMode.SCENE)
    await device.set_mode(DeviceMode.MANUAL)

Data Classes

DeviceState

Current state of a device.

@dataclass
class DeviceState:
    serial_number: str
    model: str              # "Fluora", "Monos", etc.
    nickname: str           # User-assigned name
    firmware_version: str
    is_on: bool
    brightness: float       # 0.0 - 1.0
    hue: float              # 0.0 - 1.0 (from current mode's palette)
    saturation: float       # 0.0 - 1.0 (from current mode's palette)
    mode: DeviceMode        # AUTO, SCENE, or MANUAL
    rssi: int               # WiFi signal strength (dBm)
    effects: List[EffectInfo]        # Available effects
    current_effect: str              # Current effect display name
    current_effect_id: str           # Current effect ID

EffectInfo

Effect with ID and display name.

@dataclass
class EffectInfo:
    id: str           # "auto", "scene:0", "manual:3"
    display_name: str # "Auto", "Scene: Sunset", "Rainbow"

DeviceMode

Display mode enumeration.

class DeviceMode(Enum):
    AUTO = 0    # Automatic mode
    SCENE = 1   # Scene mode (user-defined scenes)
    MANUAL = 2  # Manual animation mode

ARP Utilities

For MAC address resolution via system ARP table.

from libpixelair import lookup_ip_by_mac, lookup_mac_by_ip, normalize_mac

# Normalize MAC format
mac = normalize_mac("AA-BB-CC-DD-EE-FF")  # -> "aa:bb:cc:dd:ee:ff"

# Look up IP from MAC
ip = await lookup_ip_by_mac("aa:bb:cc:dd:ee:ff")

# Look up MAC from IP
mac = await lookup_mac_by_ip("192.168.1.100")

Home Assistant Integration

The library is designed for easy Home Assistant integration:

  1. Discovery: Use discover_with_info() to get MAC addresses
  2. Store Identifiers: Save both MAC and serial number in config
  3. Reconnection: Use from_identifiers() with fallback resolution
# Initial setup - store both identifiers
devices = await discovery.discover_with_info()
config = {
    "mac_address": device.mac_address,
    "serial_number": device.serial_number,
}

# On startup - resolve using fallback strategy
device = await PixelAirDevice.from_identifiers(
    mac_address=config["mac_address"],
    serial_number=config["serial_number"],
    listener=listener,
)

Resolution Strategy

When connecting to a device:

  1. ARP Table Lookup (fast) - Uses MAC address to find current IP
  2. Broadcast Discovery (fallback) - Uses serial number if ARP fails

Examples

See the examples/ directory:

  • discover_devices.py - Scan for devices on the network
  • poll_device.py - Monitor a device's state changes
  • control_device.py - Interactive device control CLI

Network Ports

Port Direction Purpose
9090 Client -> Device Discovery and getState commands
6767 Client -> Device Control commands
12345 Device -> Client Responses

License

MIT

Author

Aiden Vigue aiden.vigue@koiosdigital.net

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

libpixelair-0.2.1.tar.gz (49.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

libpixelair-0.2.1-py3-none-any.whl (68.1 kB view details)

Uploaded Python 3

File details

Details for the file libpixelair-0.2.1.tar.gz.

File metadata

  • Download URL: libpixelair-0.2.1.tar.gz
  • Upload date:
  • Size: 49.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.2.1 CPython/3.14.2 Darwin/25.2.0

File hashes

Hashes for libpixelair-0.2.1.tar.gz
Algorithm Hash digest
SHA256 13e20787f5bf2f646ff2de6a327a46563116ab8566e7bd53bb240f1349c2bcf0
MD5 f92748d094875378c83beaaeeef85bfd
BLAKE2b-256 39e643cfb63ca346ae7423cd102629b42228723b0857207ac9889f2380d516c3

See more details on using hashes here.

File details

Details for the file libpixelair-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: libpixelair-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 68.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.2.1 CPython/3.14.2 Darwin/25.2.0

File hashes

Hashes for libpixelair-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d7c92184c4fa68095dc4fee89f94f0fb939da714ab67b33921b12bfcbbddff64
MD5 e58563fb6050aa4533c7c0d0518b9e42
BLAKE2b-256 24d387dcd1042ddf57fd5d2cc5e7ea8388d0f4b5c787e26ad03c2fe457e6d62a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page