2 unstable releases
Uses new Rust 2024
| 0.2.0 | Feb 16, 2026 |
|---|---|
| 0.1.0 | Jan 20, 2026 |
#640 in Asynchronous
110KB
2.5K
SLoC
wiz-lights-rs
A runtime-agnostic async Rust library for controlling Philips Wiz smart lights over UDP.
Features
- Runtime Agnostic: Works with tokio, async-std, or smol
- Full Control: RGB colors, brightness, color temperature, and 36+ preset scenes
- Fan Control: Support for fan-equipped fixtures (speed, mode, direction)
- Discovery: Automatic bulb discovery via UDP broadcast
- Batch Operations: Group lights into rooms for concurrent control
- Push Notifications: Real-time state updates from bulbs
- Type Safe: Strongly typed API with validation
Installation
Add to your Cargo.toml:
[dependencies]
wiz-lights-rs = "0.1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
Runtime Selection
tokio (default)
[dependencies]
wiz-lights-rs = "0.1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
async-std
[dependencies]
wiz-lights-rs = { version = "0.1", default-features = false, features = ["runtime-async-std"] }
async-std = { version = "1.12", features = ["attributes"] }
smol
[dependencies]
wiz-lights-rs = { version = "0.1", default-features = false, features = ["runtime-smol"] }
smol = "2"
Quick Start
use std::net::Ipv4Addr;
use std::str::FromStr;
use wiz_lights_rs::{Light, Payload, Color, Brightness};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a light instance
let light = Light::new(
Ipv4Addr::from_str("192.168.1.100")?,
Some("Living Room")
);
// Set color and brightness
let mut payload = Payload::new();
payload.color(&Color::from_str("255,0,0")?); // Red
payload.brightness(&Brightness::create(80).unwrap());
light.set(&payload).await?;
// Toggle power
light.toggle().await?;
Ok(())
}
Examples
Discovery
Find all bulbs on your network:
use std::time::Duration;
use wiz_lights_rs::discover_bulbs;
let bulbs = discover_bulbs(Duration::from_secs(5)).await?;
for bulb in bulbs {
println!("Found: {} at {}", bulb.mac, bulb.ip);
let light = bulb.into_light(Some("My Light"));
}
Color Control
use wiz_lights_rs::{Payload, Color, Brightness};
// RGB color
let mut payload = Payload::new();
payload.color(&Color::from_str("0,255,0")?); // Green
payload.brightness(&Brightness::create(100).unwrap());
light.set(&payload).await?;
// Color temperature
let mut payload = Payload::new();
payload.temp(&Kelvin::create(4000).unwrap()); // Warm white
light.set(&payload).await?;
Preset Scenes
use wiz_lights_rs::{Payload, SceneMode};
let mut payload = Payload::new();
payload.scene(&SceneMode::Sunset);
light.set(&payload).await?;
Available scenes: Ocean, Romance, Sunset, Party, Fireplace, Cozy, Forest, WakeUp, Bedtime, Focus, Relax, Christmas, Halloween, and more.
Room Control
Batch operations on multiple lights:
use wiz_lights_rs::{Room, Light};
let mut room = Room::new("Living Room");
// Add lights
let light1 = Light::new(Ipv4Addr::from_str("192.168.1.100")?, Some("Lamp 1"));
let light2 = Light::new(Ipv4Addr::from_str("192.168.1.101")?, Some("Lamp 2"));
room.new_light(light1)?;
room.new_light(light2)?;
// Get status from all lights concurrently
let statuses = room.get_status().await?;
Fan Control
For fan-equipped fixtures:
use wiz_lights_rs::{FanSpeed, FanMode, FanDirection};
// Turn fan on at speed 3
light.fan_turn_on(Some(FanMode::Normal), Some(FanSpeed::create(3, None)?)).await?;
// Set fan direction
light.set_fan_direction(FanDirection::Reverse).await?;
// Toggle fan
light.fan_toggle().await?;
Push Notifications
Receive real-time updates:
use wiz_lights_rs::push::PushManager;
let mut manager = PushManager::new();
manager.register_callback(|response| {
println!("Light updated: {:?}", response);
});
manager.start().await?;
Query Status
// Get current state
let status = light.get_status().await?;
println!("Power: {}", status.emitting());
if let Some(color) = status.color() {
println!("Color: RGB({}, {}, {})", color.red(), color.green(), color.blue());
}
if let Some(brightness) = status.brightness() {
println!("Brightness: {}%", brightness.value());
}
// Get diagnostics
let diag = light.diagnostics().await;
println!("{}", serde_json::to_string_pretty(&diag)?);
Type System
All parameters use strongly-typed wrappers with validation:
- Brightness: 10-100%
- Kelvin: 1000-8000K
- Speed: 20-200%
- Color: RGB (0-255 per channel)
- FanSpeed: 1-N (varies by fixture)
Types provide two creation methods:
create(value): ReturnsOption<T>,Noneif invalidcreate_or(value): Returns valid value or default
let brightness = Brightness::create(80).unwrap(); // Some(80)
let invalid = Brightness::create(5); // None (below minimum)
let safe = Brightness::create_or(5); // Returns default (100)
Network Requirements
- Bulbs must be on the same local network
- UDP ports 38899 (commands) and 38900 (push notifications)
- Static IP addresses recommended for reliability
- No internet connection required
Error Handling
All network operations return Result<T, Error>:
match light.set(&payload).await {
Ok(response) => println!("Success"),
Err(e) => eprintln!("Error: {}", e),
}
Common errors:
Socket: Network communication failuresNoAttribute: Empty payload validationJsonLoad/JsonDump: Serialization errorsLightNotFound: Invalid light reference
Architecture
The library uses:
- UDP for all communication (ports 38899, 38900)
- JSON message format matching Wiz protocol
- Retry logic: 3 attempts with exponential backoff
- Timeout: 1000ms per request
- Runtime abstraction: Works with any async runtime
Contributing
Contributions are welcome! Please ensure code passes:
cargo test
cargo clippy
cargo fmt --check
License
This project is licensed under the MIT License.
Acknowledgments
This library is not affiliated with or endorsed by Philips or WiZ. It is a reverse-engineered implementation of the WiZ local UDP protocol.
Dependencies
~2–8.5MB
~180K SLoC