diff options
| -rw-r--r-- | examples/number.rs | 4 | ||||
| -rw-r--r-- | src/binary_state.rs | 9 | ||||
| -rw-r--r-- | src/constants.rs | 2 | ||||
| -rw-r--r-- | src/entity_binary_sensor.rs | 25 | ||||
| -rw-r--r-- | src/entity_button.rs | 17 | ||||
| -rw-r--r-- | src/entity_number.rs | 34 | ||||
| -rw-r--r-- | src/entity_sensor.rs | 21 | ||||
| -rw-r--r-- | src/entity_switch.rs | 16 | ||||
| -rw-r--r-- | src/lib.rs | 325 |
9 files changed, 337 insertions, 116 deletions
diff --git a/examples/number.rs b/examples/number.rs index 4e7fe1c..5cad84b 100644 --- a/examples/number.rs +++ b/examples/number.rs | |||
| @@ -45,8 +45,8 @@ async fn main_task(spawner: Spawner) { | |||
| 45 | #[embassy_executor::task] | 45 | #[embassy_executor::task] |
| 46 | async fn number_task(mut number: embassy_ha::Number<'static>) { | 46 | async fn number_task(mut number: embassy_ha::Number<'static>) { |
| 47 | loop { | 47 | loop { |
| 48 | let value = number.value_wait().await; | 48 | let value = number.wait().await; |
| 49 | number.value_set(value); | 49 | number.set(value); |
| 50 | Timer::after_secs(1).await; | 50 | Timer::after_secs(1).await; |
| 51 | } | 51 | } |
| 52 | } | 52 | } |
diff --git a/src/binary_state.rs b/src/binary_state.rs index d512856..5648a18 100644 --- a/src/binary_state.rs +++ b/src/binary_state.rs | |||
| @@ -2,8 +2,17 @@ use core::str::FromStr; | |||
| 2 | 2 | ||
| 3 | use crate::constants; | 3 | use crate::constants; |
| 4 | 4 | ||
| 5 | #[derive(Debug)] | ||
| 5 | pub struct InvalidBinaryState; | 6 | pub struct InvalidBinaryState; |
| 6 | 7 | ||
| 8 | impl core::fmt::Display for InvalidBinaryState { | ||
| 9 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 10 | f.write_str("invalid binary state, allowed values are 'ON' and 'OFF' (case insensitive)") | ||
| 11 | } | ||
| 12 | } | ||
| 13 | |||
| 14 | impl core::error::Error for InvalidBinaryState {} | ||
| 15 | |||
| 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 16 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 8 | pub enum BinaryState { | 17 | pub enum BinaryState { |
| 9 | On, | 18 | On, |
diff --git a/src/constants.rs b/src/constants.rs index 8c48bed..2b1a8bf 100644 --- a/src/constants.rs +++ b/src/constants.rs | |||
| @@ -200,6 +200,8 @@ pub const HA_SWITCH_STATE_OFF: &str = "OFF"; | |||
| 200 | pub const HA_BINARY_SENSOR_STATE_ON: &str = "ON"; | 200 | pub const HA_BINARY_SENSOR_STATE_ON: &str = "ON"; |
| 201 | pub const HA_BINARY_SENSOR_STATE_OFF: &str = "OFF"; | 201 | pub const HA_BINARY_SENSOR_STATE_OFF: &str = "OFF"; |
| 202 | 202 | ||
| 203 | pub const HA_BUTTON_PAYLOAD_PRESS: &str = "PRESS"; | ||
| 204 | |||
| 203 | // Number units - Energy | 205 | // Number units - Energy |
| 204 | pub const HA_UNIT_ENERGY_JOULE: &str = "J"; | 206 | pub const HA_UNIT_ENERGY_JOULE: &str = "J"; |
| 205 | pub const HA_UNIT_ENERGY_KILOJOULE: &str = "kJ"; | 207 | pub const HA_UNIT_ENERGY_KILOJOULE: &str = "kJ"; |
diff --git a/src/entity_binary_sensor.rs b/src/entity_binary_sensor.rs index b80f718..ea270f2 100644 --- a/src/entity_binary_sensor.rs +++ b/src/entity_binary_sensor.rs | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | use crate::{BinaryState, Entity, EntityCommonConfig, EntityConfig, constants}; | 1 | use crate::{BinarySensorState, BinaryState, Entity, EntityCommonConfig, EntityConfig, constants}; |
| 2 | 2 | ||
| 3 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] | 3 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] |
| 4 | pub enum BinarySensorClass { | 4 | pub enum BinarySensorClass { |
| @@ -66,13 +66,28 @@ impl<'a> BinarySensor<'a> { | |||
| 66 | } | 66 | } |
| 67 | 67 | ||
| 68 | pub fn set(&mut self, state: BinaryState) { | 68 | pub fn set(&mut self, state: BinaryState) { |
| 69 | self.0.publish(state.as_str().as_bytes()); | 69 | let publish = self.0.with_data(|data| { |
| 70 | let storage = data.storage.as_binary_sensor_mut(); | ||
| 71 | let publish = match &storage.state { | ||
| 72 | Some(s) => s.value != state, | ||
| 73 | None => true, | ||
| 74 | }; | ||
| 75 | storage.state = Some(BinarySensorState { | ||
| 76 | value: state, | ||
| 77 | timestamp: embassy_time::Instant::now(), | ||
| 78 | }); | ||
| 79 | publish | ||
| 80 | }); | ||
| 81 | if publish { | ||
| 82 | self.0.queue_publish(); | ||
| 83 | } | ||
| 70 | } | 84 | } |
| 71 | 85 | ||
| 72 | pub fn value(&self) -> Option<BinaryState> { | 86 | pub fn value(&self) -> Option<BinaryState> { |
| 73 | self.0 | 87 | self.0.with_data(|data| { |
| 74 | .with_data(|data| BinaryState::try_from(data.publish_value.as_slice())) | 88 | let storage = data.storage.as_binary_sensor_mut(); |
| 75 | .ok() | 89 | storage.state.as_ref().map(|s| s.value) |
| 90 | }) | ||
| 76 | } | 91 | } |
| 77 | 92 | ||
| 78 | pub fn toggle(&mut self) -> BinaryState { | 93 | pub fn toggle(&mut self) -> BinaryState { |
diff --git a/src/entity_button.rs b/src/entity_button.rs index baa89a4..35f787f 100644 --- a/src/entity_button.rs +++ b/src/entity_button.rs | |||
| @@ -36,6 +36,21 @@ impl<'a> Button<'a> { | |||
| 36 | } | 36 | } |
| 37 | 37 | ||
| 38 | pub async fn pressed(&mut self) { | 38 | pub async fn pressed(&mut self) { |
| 39 | self.0.wait_command().await; | 39 | loop { |
| 40 | self.0.wait_command().await; | ||
| 41 | let pressed = self.0.with_data(|data| { | ||
| 42 | let storage = data.storage.as_button_mut(); | ||
| 43 | if !storage.consumed && storage.timestamp.is_some() { | ||
| 44 | storage.consumed = true; | ||
| 45 | true | ||
| 46 | } else { | ||
| 47 | false | ||
| 48 | } | ||
| 49 | }); | ||
| 50 | |||
| 51 | if pressed { | ||
| 52 | break; | ||
| 53 | } | ||
| 54 | } | ||
| 40 | } | 55 | } |
| 41 | } | 56 | } |
diff --git a/src/entity_number.rs b/src/entity_number.rs index 90d849c..f96c3c7 100644 --- a/src/entity_number.rs +++ b/src/entity_number.rs | |||
| @@ -1,4 +1,6 @@ | |||
| 1 | use crate::{Entity, EntityCommonConfig, EntityConfig, NumberUnit, constants}; | 1 | use crate::{ |
| 2 | Entity, EntityCommonConfig, EntityConfig, NumberCommand, NumberState, NumberUnit, constants, | ||
| 3 | }; | ||
| 2 | 4 | ||
| 3 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] | 5 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] |
| 4 | pub enum NumberMode { | 6 | pub enum NumberMode { |
| @@ -167,27 +169,37 @@ impl<'a> Number<'a> { | |||
| 167 | Self(entity) | 169 | Self(entity) |
| 168 | } | 170 | } |
| 169 | 171 | ||
| 170 | pub fn value(&mut self) -> Option<f32> { | 172 | pub fn get(&mut self) -> Option<f32> { |
| 171 | self.0.with_data(|data| { | 173 | self.0.with_data(|data| { |
| 172 | str::from_utf8(&data.command_value) | 174 | let storage = data.storage.as_number_mut(); |
| 173 | .ok() | 175 | storage.state.as_ref().map(|s| s.value) |
| 174 | .and_then(|v| v.parse::<f32>().ok()) | ||
| 175 | }) | 176 | }) |
| 176 | } | 177 | } |
| 177 | 178 | ||
| 178 | pub async fn value_wait(&mut self) -> f32 { | 179 | pub async fn wait(&mut self) -> f32 { |
| 179 | loop { | 180 | loop { |
| 180 | self.0.wait_command().await; | 181 | self.0.wait_command().await; |
| 181 | match self.value() { | 182 | match self.get() { |
| 182 | Some(value) => return value, | 183 | Some(value) => return value, |
| 183 | None => continue, | 184 | None => continue, |
| 184 | } | 185 | } |
| 185 | } | 186 | } |
| 186 | } | 187 | } |
| 187 | 188 | ||
| 188 | pub fn value_set(&mut self, value: f32) { | 189 | pub fn set(&mut self, value: f32) { |
| 189 | use core::fmt::Write; | 190 | let publish = self.0.with_data(|data| { |
| 190 | self.0 | 191 | let storage = data.storage.as_number_mut(); |
| 191 | .publish_with(|view| write!(view, "{}", value).unwrap()); | 192 | let timestamp = embassy_time::Instant::now(); |
| 193 | let publish = match &storage.command { | ||
| 194 | Some(command) => command.value != value, | ||
| 195 | None => true, | ||
| 196 | }; | ||
| 197 | storage.state = Some(NumberState { value, timestamp }); | ||
| 198 | storage.command = Some(NumberCommand { value, timestamp }); | ||
| 199 | publish | ||
| 200 | }); | ||
| 201 | if publish { | ||
| 202 | self.0.queue_publish(); | ||
| 203 | } | ||
| 192 | } | 204 | } |
| 193 | } | 205 | } |
diff --git a/src/entity_sensor.rs b/src/entity_sensor.rs index 5d7794f..d70e80e 100644 --- a/src/entity_sensor.rs +++ b/src/entity_sensor.rs | |||
| @@ -1,4 +1,6 @@ | |||
| 1 | use crate::{Entity, EntityCommonConfig, EntityConfig, TemperatureUnit, constants}; | 1 | use crate::{ |
| 2 | Entity, EntityCommonConfig, EntityConfig, NumericSensorState, TemperatureUnit, constants, | ||
| 3 | }; | ||
| 2 | 4 | ||
| 3 | #[derive(Debug, Default)] | 5 | #[derive(Debug, Default)] |
| 4 | pub struct TemperatureSensorConfig { | 6 | pub struct TemperatureSensorConfig { |
| @@ -23,8 +25,19 @@ impl<'a> TemperatureSensor<'a> { | |||
| 23 | } | 25 | } |
| 24 | 26 | ||
| 25 | pub fn publish(&mut self, temperature: f32) { | 27 | pub fn publish(&mut self, temperature: f32) { |
| 26 | use core::fmt::Write; | 28 | let publish = self.0.with_data(|data| { |
| 27 | self.0 | 29 | let storage = data.storage.as_numeric_sensor_mut(); |
| 28 | .publish_with(|view| write!(view, "{}", temperature).unwrap()); | 30 | let prev_state = storage.state.replace(NumericSensorState { |
| 31 | value: temperature, | ||
| 32 | timestamp: embassy_time::Instant::now(), | ||
| 33 | }); | ||
| 34 | match prev_state { | ||
| 35 | Some(state) => state.value != temperature, | ||
| 36 | None => true, | ||
| 37 | } | ||
| 38 | }); | ||
| 39 | if publish { | ||
| 40 | self.0.queue_publish(); | ||
| 41 | } | ||
| 29 | } | 42 | } |
| 30 | } | 43 | } |
diff --git a/src/entity_switch.rs b/src/entity_switch.rs index 4d2efdb..0277288 100644 --- a/src/entity_switch.rs +++ b/src/entity_switch.rs | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | use crate::{BinaryState, Entity, EntityCommonConfig, EntityConfig, constants}; | 1 | use crate::{BinaryState, Entity, EntityCommonConfig, EntityConfig, SwitchState, constants}; |
| 2 | 2 | ||
| 3 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] | 3 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] |
| 4 | pub enum SwitchClass { | 4 | pub enum SwitchClass { |
| @@ -34,8 +34,10 @@ impl<'a> Switch<'a> { | |||
| 34 | } | 34 | } |
| 35 | 35 | ||
| 36 | pub fn state(&self) -> Option<BinaryState> { | 36 | pub fn state(&self) -> Option<BinaryState> { |
| 37 | self.0 | 37 | self.0.with_data(|data| { |
| 38 | .with_data(|data| BinaryState::try_from(data.command_value.as_slice()).ok()) | 38 | let storage = data.storage.as_switch_mut(); |
| 39 | storage.state.as_ref().map(|s| s.value) | ||
| 40 | }) | ||
| 39 | } | 41 | } |
| 40 | 42 | ||
| 41 | pub fn toggle(&mut self) -> BinaryState { | 43 | pub fn toggle(&mut self) -> BinaryState { |
| @@ -45,7 +47,13 @@ impl<'a> Switch<'a> { | |||
| 45 | } | 47 | } |
| 46 | 48 | ||
| 47 | pub fn set(&mut self, state: BinaryState) { | 49 | pub fn set(&mut self, state: BinaryState) { |
| 48 | self.0.publish(state.as_str().as_bytes()); | 50 | self.0.with_data(|data| { |
| 51 | let storage = data.storage.as_switch_mut(); | ||
| 52 | storage.state = Some(SwitchState { | ||
| 53 | value: state, | ||
| 54 | timestamp: embassy_time::Instant::now(), | ||
| 55 | }); | ||
| 56 | }) | ||
| 49 | } | 57 | } |
| 50 | 58 | ||
| 51 | pub async fn wait(&mut self) -> BinaryState { | 59 | pub async fn wait(&mut self) -> BinaryState { |
| @@ -181,49 +181,133 @@ impl Default for DeviceResources { | |||
| 181 | } | 181 | } |
| 182 | } | 182 | } |
| 183 | 183 | ||
| 184 | struct EntityData { | 184 | #[derive(Debug, Default)] |
| 185 | config: EntityConfig, | 185 | pub struct ButtonStorage { |
| 186 | publish_dirty: bool, | 186 | pub timestamp: Option<embassy_time::Instant>, |
| 187 | publish_value: heapless::Vec<u8, 64>, | 187 | pub consumed: bool, |
| 188 | command_dirty: bool, | ||
| 189 | command_value: heapless::Vec<u8, 64>, | ||
| 190 | command_wait_waker: Option<Waker>, | ||
| 191 | command_instant: Option<embassy_time::Instant>, | ||
| 192 | } | 188 | } |
| 193 | 189 | ||
| 194 | pub struct Entity<'a> { | 190 | #[derive(Debug)] |
| 195 | pub(crate) data: &'a RefCell<Option<EntityData>>, | 191 | pub struct SwitchCommand { |
| 196 | pub(crate) waker: &'a AtomicWaker, | 192 | pub value: BinaryState, |
| 193 | pub timestamp: embassy_time::Instant, | ||
| 197 | } | 194 | } |
| 198 | 195 | ||
| 199 | impl<'a> Entity<'a> { | 196 | #[derive(Debug)] |
| 200 | pub fn publish(&mut self, payload: &[u8]) { | 197 | pub struct SwitchState { |
| 201 | self.publish_with(|view| view.extend_from_slice(payload).unwrap()); | 198 | pub value: BinaryState, |
| 199 | pub timestamp: embassy_time::Instant, | ||
| 200 | } | ||
| 201 | |||
| 202 | #[derive(Debug, Default)] | ||
| 203 | pub struct SwitchStorage { | ||
| 204 | pub state: Option<SwitchState>, | ||
| 205 | pub command: Option<SwitchCommand>, | ||
| 206 | } | ||
| 207 | |||
| 208 | #[derive(Debug)] | ||
| 209 | pub struct BinarySensorState { | ||
| 210 | pub value: BinaryState, | ||
| 211 | pub timestamp: embassy_time::Instant, | ||
| 212 | } | ||
| 213 | |||
| 214 | #[derive(Debug, Default)] | ||
| 215 | pub struct BinarySensorStorage { | ||
| 216 | pub state: Option<BinarySensorState>, | ||
| 217 | } | ||
| 218 | |||
| 219 | #[derive(Debug)] | ||
| 220 | pub struct NumericSensorState { | ||
| 221 | pub value: f32, | ||
| 222 | pub timestamp: embassy_time::Instant, | ||
| 223 | } | ||
| 224 | |||
| 225 | #[derive(Debug, Default)] | ||
| 226 | pub struct NumericSensorStorage { | ||
| 227 | pub state: Option<NumericSensorState>, | ||
| 228 | } | ||
| 229 | |||
| 230 | #[derive(Debug)] | ||
| 231 | pub struct NumberState { | ||
| 232 | pub value: f32, | ||
| 233 | pub timestamp: embassy_time::Instant, | ||
| 234 | } | ||
| 235 | |||
| 236 | #[derive(Debug)] | ||
| 237 | pub struct NumberCommand { | ||
| 238 | pub value: f32, | ||
| 239 | pub timestamp: embassy_time::Instant, | ||
| 240 | } | ||
| 241 | |||
| 242 | #[derive(Debug, Default)] | ||
| 243 | pub struct NumberStorage { | ||
| 244 | pub state: Option<NumberState>, | ||
| 245 | pub command: Option<NumberCommand>, | ||
| 246 | } | ||
| 247 | |||
| 248 | #[derive(Debug)] | ||
| 249 | pub enum EntityStorage { | ||
| 250 | Button(ButtonStorage), | ||
| 251 | Switch(SwitchStorage), | ||
| 252 | BinarySensor(BinarySensorStorage), | ||
| 253 | NumericSensor(NumericSensorStorage), | ||
| 254 | Number(NumberStorage), | ||
| 255 | } | ||
| 256 | |||
| 257 | impl EntityStorage { | ||
| 258 | pub fn as_button_mut(&mut self) -> &mut ButtonStorage { | ||
| 259 | match self { | ||
| 260 | EntityStorage::Button(storage) => storage, | ||
| 261 | _ => panic!("expected storage type to be button"), | ||
| 262 | } | ||
| 202 | } | 263 | } |
| 203 | 264 | ||
| 204 | pub fn publish_with<F>(&mut self, f: F) | 265 | pub fn as_switch_mut(&mut self) -> &mut SwitchStorage { |
| 205 | where | 266 | match self { |
| 206 | F: FnOnce(&mut VecView<u8>), | 267 | EntityStorage::Switch(storage) => storage, |
| 207 | { | 268 | _ => panic!("expected storage type to be switch"), |
| 208 | self.with_data(move |data| { | 269 | } |
| 209 | data.publish_value.clear(); | ||
| 210 | f(data.publish_value.as_mut_view()); | ||
| 211 | data.publish_dirty = true; | ||
| 212 | }); | ||
| 213 | self.waker.wake(); | ||
| 214 | } | 270 | } |
| 215 | 271 | ||
| 216 | pub fn publish_str(&mut self, payload: &str) { | 272 | pub fn as_binary_sensor_mut(&mut self) -> &mut BinarySensorStorage { |
| 217 | self.publish(payload.as_bytes()); | 273 | match self { |
| 274 | EntityStorage::BinarySensor(storage) => storage, | ||
| 275 | _ => panic!("expected storage type to be binary_sensor"), | ||
| 276 | } | ||
| 218 | } | 277 | } |
| 219 | 278 | ||
| 220 | pub fn publish_display(&mut self, payload: &impl core::fmt::Display) { | 279 | pub fn as_numeric_sensor_mut(&mut self) -> &mut NumericSensorStorage { |
| 221 | use core::fmt::Write; | 280 | match self { |
| 281 | EntityStorage::NumericSensor(storage) => storage, | ||
| 282 | _ => panic!("expected storage type to be numeric_sensor"), | ||
| 283 | } | ||
| 284 | } | ||
| 222 | 285 | ||
| 223 | self.publish_with(|view| { | 286 | pub fn as_number_mut(&mut self) -> &mut NumberStorage { |
| 224 | view.clear(); | 287 | match self { |
| 225 | write!(view, "{}", payload).unwrap(); | 288 | EntityStorage::Number(storage) => storage, |
| 226 | }); | 289 | _ => panic!("expected storage type to be number"), |
| 290 | } | ||
| 291 | } | ||
| 292 | } | ||
| 293 | |||
| 294 | struct EntityData { | ||
| 295 | config: EntityConfig, | ||
| 296 | storage: EntityStorage, | ||
| 297 | publish: bool, | ||
| 298 | command: bool, | ||
| 299 | command_waker: Option<Waker>, | ||
| 300 | } | ||
| 301 | |||
| 302 | pub struct Entity<'a> { | ||
| 303 | pub(crate) data: &'a RefCell<Option<EntityData>>, | ||
| 304 | pub(crate) waker: &'a AtomicWaker, | ||
| 305 | } | ||
| 306 | |||
| 307 | impl<'a> Entity<'a> { | ||
| 308 | pub fn queue_publish(&mut self) { | ||
| 309 | self.with_data(|data| data.publish = true); | ||
| 310 | self.waker.wake(); | ||
| 227 | } | 311 | } |
| 228 | 312 | ||
| 229 | pub async fn wait_command(&mut self) { | 313 | pub async fn wait_command(&mut self) { |
| @@ -238,14 +322,14 @@ impl<'a> Entity<'a> { | |||
| 238 | ) -> core::task::Poll<Self::Output> { | 322 | ) -> core::task::Poll<Self::Output> { |
| 239 | let this = &mut self.as_mut().0; | 323 | let this = &mut self.as_mut().0; |
| 240 | this.with_data(|data| { | 324 | this.with_data(|data| { |
| 241 | let dirty = data.command_dirty; | 325 | let dirty = data.command; |
| 242 | if dirty { | 326 | if dirty { |
| 243 | data.command_dirty = false; | 327 | data.command = false; |
| 244 | data.command_wait_waker = None; | 328 | data.command_waker = None; |
| 245 | core::task::Poll::Ready(()) | 329 | core::task::Poll::Ready(()) |
| 246 | } else { | 330 | } else { |
| 247 | // TODO: avoid clone if waker would wake | 331 | // TODO: avoid clone if waker would wake |
| 248 | data.command_wait_waker = Some(cx.waker().clone()); | 332 | data.command_waker = Some(cx.waker().clone()); |
| 249 | core::task::Poll::Pending | 333 | core::task::Poll::Pending |
| 250 | } | 334 | } |
| 251 | }) | 335 | }) |
| @@ -255,13 +339,6 @@ impl<'a> Entity<'a> { | |||
| 255 | Fut(self).await | 339 | Fut(self).await |
| 256 | } | 340 | } |
| 257 | 341 | ||
| 258 | pub fn with_command<F, R>(&mut self, f: F) -> R | ||
| 259 | where | ||
| 260 | F: FnOnce(&[u8]) -> R, | ||
| 261 | { | ||
| 262 | self.with_data(|data| f(data.command_value.as_slice())) | ||
| 263 | } | ||
| 264 | |||
| 265 | fn with_data<F, R>(&self, f: F) -> R | 342 | fn with_data<F, R>(&self, f: F) -> R |
| 266 | where | 343 | where |
| 267 | F: FnOnce(&mut EntityData) -> R, | 344 | F: FnOnce(&mut EntityData) -> R, |
| @@ -303,7 +380,7 @@ impl<'a> Device<'a> { | |||
| 303 | } | 380 | } |
| 304 | } | 381 | } |
| 305 | 382 | ||
| 306 | pub fn create_entity(&self, config: EntityConfig) -> Entity<'a> { | 383 | pub fn create_entity(&self, config: EntityConfig, storage: EntityStorage) -> Entity<'a> { |
| 307 | let index = 'outer: { | 384 | let index = 'outer: { |
| 308 | for idx in 0..self.entities.len() { | 385 | for idx in 0..self.entities.len() { |
| 309 | if self.entities[idx].borrow().is_none() { | 386 | if self.entities[idx].borrow().is_none() { |
| @@ -315,12 +392,10 @@ impl<'a> Device<'a> { | |||
| 315 | 392 | ||
| 316 | let data = EntityData { | 393 | let data = EntityData { |
| 317 | config, | 394 | config, |
| 318 | publish_dirty: false, | 395 | storage, |
| 319 | publish_value: Default::default(), | 396 | publish: false, |
| 320 | command_dirty: false, | 397 | command: false, |
| 321 | command_value: Default::default(), | 398 | command_waker: None, |
| 322 | command_wait_waker: None, | ||
| 323 | command_instant: None, | ||
| 324 | }; | 399 | }; |
| 325 | self.entities[index].replace(Some(data)); | 400 | self.entities[index].replace(Some(data)); |
| 326 | 401 | ||
| @@ -339,7 +414,10 @@ impl<'a> Device<'a> { | |||
| 339 | entity_config.id = id; | 414 | entity_config.id = id; |
| 340 | config.populate(&mut entity_config); | 415 | config.populate(&mut entity_config); |
| 341 | 416 | ||
| 342 | let entity = self.create_entity(entity_config); | 417 | let entity = self.create_entity( |
| 418 | entity_config, | ||
| 419 | EntityStorage::NumericSensor(Default::default()), | ||
| 420 | ); | ||
| 343 | TemperatureSensor::new(entity) | 421 | TemperatureSensor::new(entity) |
| 344 | } | 422 | } |
| 345 | 423 | ||
| @@ -348,7 +426,7 @@ impl<'a> Device<'a> { | |||
| 348 | entity_config.id = id; | 426 | entity_config.id = id; |
| 349 | config.populate(&mut entity_config); | 427 | config.populate(&mut entity_config); |
| 350 | 428 | ||
| 351 | let entity = self.create_entity(entity_config); | 429 | let entity = self.create_entity(entity_config, EntityStorage::Button(Default::default())); |
| 352 | Button::new(entity) | 430 | Button::new(entity) |
| 353 | } | 431 | } |
| 354 | 432 | ||
| @@ -357,7 +435,7 @@ impl<'a> Device<'a> { | |||
| 357 | entity_config.id = id; | 435 | entity_config.id = id; |
| 358 | config.populate(&mut entity_config); | 436 | config.populate(&mut entity_config); |
| 359 | 437 | ||
| 360 | let entity = self.create_entity(entity_config); | 438 | let entity = self.create_entity(entity_config, EntityStorage::Number(Default::default())); |
| 361 | Number::new(entity) | 439 | Number::new(entity) |
| 362 | } | 440 | } |
| 363 | 441 | ||
| @@ -366,7 +444,7 @@ impl<'a> Device<'a> { | |||
| 366 | entity_config.id = id; | 444 | entity_config.id = id; |
| 367 | config.populate(&mut entity_config); | 445 | config.populate(&mut entity_config); |
| 368 | 446 | ||
| 369 | let entity = self.create_entity(entity_config); | 447 | let entity = self.create_entity(entity_config, EntityStorage::Switch(Default::default())); |
| 370 | Switch::new(entity) | 448 | Switch::new(entity) |
| 371 | } | 449 | } |
| 372 | 450 | ||
| @@ -379,18 +457,21 @@ impl<'a> Device<'a> { | |||
| 379 | entity_config.id = id; | 457 | entity_config.id = id; |
| 380 | config.populate(&mut entity_config); | 458 | config.populate(&mut entity_config); |
| 381 | 459 | ||
| 382 | let entity = self.create_entity(entity_config); | 460 | let entity = self.create_entity( |
| 461 | entity_config, | ||
| 462 | EntityStorage::BinarySensor(Default::default()), | ||
| 463 | ); | ||
| 383 | BinarySensor::new(entity) | 464 | BinarySensor::new(entity) |
| 384 | } | 465 | } |
| 385 | 466 | ||
| 386 | pub async fn run<T: Transport>(&mut self, transport: &mut T) -> ! { | 467 | pub async fn run<T: Transport>(&mut self, transport: &mut T) -> ! { |
| 387 | loop { | 468 | loop { |
| 388 | self.run_iteration(&mut *transport).await; | 469 | self.run_iteration(transport).await; |
| 389 | Timer::after_millis(5000).await; | 470 | Timer::after_millis(5000).await; |
| 390 | } | 471 | } |
| 391 | } | 472 | } |
| 392 | 473 | ||
| 393 | async fn run_iteration<T: Transport>(&mut self, transport: T) { | 474 | async fn run_iteration<T: Transport>(&mut self, transport: &mut T) { |
| 394 | let mut client = embedded_mqtt::Client::new(self.mqtt_resources, transport); | 475 | let mut client = embedded_mqtt::Client::new(self.mqtt_resources, transport); |
| 395 | client.connect(self.config.device_id).await.unwrap(); | 476 | client.connect(self.config.device_id).await.unwrap(); |
| 396 | 477 | ||
| @@ -491,7 +572,7 @@ impl<'a> Device<'a> { | |||
| 491 | client.subscribe(&self.command_topic_buffer).await.unwrap(); | 572 | client.subscribe(&self.command_topic_buffer).await.unwrap(); |
| 492 | } | 573 | } |
| 493 | 574 | ||
| 494 | loop { | 575 | 'outer_loop: loop { |
| 495 | use core::fmt::Write; | 576 | use core::fmt::Write; |
| 496 | 577 | ||
| 497 | for entity in self.entities { | 578 | for entity in self.entities { |
| @@ -502,11 +583,37 @@ impl<'a> Device<'a> { | |||
| 502 | None => break, | 583 | None => break, |
| 503 | }; | 584 | }; |
| 504 | 585 | ||
| 505 | if !entity.publish_dirty { | 586 | if !entity.publish { |
| 506 | continue; | 587 | continue; |
| 507 | } | 588 | } |
| 508 | 589 | ||
| 509 | entity.publish_dirty = false; | 590 | entity.publish = false; |
| 591 | self.publish_buffer.clear(); | ||
| 592 | |||
| 593 | match &entity.storage { | ||
| 594 | EntityStorage::Switch(SwitchStorage { | ||
| 595 | state: Some(SwitchState { value, .. }), | ||
| 596 | .. | ||
| 597 | }) => self | ||
| 598 | .publish_buffer | ||
| 599 | .extend_from_slice(value.as_str().as_bytes()) | ||
| 600 | .unwrap(), | ||
| 601 | EntityStorage::BinarySensor(BinarySensorStorage { | ||
| 602 | state: Some(BinarySensorState { value, .. }), | ||
| 603 | }) => self | ||
| 604 | .publish_buffer | ||
| 605 | .extend_from_slice(value.as_str().as_bytes()) | ||
| 606 | .unwrap(), | ||
| 607 | EntityStorage::NumericSensor(NumericSensorStorage { | ||
| 608 | state: Some(NumericSensorState { value, .. }), | ||
| 609 | .. | ||
| 610 | }) => write!(self.publish_buffer, "{}", value).unwrap(), | ||
| 611 | EntityStorage::Number(NumberStorage { | ||
| 612 | state: Some(NumberState { value, .. }), | ||
| 613 | .. | ||
| 614 | }) => write!(self.publish_buffer, "{}", value).unwrap(), | ||
| 615 | _ => continue, // TODO: print warning | ||
| 616 | } | ||
| 510 | 617 | ||
| 511 | self.state_topic_buffer.clear(); | 618 | self.state_topic_buffer.clear(); |
| 512 | write!( | 619 | write!( |
| @@ -518,11 +625,6 @@ impl<'a> Device<'a> { | |||
| 518 | } | 625 | } |
| 519 | ) | 626 | ) |
| 520 | .unwrap(); | 627 | .unwrap(); |
| 521 | |||
| 522 | self.publish_buffer.clear(); | ||
| 523 | self.publish_buffer | ||
| 524 | .extend_from_slice(entity.publish_value.as_slice()) | ||
| 525 | .unwrap(); | ||
| 526 | } | 628 | } |
| 527 | 629 | ||
| 528 | client | 630 | client |
| @@ -533,33 +635,78 @@ impl<'a> Device<'a> { | |||
| 533 | 635 | ||
| 534 | let receive = client.receive(); | 636 | let receive = client.receive(); |
| 535 | let waker = wait_on_atomic_waker(self.waker); | 637 | let waker = wait_on_atomic_waker(self.waker); |
| 536 | match embassy_futures::select::select(receive, waker).await { | 638 | let publish = match embassy_futures::select::select(receive, waker).await { |
| 537 | embassy_futures::select::Either::First(packet) => { | 639 | embassy_futures::select::Either::First(packet) => match packet.unwrap() { |
| 538 | let packet = packet.unwrap(); | 640 | embedded_mqtt::Packet::Publish(publish) => publish, |
| 539 | let mut read_buffer = [0u8; 128]; | 641 | _ => continue, |
| 540 | if let embedded_mqtt::Packet::Publish(publish) = packet { | 642 | }, |
| 541 | if publish.data_len > 128 { | 643 | embassy_futures::select::Either::Second(_) => continue, |
| 542 | defmt::warn!("mqtt publish payload too large, ignoring message"); | 644 | }; |
| 543 | } else { | 645 | |
| 544 | let b = &mut read_buffer[..publish.data_len]; | 646 | let entity = 'entity_search_block: { |
| 545 | client.receive_data(b).await.unwrap(); | 647 | for entity in self.entities { |
| 546 | defmt::info!("receive value {}", str::from_utf8(b).unwrap()); | 648 | let mut data = entity.borrow_mut(); |
| 547 | for entity in self.entities { | 649 | let data = match data.as_mut() { |
| 548 | let mut entity = entity.borrow_mut(); | 650 | Some(data) => data, |
| 549 | if let Some(entity) = entity.as_mut() { | 651 | None => break, |
| 550 | entity.command_dirty = true; | 652 | }; |
| 551 | entity.command_value.clear(); | 653 | |
| 552 | entity.command_value.extend_from_slice(b).unwrap(); | 654 | self.command_topic_buffer.clear(); |
| 553 | entity.command_instant = Some(embassy_time::Instant::now()); | 655 | write!( |
| 554 | if let Some(ref waker) = entity.command_wait_waker { | 656 | self.command_topic_buffer, |
| 555 | waker.wake_by_ref(); | 657 | "{}", |
| 556 | } | 658 | CommandTopicDisplay { |
| 557 | } | 659 | device_id: self.config.device_id, |
| 558 | } | 660 | entity_id: data.config.id |
| 559 | } | 661 | } |
| 662 | ) | ||
| 663 | .unwrap(); | ||
| 664 | |||
| 665 | if self.command_topic_buffer.as_bytes() == publish.topic.as_bytes() { | ||
| 666 | break 'entity_search_block entity; | ||
| 560 | } | 667 | } |
| 561 | } | 668 | } |
| 562 | embassy_futures::select::Either::Second(_) => {} | 669 | continue 'outer_loop; |
| 670 | }; | ||
| 671 | |||
| 672 | let mut read_buffer = [0u8; 128]; | ||
| 673 | if publish.data_len > read_buffer.len() { | ||
| 674 | defmt::warn!("mqtt publish payload too large, ignoring message"); | ||
| 675 | continue; | ||
| 676 | } | ||
| 677 | let b = &mut read_buffer[..publish.data_len]; | ||
| 678 | client.receive_data(b).await.unwrap(); | ||
| 679 | let command = str::from_utf8(b).unwrap(); | ||
| 680 | |||
| 681 | let mut entity = entity.borrow_mut(); | ||
| 682 | let data = entity.as_mut().unwrap(); | ||
| 683 | |||
| 684 | match &mut data.storage { | ||
| 685 | EntityStorage::Button(button_storage) => { | ||
| 686 | assert_eq!(command, constants::HA_BUTTON_PAYLOAD_PRESS); | ||
| 687 | button_storage.consumed = false; | ||
| 688 | button_storage.timestamp = Some(embassy_time::Instant::now()); | ||
| 689 | } | ||
| 690 | EntityStorage::Switch(switch_storage) => { | ||
| 691 | let command = command.parse::<BinaryState>().unwrap(); | ||
| 692 | switch_storage.command = Some(SwitchCommand { | ||
| 693 | value: command, | ||
| 694 | timestamp: embassy_time::Instant::now(), | ||
| 695 | }); | ||
| 696 | } | ||
| 697 | EntityStorage::Number(number_storage) => { | ||
| 698 | let command = command.parse::<f32>().unwrap(); | ||
| 699 | number_storage.command = Some(NumberCommand { | ||
| 700 | value: command, | ||
| 701 | timestamp: embassy_time::Instant::now(), | ||
| 702 | }); | ||
| 703 | } | ||
| 704 | _ => continue 'outer_loop, | ||
| 705 | } | ||
| 706 | |||
| 707 | data.command = true; | ||
| 708 | if let Some(waker) = data.command_waker.take() { | ||
| 709 | waker.wake(); | ||
| 563 | } | 710 | } |
| 564 | } | 711 | } |
| 565 | } | 712 | } |
