diff options
| -rw-r--r-- | src/lib.rs | 71 |
1 files changed, 63 insertions, 8 deletions
| @@ -44,6 +44,9 @@ pub use transport::Transport; | |||
| 44 | mod unit; | 44 | mod unit; |
| 45 | pub use unit::*; | 45 | pub use unit::*; |
| 46 | 46 | ||
| 47 | const AVAILABLE_PAYLOAD: &str = "online"; | ||
| 48 | const NOT_AVAILABLE_PAYLOAD: &str = "offline"; | ||
| 49 | |||
| 47 | #[derive(Debug)] | 50 | #[derive(Debug)] |
| 48 | pub struct Error(&'static str); | 51 | pub struct Error(&'static str); |
| 49 | 52 | ||
| @@ -118,6 +121,15 @@ struct EntityDiscovery<'a> { | |||
| 118 | #[serde(skip_serializing_if = "Option::is_none")] | 121 | #[serde(skip_serializing_if = "Option::is_none")] |
| 119 | suggested_display_precision: Option<u8>, | 122 | suggested_display_precision: Option<u8>, |
| 120 | 123 | ||
| 124 | #[serde(skip_serializing_if = "Option::is_none")] | ||
| 125 | availability_topic: Option<&'a str>, | ||
| 126 | |||
| 127 | #[serde(skip_serializing_if = "Option::is_none")] | ||
| 128 | payload_available: Option<&'a str>, | ||
| 129 | |||
| 130 | #[serde(skip_serializing_if = "Option::is_none")] | ||
| 131 | payload_not_available: Option<&'a str>, | ||
| 132 | |||
| 121 | device: &'a DeviceDiscovery<'a>, | 133 | device: &'a DeviceDiscovery<'a>, |
| 122 | } | 134 | } |
| 123 | 135 | ||
| @@ -163,6 +175,16 @@ impl<'a> core::fmt::Display for CommandTopicDisplay<'a> { | |||
| 163 | } | 175 | } |
| 164 | } | 176 | } |
| 165 | 177 | ||
| 178 | struct DeviceAvailabilityTopic<'a> { | ||
| 179 | device_id: &'a str, | ||
| 180 | } | ||
| 181 | |||
| 182 | impl<'a> core::fmt::Display for DeviceAvailabilityTopic<'a> { | ||
| 183 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 184 | write!(f, "embassy-ha/{}/availability", self.device_id) | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 166 | pub struct DeviceConfig { | 188 | pub struct DeviceConfig { |
| 167 | pub device_id: &'static str, | 189 | pub device_id: &'static str, |
| 168 | pub device_name: &'static str, | 190 | pub device_name: &'static str, |
| @@ -178,6 +200,7 @@ pub struct DeviceResources { | |||
| 178 | publish_buffer: Vec<u8, 2048>, | 200 | publish_buffer: Vec<u8, 2048>, |
| 179 | subscribe_buffer: Vec<u8, 128>, | 201 | subscribe_buffer: Vec<u8, 128>, |
| 180 | discovery_buffer: Vec<u8, 2048>, | 202 | discovery_buffer: Vec<u8, 2048>, |
| 203 | availability_topic_buffer: String<128>, | ||
| 181 | discovery_topic_buffer: String<128>, | 204 | discovery_topic_buffer: String<128>, |
| 182 | state_topic_buffer: String<128>, | 205 | state_topic_buffer: String<128>, |
| 183 | command_topic_buffer: String<128>, | 206 | command_topic_buffer: String<128>, |
| @@ -197,6 +220,7 @@ impl Default for DeviceResources { | |||
| 197 | publish_buffer: Default::default(), | 220 | publish_buffer: Default::default(), |
| 198 | subscribe_buffer: Default::default(), | 221 | subscribe_buffer: Default::default(), |
| 199 | discovery_buffer: Default::default(), | 222 | discovery_buffer: Default::default(), |
| 223 | availability_topic_buffer: Default::default(), | ||
| 200 | discovery_topic_buffer: Default::default(), | 224 | discovery_topic_buffer: Default::default(), |
| 201 | state_topic_buffer: Default::default(), | 225 | state_topic_buffer: Default::default(), |
| 202 | command_topic_buffer: Default::default(), | 226 | command_topic_buffer: Default::default(), |
| @@ -383,6 +407,7 @@ pub struct Device<'a> { | |||
| 383 | publish_buffer: &'a mut VecView<u8>, | 407 | publish_buffer: &'a mut VecView<u8>, |
| 384 | subscribe_buffer: &'a mut VecView<u8>, | 408 | subscribe_buffer: &'a mut VecView<u8>, |
| 385 | discovery_buffer: &'a mut VecView<u8>, | 409 | discovery_buffer: &'a mut VecView<u8>, |
| 410 | availability_topic_buffer: &'a mut StringView, | ||
| 386 | discovery_topic_buffer: &'a mut StringView, | 411 | discovery_topic_buffer: &'a mut StringView, |
| 387 | state_topic_buffer: &'a mut StringView, | 412 | state_topic_buffer: &'a mut StringView, |
| 388 | command_topic_buffer: &'a mut StringView, | 413 | command_topic_buffer: &'a mut StringView, |
| @@ -399,6 +424,7 @@ impl<'a> Device<'a> { | |||
| 399 | publish_buffer: &mut resources.publish_buffer, | 424 | publish_buffer: &mut resources.publish_buffer, |
| 400 | subscribe_buffer: &mut resources.subscribe_buffer, | 425 | subscribe_buffer: &mut resources.subscribe_buffer, |
| 401 | discovery_buffer: &mut resources.discovery_buffer, | 426 | discovery_buffer: &mut resources.discovery_buffer, |
| 427 | availability_topic_buffer: &mut resources.availability_topic_buffer, | ||
| 402 | discovery_topic_buffer: &mut resources.discovery_topic_buffer, | 428 | discovery_topic_buffer: &mut resources.discovery_topic_buffer, |
| 403 | state_topic_buffer: &mut resources.state_topic_buffer, | 429 | state_topic_buffer: &mut resources.state_topic_buffer, |
| 404 | command_topic_buffer: &mut resources.command_topic_buffer, | 430 | command_topic_buffer: &mut resources.command_topic_buffer, |
| @@ -430,11 +456,7 @@ impl<'a> Device<'a> { | |||
| 430 | } | 456 | } |
| 431 | } | 457 | } |
| 432 | 458 | ||
| 433 | pub fn create_sensor( | 459 | pub fn create_sensor(&self, id: &'static str, config: SensorConfig) -> Sensor<'a> { |
| 434 | &self, | ||
| 435 | id: &'static str, | ||
| 436 | config: SensorConfig, | ||
| 437 | ) -> Sensor<'a> { | ||
| 438 | let mut entity_config = EntityConfig::default(); | 460 | let mut entity_config = EntityConfig::default(); |
| 439 | entity_config.id = id; | 461 | entity_config.id = id; |
| 440 | config.populate(&mut entity_config); | 462 | config.populate(&mut entity_config); |
| @@ -502,8 +524,29 @@ impl<'a> Device<'a> { | |||
| 502 | } | 524 | } |
| 503 | 525 | ||
| 504 | pub async fn run<T: Transport>(&mut self, transport: &mut T) -> Result<(), Error> { | 526 | pub async fn run<T: Transport>(&mut self, transport: &mut T) -> Result<(), Error> { |
| 527 | use core::fmt::Write; | ||
| 528 | |||
| 529 | self.availability_topic_buffer.clear(); | ||
| 530 | write!( | ||
| 531 | self.availability_topic_buffer, | ||
| 532 | "{}", | ||
| 533 | DeviceAvailabilityTopic { | ||
| 534 | device_id: self.config.device_id | ||
| 535 | } | ||
| 536 | ) | ||
| 537 | .expect("device availability buffer too small"); | ||
| 538 | let availability_topic = self.availability_topic_buffer.as_str(); | ||
| 539 | |||
| 505 | let mut client = embedded_mqtt::Client::new(self.mqtt_resources, transport); | 540 | let mut client = embedded_mqtt::Client::new(self.mqtt_resources, transport); |
| 506 | if let Err(err) = client.connect(self.config.device_id).await { | 541 | let connect_params = embedded_mqtt::ConnectParams { |
| 542 | will_topic: Some(availability_topic), | ||
| 543 | will_payload: Some(NOT_AVAILABLE_PAYLOAD.as_bytes()), | ||
| 544 | ..Default::default() | ||
| 545 | }; | ||
| 546 | if let Err(err) = client | ||
| 547 | .connect_with(self.config.device_id, connect_params) | ||
| 548 | .await | ||
| 549 | { | ||
| 507 | crate::log::error!( | 550 | crate::log::error!( |
| 508 | "mqtt connect failed with: {:?}", | 551 | "mqtt connect failed with: {:?}", |
| 509 | crate::log::Debug2Format(&err) | 552 | crate::log::Debug2Format(&err) |
| @@ -520,8 +563,6 @@ impl<'a> Device<'a> { | |||
| 520 | }; | 563 | }; |
| 521 | 564 | ||
| 522 | for entity in self.entities { | 565 | for entity in self.entities { |
| 523 | use core::fmt::Write; | ||
| 524 | |||
| 525 | self.publish_buffer.clear(); | 566 | self.publish_buffer.clear(); |
| 526 | self.subscribe_buffer.clear(); | 567 | self.subscribe_buffer.clear(); |
| 527 | self.discovery_buffer.clear(); | 568 | self.discovery_buffer.clear(); |
| @@ -577,6 +618,9 @@ impl<'a> Device<'a> { | |||
| 577 | step: entity_config.step, | 618 | step: entity_config.step, |
| 578 | mode: entity_config.mode, | 619 | mode: entity_config.mode, |
| 579 | suggested_display_precision: entity_config.suggested_display_precision, | 620 | suggested_display_precision: entity_config.suggested_display_precision, |
| 621 | availability_topic: Some(availability_topic), | ||
| 622 | payload_available: Some(AVAILABLE_PAYLOAD), | ||
| 623 | payload_not_available: Some(NOT_AVAILABLE_PAYLOAD), | ||
| 580 | device: &device_discovery, | 624 | device: &device_discovery, |
| 581 | }; | 625 | }; |
| 582 | crate::log::debug!( | 626 | crate::log::debug!( |
| @@ -620,6 +664,17 @@ impl<'a> Device<'a> { | |||
| 620 | } | 664 | } |
| 621 | } | 665 | } |
| 622 | 666 | ||
| 667 | if let Err(err) = client | ||
| 668 | .publish(availability_topic, AVAILABLE_PAYLOAD.as_bytes()) | ||
| 669 | .await | ||
| 670 | { | ||
| 671 | crate::log::error!( | ||
| 672 | "mqtt availability publish failed with: {:?}", | ||
| 673 | crate::log::Debug2Format(&err) | ||
| 674 | ); | ||
| 675 | return Err(Error::new("mqtt availability publish failed")); | ||
| 676 | } | ||
| 677 | |||
| 623 | 'outer_loop: loop { | 678 | 'outer_loop: loop { |
| 624 | use core::fmt::Write; | 679 | use core::fmt::Write; |
| 625 | 680 | ||
