diff --git a/Cargo.lock b/Cargo.lock index accc16c..68e66ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,6 +128,7 @@ dependencies = [ "eui48", "google_home", "inventory", + "lua_typed", "mlua", "reqwest", "rumqttc", @@ -153,6 +154,7 @@ dependencies = [ "hostname", "indexmap", "inventory", + "lua_typed", "mlua", "rumqttc", "serde", @@ -345,6 +347,15 @@ dependencies = [ "winnow", ] +[[package]] +name = "convert_case" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -1094,6 +1105,27 @@ dependencies = [ "cc", ] +[[package]] +name = "lua_typed" +version = "0.1.0" +source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#d30a01aada8f5fc49ad3a296ddbf6e369e08d1f4" +dependencies = [ + "eui48", + "lua_typed_macro", +] + +[[package]] +name = "lua_typed_macro" +version = "0.1.0" +source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#d30a01aada8f5fc49ad3a296ddbf6e369e08d1f4" +dependencies = [ + "convert_case", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "luajit-src" version = "210.6.1+f9140a6" @@ -2256,6 +2288,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 952c0c4..5f401f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ indexmap = { version = "2.11.0", features = ["serde"] } inventory = "0.3.21" itertools = "0.14.0" json_value_merge = "2.0.1" +lua_typed = { git = "https://git.huizinga.dev/Dreaded_X/lua_typed" } mlua = { version = "0.11.3", features = [ "lua54", "vendored", diff --git a/automation_devices/Cargo.toml b/automation_devices/Cargo.toml index 4bfee79..5aba41f 100644 --- a/automation_devices/Cargo.toml +++ b/automation_devices/Cargo.toml @@ -14,6 +14,7 @@ dyn-clone = { workspace = true } eui48 = { workspace = true } google_home = { workspace = true } inventory = { workspace = true } +lua_typed = { workspace = true } mlua = { workspace = true } reqwest = { workspace = true } rumqttc = { workspace = true } diff --git a/automation_devices/src/air_filter.rs b/automation_devices/src/air_filter.rs index 5cfe965..7528f61 100644 --- a/automation_devices/src/air_filter.rs +++ b/automation_devices/src/air_filter.rs @@ -9,15 +9,19 @@ use google_home::traits::{ TemperatureUnit, }; use google_home::types::Type; +use lua_typed::Typed; use thiserror::Error; use tracing::{debug, trace}; -#[derive(Debug, Clone, LuaDeviceConfig)] +#[derive(Debug, Clone, LuaDeviceConfig, Typed)] +#[typed(as = "AirFilterConfig")] pub struct Config { #[device_config(flatten)] + #[typed(flatten)] pub info: InfoConfig, pub url: String, } +crate::register_type!(Config); #[derive(Debug, Clone, Device)] #[device(traits(OnOff))] diff --git a/automation_devices/src/contact_sensor.rs b/automation_devices/src/contact_sensor.rs index 3da27ae..45cba7a 100644 --- a/automation_devices/src/contact_sensor.rs +++ b/automation_devices/src/contact_sensor.rs @@ -13,35 +13,45 @@ use google_home::device; use google_home::errors::{DeviceError, ErrorCode}; use google_home::traits::OpenClose; use google_home::types::Type; +use lua_typed::Typed; use serde::Deserialize; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tracing::{debug, error, trace}; -#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy)] +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy, Typed)] pub enum SensorType { Door, Drawer, Window, } +crate::register_type!(SensorType); -#[derive(Debug, Clone, LuaDeviceConfig)] +#[derive(Debug, Clone, LuaDeviceConfig, Typed)] +#[typed(as = "ContactSensorConfig")] pub struct Config { #[device_config(flatten)] + #[typed(flatten)] pub info: InfoConfig, #[device_config(flatten)] + #[typed(flatten)] pub mqtt: MqttDeviceConfig, #[device_config(default(SensorType::Window))] + #[typed(default)] pub sensor_type: SensorType, #[device_config(from_lua, default)] + #[typed(default)] pub callback: ActionCallback<(ContactSensor, bool)>, #[device_config(from_lua, default)] + #[typed(default)] pub battery_callback: ActionCallback<(ContactSensor, f32)>, #[device_config(from_lua)] + #[typed(default)] pub client: WrappedAsyncClient, } +crate::register_type!(Config); #[derive(Debug)] struct State { diff --git a/automation_devices/src/hue_bridge.rs b/automation_devices/src/hue_bridge.rs index d7d2e25..b2bc1e0 100644 --- a/automation_devices/src/hue_bridge.rs +++ b/automation_devices/src/hue_bridge.rs @@ -4,6 +4,7 @@ use std::net::SocketAddr; use async_trait::async_trait; use automation_lib::device::{Device, LuaDeviceCreate}; use automation_macro::{Device, LuaDeviceConfig}; +use lua_typed::Typed; use mlua::LuaSerdeExt; use serde::{Deserialize, Serialize}; use tracing::{error, trace, warn}; @@ -15,20 +16,24 @@ pub enum Flag { Darkness, } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize, Typed)] pub struct FlagIDs { presence: isize, darkness: isize, } +crate::register_type!(FlagIDs); -#[derive(Debug, LuaDeviceConfig, Clone)] +#[derive(Debug, LuaDeviceConfig, Clone, Typed)] +#[typed(as = "HueBridgeConfig")] pub struct Config { pub identifier: String, #[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 80)))] + #[typed(as = "ip")] pub addr: SocketAddr, pub login: String, pub flags: FlagIDs, } +crate::register_type!(Config); #[derive(Debug, Clone, Device)] #[device(add_methods(Self::add_methods))] diff --git a/automation_devices/src/hue_group.rs b/automation_devices/src/hue_group.rs index a6cdad8..e2ae5d2 100644 --- a/automation_devices/src/hue_group.rs +++ b/automation_devices/src/hue_group.rs @@ -5,19 +5,23 @@ use async_trait::async_trait; use automation_macro::{Device, LuaDeviceConfig}; use google_home::errors::ErrorCode; use google_home::traits::OnOff; +use lua_typed::Typed; use tracing::{error, trace, warn}; use super::{Device, LuaDeviceCreate}; -#[derive(Debug, Clone, LuaDeviceConfig)] +#[derive(Debug, Clone, LuaDeviceConfig, Typed)] +#[typed(as = "HueGroupConfig")] pub struct Config { pub identifier: String, #[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 80)))] + #[typed(as = "ip")] pub addr: SocketAddr, pub login: String, pub group_id: isize, pub scene_id: String, } +crate::register_type!(Config); #[derive(Debug, Clone, Device)] #[device(traits(OnOff))] diff --git a/automation_devices/src/hue_switch.rs b/automation_devices/src/hue_switch.rs index e52a1e9..d3c3c76 100644 --- a/automation_devices/src/hue_switch.rs +++ b/automation_devices/src/hue_switch.rs @@ -5,36 +5,46 @@ use automation_lib::device::{Device, LuaDeviceCreate}; use automation_lib::event::OnMqtt; use automation_lib::mqtt::WrappedAsyncClient; use automation_macro::{Device, LuaDeviceConfig}; +use lua_typed::Typed; use rumqttc::{Publish, matches}; use serde::Deserialize; use tracing::{debug, trace, warn}; -#[derive(Debug, Clone, LuaDeviceConfig)] +#[derive(Debug, Clone, LuaDeviceConfig, Typed)] +#[typed(as = "HueSwitchConfig")] pub struct Config { #[device_config(flatten)] + #[typed(flatten)] pub info: InfoConfig, #[device_config(flatten)] + #[typed(flatten)] pub mqtt: MqttDeviceConfig, #[device_config(from_lua)] pub client: WrappedAsyncClient, #[device_config(from_lua, default)] + #[typed(default)] pub left_callback: ActionCallback, #[device_config(from_lua, default)] + #[typed(default)] pub right_callback: ActionCallback, #[device_config(from_lua, default)] + #[typed(default)] pub left_hold_callback: ActionCallback, #[device_config(from_lua, default)] + #[typed(default)] pub right_hold_callback: ActionCallback, #[device_config(from_lua, default)] + #[typed(default)] pub battery_callback: ActionCallback<(HueSwitch, f32)>, } +crate::register_type!(Config); #[derive(Debug, Copy, Clone, Deserialize)] #[serde(rename_all = "snake_case")] diff --git a/automation_devices/src/ikea_remote.rs b/automation_devices/src/ikea_remote.rs index 2359a52..6d522fb 100644 --- a/automation_devices/src/ikea_remote.rs +++ b/automation_devices/src/ikea_remote.rs @@ -6,28 +6,36 @@ use automation_lib::event::OnMqtt; use automation_lib::messages::{RemoteAction, RemoteMessage}; use automation_lib::mqtt::WrappedAsyncClient; use automation_macro::{Device, LuaDeviceConfig}; +use lua_typed::Typed; use rumqttc::{Publish, matches}; use tracing::{debug, error, trace}; -#[derive(Debug, Clone, LuaDeviceConfig)] +#[derive(Debug, Clone, LuaDeviceConfig, Typed)] +#[typed(as = "IkeaRemoteConfig")] pub struct Config { #[device_config(flatten)] + #[typed(flatten)] pub info: InfoConfig, #[device_config(default)] + #[typed(default)] pub single_button: bool, #[device_config(flatten)] + #[typed(flatten)] pub mqtt: MqttDeviceConfig, #[device_config(from_lua)] pub client: WrappedAsyncClient, #[device_config(from_lua, default)] + #[typed(default)] pub callback: ActionCallback<(IkeaRemote, bool)>, #[device_config(from_lua, default)] + #[typed(default)] pub battery_callback: ActionCallback<(IkeaRemote, f32)>, } +crate::register_type!(Config); #[derive(Debug, Clone, Device)] pub struct IkeaRemote { diff --git a/automation_devices/src/kasa_outlet.rs b/automation_devices/src/kasa_outlet.rs index e812cb1..02f480f 100644 --- a/automation_devices/src/kasa_outlet.rs +++ b/automation_devices/src/kasa_outlet.rs @@ -8,18 +8,22 @@ use automation_macro::{Device, LuaDeviceConfig}; use bytes::{Buf, BufMut}; use google_home::errors::{self, DeviceError}; use google_home::traits::OnOff; +use lua_typed::Typed; use serde::{Deserialize, Serialize}; use thiserror::Error; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; use tracing::trace; -#[derive(Debug, Clone, LuaDeviceConfig)] +#[derive(Debug, Clone, LuaDeviceConfig, Typed)] +#[typed(as = "KasaOutletConfig")] pub struct Config { pub identifier: String, #[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 9999)))] + #[typed(as = "ip")] pub addr: SocketAddr, } +crate::register_type!(Config); #[derive(Debug, Clone, Device)] #[device(traits(OnOff))] diff --git a/automation_devices/src/lib.rs b/automation_devices/src/lib.rs index 80f3f17..efd168f 100644 --- a/automation_devices/src/lib.rs +++ b/automation_devices/src/lib.rs @@ -22,20 +22,22 @@ macro_rules! register_device { stringify!($device), ::mlua::Lua::create_proxy::<$device> )); + + crate::register_type!($device); }; } pub(crate) use register_device; -type RegisterFn = fn(lua: &mlua::Lua) -> mlua::Result; +type RegisterDeviceFn = fn(lua: &mlua::Lua) -> mlua::Result; pub struct RegisteredDevice { name: &'static str, - register_fn: RegisterFn, + register_fn: RegisterDeviceFn, } impl RegisteredDevice { - pub const fn new(name: &'static str, register_fn: RegisterFn) -> Self { + pub const fn new(name: &'static str, register_fn: RegisterDeviceFn) -> Self { Self { name, register_fn } } @@ -64,3 +66,28 @@ pub fn create_module(lua: &mlua::Lua) -> mlua::Result { } inventory::submit! {Module::new("devices", create_module)} + +macro_rules! register_type { + ($ty:ty) => { + ::inventory::submit!(crate::RegisteredType( + <$ty as ::lua_typed::Typed>::generate_full + )); + }; +} + +pub(crate) use register_type; + +type RegisterTypeFn = fn() -> Option; + +pub struct RegisteredType(RegisterTypeFn); + +inventory::collect!(RegisteredType); + +pub fn generate_definitions() { + println!("---@meta\n\nlocal devices\n"); + for ty in inventory::iter:: { + let def = ty.0().unwrap(); + println!("{def}"); + } + println!("return devices") +} diff --git a/automation_devices/src/light_sensor.rs b/automation_devices/src/light_sensor.rs index d241c79..4ad04ae 100644 --- a/automation_devices/src/light_sensor.rs +++ b/automation_devices/src/light_sensor.rs @@ -8,24 +8,29 @@ use automation_lib::event::OnMqtt; use automation_lib::messages::BrightnessMessage; use automation_lib::mqtt::WrappedAsyncClient; use automation_macro::{Device, LuaDeviceConfig}; +use lua_typed::Typed; use rumqttc::Publish; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tracing::{debug, trace, warn}; -#[derive(Debug, Clone, LuaDeviceConfig)] +#[derive(Debug, Clone, LuaDeviceConfig, Typed)] +#[typed(as = "LightSensorConfig")] pub struct Config { pub identifier: String, #[device_config(flatten)] + #[typed(flatten)] pub mqtt: MqttDeviceConfig, pub min: isize, pub max: isize, #[device_config(from_lua, default)] + #[typed(default)] pub callback: ActionCallback<(LightSensor, bool)>, #[device_config(from_lua)] pub client: WrappedAsyncClient, } +crate::register_type!(Config); const DEFAULT: bool = false; diff --git a/automation_devices/src/ntfy.rs b/automation_devices/src/ntfy.rs index 78c7ba8..25efc90 100644 --- a/automation_devices/src/ntfy.rs +++ b/automation_devices/src/ntfy.rs @@ -4,12 +4,13 @@ use std::convert::Infallible; use async_trait::async_trait; use automation_lib::device::{Device, LuaDeviceCreate}; use automation_macro::{Device, LuaDeviceConfig}; +use lua_typed::Typed; use mlua::LuaSerdeExt; use serde::{Deserialize, Serialize}; use serde_repr::*; use tracing::{error, trace, warn}; -#[derive(Debug, Serialize_repr, Deserialize, Clone, Copy)] +#[derive(Debug, Serialize_repr, Deserialize, Clone, Copy, Typed)] #[repr(u8)] #[serde(rename_all = "snake_case")] pub enum Priority { @@ -19,34 +20,41 @@ pub enum Priority { High, Max, } +crate::register_type!(Priority); -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Typed)] #[serde(rename_all = "snake_case", tag = "action")] +#[typed(rename_all = "snake_case", tag = "action")] pub enum ActionType { Broadcast { #[serde(skip_serializing_if = "HashMap::is_empty")] + #[serde(default)] + #[typed(default)] extras: HashMap, }, // View, // Http } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Typed)] pub struct Action { #[serde(flatten)] + #[typed(flatten)] pub action: ActionType, pub label: String, pub clear: Option, } +crate::register_type!(Action); -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Typed)] struct NotificationFinal { topic: String, #[serde(flatten)] + #[typed(flatten)] inner: Notification, } -#[derive(Debug, Serialize, Clone, Deserialize)] +#[derive(Debug, Serialize, Clone, Deserialize, Typed)] pub struct Notification { title: String, message: Option, @@ -57,6 +65,7 @@ pub struct Notification { #[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")] actions: Vec, } +crate::register_type!(Notification); impl Notification { fn finalize(self, topic: &str) -> NotificationFinal { @@ -67,12 +76,15 @@ impl Notification { } } -#[derive(Debug, Clone, LuaDeviceConfig)] +#[derive(Debug, Clone, LuaDeviceConfig, Typed)] +#[typed(as = "NtfyConfig")] pub struct Config { #[device_config(default("https://ntfy.sh".into()))] + #[typed(default)] pub url: String, pub topic: String, } +crate::register_type!(Config); #[derive(Debug, Clone, Device)] #[device(add_methods(Self::add_methods))] diff --git a/automation_devices/src/presence.rs b/automation_devices/src/presence.rs index a765284..2aa8a1b 100644 --- a/automation_devices/src/presence.rs +++ b/automation_devices/src/presence.rs @@ -9,21 +9,26 @@ use automation_lib::event::OnMqtt; use automation_lib::messages::PresenceMessage; use automation_lib::mqtt::WrappedAsyncClient; use automation_macro::{Device, LuaDeviceConfig}; +use lua_typed::Typed; use rumqttc::Publish; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tracing::{debug, trace, warn}; -#[derive(Debug, Clone, LuaDeviceConfig)] +#[derive(Debug, Clone, LuaDeviceConfig, Typed)] +#[typed(as = "PresenceConfig")] pub struct Config { #[device_config(flatten)] + #[typed(flatten)] pub mqtt: MqttDeviceConfig, #[device_config(from_lua, default)] + #[typed(default)] pub callback: ActionCallback<(Presence, bool)>, #[device_config(from_lua)] pub client: WrappedAsyncClient, } +crate::register_type!(Config); pub const DEFAULT_PRESENCE: bool = false; diff --git a/automation_devices/src/wake_on_lan.rs b/automation_devices/src/wake_on_lan.rs index 0282111..72dfecf 100644 --- a/automation_devices/src/wake_on_lan.rs +++ b/automation_devices/src/wake_on_lan.rs @@ -12,21 +12,27 @@ use google_home::device; use google_home::errors::ErrorCode; use google_home::traits::{self, Scene}; use google_home::types::Type; +use lua_typed::Typed; use rumqttc::Publish; use tracing::{debug, error, trace}; -#[derive(Debug, Clone, LuaDeviceConfig)] +#[derive(Debug, Clone, LuaDeviceConfig, Typed)] +#[typed(as = "WolConfig")] pub struct Config { #[device_config(flatten)] + #[typed(flatten)] pub info: InfoConfig, #[device_config(flatten)] + #[typed(flatten)] pub mqtt: MqttDeviceConfig, pub mac_address: MacAddress, #[device_config(default(Ipv4Addr::new(255, 255, 255, 255)))] + #[typed(default)] pub broadcast_ip: Ipv4Addr, #[device_config(from_lua)] pub client: WrappedAsyncClient, } +crate::register_type!(Config); #[derive(Debug, Clone, Device)] pub struct WakeOnLAN { diff --git a/automation_devices/src/washer.rs b/automation_devices/src/washer.rs index bf821a0..a590f73 100644 --- a/automation_devices/src/washer.rs +++ b/automation_devices/src/washer.rs @@ -8,24 +8,29 @@ use automation_lib::event::OnMqtt; use automation_lib::messages::PowerMessage; use automation_lib::mqtt::WrappedAsyncClient; use automation_macro::{Device, LuaDeviceConfig}; +use lua_typed::Typed; use rumqttc::Publish; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tracing::{debug, error, trace}; -#[derive(Debug, Clone, LuaDeviceConfig)] +#[derive(Debug, Clone, LuaDeviceConfig, Typed)] +#[typed(as = "WasherConfig")] pub struct Config { pub identifier: String, #[device_config(flatten)] + #[typed(flatten)] pub mqtt: MqttDeviceConfig, // Power in Watt pub threshold: f32, #[device_config(from_lua, default)] + #[typed(default)] pub done_callback: ActionCallback, #[device_config(from_lua)] pub client: WrappedAsyncClient, } +crate::register_type!(Config); #[derive(Debug)] pub struct State { diff --git a/automation_devices/src/zigbee/light.rs b/automation_devices/src/zigbee/light.rs index d5ba0ed..cd5b01a 100644 --- a/automation_devices/src/zigbee/light.rs +++ b/automation_devices/src/zigbee/light.rs @@ -15,6 +15,7 @@ use google_home::device; use google_home::errors::ErrorCode; use google_home::traits::{Brightness, Color, ColorSetting, ColorTemperatureRange, OnOff}; use google_home::types::Type; +use lua_typed::Typed; use rumqttc::{Publish, matches}; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -22,33 +23,47 @@ use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tracing::{debug, trace, warn}; pub trait LightState: - Debug + Clone + Default + Sync + Send + Serialize + Into + 'static + Debug + Clone + Default + Sync + Send + Serialize + Into + Typed + 'static { } -#[derive(Debug, Clone, LuaDeviceConfig)] -pub struct Config { +#[derive(Debug, Clone, LuaDeviceConfig, Typed)] +#[typed(as = "ConfigLight")] +pub struct Config +where + Light: Typed, +{ #[device_config(flatten)] + #[typed(flatten)] pub info: InfoConfig, #[device_config(flatten)] + #[typed(flatten)] pub mqtt: MqttDeviceConfig, #[device_config(from_lua, default)] + #[typed(default)] pub callback: ActionCallback<(Light, T)>, #[device_config(from_lua)] + #[typed(default)] pub client: WrappedAsyncClient, } +crate::register_type!(Config); +crate::register_type!(Config); +crate::register_type!(Config); -#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize, Typed)] +#[typed(as = "LightStateOnOff")] pub struct StateOnOff { #[serde(deserialize_with = "state_deserializer")] state: bool, } impl LightState for StateOnOff {} +crate::register_type!(StateOnOff); -#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize, Typed)] +#[typed(as = "LightStateBrightness")] pub struct StateBrightness { #[serde(deserialize_with = "state_deserializer")] state: bool, @@ -56,6 +71,7 @@ pub struct StateBrightness { } impl LightState for StateBrightness {} +crate::register_type!(StateBrightness); impl From for StateOnOff { fn from(state: StateBrightness) -> Self { @@ -63,13 +79,15 @@ impl From for StateOnOff { } } -#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize, Typed)] +#[typed(as = "LightStateColorTemperature")] pub struct StateColorTemperature { #[serde(deserialize_with = "state_deserializer")] state: bool, brightness: f32, color_temp: u32, } +crate::register_type!(StateColorTemperature); impl LightState for StateColorTemperature {} @@ -92,7 +110,10 @@ impl From for StateBrightness { #[device(traits(OnOff for LightOnOff, LightBrightness, LightColorTemperature))] #[device(traits(Brightness for LightBrightness, LightColorTemperature))] #[device(traits(ColorSetting for LightColorTemperature))] -pub struct Light { +pub struct Light +where + Light: Typed, +{ config: Config, state: Arc>, @@ -107,7 +128,10 @@ crate::register_device!(LightBrightness); pub type LightColorTemperature = Light; crate::register_device!(LightColorTemperature); -impl Light { +impl Light +where + Light: Typed, +{ async fn state(&self) -> RwLockReadGuard<'_, T> { self.state.read().await } @@ -118,7 +142,10 @@ impl Light { } #[async_trait] -impl LuaDeviceCreate for Light { +impl LuaDeviceCreate for Light +where + Light: Typed, +{ type Config = Config; type Error = rumqttc::ClientError; @@ -137,7 +164,10 @@ impl LuaDeviceCreate for Light { } } -impl Device for Light { +impl Device for Light +where + Light: Typed, +{ fn get_id(&self) -> String { self.config.info.identifier() } @@ -257,7 +287,10 @@ impl OnMqtt for LightColorTemperature { } #[async_trait] -impl google_home::Device for Light { +impl google_home::Device for Light +where + Light: Typed, +{ fn get_device_type(&self) -> Type { Type::Light } @@ -288,6 +321,7 @@ impl google_home::Device for Light { impl OnOff for Light where T: LightState, + Light: Typed, { async fn on(&self) -> Result { let state = self.state().await; @@ -327,6 +361,7 @@ impl Brightness for Light where T: LightState, T: Into, + Light: Typed, { async fn brightness(&self) -> Result { let state = self.state().await; @@ -368,6 +403,7 @@ impl ColorSetting for Light where T: LightState, T: Into, + Light: Typed, { fn color_temperature_range(&self) -> ColorTemperatureRange { ColorTemperatureRange { diff --git a/automation_devices/src/zigbee/outlet.rs b/automation_devices/src/zigbee/outlet.rs index ddf640d..406621a 100644 --- a/automation_devices/src/zigbee/outlet.rs +++ b/automation_devices/src/zigbee/outlet.rs @@ -15,6 +15,7 @@ use google_home::device; use google_home::errors::ErrorCode; use google_home::traits::OnOff; use google_home::types::Type; +use lua_typed::Typed; use rumqttc::{Publish, matches}; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -22,15 +23,16 @@ use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tracing::{debug, trace, warn}; pub trait OutletState: - Debug + Clone + Default + Sync + Send + Serialize + Into + 'static + Debug + Clone + Default + Sync + Send + Serialize + Into + Typed + 'static { } -#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy)] +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy, Typed)] pub enum OutletType { Outlet, Kettle, } +crate::register_type!(OutletType); impl From for Type { fn from(outlet: OutletType) -> Self { @@ -41,36 +43,50 @@ impl From for Type { } } -#[derive(Debug, Clone, LuaDeviceConfig)] -pub struct Config { +#[derive(Debug, Clone, LuaDeviceConfig, Typed)] +#[typed(as = "ConfigOutlet")] +pub struct Config +where + Outlet: Typed, +{ #[device_config(flatten)] + #[typed(flatten)] pub info: InfoConfig, #[device_config(flatten)] + #[typed(flatten)] pub mqtt: MqttDeviceConfig, #[device_config(default(OutletType::Outlet))] + #[typed(default)] pub outlet_type: OutletType, #[device_config(from_lua, default)] + #[typed(default)] pub callback: ActionCallback<(Outlet, T)>, #[device_config(from_lua)] pub client: WrappedAsyncClient, } +crate::register_type!(Config); +crate::register_type!(Config); -#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize, Typed)] +#[typed(as = "OutletStateOnOff")] pub struct StateOnOff { #[serde(deserialize_with = "state_deserializer")] state: bool, } +crate::register_type!(StateOnOff); impl OutletState for StateOnOff {} -#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize, Typed)] +#[typed(as = "OutletStatePower")] pub struct StatePower { #[serde(deserialize_with = "state_deserializer")] state: bool, power: f64, } +crate::register_type!(StatePower); impl OutletState for StatePower {} @@ -82,7 +98,10 @@ impl From for StateOnOff { #[derive(Debug, Clone, Device)] #[device(traits(OnOff for OutletOnOff, OutletPower))] -pub struct Outlet { +pub struct Outlet +where + Outlet: Typed, +{ config: Config, state: Arc>, @@ -94,7 +113,10 @@ crate::register_device!(OutletOnOff); pub type OutletPower = Outlet; crate::register_device!(OutletPower); -impl Outlet { +impl Outlet +where + Outlet: Typed, +{ async fn state(&self) -> RwLockReadGuard<'_, T> { self.state.read().await } @@ -105,7 +127,10 @@ impl Outlet { } #[async_trait] -impl LuaDeviceCreate for Outlet { +impl LuaDeviceCreate for Outlet +where + Outlet: Typed, +{ type Config = Config; type Error = rumqttc::ClientError; @@ -124,7 +149,10 @@ impl LuaDeviceCreate for Outlet { } } -impl Device for Outlet { +impl Device for Outlet +where + Outlet: Typed, +{ fn get_id(&self) -> String { self.config.info.identifier() } @@ -201,7 +229,10 @@ impl OnMqtt for OutletPower { } #[async_trait] -impl google_home::Device for Outlet { +impl google_home::Device for Outlet +where + Outlet: Typed, +{ fn get_device_type(&self) -> Type { self.config.outlet_type.into() } @@ -232,6 +263,7 @@ impl google_home::Device for Outlet { impl OnOff for Outlet where T: OutletState, + Outlet: Typed, { async fn on(&self) -> Result { let state = self.state().await; diff --git a/automation_lib/Cargo.toml b/automation_lib/Cargo.toml index cfc48ee..15b28aa 100644 --- a/automation_lib/Cargo.toml +++ b/automation_lib/Cargo.toml @@ -13,6 +13,7 @@ google_home = { workspace = true } hostname = { workspace = true } indexmap = { workspace = true } inventory = { workspace = true } +lua_typed = { workspace = true } mlua = { workspace = true } rumqttc = { workspace = true } serde = { workspace = true } diff --git a/automation_lib/src/action_callback.rs b/automation_lib/src/action_callback.rs index 2b9479e..d1eb384 100644 --- a/automation_lib/src/action_callback.rs +++ b/automation_lib/src/action_callback.rs @@ -1,6 +1,7 @@ use std::marker::PhantomData; use futures::future::try_join_all; +use lua_typed::Typed; use mlua::{FromLua, IntoLuaMulti}; #[derive(Debug, Clone)] @@ -9,6 +10,23 @@ pub struct ActionCallback

{ _parameters: PhantomData

, } +impl Typed for ActionCallback { + fn type_name() -> String { + let type_name = A::type_name(); + format!("fun(_: {type_name}) | fun(_: {type_name})[]") + } +} + +impl Typed for ActionCallback<(A, B)> { + fn type_name() -> String { + let type_name_a = A::type_name(); + let type_name_b = B::type_name(); + format!( + "fun(_: {type_name_a}, _: {type_name_b}) | fun(_: {type_name_a}, _: {type_name_b})[]" + ) + } +} + // NOTE: For some reason the derive macro combined with PhantomData leads to issues where it // requires all types part of P to implement default, even if they never actually get constructed. // By manually implemented Default it works fine. diff --git a/automation_lib/src/config.rs b/automation_lib/src/config.rs index 42bee56..4b3b334 100644 --- a/automation_lib/src/config.rs +++ b/automation_lib/src/config.rs @@ -1,6 +1,7 @@ use std::net::{Ipv4Addr, SocketAddr}; use std::time::Duration; +use lua_typed::Typed; use rumqttc::{MqttOptions, Transport}; use serde::Deserialize; @@ -52,7 +53,7 @@ fn default_fulfillment_port() -> u16 { 7878 } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize, Typed)] pub struct InfoConfig { pub name: String, pub room: Option, @@ -68,7 +69,7 @@ impl InfoConfig { } } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize, Typed)] pub struct MqttDeviceConfig { pub topic: String, } diff --git a/automation_lib/src/lib.rs b/automation_lib/src/lib.rs index e2a3bbf..1cfedb4 100644 --- a/automation_lib/src/lib.rs +++ b/automation_lib/src/lib.rs @@ -1,5 +1,6 @@ #![allow(incomplete_features)] #![feature(iterator_try_collect)] +#![feature(with_negative_coherence)] use tracing::debug; diff --git a/automation_lib/src/mqtt.rs b/automation_lib/src/mqtt.rs index fc19fba..3fb7d4c 100644 --- a/automation_lib/src/mqtt.rs +++ b/automation_lib/src/mqtt.rs @@ -1,5 +1,6 @@ use std::ops::{Deref, DerefMut}; +use lua_typed::Typed; use mlua::FromLua; use rumqttc::{AsyncClient, Event, EventLoop, Incoming}; use tracing::{debug, warn}; @@ -9,6 +10,12 @@ use crate::event::{self, EventChannel}; #[derive(Debug, Clone, FromLua)] pub struct WrappedAsyncClient(pub AsyncClient); +impl Typed for WrappedAsyncClient { + fn type_name() -> String { + "AsyncClient".into() + } +} + impl Deref for WrappedAsyncClient { type Target = AsyncClient; diff --git a/automation_macro/src/device.rs b/automation_macro/src/device.rs index eec1b9b..6ccbdc7 100644 --- a/automation_macro/src/device.rs +++ b/automation_macro/src/device.rs @@ -163,6 +163,12 @@ impl quote::ToTokens for Implementation { )* } } + + impl ::lua_typed::Typed for #name { + fn type_name() -> String { + stringify!(#name).into() + } + } }); } }