aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordiogo464 <[email protected]>2025-12-14 14:51:08 +0000
committerdiogo464 <[email protected]>2025-12-14 14:51:08 +0000
commitf6331eec208b2160dce93a6e2d95162d19273cc1 (patch)
treedb309557c5b5d3b260ef869f7eec444e0eb31b78
parentc4f74c992b93f6fbcf8d41f77752942225e97457 (diff)
parenta16dbf1d55a6f5b28236eef566eea0f44524840c (diff)
Merge branch 'main' into embassy-git
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--README.md105
-rw-r--r--README.tpl15
-rwxr-xr-xgenerate-readme.sh5
-rw-r--r--src/command_policy.rs79
-rw-r--r--src/entity_number.rs10
-rw-r--r--src/entity_switch.rs10
-rw-r--r--src/lib.rs199
9 files changed, 394 insertions, 33 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d2b41f9..ec04651 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -175,7 +175,7 @@ source = "git+https://github.com/embassy-rs/embassy#d2740f8fad566f30bed24df970f1
175 175
176[[package]] 176[[package]]
177name = "embassy-ha" 177name = "embassy-ha"
178version = "0.1.1" 178version = "0.2.0"
179dependencies = [ 179dependencies = [
180 "critical-section", 180 "critical-section",
181 "defmt 1.0.1", 181 "defmt 1.0.1",
diff --git a/Cargo.toml b/Cargo.toml
index db4c574..3874ce3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
1[package] 1[package]
2name = "embassy-ha" 2name = "embassy-ha"
3version = "0.1.1" 3version = "0.2.0"
4edition = "2024" 4edition = "2024"
5authors = ["diogo464 <[email protected]>"] 5authors = ["diogo464 <[email protected]>"]
6description = "MQTT Home Assistant integration library for Embassy async runtime" 6description = "MQTT Home Assistant integration library for Embassy async runtime"
diff --git a/README.md b/README.md
index 4b8f818..7529443 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,102 @@
1# embassy-ha 1# embassy-ha
2 2
3Home Assistant MQTT device library for embassy. 3[![Crates.io](https://img.shields.io/crates/v/embassy-ha.svg)](https://crates.io/crates/embassy-ha)
4[![Documentation](https://docs.rs/embassy-ha/badge.svg)](https://docs.rs/embassy-ha)
4 5
5To create a device use the [`new`] function. 6MQTT Home Assistant integration library for the [Embassy](https://embassy.dev/) async runtime.
6 7
7After the device is created you should create one or more entities using functions such as 8## Features
8[`create_button`]/[`create_sensor`]/...
9 9
10Once the entities have been created either [`run`] or [`connect_and_run`] should be called in a 10- Support for multiple entity types: sensors, buttons, switches, binary sensors, numbers, device trackers
11seperate task. 11- Built on top of Embassy's async runtime for embedded systems
12- No-std compatible
13- Automatic MQTT discovery for Home Assistant
14- No runtime allocation
12 15
13There are various examples you can run locally (ex: `cargo run --features tracing --example 16## Installation
14button`) assuming you have a home assistant instance running. To run the examples the
15environment variable `MQTT_ADDRESS` should be set to the mqtt server used by home assistant.
16 17
17License: MIT OR Apache-2.0 18```bash
19cargo add embassy-ha
20```
21
22## Quick Start
23
24This example does not compile as-is because it requires device-specific setup, but it should
25be easy to adapt if you already have Embassy running on your microcontroller.
26
27```rust
28use embassy_executor::Spawner;
29use embassy_ha::{DeviceConfig, SensorConfig, SensorClass, StateClass};
30use embassy_time::Timer;
31use static_cell::StaticCell;
32
33static HA_RESOURCES: StaticCell<embassy_ha::DeviceResources> = StaticCell::new();
34
35#[embassy_executor::main]
36async fn main(spawner: Spawner) {
37 // Initialize your network stack
38 // This is device specific
39 let stack: embassy_net::Stack<'static>;
40
41 // Create a Home Assistant device
42 let device = embassy_ha::new(
43 HA_RESOURCES.init(Default::default()),
44 DeviceConfig {
45 device_id: "my-device",
46 device_name: "My Device",
47 manufacturer: "ACME Corp",
48 model: "Model X",
49 },
50 );
51
52 // Create a temperature sensor
53 let sensor_config = SensorConfig {
54 class: SensorClass::Temperature,
55 state_class: StateClass::Measurement,
56 unit: Some(embassy_ha::constants::HA_UNIT_TEMPERATURE_CELSIUS),
57 ..Default::default()
58 };
59 let mut sensor = embassy_ha::create_sensor(&device, "temp-sensor", sensor_config);
60
61 // Spawn the Home Assistant communication task
62 spawner.spawn(ha_task(stack, device)).unwrap();
63
64 // Main loop - read and publish temperature
65 loop {
66 // let temperature = read_temperature().await;
67 sensor.publish(temperature);
68 Timer::after_secs(60).await;
69 }
70}
71
72#[embassy_executor::task]
73async fn ha_task(stack: embassy_net::Stack<'static>, device: embassy_ha::Device<'static>) {
74 embassy_ha::connect_and_run(stack, device, "mqtt-broker-address").await;
75}
76```
77
78## Examples
79
80The repository includes several examples demonstrating different entity types. To run an example:
81
82```bash
83export MQTT_ADDRESS="mqtt://your-mqtt-broker:1883"
84cargo run --example sensor
85```
86
87Available examples:
88- `sensor` - Temperature and humidity sensors
89- `button` - Triggerable button entity
90- `switch` - On/off switch control
91- `binary_sensor` - Binary state sensor
92- `number` - Numeric input entity
93- `device_tracker` - Location tracking entity
94
95## License
96
97Licensed under either of:
98
99- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
100- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
101
102at your option.
diff --git a/README.tpl b/README.tpl
new file mode 100644
index 0000000..dba64ff
--- /dev/null
+++ b/README.tpl
@@ -0,0 +1,15 @@
1# {{crate}}
2
3[![Crates.io](https://img.shields.io/crates/v/embassy-ha.svg)](https://crates.io/crates/embassy-ha)
4[![Documentation](https://docs.rs/embassy-ha/badge.svg)](https://docs.rs/embassy-ha)
5
6{{readme}}
7
8## License
9
10Licensed under either of:
11
12- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
13- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
14
15at your option.
diff --git a/generate-readme.sh b/generate-readme.sh
new file mode 100755
index 0000000..18c2123
--- /dev/null
+++ b/generate-readme.sh
@@ -0,0 +1,5 @@
1#!/bin/bash
2set -e
3
4cargo readme -o README.md
5echo "README.md generated successfully"
diff --git a/src/command_policy.rs b/src/command_policy.rs
new file mode 100644
index 0000000..049d56a
--- /dev/null
+++ b/src/command_policy.rs
@@ -0,0 +1,79 @@
1/// Determines how an entity handles commands received from Home Assistant.
2///
3/// This policy controls whether an entity automatically publishes its state when it receives
4/// a command from Home Assistant, or if the application should handle state updates manually.
5///
6/// # Variants
7///
8/// ## `PublishState` (Default)
9///
10/// When a command is received from Home Assistant, the entity automatically:
11/// 1. Updates its internal state to match the command value
12/// 2. Publishes the new state back to Home Assistant
13///
14/// This is useful for simple entities where the command should immediately be reflected as the
15/// current state, such as:
16/// - A switch that turns on/off immediately when commanded
17/// - A number input that updates its value when changed in the UI
18///
19/// ## `Manual`
20///
21/// When a command is received from Home Assistant, the entity:
22/// 1. Stores the command for the application to read via `wait()` or `command()`
23/// 2. Does NOT automatically update or publish the state
24///
25/// The application must manually update the entity's state after processing the command.
26/// This is useful when:
27/// - The command triggers an action that may fail (e.g., turning on a motor)
28/// - The actual state may differ from the commanded state
29/// - You need to validate or transform the command before applying it
30///
31/// # Examples
32///
33/// ## Auto-publish (default)
34///
35/// ```no_run
36/// # use embassy_ha::{CommandPolicy, SwitchConfig};
37/// let config = SwitchConfig {
38/// command_policy: CommandPolicy::PublishState, // or just use default
39/// ..Default::default()
40/// };
41/// // When Home Assistant sends "ON", the switch state automatically becomes "ON"
42/// ```
43///
44/// ## Manual control
45///
46/// ```no_run
47/// # use embassy_ha::{CommandPolicy, SwitchConfig, BinaryState, Switch};
48/// # async fn example(mut switch: Switch<'_>) {
49/// let config = SwitchConfig {
50/// command_policy: CommandPolicy::Manual,
51/// ..Default::default()
52/// };
53///
54/// loop {
55/// let command = switch.wait().await;
56///
57/// // Try to perform the action
58/// if turn_on_motor().await.is_ok() {
59/// // Only update state if the action succeeded
60/// switch.set(command);
61/// }
62/// }
63/// # }
64/// # async fn turn_on_motor() -> Result<(), ()> { Ok(()) }
65/// ```
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum CommandPolicy {
68 /// Automatically publish the entity's state when a command is received.
69 PublishState,
70
71 /// Do not automatically publish state. The application must manually update the state.
72 Manual,
73}
74
75impl Default for CommandPolicy {
76 fn default() -> Self {
77 Self::PublishState
78 }
79}
diff --git a/src/entity_number.rs b/src/entity_number.rs
index e2a89c1..1573000 100644
--- a/src/entity_number.rs
+++ b/src/entity_number.rs
@@ -1,5 +1,6 @@
1use crate::{ 1use crate::{
2 Entity, EntityCommonConfig, EntityConfig, NumberCommand, NumberState, NumberUnit, constants, 2 CommandPolicy, Entity, EntityCommonConfig, EntityConfig, NumberCommand, NumberState,
3 NumberUnit, constants,
3}; 4};
4 5
5#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] 6#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
@@ -61,6 +62,9 @@ pub enum NumberClass {
61 WindSpeed, 62 WindSpeed,
62} 63}
63 64
65/// Configuration for a number entity.
66///
67/// See [`CommandPolicy`] for details on how commands are handled.
64#[derive(Debug)] 68#[derive(Debug)]
65pub struct NumberConfig { 69pub struct NumberConfig {
66 pub common: EntityCommonConfig, 70 pub common: EntityCommonConfig,
@@ -70,7 +74,7 @@ pub struct NumberConfig {
70 pub step: Option<f32>, 74 pub step: Option<f32>,
71 pub mode: NumberMode, 75 pub mode: NumberMode,
72 pub class: NumberClass, 76 pub class: NumberClass,
73 pub publish_on_command: bool, 77 pub command_policy: CommandPolicy,
74} 78}
75 79
76impl Default for NumberConfig { 80impl Default for NumberConfig {
@@ -83,7 +87,7 @@ impl Default for NumberConfig {
83 step: None, 87 step: None,
84 mode: NumberMode::Auto, 88 mode: NumberMode::Auto,
85 class: NumberClass::Generic, 89 class: NumberClass::Generic,
86 publish_on_command: true, 90 command_policy: CommandPolicy::default(),
87 } 91 }
88 } 92 }
89} 93}
diff --git a/src/entity_switch.rs b/src/entity_switch.rs
index 299d299..c1531eb 100644
--- a/src/entity_switch.rs
+++ b/src/entity_switch.rs
@@ -1,5 +1,6 @@
1use crate::{ 1use crate::{
2 BinaryState, Entity, EntityCommonConfig, EntityConfig, SwitchCommand, SwitchState, constants, 2 BinaryState, CommandPolicy, Entity, EntityCommonConfig, EntityConfig, SwitchCommand,
3 SwitchState, constants,
3}; 4};
4 5
5#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] 6#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
@@ -10,11 +11,14 @@ pub enum SwitchClass {
10 Switch, 11 Switch,
11} 12}
12 13
14/// Configuration for a switch entity.
15///
16/// See [`CommandPolicy`] for details on how commands are handled.
13#[derive(Debug)] 17#[derive(Debug)]
14pub struct SwitchConfig { 18pub struct SwitchConfig {
15 pub common: EntityCommonConfig, 19 pub common: EntityCommonConfig,
16 pub class: SwitchClass, 20 pub class: SwitchClass,
17 pub publish_on_command: bool, 21 pub command_policy: CommandPolicy,
18} 22}
19 23
20impl Default for SwitchConfig { 24impl Default for SwitchConfig {
@@ -22,7 +26,7 @@ impl Default for SwitchConfig {
22 Self { 26 Self {
23 common: Default::default(), 27 common: Default::default(),
24 class: Default::default(), 28 class: Default::default(),
25 publish_on_command: true, 29 command_policy: CommandPolicy::default(),
26 } 30 }
27 } 31 }
28} 32}
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<'_>,