Move front door presence logic to lua
This commit is contained in:
@@ -1,13 +1,12 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_lib::action_callback::ActionCallback;
|
use automation_lib::action_callback::ActionCallback;
|
||||||
use automation_lib::config::{InfoConfig, MqttDeviceConfig};
|
use automation_lib::config::{InfoConfig, MqttDeviceConfig};
|
||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
use automation_lib::error::DeviceConfigError;
|
use automation_lib::error::DeviceConfigError;
|
||||||
use automation_lib::event::{OnMqtt, OnPresence};
|
use automation_lib::event::OnMqtt;
|
||||||
use automation_lib::messages::{ContactMessage, PresenceMessage};
|
use automation_lib::messages::ContactMessage;
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
||||||
use google_home::device;
|
use google_home::device;
|
||||||
@@ -16,10 +15,7 @@ use google_home::traits::OpenClose;
|
|||||||
use google_home::types::Type;
|
use google_home::types::Type;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
use tokio::task::JoinHandle;
|
use tracing::{debug, error, trace};
|
||||||
use tracing::{debug, error, trace, warn};
|
|
||||||
|
|
||||||
use crate::presence::DEFAULT_PRESENCE;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy)]
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy)]
|
||||||
pub enum SensorType {
|
pub enum SensorType {
|
||||||
@@ -28,23 +24,12 @@ pub enum SensorType {
|
|||||||
Window,
|
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)]
|
#[derive(Debug, Clone, LuaDeviceConfig)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
pub info: InfoConfig,
|
pub info: InfoConfig,
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
#[device_config(from_lua, default)]
|
|
||||||
pub presence: Option<PresenceDeviceConfig>,
|
|
||||||
|
|
||||||
#[device_config(default(SensorType::Window))]
|
#[device_config(default(SensorType::Window))]
|
||||||
pub sensor_type: SensorType,
|
pub sensor_type: SensorType,
|
||||||
@@ -57,9 +42,7 @@ pub struct Config {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct State {
|
struct State {
|
||||||
overall_presence: bool,
|
|
||||||
is_closed: bool,
|
is_closed: bool,
|
||||||
handle: Option<JoinHandle<()>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDevice)]
|
#[derive(Debug, Clone, LuaDevice)]
|
||||||
@@ -92,11 +75,7 @@ impl LuaDeviceCreate for ContactSensor {
|
|||||||
.subscribe(&config.mqtt.topic, rumqttc::QoS::AtLeastOnce)
|
.subscribe(&config.mqtt.topic, rumqttc::QoS::AtLeastOnce)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let state = State {
|
let state = State { is_closed: true };
|
||||||
overall_presence: DEFAULT_PRESENCE,
|
|
||||||
is_closed: true,
|
|
||||||
handle: None,
|
|
||||||
};
|
|
||||||
let state = Arc::new(RwLock::new(state));
|
let state = Arc::new(RwLock::new(state));
|
||||||
|
|
||||||
Ok(Self { config, 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]
|
#[async_trait]
|
||||||
impl OnMqtt for ContactSensor {
|
impl OnMqtt for ContactSensor {
|
||||||
async fn on_mqtt(&self, message: rumqttc::Publish) {
|
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}");
|
debug!(id = self.get_id(), "Updating state to {is_closed}");
|
||||||
self.state_mut().await.is_closed = 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();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use automation_lib::action_callback::ActionCallback;
|
|||||||
use automation_lib::config::MqttDeviceConfig;
|
use automation_lib::config::MqttDeviceConfig;
|
||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
use automation_lib::event::{self, Event, EventChannel, OnMqtt};
|
use automation_lib::event::{self, Event, EventChannel, OnMqtt};
|
||||||
|
use automation_lib::lua::traits::AddAdditionalMethods;
|
||||||
use automation_lib::messages::PresenceMessage;
|
use automation_lib::messages::PresenceMessage;
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
||||||
@@ -36,6 +37,7 @@ pub struct State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDevice)]
|
#[derive(Debug, Clone, LuaDevice)]
|
||||||
|
#[traits(AddAdditionalMethods)]
|
||||||
pub struct Presence {
|
pub struct Presence {
|
||||||
config: Config,
|
config: Config,
|
||||||
state: Arc<RwLock<State>>,
|
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)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
29
config.lua
29
config.lua
@@ -39,7 +39,8 @@ local on_presence = {
|
|||||||
self[#self + 1] = f
|
self[#self + 1] = f
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
automation.device_manager:add(Presence.new({
|
|
||||||
|
local presence_system = Presence.new({
|
||||||
topic = mqtt_automation("presence/+/#"),
|
topic = mqtt_automation("presence/+/#"),
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
event_channel = automation.device_manager:event_channel(),
|
event_channel = automation.device_manager:event_channel(),
|
||||||
@@ -50,7 +51,8 @@ automation.device_manager:add(Presence.new({
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
}))
|
})
|
||||||
|
automation.device_manager:add(presence_system)
|
||||||
on_presence:add(function(presence)
|
on_presence:add(function(presence)
|
||||||
ntfy:send_notification({
|
ntfy:send_notification({
|
||||||
title = "Presence",
|
title = "Presence",
|
||||||
@@ -457,6 +459,28 @@ hallway_light_automation.group = {
|
|||||||
end,
|
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({
|
automation.device_manager:add(IkeaRemote.new({
|
||||||
name = "Remote",
|
name = "Remote",
|
||||||
room = "Hallway",
|
room = "Hallway",
|
||||||
@@ -478,6 +502,7 @@ local hallway_frontdoor = ContactSensor.new({
|
|||||||
},
|
},
|
||||||
callback = function(_, open)
|
callback = function(_, open)
|
||||||
hallway_light_automation:door_callback(open)
|
hallway_light_automation:door_callback(open)
|
||||||
|
frontdoor_presence(open)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
automation.device_manager:add(hallway_frontdoor)
|
automation.device_manager:add(hallway_frontdoor)
|
||||||
|
|||||||
Reference in New Issue
Block a user