From d2b01123b86e83aee5802a1b257e77d7fe613d66 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Thu, 28 Aug 2025 03:02:45 +0200 Subject: [PATCH] Made the impl_device macro more explicit about the implemented traits This also converts impl_device into a procedural macro and get rid of a lot of "magic" that was happening. --- Cargo.lock | 9 -- Cargo.toml | 1 - automation_devices/Cargo.toml | 2 - automation_devices/src/air_filter.rs | 3 +- automation_devices/src/contact_sensor.rs | 3 +- automation_devices/src/debug_bridge.rs | 3 +- automation_devices/src/hue_bridge.rs | 3 +- automation_devices/src/hue_group.rs | 3 +- automation_devices/src/hue_switch.rs | 3 +- automation_devices/src/ikea_remote.rs | 3 +- automation_devices/src/kasa_outlet.rs | 3 +- automation_devices/src/lib.rs | 125 ----------------------- automation_devices/src/light_sensor.rs | 3 +- automation_devices/src/wake_on_lan.rs | 3 +- automation_devices/src/washer.rs | 3 +- automation_devices/src/zigbee/light.rs | 5 +- automation_devices/src/zigbee/outlet.rs | 4 +- automation_lib/Cargo.toml | 1 - automation_lib/src/device.rs | 45 -------- automation_lib/src/lib.rs | 2 +- automation_lib/src/lua/mod.rs | 1 + automation_lib/src/lua/traits.rs | 83 +++++++++++++++ automation_lib/src/ntfy.rs | 7 +- automation_lib/src/presence.rs | 9 +- automation_macro/src/impl_device.rs | 70 +++++++++++++ automation_macro/src/lib.rs | 8 ++ 26 files changed, 197 insertions(+), 208 deletions(-) create mode 100644 automation_lib/src/lua/mod.rs create mode 100644 automation_lib/src/lua/traits.rs create mode 100644 automation_macro/src/impl_device.rs 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 1381ad1..27726d3 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..971fd74 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::{LuaDeviceConfig, impl_device}; use google_home::device::Name; use google_home::errors::ErrorCode; use google_home::traits::{ @@ -62,6 +62,7 @@ impl AirFilter { Ok(reqwest::get(url).await?.json().await?) } } +impl_device!(AirFilter); #[async_trait] impl LuaDeviceCreate for AirFilter { diff --git a/automation_devices/src/contact_sensor.rs b/automation_devices/src/contact_sensor.rs index f35598e..657e9ff 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::{LuaDeviceConfig, impl_device}; use google_home::device; use google_home::errors::{DeviceError, ErrorCode}; use google_home::traits::OpenClose; @@ -66,6 +66,7 @@ pub struct ContactSensor { config: Config, state: Arc>, } +impl_device!(ContactSensor); impl ContactSensor { async fn state(&self) -> RwLockReadGuard<'_, State> { diff --git a/automation_devices/src/debug_bridge.rs b/automation_devices/src/debug_bridge.rs index eaabfb0..7e41e2d 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::{LuaDeviceConfig, impl_device}; use tracing::{trace, warn}; #[derive(Debug, LuaDeviceConfig, Clone)] @@ -22,6 +22,7 @@ pub struct Config { pub struct DebugBridge { config: Config, } +impl_device!(DebugBridge); #[async_trait] impl LuaDeviceCreate for DebugBridge { diff --git a/automation_devices/src/hue_bridge.rs b/automation_devices/src/hue_bridge.rs index cd5dfbd..b214ca6 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::{LuaDeviceConfig, impl_device}; use serde::{Deserialize, Serialize}; use tracing::{error, trace, warn}; @@ -33,6 +33,7 @@ pub struct Config { pub struct HueBridge { config: Config, } +impl_device!(HueBridge); #[derive(Debug, Serialize)] struct FlagMessage { diff --git a/automation_devices/src/hue_group.rs b/automation_devices/src/hue_group.rs index f2d074d..78fc90e 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::{LuaDeviceConfig, impl_device}; use google_home::errors::ErrorCode; use google_home::traits::OnOff; use tracing::{error, trace, warn}; @@ -23,6 +23,7 @@ pub struct Config { pub struct HueGroup { config: Config, } +impl_device!(HueGroup); // Couple of helper function to get the correct urls #[async_trait] diff --git a/automation_devices/src/hue_switch.rs b/automation_devices/src/hue_switch.rs index d881f08..23f6bfd 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::{LuaDeviceConfig, impl_device}; use rumqttc::{Publish, matches}; use serde::Deserialize; use tracing::{debug, trace, warn}; @@ -55,6 +55,7 @@ struct State { pub struct HueSwitch { config: Config, } +impl_device!(HueSwitch); impl Device for HueSwitch { fn get_id(&self) -> String { diff --git a/automation_devices/src/ikea_remote.rs b/automation_devices/src/ikea_remote.rs index 58c2350..cd0cdcf 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::{LuaDeviceConfig, impl_device}; use axum::async_trait; use rumqttc::{Publish, matches}; use tracing::{debug, error, trace}; @@ -31,6 +31,7 @@ pub struct Config { pub struct IkeaRemote { config: Config, } +impl_device!(IkeaRemote); impl Device for IkeaRemote { fn get_id(&self) -> String { diff --git a/automation_devices/src/kasa_outlet.rs b/automation_devices/src/kasa_outlet.rs index 956278e..d237df1 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::{LuaDeviceConfig, impl_device}; use bytes::{Buf, BufMut}; use google_home::errors::{self, DeviceError}; use google_home::traits::OnOff; @@ -26,6 +26,7 @@ pub struct Config { pub struct KasaOutlet { config: Config, } +impl_device!(KasaOutlet); #[async_trait] impl LuaDeviceCreate for KasaOutlet { 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..4f82b0e 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::{LuaDeviceConfig, impl_device}; use rumqttc::Publish; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tracing::{debug, trace, warn}; @@ -36,6 +36,7 @@ pub struct LightSensor { config: Config, state: Arc>, } +impl_device!(LightSensor); impl LightSensor { async fn state(&self) -> RwLockReadGuard<'_, State> { diff --git a/automation_devices/src/wake_on_lan.rs b/automation_devices/src/wake_on_lan.rs index e7f04e7..7c5ad9c 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::{LuaDeviceConfig, impl_device}; use eui48::MacAddress; use google_home::device; use google_home::errors::ErrorCode; @@ -32,6 +32,7 @@ pub struct Config { pub struct WakeOnLAN { config: Config, } +impl_device!(WakeOnLAN); #[async_trait] impl LuaDeviceCreate for WakeOnLAN { diff --git a/automation_devices/src/washer.rs b/automation_devices/src/washer.rs index c7f2934..f72b9bd 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::{LuaDeviceConfig, impl_device}; use rumqttc::Publish; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tracing::{debug, error, trace, warn}; @@ -36,6 +36,7 @@ pub struct Washer { config: Config, state: Arc>, } +impl_device!(Washer); impl Washer { async fn state(&self) -> RwLockReadGuard<'_, State> { diff --git a/automation_devices/src/zigbee/light.rs b/automation_devices/src/zigbee/light.rs index 5bcc3cb..8e4a831 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::{LuaDeviceConfig, impl_device}; use google_home::device; use google_home::errors::ErrorCode; use google_home::traits::{Brightness, Color, ColorSetting, ColorTemperatureRange, OnOff}; @@ -96,8 +96,11 @@ pub struct Light { } pub type LightOnOff = Light; +impl_device!(LightOnOff -> OnOff); pub type LightBrightness = Light; +impl_device!(LightBrightness -> OnOff, Brightness); pub type LightColorTemperature = Light; +impl_device!(LightColorTemperature -> OnOff, Brightness, ColorSetting); impl Light { async fn state(&self) -> RwLockReadGuard<'_, T> { diff --git a/automation_devices/src/zigbee/outlet.rs b/automation_devices/src/zigbee/outlet.rs index 9f22df4..732bade 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::{LuaDeviceConfig, impl_device}; use google_home::device; use google_home::errors::ErrorCode; use google_home::traits::OnOff; @@ -92,7 +92,9 @@ pub struct Outlet { } pub type OutletOnOff = Outlet; +impl_device!(OutletOnOff -> OnOff); pub type OutletPower = Outlet; +impl_device!(OutletPower -> OnOff); impl Outlet { async fn state(&self) -> RwLockReadGuard<'_, T> { 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..55c8e13 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::{LuaDeviceConfig, impl_device}; 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)] @@ -125,7 +123,6 @@ pub struct Config { pub struct Ntfy { config: Config, } - impl_device!(Ntfy); #[async_trait] diff --git a/automation_lib/src/presence.rs b/automation_lib/src/presence.rs index bd07aae..2d96bf1 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::{LuaDeviceConfig, impl_device}; 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; @@ -38,6 +36,7 @@ pub struct Presence { config: Config, state: Arc>, } +impl_device!(Presence); impl Presence { async fn state(&self) -> RwLockReadGuard<'_, State> { @@ -49,8 +48,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..0a08960 --- /dev/null +++ b/automation_macro/src/impl_device.rs @@ -0,0 +1,70 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::parse::Parse; +use syn::punctuated::Punctuated; +use syn::{Path, Token, parse_macro_input}; + +struct ImplDevice { + ty: Path, + impls: Option>, +} + +impl Parse for ImplDevice { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let ty = input.parse()?; + let impls = if input.peek(Token![->]) { + input.parse::]>()?; + Some(input.parse_terminated(Path::parse, Token![,])?) + } else { + None + }; + + Ok(ImplDevice { ty, impls }) + } +} + +pub fn impl_device_macro(input: proc_macro::TokenStream) -> TokenStream { + let ImplDevice { ty, impls } = parse_macro_input!(input as ImplDevice); + + let impls: Vec<_> = impls + .iter() + .flatten() + .map(|i| { + let ident = i + .segments + .last() + .expect("There should be at least one segment") + .ident + .clone(); + + quote! { + ::automation_lib::lua::traits::#ident::add_methods(methods); + } + }) + .collect(); + + quote! { + impl mlua::UserData for #ty { + fn add_methods>(methods: &mut M) { + methods.add_async_function("new", |_lua, config| async { + let device: #ty = 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()) }); + + #( + #impls + )* + } + } + }.into() +} diff --git a/automation_macro/src/lib.rs b/automation_macro/src/lib.rs index 261b5a4..f7e9dfc 100644 --- a/automation_macro/src/lib.rs +++ b/automation_macro/src/lib.rs @@ -1,12 +1,20 @@ #![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] +pub fn impl_device(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + impl_device_macro(input) +}