aboutsummaryrefslogtreecommitdiff
path: root/src/lib.rs
diff options
context:
space:
mode:
authordiogo464 <[email protected]>2025-12-14 14:49:02 +0000
committerdiogo464 <[email protected]>2025-12-14 14:49:02 +0000
commit993d2a9fd34ce08760933a013e638108827f6f70 (patch)
tree13897ca1a8eac0564fabc5b730bf9ae49a360fbb /src/lib.rs
parentab4a7c83e00314a2f5d2f455987ba530fd08bdd7 (diff)
Improve documentation and replace publish_on_command with CommandPolicy enum
- Enhanced crate-level documentation with comprehensive examples and feature list - Improved README with badges, better structure, and clearer examples - Added README.tpl template and generate-readme.sh script for cargo-readme - Documented run() and connect_and_run() functions with detailed behavior explanations - Replaced publish_on_command boolean with CommandPolicy enum (PublishState/Manual) - Added comprehensive documentation for CommandPolicy explaining both modes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs199
1 files changed, 184 insertions, 15 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 7766171..1b7c79a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,16 +1,93 @@
1//! Home Assistant MQTT device library for embassy. 1//! MQTT Home Assistant integration library for the [Embassy](https://embassy.dev/) async runtime.
2//! 2//!
3//! To create a device use the [`new`] function. 3//! # Features
4//! 4//!
5//! After the device is created you should create one or more entities using functions such as 5//! - Support for multiple entity types: sensors, buttons, switches, binary sensors, numbers, device trackers
6//! [`create_button`]/[`create_sensor`]/... 6//! - Built on top of Embassy's async runtime for embedded systems
7//! - No-std compatible
8//! - Automatic MQTT discovery for Home Assistant
9//! - No runtime allocation
7//! 10//!
8//! Once the entities have been created either [`run`] or [`connect_and_run`] should be called in a 11//! # Installation
9//! seperate task.
10//! 12//!
11//! There are various examples you can run locally (ex: `cargo run --features tracing --example 13//! ```bash
12//! button`) assuming you have a home assistant instance running. To run the examples the 14//! cargo add embassy-ha
13//! environment variable `MQTT_ADDRESS` should be set to the mqtt server used by home assistant. 15//! ```
16//!
17//! # Quick Start
18//!
19//! This example does not compile as-is because it requires device-specific setup, but it should
20//! be easy to adapt if you already have Embassy running on your microcontroller.
21//!
22//! ```no_run
23//! use embassy_executor::Spawner;
24//! use embassy_ha::{DeviceConfig, SensorConfig, SensorClass, StateClass};
25//! use embassy_time::Timer;
26//! use static_cell::StaticCell;
27//!
28//! static HA_RESOURCES: StaticCell<embassy_ha::DeviceResources> = StaticCell::new();
29//!
30//! #[embassy_executor::main]
31//! async fn main(spawner: Spawner) {
32//! // Initialize your network stack
33//! // This is device specific
34//! let stack: embassy_net::Stack<'static>;
35//! # let stack = unsafe { core::mem::zeroed() };
36//!
37//! // Create a Home Assistant device
38//! let device = embassy_ha::new(
39//! HA_RESOURCES.init(Default::default()),
40//! DeviceConfig {
41//! device_id: "my-device",
42//! device_name: "My Device",
43//! manufacturer: "ACME Corp",
44//! model: "Model X",
45//! },
46//! );
47//!
48//! // Create a temperature sensor
49//! let sensor_config = SensorConfig {
50//! class: SensorClass::Temperature,
51//! state_class: StateClass::Measurement,
52//! unit: Some(embassy_ha::constants::HA_UNIT_TEMPERATURE_CELSIUS),
53//! ..Default::default()
54//! };
55//! let mut sensor = embassy_ha::create_sensor(&device, "temp-sensor", sensor_config);
56//!
57//! // Spawn the Home Assistant communication task
58//! spawner.spawn(ha_task(stack, device)).unwrap();
59//!
60//! // Main loop - read and publish temperature
61//! loop {
62//! # let temperature = 0.0;
63//! // let temperature = read_temperature().await;
64//! sensor.publish(temperature);
65//! Timer::after_secs(60).await;
66//! }
67//! }
68//!
69//! #[embassy_executor::task]
70//! async fn ha_task(stack: embassy_net::Stack<'static>, device: embassy_ha::Device<'static>) {
71//! embassy_ha::connect_and_run(stack, device, "mqtt-broker-address").await;
72//! }
73//! ```
74//!
75//! # Examples
76//!
77//! The repository includes several examples demonstrating different entity types. To run an example:
78//!
79//! ```bash
80//! export MQTT_ADDRESS="mqtt://your-mqtt-broker:1883"
81//! cargo run --example sensor
82//! ```
83//!
84//! Available examples:
85//! - `sensor` - Temperature and humidity sensors
86//! - `button` - Triggerable button entity
87//! - `switch` - On/off switch control
88//! - `binary_sensor` - Binary state sensor
89//! - `number` - Numeric input entity
90//! - `device_tracker` - Location tracking entity
14 91
15#![no_std] 92#![no_std]
16 93
@@ -40,6 +117,9 @@ pub mod constants;
40mod binary_state; 117mod binary_state;
41pub use binary_state::*; 118pub use binary_state::*;
42 119
120mod command_policy;
121pub use command_policy::*;
122
43mod entity; 123mod entity;
44pub use entity::*; 124pub use entity::*;
45 125
@@ -305,7 +385,7 @@ pub(crate) struct SwitchState {
305pub(crate) struct SwitchStorage { 385pub(crate) struct SwitchStorage {
306 pub state: Option<SwitchState>, 386 pub state: Option<SwitchState>,
307 pub command: Option<SwitchCommand>, 387 pub command: Option<SwitchCommand>,
308 pub publish_on_command: bool, 388 pub command_policy: CommandPolicy,
309} 389}
310 390
311#[derive(Debug)] 391#[derive(Debug)]
@@ -350,7 +430,7 @@ pub(crate) struct NumberCommand {
350pub(crate) struct NumberStorage { 430pub(crate) struct NumberStorage {
351 pub state: Option<NumberState>, 431 pub state: Option<NumberState>,
352 pub command: Option<NumberCommand>, 432 pub command: Option<NumberCommand>,
353 pub publish_on_command: bool, 433 pub command_policy: CommandPolicy,
354} 434}
355 435
356#[derive(Debug, Serialize)] 436#[derive(Debug, Serialize)]
@@ -594,7 +674,7 @@ pub fn create_number<'a>(
594 device, 674 device,
595 entity_config, 675 entity_config,
596 EntityStorage::Number(NumberStorage { 676 EntityStorage::Number(NumberStorage {
597 publish_on_command: config.publish_on_command, 677 command_policy: config.command_policy,
598 ..Default::default() 678 ..Default::default()
599 }), 679 }),
600 ); 680 );
@@ -616,7 +696,7 @@ pub fn create_switch<'a>(
616 device, 696 device,
617 entity_config, 697 entity_config,
618 EntityStorage::Switch(SwitchStorage { 698 EntityStorage::Switch(SwitchStorage {
619 publish_on_command: config.publish_on_command, 699 command_policy: config.command_policy,
620 ..Default::default() 700 ..Default::default()
621 }), 701 }),
622 ); 702 );
@@ -661,6 +741,49 @@ pub fn create_device_tracker<'a>(
661 DeviceTracker::new(entity) 741 DeviceTracker::new(entity)
662} 742}
663 743
744/// Runs the main Home Assistant device event loop.
745///
746/// This function handles MQTT communication, entity discovery, and state updates. It will run
747/// until the first error is encountered, at which point it returns immediately.
748///
749/// # Behavior
750///
751/// - Connects to the MQTT broker using the provided transport
752/// - Publishes discovery messages for all entities
753/// - Subscribes to command topics for controllable entities
754/// - Enters the main event loop to handle state updates and commands
755/// - Returns on the first error (connection loss, timeout, protocol error, etc.)
756///
757/// # Error Handling
758///
759/// This function should be called inside a retry loop, as any network error will cause this
760/// function to fail. When an error occurs, the transport may be in an invalid state and should
761/// be re-established before calling `run` again.
762///
763/// # Example
764///
765/// ```no_run
766/// # use embassy_ha::{Device, Transport};
767/// # async fn example(mut device: Device<'_>, create_transport: impl Fn() -> impl Transport) {
768/// loop {
769/// let mut transport = create_transport();
770///
771/// match embassy_ha::run(&mut device, &mut transport).await {
772/// Ok(()) => {
773/// // Normal exit (this shouldn't happen in practice)
774/// break;
775/// }
776/// Err(err) => {
777/// // Log error and retry after delay
778/// // The transport connection should be re-established
779/// embassy_time::Timer::after_secs(5).await;
780/// }
781/// }
782/// }
783/// # }
784/// ```
785///
786/// For a higher-level alternative that handles retries automatically, see [`connect_and_run`].
664pub async fn run<T: Transport>(device: &mut Device<'_>, transport: &mut T) -> Result<(), Error> { 787pub async fn run<T: Transport>(device: &mut Device<'_>, transport: &mut T) -> Result<(), Error> {
665 use core::fmt::Write; 788 use core::fmt::Write;
666 789
@@ -1109,7 +1232,7 @@ pub async fn run<T: Transport>(device: &mut Device<'_>, transport: &mut T) -> Re
1109 } 1232 }
1110 }; 1233 };
1111 let timestamp = embassy_time::Instant::now(); 1234 let timestamp = embassy_time::Instant::now();
1112 if switch_storage.publish_on_command { 1235 if switch_storage.command_policy == CommandPolicy::PublishState {
1113 data.publish = true; 1236 data.publish = true;
1114 switch_storage.state = Some(SwitchState { 1237 switch_storage.state = Some(SwitchState {
1115 value: command, 1238 value: command,
@@ -1134,7 +1257,7 @@ pub async fn run<T: Transport>(device: &mut Device<'_>, transport: &mut T) -> Re
1134 } 1257 }
1135 }; 1258 };
1136 let timestamp = embassy_time::Instant::now(); 1259 let timestamp = embassy_time::Instant::now();
1137 if number_storage.publish_on_command { 1260 if number_storage.command_policy == CommandPolicy::PublishState {
1138 data.publish = true; 1261 data.publish = true;
1139 number_storage.state = Some(NumberState { 1262 number_storage.state = Some(NumberState {
1140 value: command, 1263 value: command,
@@ -1156,6 +1279,52 @@ pub async fn run<T: Transport>(device: &mut Device<'_>, transport: &mut T) -> Re
1156 } 1279 }
1157} 1280}
1158 1281
1282/// High-level function that manages TCP connections and runs the device event loop with automatic retries.
1283///
1284/// This is a convenience wrapper around [`run`] that handles:
1285/// - DNS resolution (if hostname is provided)
1286/// - TCP connection establishment
1287/// - Automatic reconnection on failure with 5-second delay
1288/// - Infinite retry loop
1289///
1290/// # Arguments
1291///
1292/// * `stack` - The Embassy network stack for TCP connections
1293/// * `device` - The Home Assistant device to run
1294/// * `address` - MQTT broker address in one of these formats:
1295/// - `"192.168.1.100"` - IPv4 address (uses default port 1883)
1296/// - `"192.168.1.100:1883"` - IPv4 address with explicit port
1297/// - `"mqtt.example.com"` - Hostname (uses default port 1883)
1298/// - `"mqtt.example.com:1883"` - Hostname with explicit port
1299///
1300/// # Returns
1301///
1302/// This function never returns normally (returns `!`). It runs indefinitely, automatically
1303/// reconnecting on any error.
1304///
1305/// # Example
1306///
1307/// ```no_run
1308/// # use embassy_executor::Spawner;
1309/// # use embassy_ha::{Device, DeviceConfig};
1310/// # use static_cell::StaticCell;
1311/// # static HA_RESOURCES: StaticCell<embassy_ha::DeviceResources> = StaticCell::new();
1312/// #[embassy_executor::task]
1313/// async fn ha_task(stack: embassy_net::Stack<'static>) {
1314/// let device = embassy_ha::new(
1315/// HA_RESOURCES.init(Default::default()),
1316/// DeviceConfig {
1317/// device_id: "my-device",
1318/// device_name: "My Device",
1319/// manufacturer: "ACME",
1320/// model: "X",
1321/// },
1322/// );
1323///
1324/// // This function never returns
1325/// embassy_ha::connect_and_run(stack, device, "mqtt.example.com:1883").await;
1326/// }
1327/// ```
1159pub async fn connect_and_run( 1328pub async fn connect_and_run(
1160 stack: embassy_net::Stack<'_>, 1329 stack: embassy_net::Stack<'_>,
1161 mut device: Device<'_>, 1330 mut device: Device<'_>,