TasmoR Lib

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
- 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 (370+ 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 speed control |
| Light schemes |
Effects (wakeup, color cycling, random) |
| RGB colors |
Hex color input (#RRGGBB) with HSB conversion |
Installation
Add to your Cargo.toml:
[dependencies]
tasmor_lib = "0.1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
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.1", default-features = false, features = ["http"] }
tasmor_lib = { version = "0.1", 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::{Device, Capabilities};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (device, initial_state) = Device::mqtt("mqtt://192.168.1.50:1883", "tasmota_switch")
.with_credentials("mqtt_user", "mqtt_password")
.with_capabilities(Capabilities::basic())
.build_without_probe()
.await?;
println!("Power is {:?}", initial_state.power(1));
device.power_on().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 Status 0. 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::{Device, Capabilities};
use tasmor_lib::subscription::Subscribable;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (device, _) = Device::mqtt("mqtt://192.168.1.50:1883", "tasmota_bulb")
.with_credentials("mqtt_user", "mqtt_pass")
.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);
});
tokio::signal::ctrl_c().await?;
Ok(())
}
Multi-Device Management
use tasmor_lib::{Device, Capabilities, Dimmer};
use tasmor_lib::subscription::Subscribable;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (living_room, living_state) = Device::mqtt("mqtt://192.168.1.50:1883", "tasmota_living")
.with_credentials("mqtt_user", "mqtt_pass")
.with_capabilities(Capabilities::rgbcct_light())
.build_without_probe()
.await?;
let (bedroom, bedroom_state) = Device::mqtt("mqtt://192.168.1.50:1883", "tasmota_bedroom")
.with_credentials("mqtt_user", "mqtt_pass")
.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?;
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 |
cargo run --example bulb_test -- mqtt://192.168.1.50:1883 tasmota_topic user pass
cargo run --example energy_test -- mqtt://192.168.1.50:1883 tasmota_plug 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: