diff --git a/automation_devices/src/contact_sensor.rs b/automation_devices/src/contact_sensor.rs index ce351a0..92cb7dd 100644 --- a/automation_devices/src/contact_sensor.rs +++ b/automation_devices/src/contact_sensor.rs @@ -1,13 +1,12 @@ use std::sync::Arc; -use std::time::Duration; use async_trait::async_trait; use automation_lib::action_callback::ActionCallback; use automation_lib::config::{InfoConfig, MqttDeviceConfig}; use automation_lib::device::{Device, LuaDeviceCreate}; use automation_lib::error::DeviceConfigError; -use automation_lib::event::{OnMqtt, OnPresence}; -use automation_lib::messages::{ContactMessage, PresenceMessage}; +use automation_lib::event::OnMqtt; +use automation_lib::messages::ContactMessage; use automation_lib::mqtt::WrappedAsyncClient; use automation_macro::{LuaDevice, LuaDeviceConfig}; use google_home::device; @@ -16,10 +15,7 @@ use google_home::traits::OpenClose; use google_home::types::Type; use serde::Deserialize; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; -use tokio::task::JoinHandle; -use tracing::{debug, error, trace, warn}; - -use crate::presence::DEFAULT_PRESENCE; +use tracing::{debug, error, trace}; #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy)] pub enum SensorType { @@ -28,23 +24,12 @@ pub enum SensorType { Window, } -// NOTE: If we add more presence devices we might need to move this out of here -#[derive(Debug, Clone, LuaDeviceConfig)] -pub struct PresenceDeviceConfig { - #[device_config(flatten)] - pub mqtt: MqttDeviceConfig, - #[device_config(with(Duration::from_secs))] - pub timeout: Duration, -} - #[derive(Debug, Clone, LuaDeviceConfig)] pub struct Config { #[device_config(flatten)] pub info: InfoConfig, #[device_config(flatten)] pub mqtt: MqttDeviceConfig, - #[device_config(from_lua, default)] - pub presence: Option, #[device_config(default(SensorType::Window))] pub sensor_type: SensorType, @@ -57,9 +42,7 @@ pub struct Config { #[derive(Debug)] struct State { - overall_presence: bool, is_closed: bool, - handle: Option>, } #[derive(Debug, Clone, LuaDevice)] @@ -92,11 +75,7 @@ impl LuaDeviceCreate for ContactSensor { .subscribe(&config.mqtt.topic, rumqttc::QoS::AtLeastOnce) .await?; - let state = State { - overall_presence: DEFAULT_PRESENCE, - is_closed: true, - handle: None, - }; + let state = State { is_closed: true }; let state = Arc::new(RwLock::new(state)); Ok(Self { config, state }) @@ -163,13 +142,6 @@ impl OpenClose for ContactSensor { } } -#[async_trait] -impl OnPresence for ContactSensor { - async fn on_presence(&self, presence: bool) { - self.state_mut().await.overall_presence = presence; - } -} - #[async_trait] impl OnMqtt for ContactSensor { async fn on_mqtt(&self, message: rumqttc::Publish) { @@ -193,64 +165,5 @@ impl OnMqtt for ContactSensor { debug!(id = self.get_id(), "Updating state to {is_closed}"); self.state_mut().await.is_closed = is_closed; - - // Check if this contact sensor works as a presence device - // If not we are done here - let presence = match &self.config.presence { - Some(presence) => presence.clone(), - None => return, - }; - - if !is_closed { - // Activate presence and stop any timeout once we open the door - if let Some(handle) = self.state_mut().await.handle.take() { - handle.abort(); - } - - // Only use the door as an presence sensor if there the current presence is set false - // This is to prevent the house from being marked as present for however long the - // timeout is set when leaving the house - if !self.state().await.overall_presence { - self.config - .client - .publish( - &presence.mqtt.topic, - rumqttc::QoS::AtLeastOnce, - false, - serde_json::to_string(&PresenceMessage::new(true)).unwrap(), - ) - .await - .map_err(|err| { - warn!( - "Failed to publish presence on {}: {err}", - presence.mqtt.topic - ) - }) - .ok(); - } - } else { - // Once the door is closed again we start a timeout for removing the presence - let device = self.clone(); - self.state_mut().await.handle = Some(tokio::spawn(async move { - debug!( - id = device.get_id(), - "Starting timeout ({:?}) for contact sensor...", presence.timeout - ); - tokio::time::sleep(presence.timeout).await; - debug!(id = device.get_id(), "Removing door device!"); - device - .config - .client - .publish(&presence.mqtt.topic, rumqttc::QoS::AtLeastOnce, false, "") - .await - .map_err(|err| { - warn!( - "Failed to publish presence on {}: {err}", - presence.mqtt.topic - ) - }) - .ok(); - })); - } } } diff --git a/automation_devices/src/presence.rs b/automation_devices/src/presence.rs index 491721e..b637d13 100644 --- a/automation_devices/src/presence.rs +++ b/automation_devices/src/presence.rs @@ -6,6 +6,7 @@ use automation_lib::action_callback::ActionCallback; use automation_lib::config::MqttDeviceConfig; use automation_lib::device::{Device, LuaDeviceCreate}; use automation_lib::event::{self, Event, EventChannel, OnMqtt}; +use automation_lib::lua::traits::AddAdditionalMethods; use automation_lib::messages::PresenceMessage; use automation_lib::mqtt::WrappedAsyncClient; use automation_macro::{LuaDevice, LuaDeviceConfig}; @@ -36,6 +37,7 @@ pub struct State { } #[derive(Debug, Clone, LuaDevice)] +#[traits(AddAdditionalMethods)] pub struct Presence { config: Config, state: Arc>, @@ -132,3 +134,14 @@ impl OnMqtt for Presence { } } } + +impl AddAdditionalMethods for Presence { + fn add_methods>(methods: &mut M) + where + Self: Sized + 'static, + { + methods.add_async_method("overall_presence", async |_lua, this, ()| { + Ok(this.state().await.current_overall_presence) + }); + } +} diff --git a/config.lua b/config.lua index 0b1f597..6b404ef 100644 --- a/config.lua +++ b/config.lua @@ -39,7 +39,8 @@ local on_presence = { self[#self + 1] = f end, } -automation.device_manager:add(Presence.new({ + +local presence_system = Presence.new({ topic = mqtt_automation("presence/+/#"), client = mqtt_client, event_channel = automation.device_manager:event_channel(), @@ -50,7 +51,8 @@ automation.device_manager:add(Presence.new({ end end end, -})) +}) +automation.device_manager:add(presence_system) on_presence:add(function(presence) ntfy:send_notification({ title = "Presence", @@ -457,6 +459,28 @@ hallway_light_automation.group = { end, } +local frontdoor_presence = { + timeout = Timeout.new(), +} +setmetatable(frontdoor_presence, { + __call = function(self, open) + if open then + self.timeout:cancel() + + if not presence_system:overall_presence() then + mqtt_client:send_message(mqtt_automation("presence/contact/frontdoor"), { + state = true, + updated = automation.util.get_epoch(), + }) + end + else + self.timeout:start(debug and 10 or 15 * 60, function() + mqtt_client:send_message(mqtt_automation("presence/contact/frontdoor"), {}) + end) + end + end, +}) + automation.device_manager:add(IkeaRemote.new({ name = "Remote", room = "Hallway", @@ -478,6 +502,7 @@ local hallway_frontdoor = ContactSensor.new({ }, callback = function(_, open) hallway_light_automation:door_callback(open) + frontdoor_presence(open) end, }) automation.device_manager:add(hallway_frontdoor)