WIP: Lua action callback
All checks were successful
Build and deploy / Build application (push) Successful in 7m28s
Check / Run checks (push) Successful in 2m20s
Build and deploy / Build container (push) Successful in 1m5s
Build and deploy / Deploy container (push) Has been skipped

This commit is contained in:
Dreaded_X 2024-07-19 22:50:58 +02:00
parent 006320be18
commit ce870cfcb5
Signed by: Dreaded_X
GPG Key ID: FA5F485356B0D2D4
7 changed files with 112 additions and 10 deletions

4
Cargo.lock generated
View File

@ -2221,9 +2221,9 @@ dependencies = [
[[package]]
name = "uuid"
version = "1.8.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
dependencies = [
"getrandom",
]

View File

@ -54,7 +54,7 @@ mlua = { version = "0.9.7", features = [
once_cell = "1.19.0"
hostname = "0.4.0"
tokio-util = { version = "0.7.11", features = ["full"] }
uuid = "1.8.0"
uuid = { version = "1.8.0", features = ["v4"] }
[patch.crates-io]
wakey = { git = "https://git.huizinga.dev/Dreaded_X/wakey" }

View File

@ -36,11 +36,12 @@ automation.device_manager:add(Ntfy.new({
event_channel = automation.device_manager:event_channel(),
}))
automation.device_manager:add(Presence.new({
local presence = Presence.new({
topic = mqtt_automation("presence/+/#"),
client = mqtt_client,
event_channel = automation.device_manager:event_channel(),
}))
})
automation.device_manager:add(presence)
automation.device_manager:add(DebugBridge.new({
identifier = "debug_bridge",
@ -164,15 +165,48 @@ automation.device_manager:add(ContactSensor.new({
devices = { hallway_lights },
timeout = debug and 10 or 2 * 60,
},
action = function(closed)
-- if state then
-- hallway_lights:set_on(true)
-- presence:set_presence("contact/frontdoor", true)
-- else
-- -- TODO: New timeout implementation -> Device stores timestamp of last state change.
-- -- Generic timeout implementation then works by storing the timestamp at the start and comparing once the timer expires.
-- -- If the timestamp has changed -> Cancel the state change
-- -- Maybe add some sort identifier, such that if the function gets called repeatedly it will overwrite the existing timeout?
-- -- Tag? Autogenerated from position in lua code? Not sure if that is possible
-- automation.timeout(2 * 60, function()
-- hallway_lights:set_on(false)
-- end)
-- automation.timeout(15 * 60, function()
-- presence:set_presence("contact/frontdoor", false)
-- end)
-- end
end,
}))
local function trash_action(device)
local previous = device:is_on()
local function f(closed)
if closed then
if not previous then
device:set_on(false)
end
else
previous = device:is_on()
device:set_on(true)
end
end
return f
end
automation.device_manager:add(ContactSensor.new({
identifier = "hallway_trash",
topic = mqtt_z2m("hallway/trash"),
client = mqtt_client,
trigger = {
devices = { hallway_lights },
},
trigger = { devices = {} },
action = trash_action(hallway_lights),
}))
local bedroom_air_filter = AirFilter.new({
@ -189,3 +223,7 @@ end)
automation.device_manager:schedule("0 0 20 * * *", function()
bedroom_air_filter:set_on(false)
end)
automation.timeout(10, function()
print("Cool stuff")
end)

View File

@ -54,6 +54,17 @@ impl mlua::UserData for WrappedDevice {
Ok(())
});
methods.add_async_method("is_on", |_lua, this, _: ()| async move {
let device = this.0.read().await;
let device = device.as_ref();
if let Some(device) = device.cast() as Option<&dyn OnOff> {
return Ok(device.on().await.unwrap());
};
Ok(false)
});
}
}
@ -229,8 +240,7 @@ impl mlua::UserData for DeviceManager {
let uuid = this.scheduler.add(job).await.unwrap();
// Store the function in the registry
lua.set_named_registry_value(uuid.to_string().as_str(), f)
.unwrap();
lua.set_named_registry_value(&uuid.to_string(), f).unwrap();
Ok(())
},

View File

@ -5,6 +5,7 @@ use automation_macro::{LuaDevice, LuaDeviceConfig};
use google_home::traits::OnOff;
use mlua::FromLua;
use tokio::task::JoinHandle;
use tokio_util::task::LocalPoolHandle;
use tracing::{debug, error, trace, warn};
use super::{Device, LuaDeviceCreate};
@ -16,6 +17,7 @@ use crate::event::{OnMqtt, OnPresence};
use crate::messages::{ContactMessage, PresenceMessage};
use crate::mqtt::WrappedAsyncClient;
use crate::traits::Timeout;
use crate::LUA;
// NOTE: If we add more presence devices we might need to move this out of here
#[derive(Debug, Clone, LuaDeviceConfig)]
@ -49,6 +51,37 @@ pub struct TriggerConfig {
pub timeout: Option<Duration>,
}
#[derive(Debug, Clone, Copy)]
pub struct ActionCallback(uuid::Uuid);
impl<'lua> FromLua<'lua> for ActionCallback {
fn from_lua(value: mlua::Value<'lua>, lua: &'lua mlua::Lua) -> mlua::Result<Self> {
let uuid = uuid::Uuid::new_v4();
lua.set_named_registry_value(&uuid.to_string(), value)?;
Ok(ActionCallback(uuid))
}
}
impl ActionCallback {
async fn call(&self, closed: bool) {
let pool = LocalPoolHandle::new(1);
let uuid = self.0;
pool.spawn_pinned(move || async move {
let lua = LUA.lock().await;
let action: mlua::Value = lua.named_registry_value(&uuid.to_string())?;
match action {
mlua::Value::Function(f) => f.call_async::<_, ()>(closed).await,
_ => todo!("Only functions are currently supported"),
}
})
.await
.unwrap()
.unwrap()
}
}
#[derive(Debug, Clone, LuaDeviceConfig)]
pub struct ContactSensorConfig {
pub identifier: String,
@ -60,6 +93,8 @@ pub struct ContactSensorConfig {
pub trigger: Option<TriggerConfig>,
#[device_config(from_lua)]
pub client: WrappedAsyncClient,
#[device_config(from_lua)]
pub action: ActionCallback,
}
#[derive(Debug, LuaDevice)]
@ -150,6 +185,8 @@ impl OnMqtt for ContactSensor {
debug!(id = self.config.identifier, "Updating state to {is_closed}");
self.is_closed = is_closed;
self.config.action.call(self.is_closed).await;
if let Some(trigger) = &mut self.config.trigger {
if !self.is_closed {
for (light, previous) in &mut trigger.devices {

View File

@ -1,6 +1,7 @@
#![allow(incomplete_features)]
#![feature(specialization)]
#![feature(let_chains)]
#![feature(unboxed_closures)]
use once_cell::sync::Lazy;
use tokio::sync::Mutex;

View File

@ -18,6 +18,7 @@ use dotenvy::dotenv;
use google_home::{GoogleHome, Request};
use mlua::LuaSerdeExt;
use rumqttc::AsyncClient;
use tokio_util::task::LocalPoolHandle;
use tracing::{debug, error, info, warn};
#[derive(Clone)]
@ -79,6 +80,21 @@ async fn app() -> anyhow::Result<()> {
automation.set("new_mqtt_client", new_mqtt_client)?;
automation.set("device_manager", device_manager.clone())?;
let timeout = lua.create_function(|lua, (t, f): (u64, mlua::Function)| {
let pool = LocalPoolHandle::new(1);
let key = lua.create_registry_value(f).unwrap();
pool.spawn_pinned(move || async move {
tokio::time::sleep(std::time::Duration::from_secs(t)).await;
let lua = LUA.lock().await;
let f: mlua::Function = lua.registry_value(&key).unwrap();
f.call_async::<_, ()>(()).await.unwrap();
lua.remove_registry_value(key).unwrap();
});
Ok(())
})?;
automation.set("timeout", timeout)?;
let util = lua.create_table()?;
let get_env = lua.create_function(|_lua, name: String| {
std::env::var(name).map_err(mlua::ExternalError::into_lua_err)