diff --git a/Cargo.lock b/Cargo.lock index bb09b53..ea2ab4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,6 +106,7 @@ dependencies = [ "futures", "google_home", "hostname", + "impls", "indexmap 2.2.6", "mlua", "once_cell", @@ -877,6 +878,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impls" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a46645bbd70538861a90d0f26c31537cdf1e44aae99a794fb75a664b70951bc" + [[package]] name = "indexmap" version = "1.9.3" diff --git a/Cargo.toml b/Cargo.toml index 033babd..c351ef5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ hostname = "0.4.0" tokio-util = { version = "0.7.11", features = ["full"] } uuid = "1.8.0" dyn-clone = "1.0.17" +impls = "1.0.3" [patch.crates-io] wakey = { git = "https://git.huizinga.dev/Dreaded_X/wakey" } diff --git a/src/device_manager.rs b/src/device_manager.rs index f6d1108..c6978d9 100644 --- a/src/device_manager.rs +++ b/src/device_manager.rs @@ -1,12 +1,9 @@ use std::collections::HashMap; -use std::ops::Deref; use std::pin::Pin; use std::sync::Arc; use futures::future::join_all; use futures::Future; -use google_home::traits::OnOff; -use mlua::FromLua; use tokio::sync::{RwLock, RwLockReadGuard}; use tokio_cron_scheduler::{Job, JobScheduler}; use tokio_util::task::LocalPoolHandle; @@ -16,37 +13,6 @@ use crate::devices::Device; use crate::event::{Event, EventChannel, OnDarkness, OnMqtt, OnNotification, OnPresence}; use crate::LUA; -#[derive(Debug, FromLua, Clone)] -pub struct WrappedDevice(Box); - -impl WrappedDevice { - pub fn new(device: impl Device + 'static) -> Self { - Self(Box::new(device)) - } -} - -impl Deref for WrappedDevice { - type Target = Box; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl mlua::UserData for WrappedDevice { - fn add_methods<'lua, M: mlua::prelude::LuaUserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_async_method("get_id", |_lua, this, _: ()| async { Ok(this.get_id()) }); - - methods.add_async_method("set_on", |_lua, this, on: bool| async move { - if let Some(device) = this.cast() as Option<&dyn OnOff> { - device.set_on(on).await.unwrap() - }; - - Ok(()) - }); - } -} - pub type DeviceMap = HashMap>; #[derive(Clone)] @@ -195,8 +161,8 @@ fn run_schedule( impl mlua::UserData for DeviceManager { fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_async_method("add", |_lua, this, device: WrappedDevice| async move { - this.add(device.0).await; + methods.add_async_method("add", |_lua, this, device: Box| async move { + this.add(device).await; Ok(()) }); diff --git a/src/devices/audio_setup.rs b/src/devices/audio_setup.rs index 4caebfe..0022fb7 100644 --- a/src/devices/audio_setup.rs +++ b/src/devices/audio_setup.rs @@ -5,7 +5,6 @@ use tracing::{debug, error, trace, warn}; use super::{Device, LuaDeviceCreate}; use crate::config::MqttDeviceConfig; -use crate::device_manager::WrappedDevice; use crate::error::DeviceConfigError; use crate::event::{OnMqtt, OnPresence}; use crate::messages::{RemoteAction, RemoteMessage}; @@ -17,9 +16,9 @@ pub struct Config { #[device_config(flatten)] pub mqtt: MqttDeviceConfig, #[device_config(from_lua)] - pub mixer: WrappedDevice, + pub mixer: Box, #[device_config(from_lua)] - pub speakers: WrappedDevice, + pub speakers: Box, #[device_config(from_lua)] pub client: WrappedAsyncClient, } diff --git a/src/devices/contact_sensor.rs b/src/devices/contact_sensor.rs index c0e82c3..03c8e05 100644 --- a/src/devices/contact_sensor.rs +++ b/src/devices/contact_sensor.rs @@ -10,7 +10,6 @@ use tracing::{debug, error, trace, warn}; use super::{Device, LuaDeviceCreate}; use crate::config::MqttDeviceConfig; -use crate::device_manager::WrappedDevice; use crate::devices::DEFAULT_PRESENCE; use crate::error::DeviceConfigError; use crate::event::{OnMqtt, OnPresence}; @@ -30,7 +29,7 @@ pub struct PresenceDeviceConfig { #[derive(Debug, Clone, LuaDeviceConfig)] pub struct TriggerConfig { #[device_config(from_lua)] - pub devices: Vec, + pub devices: Vec>, #[device_config(default, with(|t: Option<_>| t.map(Duration::from_secs)))] pub timeout: Option, } diff --git a/src/devices/mod.rs b/src/devices/mod.rs index d2f1e98..d7f8aca 100644 --- a/src/devices/mod.rs +++ b/src/devices/mod.rs @@ -18,6 +18,7 @@ use async_trait::async_trait; use automation_cast::Cast; use dyn_clone::DynClone; use google_home::traits::OnOff; +use mlua::AnyUserDataExt; pub use self::air_filter::AirFilter; pub use self::audio_setup::AudioSetup; @@ -53,37 +54,61 @@ macro_rules! register_device { } macro_rules! impl_device { - ($lua:expr, $device:ty) => { + ($device:ty) => { impl mlua::UserData for $device { fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_async_function("new", |lua, config: mlua::Value| async { - let config = mlua::FromLua::from_lua(config, lua)?; - - // TODO: Using crate:: could cause issues + methods.add_async_function("new", |_lua, config| async { let device: $device = crate::devices::LuaDeviceCreate::create(config) .await .map_err(mlua::ExternalError::into_lua_err)?; - Ok(crate::device_manager::WrappedDevice::new(device)) + Ok(device) }); + + methods.add_method("__box", |_lua, this, _: ()| { + let b: Box = Box::new(this.clone()); + Ok(b) + }); + + methods.add_async_method("get_id", |_lua, this, _: ()| async { Ok(this.get_id()) }); + + if impls::impls!($device: OnOff) { + methods.add_async_method("set_on", |_lua, this, on: bool| async move { + (this.cast() as Option<&dyn OnOff>) + .unwrap() + .set_on(on) + .await + .unwrap(); + + Ok(()) + }); + + methods.add_async_method("is_on", |_lua, this, _: ()| async move { + Ok((this.cast() as Option<&dyn OnOff>) + .unwrap() + .on() + .await + .unwrap()) + }); + } } } }; } -impl_device!(lua, AirFilter); -impl_device!(lua, AudioSetup); -impl_device!(lua, ContactSensor); -impl_device!(lua, DebugBridge); -impl_device!(lua, HueBridge); -impl_device!(lua, HueGroup); -impl_device!(lua, IkeaOutlet); -impl_device!(lua, KasaOutlet); -impl_device!(lua, LightSensor); -impl_device!(lua, Ntfy); -impl_device!(lua, Presence); -impl_device!(lua, WakeOnLAN); -impl_device!(lua, Washer); +impl_device!(AirFilter); +impl_device!(AudioSetup); +impl_device!(ContactSensor); +impl_device!(DebugBridge); +impl_device!(HueBridge); +impl_device!(HueGroup); +impl_device!(IkeaOutlet); +impl_device!(KasaOutlet); +impl_device!(LightSensor); +impl_device!(Ntfy); +impl_device!(Presence); +impl_device!(WakeOnLAN); +impl_device!(Washer); pub fn register_with_lua(lua: &mlua::Lua) -> mlua::Result<()> { register_device!(lua, AirFilter); @@ -120,4 +145,23 @@ pub trait Device: fn get_id(&self) -> String; } +impl<'lua> mlua::FromLua<'lua> for Box { + fn from_lua(value: mlua::Value<'lua>, _lua: &'lua mlua::Lua) -> mlua::Result { + match value { + mlua::Value::UserData(ud) => { + let ud = if ud.is::>() { + ud + } else { + ud.call_method::<_, mlua::AnyUserData>("__box", ())? + }; + + let b = ud.borrow::()?.clone(); + Ok(b) + } + _ => Err(mlua::Error::RuntimeError("Expected user data".into())), + } + } +} +impl mlua::UserData for Box {} + dyn_clone::clone_trait_object!(Device);