Move front door presence logic to lua

This commit is contained in:
2025-08-31 23:30:09 +02:00
parent 74568b4e1f
commit 2a1f75f158
3 changed files with 44 additions and 93 deletions

View File

@@ -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<PresenceDeviceConfig>,
#[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<JoinHandle<()>>,
}
#[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();
}));
}
}
}

View File

@@ -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<RwLock<State>>,
@@ -132,3 +134,14 @@ impl OnMqtt for Presence {
}
}
}
impl AddAdditionalMethods for Presence {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M)
where
Self: Sized + 'static,
{
methods.add_async_method("overall_presence", async |_lua, this, ()| {
Ok(this.state().await.current_overall_presence)
});
}
}

View File

@@ -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)