Contact sensor can now turn on device when opened and turn them off again after a timeout

This commit is contained in:
Dreaded_X 2023-08-14 02:58:58 +02:00
parent 12ca577a65
commit b97b682a5e
Signed by: Dreaded_X
GPG Key ID: FA5F485356B0D2D4
2 changed files with 69 additions and 5 deletions

View File

@ -42,7 +42,7 @@ outlet_type = "Light"
name = "Bathroom light" name = "Bathroom light"
room = "Bathroom" room = "Bathroom"
topic = "zigbee2mqtt/bathroom/light" topic = "zigbee2mqtt/bathroom/light"
timeout = 5 timeout = 60
[devices.workbench_charger] [devices.workbench_charger]
type = "IkeaOutlet" type = "IkeaOutlet"
@ -76,10 +76,11 @@ ip = "10.0.0.182"
[devices.living_audio] [devices.living_audio]
type = "AudioSetup" type = "AudioSetup"
topic = "zigbee2mqtt/living/remote" topic = "zigbee2mqtt/living/remote"
mixer = "light_sensor" mixer = "living_mixer"
speakers = "living_speakers" speakers = "living_speakers"
[devices.hallway_frontdoor] [devices.hallway_frontdoor]
type = "ContactSensor" type = "ContactSensor"
topic = "zigbee2mqtt/hallway/frontdoor" topic = "zigbee2mqtt/hallway/frontdoor"
presence = { timeout = 10 } presence = { timeout = 10 }
lights = { lights = ["bathroom_light"], timeout = 10 }

View File

@ -1,6 +1,7 @@
use std::time::Duration; use std::time::Duration;
use async_trait::async_trait; use async_trait::async_trait;
use google_home::traits::OnOff;
use rumqttc::{has_wildcards, AsyncClient}; use rumqttc::{has_wildcards, AsyncClient};
use serde::Deserialize; use serde::Deserialize;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
@ -8,13 +9,14 @@ use tracing::{debug, error, trace, warn};
use crate::{ use crate::{
config::{CreateDevice, MqttDeviceConfig}, config::{CreateDevice, MqttDeviceConfig},
device_manager::DeviceManager, device_manager::{DeviceManager, WrappedDevice},
devices::DEFAULT_PRESENCE, devices::{As, DEFAULT_PRESENCE},
error::{CreateDeviceError, MissingWildcard}, error::{CreateDeviceError, MissingWildcard},
event::EventChannel, event::EventChannel,
event::OnMqtt, event::OnMqtt,
event::OnPresence, event::OnPresence,
messages::{ContactMessage, PresenceMessage}, messages::{ContactMessage, PresenceMessage},
traits::Timeout,
}; };
use super::Device; use super::Device;
@ -54,11 +56,24 @@ impl PresenceDeviceConfig {
} }
} }
#[derive(Debug, Clone, Deserialize)]
pub struct LightsConfig {
lights: Vec<String>,
timeout: u64, // Timeout in seconds
}
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct ContactSensorConfig { pub struct ContactSensorConfig {
#[serde(flatten)] #[serde(flatten)]
mqtt: MqttDeviceConfig, mqtt: MqttDeviceConfig,
presence: Option<PresenceDeviceConfig>, presence: Option<PresenceDeviceConfig>,
lights: Option<LightsConfig>,
}
#[derive(Debug)]
pub struct Lights {
lights: Vec<(WrappedDevice, bool)>,
timeout: Duration, // Timeout in seconds
} }
#[derive(Debug)] #[derive(Debug)]
@ -71,6 +86,8 @@ pub struct ContactSensor {
overall_presence: bool, overall_presence: bool,
is_closed: bool, is_closed: bool,
handle: Option<JoinHandle<()>>, handle: Option<JoinHandle<()>>,
lights: Option<Lights>,
} }
#[async_trait] #[async_trait]
@ -83,7 +100,7 @@ impl CreateDevice for ContactSensor {
_event_channel: &EventChannel, _event_channel: &EventChannel,
client: &AsyncClient, client: &AsyncClient,
presence_topic: &str, presence_topic: &str,
_device_manager: &DeviceManager, device_manager: &DeviceManager,
) -> Result<Self, CreateDeviceError> { ) -> Result<Self, CreateDeviceError> {
trace!(id = identifier, "Setting up ContactSensor"); trace!(id = identifier, "Setting up ContactSensor");
@ -92,6 +109,32 @@ impl CreateDevice for ContactSensor {
.map(|p| p.generate_topic("contact", identifier, presence_topic)) .map(|p| p.generate_topic("contact", identifier, presence_topic))
.transpose()?; .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::<dyn OnOff>::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 { Ok(Self {
identifier: identifier.to_owned(), identifier: identifier.to_owned(),
mqtt: config.mqtt, mqtt: config.mqtt,
@ -100,6 +143,7 @@ impl CreateDevice for ContactSensor {
overall_presence: DEFAULT_PRESENCE, overall_presence: DEFAULT_PRESENCE,
is_closed: true, is_closed: true,
handle: None, handle: None,
lights,
}) })
} }
} }
@ -139,6 +183,25 @@ impl OnMqtt for ContactSensor {
debug!(id = self.identifier, "Updating state to {is_closed}"); debug!(id = self.identifier, "Updating state to {is_closed}");
self.is_closed = 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::<dyn OnOff>::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::<dyn Timeout>::cast_mut(light.as_mut()) {
light.start_timeout(lights.timeout);
}
}
}
}
// Check if this contact sensor works as a presence device // Check if this contact sensor works as a presence device
// If not we are done here // If not we are done here
let presence = match &self.presence { let presence = match &self.presence {