From b97b682a5ecfac3124e1beb61118426fedaaf946 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Mon, 14 Aug 2023 02:58:58 +0200 Subject: [PATCH] Contact sensor can now turn on device when opened and turn them off again after a timeout --- config/zeus.dev.toml | 5 ++- src/devices/contact_sensor.rs | 69 +++++++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/config/zeus.dev.toml b/config/zeus.dev.toml index 66e1876..1c1a6ee 100644 --- a/config/zeus.dev.toml +++ b/config/zeus.dev.toml @@ -42,7 +42,7 @@ outlet_type = "Light" name = "Bathroom light" room = "Bathroom" topic = "zigbee2mqtt/bathroom/light" -timeout = 5 +timeout = 60 [devices.workbench_charger] type = "IkeaOutlet" @@ -76,10 +76,11 @@ ip = "10.0.0.182" [devices.living_audio] type = "AudioSetup" topic = "zigbee2mqtt/living/remote" -mixer = "light_sensor" +mixer = "living_mixer" speakers = "living_speakers" [devices.hallway_frontdoor] type = "ContactSensor" topic = "zigbee2mqtt/hallway/frontdoor" presence = { timeout = 10 } +lights = { lights = ["bathroom_light"], timeout = 10 } diff --git a/src/devices/contact_sensor.rs b/src/devices/contact_sensor.rs index d79a415..8c9c444 100644 --- a/src/devices/contact_sensor.rs +++ b/src/devices/contact_sensor.rs @@ -1,6 +1,7 @@ use std::time::Duration; use async_trait::async_trait; +use google_home::traits::OnOff; use rumqttc::{has_wildcards, AsyncClient}; use serde::Deserialize; use tokio::task::JoinHandle; @@ -8,13 +9,14 @@ use tracing::{debug, error, trace, warn}; use crate::{ config::{CreateDevice, MqttDeviceConfig}, - device_manager::DeviceManager, - devices::DEFAULT_PRESENCE, + device_manager::{DeviceManager, WrappedDevice}, + devices::{As, DEFAULT_PRESENCE}, error::{CreateDeviceError, MissingWildcard}, event::EventChannel, event::OnMqtt, event::OnPresence, messages::{ContactMessage, PresenceMessage}, + traits::Timeout, }; use super::Device; @@ -54,11 +56,24 @@ impl PresenceDeviceConfig { } } +#[derive(Debug, Clone, Deserialize)] +pub struct LightsConfig { + lights: Vec, + timeout: u64, // Timeout in seconds +} + #[derive(Debug, Clone, Deserialize)] pub struct ContactSensorConfig { #[serde(flatten)] mqtt: MqttDeviceConfig, presence: Option, + lights: Option, +} + +#[derive(Debug)] +pub struct Lights { + lights: Vec<(WrappedDevice, bool)>, + timeout: Duration, // Timeout in seconds } #[derive(Debug)] @@ -71,6 +86,8 @@ pub struct ContactSensor { overall_presence: bool, is_closed: bool, handle: Option>, + + lights: Option, } #[async_trait] @@ -83,7 +100,7 @@ impl CreateDevice for ContactSensor { _event_channel: &EventChannel, client: &AsyncClient, presence_topic: &str, - _device_manager: &DeviceManager, + device_manager: &DeviceManager, ) -> Result { trace!(id = identifier, "Setting up ContactSensor"); @@ -92,6 +109,32 @@ impl CreateDevice for ContactSensor { .map(|p| p.generate_topic("contact", identifier, presence_topic)) .transpose()?; + let lights = if let Some(lights_config) = config.lights { + let mut lights = Vec::new(); + for name in lights_config.lights { + let light = device_manager + .get(&name) + .await + .ok_or(CreateDeviceError::DeviceDoesNotExist(name.clone()))?; + + { + let light = light.read().await; + if As::::cast(light.as_ref()).is_none() { + return Err(CreateDeviceError::OnOffExpected(name)); + } + } + + lights.push((light, false)); + } + + Some(Lights { + lights, + timeout: Duration::from_secs(lights_config.timeout), + }) + } else { + None + }; + Ok(Self { identifier: identifier.to_owned(), mqtt: config.mqtt, @@ -100,6 +143,7 @@ impl CreateDevice for ContactSensor { overall_presence: DEFAULT_PRESENCE, is_closed: true, handle: None, + lights, }) } } @@ -139,6 +183,25 @@ impl OnMqtt for ContactSensor { debug!(id = self.identifier, "Updating state to {is_closed}"); self.is_closed = is_closed; + if let Some(lights) = &mut self.lights { + if !self.is_closed { + for (light, previous) in &mut lights.lights { + let mut light = light.write().await; + if let Some(light) = As::::cast_mut(light.as_mut()) { + *previous = light.is_on().await.unwrap(); + light.set_on(true).await.ok(); + } + } + } else { + for (light, previous) in &lights.lights { + let mut light = light.write().await; + if !previous && let Some(light) = As::::cast_mut(light.as_mut()) { + light.start_timeout(lights.timeout); + } + } + } + } + // Check if this contact sensor works as a presence device // If not we are done here let presence = match &self.presence {