diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/command_policy.rs | 79 | ||||
| -rw-r--r-- | src/entity_number.rs | 10 | ||||
| -rw-r--r-- | src/entity_switch.rs | 10 | ||||
| -rw-r--r-- | src/lib.rs | 199 |
4 files changed, 277 insertions, 21 deletions
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)] | ||
| 67 | pub 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 | |||
| 75 | impl 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 @@ | |||
| 1 | use crate::{ | 1 | use 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)] |
| 65 | pub struct NumberConfig { | 69 | pub 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 | ||
| 76 | impl Default for NumberConfig { | 80 | impl 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 @@ | |||
| 1 | use crate::{ | 1 | use 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)] |
| 14 | pub struct SwitchConfig { | 18 | pub 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 | ||
| 20 | impl Default for SwitchConfig { | 24 | impl 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 | } |
| @@ -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; | |||
| 40 | mod binary_state; | 117 | mod binary_state; |
| 41 | pub use binary_state::*; | 118 | pub use binary_state::*; |
| 42 | 119 | ||
| 120 | mod command_policy; | ||
| 121 | pub use command_policy::*; | ||
| 122 | |||
| 43 | mod entity; | 123 | mod entity; |
| 44 | pub use entity::*; | 124 | pub use entity::*; |
| 45 | 125 | ||
| @@ -305,7 +385,7 @@ pub(crate) struct SwitchState { | |||
| 305 | pub(crate) struct SwitchStorage { | 385 | pub(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 { | |||
| 350 | pub(crate) struct NumberStorage { | 430 | pub(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`]. | ||
| 664 | pub async fn run<T: Transport>(device: &mut Device<'_>, transport: &mut T) -> Result<(), Error> { | 787 | pub 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 | /// ``` | ||
| 1159 | pub async fn connect_and_run( | 1328 | pub async fn connect_and_run( |
| 1160 | stack: embassy_net::Stack<'_>, | 1329 | stack: embassy_net::Stack<'_>, |
| 1161 | mut device: Device<'_>, | 1330 | mut device: Device<'_>, |
