aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordiogo464 <[email protected]>2025-12-04 16:58:57 +0000
committerdiogo464 <[email protected]>2025-12-04 16:58:57 +0000
commit4a9d786fa776c1601f3f9e28870225a33c908604 (patch)
tree85d731b4b584005307444a81fd22fc7e93a58f93
parent945f38391052773c6b16e54006a434ff7c9f5d98 (diff)
added number example
-rw-r--r--examples/number.rs40
-rw-r--r--src/constants.rs27
-rw-r--r--src/lib.rs417
3 files changed, 149 insertions, 335 deletions
diff --git a/examples/number.rs b/examples/number.rs
new file mode 100644
index 0000000..78307d6
--- /dev/null
+++ b/examples/number.rs
@@ -0,0 +1,40 @@
1mod common;
2
3use common::AsyncTcp;
4use embassy_executor::{Executor, Spawner};
5use embassy_time::Timer;
6use static_cell::StaticCell;
7
8static RESOURCES: StaticCell<embassy_ha::DeviceResources> = StaticCell::new();
9
10#[embassy_executor::task]
11async fn main_task(spawner: Spawner) {
12 let mut stream = AsyncTcp::connect(std::env!("MQTT_ADDRESS"));
13
14 let mut device = embassy_ha::Device::new(
15 RESOURCES.init(Default::default()),
16 embassy_ha::DeviceConfig {
17 device_id: "example-device-id",
18 device_name: "Example Device Name",
19 manufacturer: "Example Device Manufacturer",
20 model: "Example Device Model",
21 },
22 );
23
24 let number = device.create_number("number-id", "Number Name");
25
26 spawner.must_spawn(number_task(number));
27
28 device.run(&mut stream).await;
29}
30
31#[embassy_executor::task]
32async fn number_task(mut number: embassy_ha::Number<'static>) {
33 loop {
34 let value = number.value_wait().await;
35 number.value_set(value);
36 Timer::after_secs(1).await;
37 }
38}
39
40example_main!();
diff --git a/src/constants.rs b/src/constants.rs
index ca4e53c..44ba09e 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -6,6 +6,11 @@ pub const HA_DOMAIN_SWITCH: &str = "switch";
6pub const HA_DOMAIN_LIGHT: &str = "light"; 6pub const HA_DOMAIN_LIGHT: &str = "light";
7pub const HA_DOMAIN_BUTTON: &str = "button"; 7pub const HA_DOMAIN_BUTTON: &str = "button";
8pub const HA_DOMAIN_SELECT: &str = "select"; 8pub const HA_DOMAIN_SELECT: &str = "select";
9pub const HA_DOMAIN_NUMBER: &str = "number";
10
11pub const HA_NUMBER_MODE_AUTO: &str = "auto";
12pub const HA_NUMBER_MODE_BOX: &str = "box";
13pub const HA_NUMBER_MODE_SLIDER: &str = "slider";
9 14
10pub const HA_DEVICE_CLASS_SENSOR_APPARENT_POWER: &str = "apparent_power"; 15pub const HA_DEVICE_CLASS_SENSOR_APPARENT_POWER: &str = "apparent_power";
11pub const HA_DEVICE_CLASS_SENSOR_AQI: &str = "aqi"; 16pub const HA_DEVICE_CLASS_SENSOR_AQI: &str = "aqi";
@@ -99,3 +104,25 @@ pub const HA_DEVICE_CLASS_SWITCH_SWITCH: &str = "switch";
99pub const HA_UNIT_TEMPERATURE_CELSIUS: &str = "°C"; 104pub const HA_UNIT_TEMPERATURE_CELSIUS: &str = "°C";
100pub const HA_UNIT_TEMPERATURE_KELVIN: &str = "K"; 105pub const HA_UNIT_TEMPERATURE_KELVIN: &str = "K";
101pub const HA_UNIT_TEMPERATURE_FAHRENHEIT: &str = "°F"; 106pub const HA_UNIT_TEMPERATURE_FAHRENHEIT: &str = "°F";
107
108pub const HA_UNIT_TIME_MILLISECONDS: &str = "ms";
109pub const HA_UNIT_TIME_SECONDS: &str = "s";
110pub const HA_UNIT_TIME_MINUTES: &str = "min";
111pub const HA_UNIT_TIME_HOURS: &str = "h";
112pub const HA_UNIT_TIME_DAYS: &str = "d";
113
114pub const HA_UNIT_PERCENTAGE: &str = "%";
115
116pub const HA_UNIT_POWER_WATT: &str = "W";
117pub const HA_UNIT_POWER_KILOWATT: &str = "kW";
118
119pub const HA_UNIT_VOLTAGE_VOLT: &str = "V";
120pub const HA_UNIT_CURRENT_AMPERE: &str = "A";
121
122pub const HA_UNIT_DISTANCE_MILLIMETER: &str = "mm";
123pub const HA_UNIT_DISTANCE_CENTIMETER: &str = "cm";
124pub const HA_UNIT_DISTANCE_METER: &str = "m";
125pub const HA_UNIT_DISTANCE_KILOMETER: &str = "km";
126
127pub const HA_ENTITY_CATEGORY_CONFIG: &str = "config";
128pub const HA_ENTITY_CATEGORY_DIAGNOSTIC: &str = "diagnostic";
diff --git a/src/lib.rs b/src/lib.rs
index d1d665d..1f9d2d5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,14 +1,8 @@
1#![no_std] 1#![no_std]
2 2
3use core::{ 3use core::{cell::RefCell, task::Waker};
4 cell::RefCell,
5 net::SocketAddrV4,
6 sync::atomic::{AtomicBool, AtomicU32},
7 task::Waker,
8};
9 4
10use defmt::Format; 5use defmt::Format;
11use embassy_net::tcp::TcpSocket;
12use embassy_sync::waitqueue::AtomicWaker; 6use embassy_sync::waitqueue::AtomicWaker;
13use embassy_time::Timer; 7use embassy_time::Timer;
14use heapless::{ 8use heapless::{
@@ -17,166 +11,13 @@ use heapless::{
17}; 11};
18use serde::Serialize; 12use serde::Serialize;
19 13
20mod constants; 14pub mod constants;
21mod transport; 15mod transport;
22mod unit; 16mod unit;
23 17
24pub use constants::*;
25pub use transport::Transport; 18pub use transport::Transport;
26pub use unit::*; 19pub use unit::*;
27 20
28enum Unit {
29 Temperature(TemperatureUnit),
30}
31
32impl Unit {
33 fn as_str(&self) -> &'static str {
34 match self {
35 Unit::Temperature(unit) => unit.as_str(),
36 }
37 }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub enum ComponentType {
42 Sensor,
43 BinarySensor,
44}
45
46impl core::fmt::Display for ComponentType {
47 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
48 f.write_str(self.as_str())
49 }
50}
51
52impl ComponentType {
53 fn as_str(&self) -> &'static str {
54 match self {
55 ComponentType::Sensor => "sensor",
56 ComponentType::BinarySensor => "binary_sensor",
57 }
58 }
59}
60
61// TODO: see what classes need this and defaults
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum StateClass {
64 Measurement,
65 Total,
66 TotalIncreasing,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum DeviceClass {
71 Temperature {
72 unit: TemperatureUnit,
73 },
74 Humidity {
75 unit: HumidityUnit,
76 },
77
78 // binary sensors
79 Door,
80 Window,
81 Motion,
82 Occupancy,
83 Opening,
84 Plug,
85 Presence,
86 Problem,
87 Safety,
88 Smoke,
89 Sound,
90 Vibration,
91
92 Battery {
93 unit: BatteryUnit,
94 },
95 Illuminance {
96 unit: LightUnit,
97 },
98 Pressure {
99 unit: PressureUnit,
100 },
101 Generic {
102 device_class: Option<&'static str>,
103 unit: Option<&'static str>,
104 },
105 Energy {
106 unit: EnergyUnit,
107 },
108}
109
110impl DeviceClass {
111 fn tag(&self) -> &'static str {
112 match self {
113 DeviceClass::Temperature { .. } => "temperature",
114 DeviceClass::Humidity { .. } => "humidity",
115 _ => todo!(),
116 }
117 }
118
119 fn unit_of_measurement(&self) -> Option<Unit> {
120 // TODO: fix
121 Some(Unit::Temperature(TemperatureUnit::Celcius))
122 }
123
124 fn component_type(&self) -> ComponentType {
125 match self {
126 DeviceClass::Temperature { .. } => ComponentType::Sensor,
127 DeviceClass::Humidity { .. } => ComponentType::Sensor,
128 DeviceClass::Door => ComponentType::BinarySensor,
129 DeviceClass::Window => ComponentType::BinarySensor,
130 _ => todo!(),
131 }
132 }
133}
134
135pub trait Entity {
136 // TODO: possibly collapse all these functions into a single one that returns a struct
137 fn id(&self) -> &'static str;
138 fn name(&self) -> &'static str;
139 fn device_class(&self) -> DeviceClass;
140 fn register_waker(&self, waker: &Waker);
141 fn value(&self) -> Option<StateValue>;
142}
143
144// TODO: figure out proper atomic orderings
145
146struct StateContainer {
147 dirty: AtomicBool,
148 waker: AtomicWaker,
149 value: StateContainerValue,
150}
151
152impl StateContainer {
153 const fn new(value: StateContainerValue) -> Self {
154 Self {
155 dirty: AtomicBool::new(false),
156 waker: AtomicWaker::new(),
157 value,
158 }
159 }
160
161 pub const fn new_u32() -> Self {
162 Self::new(StateContainerValue::U32(AtomicU32::new(0)))
163 }
164
165 pub const fn new_f32() -> Self {
166 Self::new(StateContainerValue::F32(AtomicU32::new(0)))
167 }
168}
169
170enum StateContainerValue {
171 U32(AtomicU32),
172 F32(AtomicU32),
173}
174
175pub enum StateValue {
176 U32(u32),
177 F32(f32),
178}
179
180#[derive(Debug, Format, Clone, Copy, Serialize)] 21#[derive(Debug, Format, Clone, Copy, Serialize)]
181struct DeviceDiscovery<'a> { 22struct DeviceDiscovery<'a> {
182 identifiers: &'a [&'a str], 23 identifiers: &'a [&'a str],
@@ -185,157 +26,6 @@ struct DeviceDiscovery<'a> {
185 model: &'a str, 26 model: &'a str,
186} 27}
187 28
188pub enum SensorKind {
189 Generic,
190 Temperature { unit: TemperatureUnit },
191 Humidity { unit: HumidityUnit },
192 // TODO: complete
193}
194
195impl SensorKind {
196 fn as_str(&self) -> &'static str {
197 match self {
198 SensorKind::Generic => "sensor",
199 SensorKind::Temperature { .. } => "temperature",
200 SensorKind::Humidity { .. } => "humidity",
201 }
202 }
203}
204
205impl core::fmt::Display for SensorKind {
206 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
207 f.write_str(self.as_str())
208 }
209}
210
211enum BinarySensorKind {
212 Generic,
213 Motion,
214 Door,
215 Window,
216 Occupancy,
217 // TODO: complete
218}
219
220impl BinarySensorKind {
221 fn as_str(&self) -> &'static str {
222 match self {
223 BinarySensorKind::Generic => "binary_sensor",
224 BinarySensorKind::Motion => "motion",
225 BinarySensorKind::Door => "door",
226 BinarySensorKind::Window => "window",
227 BinarySensorKind::Occupancy => "occupancy",
228 }
229 }
230}
231
232impl core::fmt::Display for BinarySensorKind {
233 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
234 f.write_str(self.as_str())
235 }
236}
237
238enum SwitchKind {
239 Generic,
240 Outlet,
241 Switch,
242}
243
244impl SwitchKind {
245 fn as_str(&self) -> &'static str {
246 match self {
247 SwitchKind::Generic => "switch",
248 SwitchKind::Outlet => "outlet",
249 SwitchKind::Switch => "switch",
250 }
251 }
252}
253
254impl core::fmt::Display for SwitchKind {
255 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
256 f.write_str(self.as_str())
257 }
258}
259
260enum ButtonKind {
261 Generic,
262 Identify,
263 Restart,
264 Update,
265}
266
267impl ButtonKind {
268 fn as_str(&self) -> &'static str {
269 match self {
270 ButtonKind::Generic => "button",
271 ButtonKind::Identify => "identify",
272 ButtonKind::Restart => "restart",
273 ButtonKind::Update => "update",
274 }
275 }
276}
277
278impl core::fmt::Display for ButtonKind {
279 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
280 f.write_str(self.as_str())
281 }
282}
283
284enum NumberKind {
285 Generic,
286 // TODO: alot of different ones
287 // https://www.home-assistant.io/integrations/number
288}
289
290impl NumberKind {
291 fn as_str(&self) -> &'static str {
292 match self {
293 NumberKind::Generic => "number",
294 }
295 }
296}
297
298impl core::fmt::Display for NumberKind {
299 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
300 f.write_str(self.as_str())
301 }
302}
303
304// this is called the component type in the ha api
305pub enum EntityDomain {
306 Sensor(SensorKind),
307 BinarySensor(BinarySensorKind),
308 Switch(SwitchKind),
309 Light,
310 Button(ButtonKind),
311 Select,
312}
313
314impl core::fmt::Display for EntityDomain {
315 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
316 f.write_str(self.as_str())
317 }
318}
319
320impl EntityDomain {
321 fn as_str(&self) -> &'static str {
322 match self {
323 EntityDomain::Sensor(_) => "sensor",
324 EntityDomain::BinarySensor(_) => "binary_sensor",
325 EntityDomain::Switch(_) => "switch",
326 EntityDomain::Light => "light",
327 EntityDomain::Button(_) => "button",
328 EntityDomain::Select => "select",
329 }
330 }
331}
332
333#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
334enum EntityCategory {
335 Config,
336 Diagnostic,
337}
338
339#[derive(Debug, Format, Serialize)] 29#[derive(Debug, Format, Serialize)]
340struct EntityDiscovery<'a> { 30struct EntityDiscovery<'a> {
341 #[serde(rename = "unique_id")] 31 #[serde(rename = "unique_id")]
@@ -364,6 +54,18 @@ struct EntityDiscovery<'a> {
364 #[serde(skip_serializing_if = "Option::is_none")] 54 #[serde(skip_serializing_if = "Option::is_none")]
365 icon: Option<&'a str>, 55 icon: Option<&'a str>,
366 56
57 #[serde(skip_serializing_if = "Option::is_none")]
58 min: Option<f32>,
59
60 #[serde(skip_serializing_if = "Option::is_none")]
61 max: Option<f32>,
62
63 #[serde(skip_serializing_if = "Option::is_none")]
64 step: Option<f32>,
65
66 #[serde(skip_serializing_if = "Option::is_none")]
67 mode: Option<&'a str>,
68
367 device: &'a DeviceDiscovery<'a>, 69 device: &'a DeviceDiscovery<'a>,
368} 70}
369 71
@@ -430,8 +132,6 @@ pub struct DeviceResources {
430} 132}
431 133
432impl DeviceResources { 134impl DeviceResources {
433 const RX_BUFFER_LEN: usize = 2048;
434 const TX_BUFFER_LEN: usize = 2048;
435 const ENTITY_LIMIT: usize = 16; 135 const ENTITY_LIMIT: usize = 16;
436} 136}
437 137
@@ -452,7 +152,7 @@ impl Default for DeviceResources {
452 } 152 }
453} 153}
454 154
455pub struct TemperatureSensor<'a>(Entity2<'a>); 155pub struct TemperatureSensor<'a>(Entity<'a>);
456 156
457impl<'a> TemperatureSensor<'a> { 157impl<'a> TemperatureSensor<'a> {
458 pub fn publish(&mut self, temperature: f32) { 158 pub fn publish(&mut self, temperature: f32) {
@@ -462,7 +162,7 @@ impl<'a> TemperatureSensor<'a> {
462 } 162 }
463} 163}
464 164
465pub struct Button<'a>(Entity2<'a>); 165pub struct Button<'a>(Entity<'a>);
466 166
467impl<'a> Button<'a> { 167impl<'a> Button<'a> {
468 pub async fn pressed(&mut self) { 168 pub async fn pressed(&mut self) {
@@ -470,6 +170,35 @@ impl<'a> Button<'a> {
470 } 170 }
471} 171}
472 172
173pub struct Number<'a>(Entity<'a>);
174
175impl<'a> Number<'a> {
176 pub fn value(&mut self) -> Option<f32> {
177 self.0.with_data(|data| {
178 str::from_utf8(&data.command_value)
179 .ok()
180 .and_then(|v| v.parse::<f32>().ok())
181 })
182 }
183
184 pub async fn value_wait(&mut self) -> f32 {
185 loop {
186 self.0.wait_command().await;
187 match self.value() {
188 Some(value) => return value,
189 None => continue,
190 }
191 }
192 }
193
194 pub fn value_set(&mut self, value: f32) {
195 use core::fmt::Write;
196 self.0
197 .publish_with(|view| write!(view, "{}", value).unwrap());
198 }
199}
200
201#[derive(Default)]
473pub struct EntityConfig { 202pub struct EntityConfig {
474 pub id: &'static str, 203 pub id: &'static str,
475 pub name: &'static str, 204 pub name: &'static str,
@@ -480,6 +209,10 @@ pub struct EntityConfig {
480 pub category: Option<&'static str>, 209 pub category: Option<&'static str>,
481 pub state_class: Option<&'static str>, 210 pub state_class: Option<&'static str>,
482 pub schema: Option<&'static str>, 211 pub schema: Option<&'static str>,
212 pub min: Option<f32>,
213 pub max: Option<f32>,
214 pub step: Option<f32>,
215 pub mode: Option<&'static str>,
483} 216}
484 217
485struct EntityData { 218struct EntityData {
@@ -489,14 +222,15 @@ struct EntityData {
489 command_dirty: bool, 222 command_dirty: bool,
490 command_value: heapless::Vec<u8, 64>, 223 command_value: heapless::Vec<u8, 64>,
491 command_wait_waker: Option<Waker>, 224 command_wait_waker: Option<Waker>,
225 command_instant: Option<embassy_time::Instant>,
492} 226}
493 227
494pub struct Entity2<'a> { 228pub struct Entity<'a> {
495 data: &'a RefCell<Option<EntityData>>, 229 data: &'a RefCell<Option<EntityData>>,
496 waker: &'a AtomicWaker, 230 waker: &'a AtomicWaker,
497} 231}
498 232
499impl<'a> Entity2<'a> { 233impl<'a> Entity<'a> {
500 pub fn publish(&mut self, payload: &[u8]) { 234 pub fn publish(&mut self, payload: &[u8]) {
501 self.publish_with(|view| view.extend_from_slice(payload).unwrap()); 235 self.publish_with(|view| view.extend_from_slice(payload).unwrap());
502 } 236 }
@@ -514,7 +248,7 @@ impl<'a> Entity2<'a> {
514 } 248 }
515 249
516 pub async fn wait_command(&mut self) { 250 pub async fn wait_command(&mut self) {
517 struct Fut<'a, 'b>(&'a mut Entity2<'b>); 251 struct Fut<'a, 'b>(&'a mut Entity<'b>);
518 252
519 impl<'a, 'b> core::future::Future for Fut<'a, 'b> { 253 impl<'a, 'b> core::future::Future for Fut<'a, 'b> {
520 type Output = (); 254 type Output = ();
@@ -590,7 +324,7 @@ impl<'a> Device<'a> {
590 } 324 }
591 } 325 }
592 326
593 pub fn create_entity(&self, config: EntityConfig) -> Entity2<'a> { 327 pub fn create_entity(&self, config: EntityConfig) -> Entity<'a> {
594 let index = 'outer: { 328 let index = 'outer: {
595 for idx in 0..self.entities.len() { 329 for idx in 0..self.entities.len() {
596 if self.entities[idx].borrow().is_none() { 330 if self.entities[idx].borrow().is_none() {
@@ -607,10 +341,11 @@ impl<'a> Device<'a> {
607 command_dirty: false, 341 command_dirty: false,
608 command_value: Default::default(), 342 command_value: Default::default(),
609 command_wait_waker: None, 343 command_wait_waker: None,
344 command_instant: None,
610 }; 345 };
611 self.entities[index].replace(Some(data)); 346 self.entities[index].replace(Some(data));
612 347
613 Entity2 { 348 Entity {
614 data: &self.entities[index], 349 data: &self.entities[index],
615 waker: self.waker, 350 waker: self.waker,
616 } 351 }
@@ -625,13 +360,10 @@ impl<'a> Device<'a> {
625 let entity = self.create_entity(EntityConfig { 360 let entity = self.create_entity(EntityConfig {
626 id, 361 id,
627 name, 362 name,
628 domain: HA_DOMAIN_SENSOR, 363 domain: constants::HA_DOMAIN_SENSOR,
629 device_class: Some(HA_DEVICE_CLASS_SENSOR_TEMPERATURE), 364 device_class: Some(constants::HA_DEVICE_CLASS_SENSOR_TEMPERATURE),
630 measurement_unit: Some(unit.as_str()), 365 measurement_unit: Some(unit.as_str()),
631 icon: None, 366 ..Default::default()
632 category: None,
633 state_class: None,
634 schema: None,
635 }); 367 });
636 TemperatureSensor(entity) 368 TemperatureSensor(entity)
637 } 369 }
@@ -640,17 +372,27 @@ impl<'a> Device<'a> {
640 let entity = self.create_entity(EntityConfig { 372 let entity = self.create_entity(EntityConfig {
641 id, 373 id,
642 name, 374 name,
643 domain: HA_DOMAIN_BUTTON, 375 domain: constants::HA_DOMAIN_BUTTON,
644 device_class: None, 376 ..Default::default()
645 measurement_unit: None,
646 icon: None,
647 category: None,
648 state_class: None,
649 schema: None,
650 }); 377 });
651 Button(entity) 378 Button(entity)
652 } 379 }
653 380
381 pub fn create_number(&self, id: &'static str, name: &'static str) -> Number<'a> {
382 let entity = self.create_entity(EntityConfig {
383 id,
384 name,
385 domain: constants::HA_DOMAIN_NUMBER,
386 measurement_unit: Some("s"),
387 min: Some(0.0),
388 max: Some(200.0),
389 step: Some(2.0),
390 mode: Some(constants::HA_NUMBER_MODE_AUTO),
391 ..Default::default()
392 });
393 Number(entity)
394 }
395
654 pub async fn run<T: Transport>(&mut self, transport: &mut T) -> ! { 396 pub async fn run<T: Transport>(&mut self, transport: &mut T) -> ! {
655 loop { 397 loop {
656 self.run_iteration(&mut *transport).await; 398 self.run_iteration(&mut *transport).await;
@@ -732,6 +474,10 @@ impl<'a> Device<'a> {
732 schema: entity_config.schema, 474 schema: entity_config.schema,
733 state_class: entity_config.state_class, 475 state_class: entity_config.state_class,
734 icon: entity_config.icon, 476 icon: entity_config.icon,
477 min: entity_config.min,
478 max: entity_config.max,
479 step: entity_config.step,
480 mode: entity_config.mode,
735 device: &device_discovery, 481 device: &device_discovery,
736 }; 482 };
737 defmt::info!("discovery: {}", discovery); 483 defmt::info!("discovery: {}", discovery);
@@ -812,7 +558,8 @@ impl<'a> Device<'a> {
812 if let Some(entity) = entity.as_mut() { 558 if let Some(entity) = entity.as_mut() {
813 entity.command_dirty = true; 559 entity.command_dirty = true;
814 entity.command_value.clear(); 560 entity.command_value.clear();
815 entity.command_value.extend_from_slice(b"ON").unwrap(); 561 entity.command_value.extend_from_slice(b).unwrap();
562 entity.command_instant = Some(embassy_time::Instant::now());
816 if let Some(ref waker) = entity.command_wait_waker { 563 if let Some(ref waker) = entity.command_wait_waker {
817 waker.wake_by_ref(); 564 waker.wake_by_ref();
818 } 565 }