Python client library for PixelAir LED devices (Fluora, Monos)
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 encodingflatbuffers- State serializationnetifaces- 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:
- Discovery: Use
discover_with_info()to get MAC addresses - Store Identifiers: Save both MAC and serial number in config
- 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:
- ARP Table Lookup (fast) - Uses MAC address to find current IP
- Broadcast Discovery (fallback) - Uses serial number if ARP fails
Examples
See the examples/ directory:
discover_devices.py- Scan for devices on the networkpoll_device.py- Monitor a device's state changescontrol_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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file libpixelair-1.0.0.tar.gz.
File metadata
- Download URL: libpixelair-1.0.0.tar.gz
- Upload date:
- Size: 52.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.2.1 CPython/3.14.2 Darwin/25.2.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6877ca4661082b6192d3eb0f41e24b54d9c1b3224ee8059568c80285b21b9e0a
|
|
| MD5 |
facf7e2630c4bc19e2f2525c28c89d14
|
|
| BLAKE2b-256 |
18a1521808ff81b647e47cca3cedabcb43c3e22a0c7cab44d27ad81f204346a6
|
File details
Details for the file libpixelair-1.0.0-py3-none-any.whl.
File metadata
- Download URL: libpixelair-1.0.0-py3-none-any.whl
- Upload date:
- Size: 70.7 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8e6daa77642a4cb5fc70e6abbd17ada60bcfc41fcc6048de2270ab3fda8c6afa
|
|
| MD5 |
d274fc60be7b6d4699468365c46f87aa
|
|
| BLAKE2b-256 |
69dfecdad24d17bf2c5846f5cefd0c6289a7a34fbf7d9c37d43f7b79a4a475ec
|