From 27a63b1a796ae7dcea2cbf38ccde613695129d90 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Fri, 6 Jan 2023 04:42:42 +0100 Subject: [PATCH] Setting the presence mqtt topic is now optional, if not set it will generate an appropriate value automatically --- config/zeus.dev.toml | 3 +-- src/config.rs | 32 +++++++++++++++++++++++--------- src/devices/contact_sensor.rs | 14 +++++++++++--- src/main.rs | 29 +++++++++++++++-------------- 4 files changed, 50 insertions(+), 28 deletions(-) diff --git a/config/zeus.dev.toml b/config/zeus.dev.toml index 223b402..1fe5e6e 100644 --- a/config/zeus.dev.toml +++ b/config/zeus.dev.toml @@ -52,5 +52,4 @@ speakers = "10.0.0.182" [devices.hallway_frontdoor] type = "ContactSensor" topic = "zigbee2mqtt/hallway/frontdoor" -# @TODO This should be automatically constructed from the identifier and presence topic -presence = { topic = "automation_dev/presence/frontdoor", timeout = 10 } +presence = { timeout = 10 } diff --git a/src/config.rs b/src/config.rs index 651aef4..861d377 100644 --- a/src/config.rs +++ b/src/config.rs @@ -28,7 +28,7 @@ pub struct OpenIDConfig { pub base_url: String } -#[derive(Debug, Deserialize)] +#[derive(Debug, Clone, Deserialize)] pub struct MqttConfig { pub host: String, pub port: u16, @@ -96,32 +96,43 @@ pub struct HueBridgeConfig { pub flags: Flags, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Clone, Deserialize)] pub struct InfoConfig { pub name: String, pub room: Option, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Clone, Deserialize)] pub struct MqttDeviceConfig { pub topic: String, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Clone, Deserialize)] pub struct KettleConfig { pub timeout: Option, // Timeout in seconds } -#[derive(Debug, Deserialize)] +#[derive(Debug, Clone, Deserialize)] pub struct PresenceDeviceConfig { #[serde(flatten)] - pub mqtt: MqttDeviceConfig, + pub mqtt: Option, // @TODO Maybe make this an option? That way if no timeout is set it will immediately turn the // device off again? pub timeout: u64 // Timeout in seconds } -#[derive(Debug, Deserialize)] +impl PresenceDeviceConfig { + /// Set the mqtt topic to an appropriate value if it is not already set + fn generate_topic(&mut self, identifier: &str, config: &Config) { + if self.mqtt.is_none() { + let topic = config.presence.topic.clone() + "/" + identifier; + trace!("Setting presence mqtt topic: {topic}"); + self.mqtt = Some(MqttDeviceConfig { topic }); + } + } +} + +#[derive(Debug, Clone, Deserialize)] #[serde(tag = "type")] pub enum Device { IkeaOutlet { @@ -182,7 +193,7 @@ impl Config { } impl Device { - pub fn into(self, identifier: String, client: AsyncClient) -> DeviceBox { + pub fn into(self, identifier: String, config: &Config, client: AsyncClient) -> DeviceBox { match self { Device::IkeaOutlet { info, mqtt, kettle } => { trace!(id = identifier, "IkeaOutlet [{} in {:?}]", info.name, info.room); @@ -196,8 +207,11 @@ impl Device { trace!(id = identifier, "AudioSetup [{}]", identifier); Box::new(AudioSetup::new(identifier, mqtt, mixer, speakers, client)) }, - Device::ContactSensor { mqtt, presence } => { + Device::ContactSensor { mqtt, mut presence } => { trace!(id = identifier, "ContactSensor [{}]", identifier); + if let Some(presence) = &mut presence { + presence.generate_topic(&identifier, &config); + } Box::new(ContactSensor::new(identifier, mqtt, presence, client)) }, } diff --git a/src/devices/contact_sensor.rs b/src/devices/contact_sensor.rs index 719918d..fffbbd6 100644 --- a/src/devices/contact_sensor.rs +++ b/src/devices/contact_sensor.rs @@ -3,7 +3,7 @@ use std::time::Duration; use pollster::FutureExt; use rumqttc::AsyncClient; use tokio::task::JoinHandle; -use tracing::{error, debug}; +use tracing::{error, debug, warn}; use crate::{config::{MqttDeviceConfig, PresenceDeviceConfig}, mqtt::{OnMqtt, ContactMessage, PresenceMessage}}; @@ -68,17 +68,25 @@ impl OnMqtt for ContactSensor { None => return, }; + let topic = match &presence.mqtt { + Some(mqtt) => mqtt.topic.clone(), + None => { + warn!("Contact sensors is configured as a presence sensor, but no mqtt topic is specified"); + return; + } + }; + if !is_closed { // Activate presence and stop any timeout once we open the door if let Some(handle) = self.handle.take() { handle.abort(); } - self.client.publish(presence.mqtt.topic.clone(), rumqttc::QoS::AtLeastOnce, false, serde_json::to_string(&PresenceMessage::new(true)).unwrap()).block_on().unwrap(); + + self.client.publish(topic, rumqttc::QoS::AtLeastOnce, false, serde_json::to_string(&PresenceMessage::new(true)).unwrap()).block_on().unwrap(); } else { // Once the door is closed again we start a timeout for removing the presence let client = self.client.clone(); - let topic = presence.mqtt.topic.clone(); let id = self.identifier.clone(); let timeout = Duration::from_secs(presence.timeout); self.handle = Some( diff --git a/src/main.rs b/src/main.rs index 539c035..a687910 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,8 +44,9 @@ async fn main() { info!("Starting automation_rs..."); // Configure MQTT - let mut mqttoptions = MqttOptions::new("rust-test", config.mqtt.host, config.mqtt.port); - mqttoptions.set_credentials(config.mqtt.username, config.mqtt.password); + let mqtt = config.mqtt.clone(); + let mut mqttoptions = MqttOptions::new("rust-test", mqtt.host, mqtt.port); + mqttoptions.set_credentials(mqtt.username, mqtt.password); mqttoptions.set_keep_alive(Duration::from_secs(5)); mqttoptions.set_transport(Transport::tls_with_default_config()); @@ -57,6 +58,18 @@ async fn main() { let devices = Arc::new(RwLock::new(Devices::new())); mqtt.add_listener(Arc::downgrade(&devices)); + // Turn the config into actual devices and add them + config.devices.clone() + .into_iter() + .map(|(identifier, device_config)| { + // This can technically block, but this only happens during start-up, so should not be + // a problem + device_config.into(identifier, &config, client.clone()) + }) + .for_each(|device| { + devices.write().unwrap().add_device(device); + }); + // Setup presence system let mut presence = Presence::new(config.presence, client.clone()); // Register devices as presence listener @@ -82,18 +95,6 @@ async fn main() { // Start mqtt, this spawns a seperate async task mqtt.start(); - // Turn the config into actual devices and add them - config.devices - .into_iter() - .map(|(identifier, device_config)| { - // This can technically block, but this only happens during start-up, so should not be - // a problem - device_config.into(identifier, client.clone()) - }) - .for_each(|device| { - devices.write().unwrap().add_device(device); - }); - // Create google home fullfillment route let fullfillment = Router::new() .route("/google_home", post(async move |user: User, Json(payload): Json| {