diff --git a/Cargo.lock b/Cargo.lock index 19f8a58..fce38ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,7 +119,6 @@ dependencies = [ "air_filter_types", "anyhow", "async-trait", - "automation_cast", "automation_lib", "automation_macro", "axum", @@ -127,7 +126,6 @@ dependencies = [ "dyn-clone", "eui48", "google_home", - "impls", "mlua", "reqwest", "rumqttc", @@ -150,7 +148,6 @@ dependencies = [ "dyn-clone", "futures", "google_home", - "impls", "indexmap", "mlua", "reqwest", @@ -919,12 +916,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "impls" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a46645bbd70538861a90d0f26c31537cdf1e44aae99a794fb75a664b70951bc" - [[package]] name = "indexmap" version = "2.11.0" diff --git a/Cargo.toml b/Cargo.toml index dd7df2c..08c48cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,6 @@ eui48 = { version = "1.1.0", features = [ ], default-features = false } futures = "0.3.25" hostname = "0.4.0" -impls = "1.0.3" indexmap = { version = "2.0.0", features = ["serde"] } itertools = "0.13.0" json_value_merge = "2.0.0" diff --git a/automation_devices/Cargo.toml b/automation_devices/Cargo.toml index c696bd0..3b80c03 100644 --- a/automation_devices/Cargo.toml +++ b/automation_devices/Cargo.toml @@ -6,7 +6,6 @@ edition = "2024" [dependencies] automation_lib = { workspace = true } automation_macro = { workspace = true } -automation_cast = { workspace = true } google_home = { workspace = true } mlua = { workspace = true } async-trait = { workspace = true } @@ -15,7 +14,6 @@ rumqttc = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } serde_json = { workspace = true } -impls = { workspace = true } serde = { workspace = true } reqwest = { workspace = true } # Use rustls, since the other packages also use rustls anyhow = { workspace = true } diff --git a/automation_devices/src/air_filter.rs b/automation_devices/src/air_filter.rs index d206d12..d3593ae 100644 --- a/automation_devices/src/air_filter.rs +++ b/automation_devices/src/air_filter.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; use automation_lib::config::InfoConfig; use automation_lib::device::{Device, LuaDeviceCreate}; -use automation_macro::LuaDeviceConfig; +use automation_macro::{LuaDevice, LuaDeviceConfig}; use google_home::device::Name; use google_home::errors::ErrorCode; use google_home::traits::{ @@ -19,7 +19,8 @@ pub struct Config { pub url: String, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, LuaDevice)] +#[traits(OnOff)] pub struct AirFilter { config: Config, } diff --git a/automation_devices/src/contact_sensor.rs b/automation_devices/src/contact_sensor.rs index f35598e..866cebb 100644 --- a/automation_devices/src/contact_sensor.rs +++ b/automation_devices/src/contact_sensor.rs @@ -10,7 +10,7 @@ use automation_lib::event::{OnMqtt, OnPresence}; use automation_lib::messages::{ContactMessage, PresenceMessage}; use automation_lib::mqtt::WrappedAsyncClient; use automation_lib::presence::DEFAULT_PRESENCE; -use automation_macro::LuaDeviceConfig; +use automation_macro::{LuaDevice, LuaDeviceConfig}; use google_home::device; use google_home::errors::{DeviceError, ErrorCode}; use google_home::traits::OpenClose; @@ -61,7 +61,7 @@ struct State { handle: Option>, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, LuaDevice)] pub struct ContactSensor { config: Config, state: Arc>, diff --git a/automation_devices/src/debug_bridge.rs b/automation_devices/src/debug_bridge.rs index eaabfb0..700771d 100644 --- a/automation_devices/src/debug_bridge.rs +++ b/automation_devices/src/debug_bridge.rs @@ -6,7 +6,7 @@ use automation_lib::device::{Device, LuaDeviceCreate}; use automation_lib::event::{OnDarkness, OnPresence}; use automation_lib::messages::{DarknessMessage, PresenceMessage}; use automation_lib::mqtt::WrappedAsyncClient; -use automation_macro::LuaDeviceConfig; +use automation_macro::{LuaDevice, LuaDeviceConfig}; use tracing::{trace, warn}; #[derive(Debug, LuaDeviceConfig, Clone)] @@ -18,7 +18,7 @@ pub struct Config { pub client: WrappedAsyncClient, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, LuaDevice)] pub struct DebugBridge { config: Config, } diff --git a/automation_devices/src/hue_bridge.rs b/automation_devices/src/hue_bridge.rs index cd5dfbd..0f37c6b 100644 --- a/automation_devices/src/hue_bridge.rs +++ b/automation_devices/src/hue_bridge.rs @@ -4,7 +4,7 @@ use std::net::SocketAddr; use async_trait::async_trait; use automation_lib::device::{Device, LuaDeviceCreate}; use automation_lib::event::{OnDarkness, OnPresence}; -use automation_macro::LuaDeviceConfig; +use automation_macro::{LuaDevice, LuaDeviceConfig}; use serde::{Deserialize, Serialize}; use tracing::{error, trace, warn}; @@ -29,7 +29,7 @@ pub struct Config { pub flags: FlagIDs, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, LuaDevice)] pub struct HueBridge { config: Config, } diff --git a/automation_devices/src/hue_group.rs b/automation_devices/src/hue_group.rs index f2d074d..836776b 100644 --- a/automation_devices/src/hue_group.rs +++ b/automation_devices/src/hue_group.rs @@ -2,7 +2,7 @@ use std::net::SocketAddr; use anyhow::Result; use async_trait::async_trait; -use automation_macro::LuaDeviceConfig; +use automation_macro::{LuaDevice, LuaDeviceConfig}; use google_home::errors::ErrorCode; use google_home::traits::OnOff; use tracing::{error, trace, warn}; @@ -19,7 +19,8 @@ pub struct Config { pub scene_id: String, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, LuaDevice)] +#[traits(OnOff)] pub struct HueGroup { config: Config, } diff --git a/automation_devices/src/hue_switch.rs b/automation_devices/src/hue_switch.rs index d881f08..e456463 100644 --- a/automation_devices/src/hue_switch.rs +++ b/automation_devices/src/hue_switch.rs @@ -4,7 +4,7 @@ use automation_lib::config::{InfoConfig, MqttDeviceConfig}; use automation_lib::device::{Device, LuaDeviceCreate}; use automation_lib::event::OnMqtt; use automation_lib::mqtt::WrappedAsyncClient; -use automation_macro::LuaDeviceConfig; +use automation_macro::{LuaDevice, LuaDeviceConfig}; use rumqttc::{Publish, matches}; use serde::Deserialize; use tracing::{debug, trace, warn}; @@ -51,7 +51,7 @@ struct State { action: Action, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, LuaDevice)] pub struct HueSwitch { config: Config, } diff --git a/automation_devices/src/ikea_remote.rs b/automation_devices/src/ikea_remote.rs index 58c2350..a3602ae 100644 --- a/automation_devices/src/ikea_remote.rs +++ b/automation_devices/src/ikea_remote.rs @@ -4,7 +4,7 @@ use automation_lib::device::{Device, LuaDeviceCreate}; use automation_lib::event::OnMqtt; use automation_lib::messages::{RemoteAction, RemoteMessage}; use automation_lib::mqtt::WrappedAsyncClient; -use automation_macro::LuaDeviceConfig; +use automation_macro::{LuaDevice, LuaDeviceConfig}; use axum::async_trait; use rumqttc::{Publish, matches}; use tracing::{debug, error, trace}; @@ -27,7 +27,7 @@ pub struct Config { pub callback: ActionCallback, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, LuaDevice)] pub struct IkeaRemote { config: Config, } diff --git a/automation_devices/src/kasa_outlet.rs b/automation_devices/src/kasa_outlet.rs index 956278e..a480213 100644 --- a/automation_devices/src/kasa_outlet.rs +++ b/automation_devices/src/kasa_outlet.rs @@ -5,7 +5,7 @@ use std::str::Utf8Error; use async_trait::async_trait; use automation_lib::device::{Device, LuaDeviceCreate}; use automation_lib::event::OnPresence; -use automation_macro::LuaDeviceConfig; +use automation_macro::{LuaDevice, LuaDeviceConfig}; use bytes::{Buf, BufMut}; use google_home::errors::{self, DeviceError}; use google_home::traits::OnOff; @@ -22,7 +22,8 @@ pub struct Config { pub addr: SocketAddr, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, LuaDevice)] +#[traits(OnOff)] pub struct KasaOutlet { config: Config, } diff --git a/automation_devices/src/lib.rs b/automation_devices/src/lib.rs index 70679cf..238aacd 100644 --- a/automation_devices/src/lib.rs +++ b/automation_devices/src/lib.rs @@ -11,9 +11,6 @@ mod wake_on_lan; mod washer; mod zigbee; -use std::ops::Deref; - -use automation_cast::Cast; use automation_lib::device::{Device, LuaDeviceCreate}; use zigbee::light::{LightBrightness, LightColorTemperature, LightOnOff}; use zigbee::outlet::{OutletOnOff, OutletPower}; @@ -37,128 +34,6 @@ macro_rules! register_device { }; } -macro_rules! impl_device { - ($device:ty) => { - impl mlua::UserData for $device { - fn add_methods>(methods: &mut M) { - methods.add_async_function("new", |_lua, config| async { - let device: $device = LuaDeviceCreate::create(config) - .await - .map_err(mlua::ExternalError::into_lua_err)?; - - 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 move { Ok(this.get_id()) }); - - if impls::impls!($device: google_home::traits::OnOff) { - methods.add_async_method("set_on", |_lua, this, on: bool| async move { - (this.deref().cast() as Option<&dyn google_home::traits::OnOff>) - .expect("Cast should be valid") - .set_on(on) - .await - .unwrap(); - - Ok(()) - }); - - methods.add_async_method("on", |_lua, this, _: ()| async move { - Ok((this.deref().cast() as Option<&dyn google_home::traits::OnOff>) - .expect("Cast should be valid") - .on() - .await - .unwrap()) - }); - } - - if impls::impls!($device: google_home::traits::Brightness) { - methods.add_async_method("set_brightness", |_lua, this, brightness: u8| async move { - (this.deref().cast() as Option<&dyn google_home::traits::Brightness>) - .expect("Cast should be valid") - .set_brightness(brightness) - .await - .unwrap(); - - Ok(()) - }); - - methods.add_async_method("brightness", |_lua, this, _: ()| async move { - Ok((this.deref().cast() as Option<&dyn google_home::traits::Brightness>) - .expect("Cast should be valid") - .brightness() - .await - .unwrap()) - }); - } - - if impls::impls!($device: google_home::traits::ColorSetting) { - methods.add_async_method("set_color_temperature", |_lua, this, temperature: u32| async move { - (this.deref().cast() as Option<&dyn google_home::traits::ColorSetting>) - .expect("Cast should be valid") - .set_color(google_home::traits::Color {temperature}) - .await - .unwrap(); - - Ok(()) - }); - - methods.add_async_method("color_temperature", |_lua, this, _: ()| async move { - Ok((this.deref().cast() as Option<&dyn google_home::traits::ColorSetting>) - .expect("Cast should be valid") - .color() - .await - .temperature) - }); - } - - if impls::impls!($device: google_home::traits::OpenClose) { - // TODO: Make discrete_only_open_close and query_only_open_close static, that way we can - // add only the supported functions and drop _percet if discrete is true - methods.add_async_method("set_open_percent", |_lua, this, open_percent: u8| async move { - (this.deref().cast() as Option<&dyn google_home::traits::OpenClose>) - .expect("Cast should be valid") - .set_open_percent(open_percent) - .await - .unwrap(); - - Ok(()) - }); - - methods.add_async_method("open_percent", |_lua, this, _: ()| async move { - Ok((this.deref().cast() as Option<&dyn google_home::traits::OpenClose>) - .expect("Cast should be valid") - .open_percent() - .await - .unwrap()) - }); - } - } - } - }; -} - -impl_device!(LightOnOff); -impl_device!(LightBrightness); -impl_device!(LightColorTemperature); -impl_device!(OutletOnOff); -impl_device!(OutletPower); -impl_device!(AirFilter); -impl_device!(ContactSensor); -impl_device!(DebugBridge); -impl_device!(HueBridge); -impl_device!(HueGroup); -impl_device!(HueSwitch); -impl_device!(IkeaRemote); -impl_device!(KasaOutlet); -impl_device!(LightSensor); -impl_device!(WakeOnLAN); -impl_device!(Washer); - pub fn register_with_lua(lua: &mlua::Lua) -> mlua::Result<()> { register_device!(lua, LightOnOff); register_device!(lua, LightBrightness); diff --git a/automation_devices/src/light_sensor.rs b/automation_devices/src/light_sensor.rs index feb034d..66e93cc 100644 --- a/automation_devices/src/light_sensor.rs +++ b/automation_devices/src/light_sensor.rs @@ -6,7 +6,7 @@ use automation_lib::device::{Device, LuaDeviceCreate}; use automation_lib::event::{self, Event, EventChannel, OnMqtt}; use automation_lib::messages::BrightnessMessage; use automation_lib::mqtt::WrappedAsyncClient; -use automation_macro::LuaDeviceConfig; +use automation_macro::{LuaDevice, LuaDeviceConfig}; use rumqttc::Publish; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tracing::{debug, trace, warn}; @@ -31,7 +31,7 @@ pub struct State { is_dark: bool, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, LuaDevice)] pub struct LightSensor { config: Config, state: Arc>, diff --git a/automation_devices/src/wake_on_lan.rs b/automation_devices/src/wake_on_lan.rs index e7f04e7..5eb2f00 100644 --- a/automation_devices/src/wake_on_lan.rs +++ b/automation_devices/src/wake_on_lan.rs @@ -6,7 +6,7 @@ use automation_lib::device::{Device, LuaDeviceCreate}; use automation_lib::event::OnMqtt; use automation_lib::messages::ActivateMessage; use automation_lib::mqtt::WrappedAsyncClient; -use automation_macro::LuaDeviceConfig; +use automation_macro::{LuaDevice, LuaDeviceConfig}; use eui48::MacAddress; use google_home::device; use google_home::errors::ErrorCode; @@ -28,7 +28,7 @@ pub struct Config { pub client: WrappedAsyncClient, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, LuaDevice)] pub struct WakeOnLAN { config: Config, } diff --git a/automation_devices/src/washer.rs b/automation_devices/src/washer.rs index c7f2934..f6d5a91 100644 --- a/automation_devices/src/washer.rs +++ b/automation_devices/src/washer.rs @@ -7,7 +7,7 @@ use automation_lib::event::{self, Event, EventChannel, OnMqtt}; use automation_lib::messages::PowerMessage; use automation_lib::mqtt::WrappedAsyncClient; use automation_lib::ntfy::{Notification, Priority}; -use automation_macro::LuaDeviceConfig; +use automation_macro::{LuaDevice, LuaDeviceConfig}; use rumqttc::Publish; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tracing::{debug, error, trace, warn}; @@ -31,7 +31,7 @@ pub struct State { } // TODO: Add google home integration -#[derive(Debug, Clone)] +#[derive(Debug, Clone, LuaDevice)] pub struct Washer { config: Config, state: Arc>, diff --git a/automation_devices/src/zigbee/light.rs b/automation_devices/src/zigbee/light.rs index 5bcc3cb..64157dd 100644 --- a/automation_devices/src/zigbee/light.rs +++ b/automation_devices/src/zigbee/light.rs @@ -10,7 +10,7 @@ use automation_lib::device::{Device, LuaDeviceCreate}; use automation_lib::event::{OnMqtt, OnPresence}; use automation_lib::helpers::serialization::state_deserializer; use automation_lib::mqtt::WrappedAsyncClient; -use automation_macro::LuaDeviceConfig; +use automation_macro::{LuaDevice, LuaDeviceConfig}; use google_home::device; use google_home::errors::ErrorCode; use google_home::traits::{Brightness, Color, ColorSetting, ColorTemperatureRange, OnOff}; @@ -88,7 +88,10 @@ impl From for StateBrightness { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, LuaDevice)] +#[traits(: OnOff)] +#[traits(: OnOff, Brightness)] +#[traits(: OnOff, Brightness, ColorSetting)] pub struct Light { config: Config, diff --git a/automation_devices/src/zigbee/outlet.rs b/automation_devices/src/zigbee/outlet.rs index 9f22df4..3b69cca 100644 --- a/automation_devices/src/zigbee/outlet.rs +++ b/automation_devices/src/zigbee/outlet.rs @@ -10,7 +10,7 @@ use automation_lib::device::{Device, LuaDeviceCreate}; use automation_lib::event::{OnMqtt, OnPresence}; use automation_lib::helpers::serialization::state_deserializer; use automation_lib::mqtt::WrappedAsyncClient; -use automation_macro::LuaDeviceConfig; +use automation_macro::{LuaDevice, LuaDeviceConfig}; use google_home::device; use google_home::errors::ErrorCode; use google_home::traits::OnOff; @@ -84,7 +84,9 @@ impl From for StateOnOff { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, LuaDevice)] +#[traits(: OnOff)] +#[traits(: OnOff)] pub struct Outlet { config: Config, diff --git a/automation_lib/Cargo.toml b/automation_lib/Cargo.toml index e95074a..039475e 100644 --- a/automation_lib/Cargo.toml +++ b/automation_lib/Cargo.toml @@ -23,4 +23,3 @@ tokio-cron-scheduler = { workspace = true } mlua = { workspace = true } uuid = { workspace = true } dyn-clone = { workspace = true } -impls = { workspace = true } diff --git a/automation_lib/src/device.rs b/automation_lib/src/device.rs index 0845f85..0e03ed2 100644 --- a/automation_lib/src/device.rs +++ b/automation_lib/src/device.rs @@ -7,51 +7,6 @@ use mlua::ObjectLike; use crate::event::{OnDarkness, OnMqtt, OnNotification, OnPresence}; -// TODO: Make this a proper macro -macro_rules! impl_device { - ($device:ty) => { - impl mlua::UserData for $device { - fn add_methods>(methods: &mut M) { - methods.add_async_function("new", |_lua, config| async { - let device: $device = LuaDeviceCreate::create(config) - .await - .map_err(mlua::ExternalError::into_lua_err)?; - - 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 move { Ok(this.get_id()) }); - - if impls::impls!($device: google_home::traits::OnOff) { - methods.add_async_method("set_on", |_lua, this, on: bool| async move { - (this.deref().cast() as Option<&dyn google_home::traits::OnOff>) - .expect("Cast should be valid") - .set_on(on) - .await - .unwrap(); - - Ok(()) - }); - - methods.add_async_method("is_on", |_lua, this, _: ()| async move { - Ok((this.deref().cast() as Option<&dyn google_home::traits::OnOff>) - .expect("Cast should be valid") - .on() - .await - .unwrap()) - }); - } - } - } - }; -} -pub(crate) use impl_device; - #[async_trait::async_trait] pub trait LuaDeviceCreate { type Config; diff --git a/automation_lib/src/lib.rs b/automation_lib/src/lib.rs index 7ea97fa..86434eb 100644 --- a/automation_lib/src/lib.rs +++ b/automation_lib/src/lib.rs @@ -1,5 +1,4 @@ #![allow(incomplete_features)] -#![feature(specialization)] pub mod action_callback; pub mod config; @@ -8,6 +7,7 @@ pub mod device_manager; pub mod error; pub mod event; pub mod helpers; +pub mod lua; pub mod messages; pub mod mqtt; pub mod ntfy; diff --git a/automation_lib/src/lua/mod.rs b/automation_lib/src/lua/mod.rs new file mode 100644 index 0000000..f6ac8fc --- /dev/null +++ b/automation_lib/src/lua/mod.rs @@ -0,0 +1 @@ +pub mod traits; diff --git a/automation_lib/src/lua/traits.rs b/automation_lib/src/lua/traits.rs new file mode 100644 index 0000000..cb191d0 --- /dev/null +++ b/automation_lib/src/lua/traits.rs @@ -0,0 +1,83 @@ +use std::ops::Deref; + +// TODO: Enable and disable functions based on query_only and command_only + +pub trait OnOff { + fn add_methods>(methods: &mut M) + where + Self: Sized + google_home::traits::OnOff + 'static, + { + methods.add_async_method("set_on", |_lua, this, on: bool| async move { + this.deref().set_on(on).await.unwrap(); + + Ok(()) + }); + + methods.add_async_method("on", |_lua, this, ()| async move { + Ok(this.deref().on().await.unwrap()) + }); + } +} +impl OnOff for T where T: google_home::traits::OnOff {} + +pub trait Brightness { + fn add_methods>(methods: &mut M) + where + Self: Sized + google_home::traits::Brightness + 'static, + { + methods.add_async_method("set_brightness", |_lua, this, brightness: u8| async move { + this.set_brightness(brightness).await.unwrap(); + + Ok(()) + }); + + methods.add_async_method("brightness", |_lua, this, _: ()| async move { + Ok(this.brightness().await.unwrap()) + }); + } +} +impl Brightness for T where T: google_home::traits::Brightness {} + +pub trait ColorSetting { + fn add_methods>(methods: &mut M) + where + Self: Sized + google_home::traits::ColorSetting + 'static, + { + methods.add_async_method( + "set_color_temperature", + |_lua, this, temperature: u32| async move { + this.set_color(google_home::traits::Color { temperature }) + .await + .unwrap(); + + Ok(()) + }, + ); + + methods.add_async_method("color_temperature", |_lua, this, ()| async move { + Ok(this.color().await.temperature) + }); + } +} +impl ColorSetting for T where T: google_home::traits::ColorSetting {} + +pub trait OpenClose { + fn add_methods>(methods: &mut M) + where + Self: Sized + google_home::traits::OpenClose + 'static, + { + methods.add_async_method( + "set_open_percent", + |_lua, this, open_percent: u8| async move { + this.set_open_percent(open_percent).await.unwrap(); + + Ok(()) + }, + ); + + methods.add_async_method("open_percent", |_lua, this, _: ()| async move { + Ok(this.open_percent().await.unwrap()) + }); + } +} +impl OpenClose for T where T: google_home::traits::OpenClose {} diff --git a/automation_lib/src/ntfy.rs b/automation_lib/src/ntfy.rs index 27c209b..a27c8a2 100644 --- a/automation_lib/src/ntfy.rs +++ b/automation_lib/src/ntfy.rs @@ -1,15 +1,13 @@ use std::collections::HashMap; use std::convert::Infallible; -use std::ops::Deref; use async_trait::async_trait; -use automation_cast::Cast; -use automation_macro::LuaDeviceConfig; +use automation_macro::{LuaDevice, LuaDeviceConfig}; use serde::Serialize; use serde_repr::*; use tracing::{error, trace, warn}; -use crate::device::{Device, LuaDeviceCreate, impl_device}; +use crate::device::{Device, LuaDeviceCreate}; use crate::event::{self, Event, EventChannel, OnNotification, OnPresence}; #[derive(Debug, Serialize_repr, Clone, Copy)] @@ -121,13 +119,11 @@ pub struct Config { pub tx: event::Sender, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, LuaDevice)] pub struct Ntfy { config: Config, } -impl_device!(Ntfy); - #[async_trait] impl LuaDeviceCreate for Ntfy { type Config = Config; diff --git a/automation_lib/src/presence.rs b/automation_lib/src/presence.rs index bd07aae..b246c82 100644 --- a/automation_lib/src/presence.rs +++ b/automation_lib/src/presence.rs @@ -1,16 +1,14 @@ use std::collections::HashMap; -use std::ops::Deref; use std::sync::Arc; use async_trait::async_trait; -use automation_cast::Cast; -use automation_macro::LuaDeviceConfig; +use automation_macro::{LuaDevice, LuaDeviceConfig}; use rumqttc::Publish; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tracing::{debug, trace, warn}; use crate::config::MqttDeviceConfig; -use crate::device::{Device, LuaDeviceCreate, impl_device}; +use crate::device::{Device, LuaDeviceCreate}; use crate::event::{self, Event, EventChannel, OnMqtt}; use crate::messages::PresenceMessage; use crate::mqtt::WrappedAsyncClient; @@ -33,7 +31,7 @@ pub struct State { current_overall_presence: bool, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, LuaDevice)] pub struct Presence { config: Config, state: Arc>, @@ -49,8 +47,6 @@ impl Presence { } } -impl_device!(Presence); - #[async_trait] impl LuaDeviceCreate for Presence { type Config = Config; diff --git a/automation_macro/src/impl_device.rs b/automation_macro/src/impl_device.rs new file mode 100644 index 0000000..f3c3d67 --- /dev/null +++ b/automation_macro/src/impl_device.rs @@ -0,0 +1,88 @@ +use proc_macro2::TokenStream; +use quote::{ToTokens, quote}; +use syn::parse::Parse; +use syn::punctuated::Punctuated; +use syn::{AngleBracketedGenericArguments, Attribute, DeriveInput, Ident, Path, Token}; + +#[derive(Debug, Default)] +struct Impl { + generics: Option, + traits: Vec, +} + +impl Parse for Impl { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let generics = if input.peek(Token![<]) { + let generics = input.parse()?; + input.parse::()?; + + Some(generics) + } else { + None + }; + + let traits: Punctuated<_, _> = input.parse_terminated(Path::parse, Token![,])?; + let traits = traits.into_iter().collect(); + + Ok(Impl { generics, traits }) + } +} + +impl Impl { + fn generate(&self, name: &Ident) -> TokenStream { + let generics = &self.generics; + + // If an identifier is specified, assume it is placed in ::automation_lib::lua::traits, + // otherwise use the provided path + let traits = self.traits.iter().map(|t| { + if let Some(ident) = t.get_ident() { + quote! {::automation_lib::lua::traits::#ident } + } else { + t.to_token_stream() + } + }); + + quote! { + impl mlua::UserData for #name #generics { + fn add_methods>(methods: &mut M) { + methods.add_async_function("new", |_lua, config| async { + let device: Self = LuaDeviceCreate::create(config) + .await + .map_err(mlua::ExternalError::into_lua_err)?; + + 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 move { Ok(this.get_id()) }); + + #( + #traits::add_methods(methods); + )* + } + } + } + } +} + +pub fn impl_device_macro(ast: &DeriveInput) -> TokenStream { + let name = &ast.ident; + + let impls: TokenStream = ast + .attrs + .iter() + .filter(|attr| attr.path().is_ident("traits")) + .flat_map(Attribute::parse_args::) + .map(|im| im.generate(name)) + .collect(); + + if impls.is_empty() { + Impl::default().generate(name) + } else { + impls + } +} diff --git a/automation_macro/src/lib.rs b/automation_macro/src/lib.rs index 261b5a4..17b28eb 100644 --- a/automation_macro/src/lib.rs +++ b/automation_macro/src/lib.rs @@ -1,12 +1,22 @@ #![feature(iter_intersperse)] +mod impl_device; mod lua_device_config; use lua_device_config::impl_lua_device_config_macro; use syn::{DeriveInput, parse_macro_input}; +use crate::impl_device::impl_device_macro; + #[proc_macro_derive(LuaDeviceConfig, attributes(device_config))] pub fn lua_device_config_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = parse_macro_input!(input as DeriveInput); impl_lua_device_config_macro(&ast).into() } + +#[proc_macro_derive(LuaDevice, attributes(traits))] +pub fn impl_device(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + + impl_device_macro(&ast).into() +}