| Crates.io | tasmor_lib |
| lib.rs | tasmor_lib |
| version | 0.5.0 |
| created_at | 2025-12-27 01:06:23.620853+00 |
| updated_at | 2026-01-09 14:07:38.06308+00 |
| description | Rust library to control Tasmota devices via MQTT and HTTP |
| homepage | https://codeberg.org/Bawycle/tasmor_lib |
| repository | https://codeberg.org/Bawycle/tasmor_lib.git |
| max_upload_size | |
| id | 2006525 |
| size | 827,649 |
Primary repository: Codeberg — Please submit issues and pull requests there.
A modern, type-safe Rust library for controlling Tasmota IoT devices via MQTT and HTTP protocols.
Early Development: This project is in active development (v0.x.x). The API may change between versions. Not recommended for production use yet.
Tested with: Tasmota firmware v15.2.0
| Capability | Description |
|---|---|
| Power control | On/Off/Toggle for single and multi-relay devices |
| Lighting | Dimmer, color temperature (CCT), HSB color control |
| Energy monitoring | Power, voltage, current, energy consumption tracking |
| Device status | Query firmware, network, and sensor information |
| Transitions | Fade effects and duration control |
| Light schemes | Effects (wakeup, color cycling, random) |
| RGB colors | Hex color input (#RRGGBB) with HSB conversion |
| Routines | Execute multiple commands atomically via Backlog0 |
| Device discovery | Auto-discover Tasmota devices on MQTT broker |
Add to your Cargo.toml:
[dependencies]
tasmor_lib = "0.5"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
Both HTTP and MQTT protocols are enabled by default. To reduce compile time and binary size, you can enable only the protocol you need:
# HTTP only (no MQTT dependencies)
tasmor_lib = { version = "0.5", default-features = false, features = ["http"] }
# MQTT only (no HTTP dependencies)
tasmor_lib = { version = "0.5", default-features = false, features = ["mqtt"] }
The simplest use case - controlling a smart switch or relay:
use tasmor_lib::{Device, Capabilities};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connect to a Tasmota switch via HTTP
let (device, initial_state) = Device::http("192.168.1.100")
.with_capabilities(Capabilities::basic())
.build_without_probe()
.await?;
// Check current state
println!("Power is {:?}", initial_state.power(1));
// Toggle power and get the response
let response = device.power_toggle().await?;
println!("Power is now {:?}", response.power_state(1));
Ok(())
}
For persistent connections with real-time updates:
use tasmor_lib::{MqttBroker, Capabilities};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connect to MQTT broker
let broker = MqttBroker::builder()
.host("192.168.1.50")
.credentials("mqtt_user", "mqtt_password")
.build()
.await?;
// Create device from broker
let (device, initial_state) = broker.device("tasmota_switch")
.with_capabilities(Capabilities::basic())
.build_without_probe()
.await?;
println!("Power is {:?}", initial_state.power(1));
device.power_on().await?;
// Clean disconnect when done
device.disconnect().await;
broker.disconnect().await?;
Ok(())
}
build() vs build_without_probe()Both methods return (Device, DeviceState) - the device handle and its initial state.
| Method | When to use |
|---|---|
build() |
Auto-detects device capabilities by querying device status. Use when you don't know the device type. |
build_without_probe() |
Uses capabilities you provide. Faster startup, recommended when you know the device type. |
// Auto-detection: queries the device to discover capabilities
let (device, state) = Device::http("192.168.1.100")
.build()
.await?;
// Manual: you specify the capabilities (no capability query)
let (device, state) = Device::http("192.168.1.100")
.with_capabilities(Capabilities::rgbcct_light())
.build_without_probe()
.await?;
Both methods query the device for its current state (power, dimmer, energy, etc.) and return it as DeviceState.
use tasmor_lib::Capabilities;
Capabilities::basic() // Simple switch (1 relay)
Capabilities::neo_coolcam() // Smart plug with energy monitoring
Capabilities::rgbcct_light() // Full RGB + CCT light bulb
Capabilities::rgb_light() // RGB only light
Capabilities::cct_light() // Color temperature only light
use tasmor_lib::CapabilitiesBuilder;
let caps = CapabilitiesBuilder::new()
.with_power_channels(2) // Dual relay
.with_dimmer_control()
.with_energy_monitoring()
.build();
use tasmor_lib::{Device, Capabilities, ColorTemperature, Dimmer, HsbColor};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (device, _) = Device::http("192.168.1.100")
.with_capabilities(Capabilities::rgbcct_light())
.build_without_probe()
.await?;
// Power and brightness
device.power_on().await?;
device.set_dimmer(Dimmer::new(75)?).await?;
// Color temperature (warm to cold)
device.set_color_temperature(ColorTemperature::WARM).await?;
// RGB color
device.set_hsb_color(HsbColor::blue()).await?;
// Custom HSB color (hue: 0-360, saturation: 0-100, brightness: 0-100)
device.set_hsb_color(HsbColor::new(120, 80, 100)?).await?;
Ok(())
}
For devices with multiple relays (e.g., dual switch):
use tasmor_lib::{Device, CapabilitiesBuilder, PowerIndex, PowerState};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let caps = CapabilitiesBuilder::new()
.with_power_channels(2)
.build();
let (device, state) = Device::http("192.168.1.100")
.with_capabilities(caps)
.build_without_probe()
.await?;
// Check each relay
println!("Relay 1: {:?}", state.power(1));
println!("Relay 2: {:?}", state.power(2));
// Control individual relays
device.set_power(PowerIndex::new(1)?, PowerState::On).await?;
device.set_power(PowerIndex::new(2)?, PowerState::Off).await?;
// Toggle a specific relay
device.toggle_power(PowerIndex::new(2)?).await?;
Ok(())
}
use tasmor_lib::{Device, Capabilities};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (device, state) = Device::http("192.168.1.101")
.with_capabilities(Capabilities::neo_coolcam())
.build_without_probe()
.await?;
// Energy data is available in initial state
println!("Power: {:?} W", state.power_consumption());
println!("Voltage: {:?} V", state.voltage());
println!("Current: {:?} A", state.current());
println!("Today: {:?} kWh", state.energy_today());
println!("Yesterday: {:?} kWh", state.energy_yesterday());
println!("Total: {:?} kWh", state.energy_total());
// Reset total energy counter
let updated = device.reset_energy_total().await?;
if let Some(energy) = updated.energy() {
println!("Reset! New total: {} kWh", energy.total);
}
Ok(())
}
All commands return typed responses for reliable parsing:
use tasmor_lib::{Device, Capabilities, Dimmer};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (device, _) = Device::http("192.168.1.100")
.with_capabilities(Capabilities::rgbcct_light())
.build_without_probe()
.await?;
// PowerResponse
let power_resp = device.power_on().await?;
println!("Relay 1 state: {:?}", power_resp.power_state(1));
println!("All relays: {:?}", power_resp.all_power_states());
// DimmerResponse
let dimmer_resp = device.set_dimmer(Dimmer::new(50)?).await?;
println!("Dimmer level: {}", dimmer_resp.dimmer());
// ColorTemperatureResponse
let ct_resp = device.set_color_temperature(153u16.try_into()?).await?;
println!("Color temp: {} mireds", ct_resp.color_temperature());
// HsbColorResponse
let hsb_resp = device.set_hsb_color((180u16, 100u8, 100u8).try_into()?).await?;
println!("HSB: {:?}", hsb_resp.hsb_color());
Ok(())
}
Subscribe to device state changes pushed via MQTT:
use tasmor_lib::{MqttBroker, Capabilities};
use tasmor_lib::subscription::Subscribable;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let broker = MqttBroker::builder()
.host("192.168.1.50")
.credentials("mqtt_user", "mqtt_pass")
.build()
.await?;
let (device, _) = broker.device("tasmota_bulb")
.with_capabilities(Capabilities::rgbcct_light())
.build_without_probe()
.await?;
// Subscribe to power changes (triggered by external events)
device.on_power_changed(|relay_index, power_state| {
println!("Relay {} changed to {:?}", relay_index, power_state);
});
// Subscribe to dimmer changes
device.on_dimmer_changed(|dimmer| {
println!("Dimmer changed to {}", dimmer.value());
});
// Subscribe to all state changes
device.on_state_changed(|change| {
println!("State change: {:?}", change);
});
// Handle connection events
device.on_disconnected(|| {
println!("Connection lost!");
});
device.on_reconnected(|| {
println!("Reconnected! Consider calling query_state() to refresh");
});
// Keep the application running to receive callbacks
tokio::signal::ctrl_c().await?;
device.disconnect().await;
broker.disconnect().await?;
Ok(())
}
Multiple devices can share a single broker connection for efficiency:
use tasmor_lib::{MqttBroker, Capabilities, Dimmer};
use tasmor_lib::subscription::Subscribable;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connect to broker once
let broker = MqttBroker::builder()
.host("192.168.1.50")
.credentials("mqtt_user", "mqtt_pass")
.build()
.await?;
// Create multiple devices sharing the broker connection
let (living_room, living_state) = broker.device("tasmota_living")
.with_capabilities(Capabilities::rgbcct_light())
.build_without_probe()
.await?;
let (bedroom, bedroom_state) = broker.device("tasmota_bedroom")
.with_capabilities(Capabilities::rgbcct_light())
.build_without_probe()
.await?;
// Initial states are immediately available
println!("Living room: {:?}", living_state.power(1));
println!("Bedroom: {:?}", bedroom_state.power(1));
// Subscribe to changes on each device
living_room.on_power_changed(|relay, state| {
println!("Living room relay {} -> {:?}", relay, state);
});
bedroom.on_power_changed(|relay, state| {
println!("Bedroom relay {} -> {:?}", relay, state);
});
// Control devices
living_room.power_on().await?;
living_room.set_dimmer(Dimmer::new(75)?).await?;
bedroom.power_on().await?;
// Clean disconnect
living_room.disconnect().await;
bedroom.disconnect().await;
broker.disconnect().await?;
Ok(())
}
Automatically discover all Tasmota devices on an MQTT broker:
use tasmor_lib::MqttBroker;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connect to broker
let broker = MqttBroker::builder()
.host("192.168.1.50")
.port(1883)
.credentials("mqtt_user", "mqtt_pass")
.build()
.await?;
// Discover devices (10 second timeout)
let devices = broker.discover_devices(Duration::from_secs(10)).await?;
println!("Found {} devices:", devices.len());
for (device, state) in &devices {
println!(" - Power: {:?}, Dimmer: {:?}", state.power(1), state.dimmer());
}
// Use discovered devices...
for (device, _) in devices {
device.power_toggle().await?;
}
broker.disconnect().await?;
Ok(())
}
Execute multiple commands atomically using the Routine builder:
use std::time::Duration;
use tasmor_lib::{Device, Capabilities, Routine, Dimmer, HsbColor};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (device, _) = Device::http("192.168.1.100")
.with_capabilities(Capabilities::rgbcct_light())
.build_without_probe()
.await?;
// Build a routine with multiple commands
let wakeup_routine = Routine::builder()
.set_dimmer(Dimmer::new(10)?)
.power_on()
.delay(Duration::from_secs(5))
.set_dimmer(Dimmer::new(50)?)
.delay(Duration::from_secs(5))
.set_dimmer(Dimmer::new(100)?)
.build()?;
// Execute all commands atomically
device.run(&wakeup_routine).await?;
// RGB color transition routine
let color_routine = Routine::builder()
.set_hsb_color(HsbColor::red())
.delay(Duration::from_secs(2))
.set_hsb_color(HsbColor::green())
.delay(Duration::from_secs(2))
.set_hsb_color(HsbColor::blue())
.build()?;
device.run(&color_routine).await?;
Ok(())
}
If you're using your own MQTT client and want to parse Tasmota messages:
use tasmor_lib::telemetry::{parse_telemetry, TelemetryMessage};
fn handle_mqtt_message(topic: &str, payload: &str) {
if let Ok(msg) = parse_telemetry(topic, payload) {
match msg {
TelemetryMessage::State { device_topic, state } => {
println!("[{}] Power: {:?}, Dimmer: {:?}",
device_topic, state.power(), state.dimmer());
}
TelemetryMessage::Sensor { device_topic, data } => {
if let Some(energy) = data.energy() {
println!("[{}] Power: {} W", device_topic, energy.power);
}
}
TelemetryMessage::LastWill { device_topic, online } => {
println!("[{}] {}", device_topic, if online { "online" } else { "offline" });
}
_ => {}
}
}
}
The examples/ directory contains complete runnable examples:
| Example | Description |
|---|---|
bulb_test.rs |
Basic light bulb control |
energy_test.rs |
Energy monitoring with formatted output |
routine_test.rs |
Wakeup routine with gradual brightness increase |
discovery_test.rs |
MQTT device discovery |
uptime.rs |
Device uptime retrieval via HTTP, MQTT, and subscriptions |
cargo run --example bulb_test -- 192.168.1.50 tasmota_topic user pass
cargo run --example energy_test -- 192.168.1.50 tasmota_plug user pass
cargo run --example routine_test -- 192.168.1.50 tasmota_bulb user pass
cargo run --example discovery_test -- 192.168.1.50 user pass
cargo run --example uptime -- http 192.168.1.100 admin password
cargo run --example uptime -- mqtt 192.168.1.50 tasmota_device user pass
# Run tests
cargo test
# Run tests with serde feature
cargo test --features serde
# Check code coverage
cargo tarpaulin --out Stdout
# Full verification pipeline
cargo check && cargo build && cargo test && cargo fmt --check && cargo clippy -- -D warnings -W clippy::pedantic
Contributions are welcome! Please read our Contributing Guide for details on:
Licensed under the Mozilla Public License 2.0 (MPL-2.0).
Built for controlling Tasmota open-source firmware.
Key dependencies:
Testing: