TasmoR Lib

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
Features
- Type-safe API - Compile-time guarantees for valid commands and values
- Dual protocol support - Control devices via MQTT or HTTP with full feature parity
- Async/await - Built on Tokio for efficient async I/O
- Full device support - Lights (RGB/CCT), switches, relays, energy monitors
- Event-driven architecture - Subscribe to device state changes in real-time (MQTT)
- Well-tested - Comprehensive unit and integration tests (630+ tests)
Supported Capabilities
| 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 |
Installation
Add to your Cargo.toml:
[dependencies]
tasmor_lib = "0.8"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
System Requirements
The MQTT feature (enabled by default) links against the Eclipse Paho MQTT C library, which is compiled from source during cargo build.
Build-time dependencies (required when the mqtt feature is enabled):
| Dependency |
Purpose |
Install |
C compiler (cc) |
Compile paho.mqtt.c |
Usually pre-installed |
cmake ≥ 3.x |
Build system for paho.mqtt.c |
apt install cmake / brew install cmake / pacman -S cmake |
Runtime dependencies (required in the final binary):
| Library |
Purpose |
libssl + libcrypto (OpenSSL) |
TLS support in paho.mqtt.c |
OpenSSL is present on most Linux/macOS systems. For minimal Docker images (e.g., debian:slim, alpine), add it explicitly:
# Debian/Ubuntu
RUN apt-get install -y libssl3
# Alpine
RUN apk add --no-cache openssl-libs-static
If you only use HTTP (default-features = false, features = ["http"]), none of these system dependencies apply.
Feature Flags
Both HTTP and MQTT protocols are enabled by default. To reduce compile time and binary size, you can enable only the protocol you need:
tasmor_lib = { version = "0.8", default-features = false, features = ["http"] }
tasmor_lib = { version = "0.8", default-features = false, features = ["mqtt"] }
Quick Start
Basic Switch Control
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>> {
let (device, initial_state) = Device::http("192.168.1.100")
.with_capabilities(Capabilities::basic())
.build_without_probe()
.await?;
println!("Power is {:?}", initial_state.power(1));
let response = device.power_toggle().await?;
println!("Power is now {:?}", response.power_state(1));
Ok(())
}
MQTT Connection
For persistent connections with real-time updates:
use tasmor_lib::{MqttBroker, Capabilities};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let broker = MqttBroker::builder()
.host("192.168.1.50")
.credentials("mqtt_user", "mqtt_password")
.build()
.await?;
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?;
device.disconnect().await;
broker.disconnect().await?;
Ok(())
}
Building Devices
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. |
let (device, state) = Device::http("192.168.1.100")
.build()
.await?;
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.
Predefined Capabilities
use tasmor_lib::Capabilities;
Capabilities::basic() Capabilities::neo_coolcam() Capabilities::rgbcct_light() Capabilities::rgb_light() Capabilities::cct_light()
Custom Capabilities
use tasmor_lib::CapabilitiesBuilder;
let caps = CapabilitiesBuilder::new()
.with_power_channels(2) .with_dimmer_control()
.with_energy_monitoring()
.build();
Examples by Use Case
Light Control (RGB/CCT)
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?;
device.power_on().await?;
device.set_dimmer(Dimmer::new(75)?).await?;
device.set_color_temperature(ColorTemperature::WARM).await?;
device.set_hsb_color(HsbColor::blue()).await?;
device.set_hsb_color(HsbColor::new(120, 80, 100)?).await?;
Ok(())
}
Multi-Relay Control
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?;
println!("Relay 1: {:?}", state.power(1));
println!("Relay 2: {:?}", state.power(2));
device.set_power(PowerIndex::new(1)?, PowerState::On).await?;
device.set_power(PowerIndex::new(2)?, PowerState::Off).await?;
device.toggle_power(PowerIndex::new(2)?).await?;
Ok(())
}
Energy Monitoring
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?;
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());
let updated = device.reset_energy_total().await?;
if let Some(energy) = updated.energy() {
println!("Reset! New total: {} kWh", energy.total);
}
Ok(())
}
Typed Responses
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?;
let power_resp = device.power_on().await?;
println!("Relay 1 state: {:?}", power_resp.power_state(1));
println!("All relays: {:?}", power_resp.all_power_states());
let dimmer_resp = device.set_dimmer(Dimmer::new(50)?).await?;
println!("Dimmer level: {}", dimmer_resp.dimmer());
let ct_resp = device.set_color_temperature(153u16.try_into()?).await?;
println!("Color temp: {} mireds", ct_resp.color_temperature());
let hsb_resp = device.set_hsb_color((180u16, 100u8, 100u8).try_into()?).await?;
println!("HSB: {:?}", hsb_resp.hsb_color());
Ok(())
}
Real-Time Updates (MQTT Callbacks)
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?;
device.on_power_changed(|relay_index, power_state| {
println!("Relay {} changed to {:?}", relay_index, power_state);
});
device.on_dimmer_changed(|dimmer| {
println!("Dimmer changed to {}", dimmer.value());
});
device.on_state_changed(|change| {
println!("State change: {:?}", change);
});
device.on_disconnected(|| {
println!("Connection lost!");
});
device.on_reconnected(|| {
println!("Reconnected! Consider calling query_state() to refresh");
});
tokio::signal::ctrl_c().await?;
device.disconnect().await;
broker.disconnect().await?;
Ok(())
}
Multi-Device Management
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>> {
let broker = MqttBroker::builder()
.host("192.168.1.50")
.credentials("mqtt_user", "mqtt_pass")
.build()
.await?;
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?;
println!("Living room: {:?}", living_state.power(1));
println!("Bedroom: {:?}", bedroom_state.power(1));
living_room.on_power_changed(|relay, state| {
println!("Living room relay {} -> {:?}", relay, state);
});
bedroom.on_power_changed(|relay, state| {
println!("Bedroom relay {} -> {:?}", relay, state);
});
living_room.power_on().await?;
living_room.set_dimmer(Dimmer::new(75)?).await?;
bedroom.power_on().await?;
living_room.disconnect().await;
bedroom.disconnect().await;
broker.disconnect().await?;
Ok(())
}
MQTT Device Discovery
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>> {
let broker = MqttBroker::builder()
.host("192.168.1.50")
.port(1883)
.credentials("mqtt_user", "mqtt_pass")
.build()
.await?;
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());
}
for (device, _) in devices {
device.power_toggle().await?;
}
broker.disconnect().await?;
Ok(())
}
Command Routines
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?;
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()?;
device.run(&wakeup_routine).await?;
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(())
}
Parsing External Telemetry
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" });
}
_ => {}
}
}
}
Runnable Examples
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
Documentation
Roadmap
Development
cargo test
cargo test --features serde
cargo tarpaulin --out Stdout
cargo check && cargo build && cargo test && cargo fmt --check && cargo clippy -- -D warnings -W clippy::pedantic
Contributing
Contributions are welcome! Please read our Contributing Guide for details on:
- Development setup
- Code style and linting
- Testing requirements
- Commit message conventions
- Pull request process
License
Licensed under the Mozilla Public License 2.0 (MPL-2.0).
Credits
Built for controlling Tasmota open-source firmware.
Key dependencies:
Testing: