feat: Added Typed impl for all automation devices

To accomplish this a basic implementation was also provided for some
types in automation_lib
This commit is contained in:
2025-10-10 03:47:52 +02:00
parent 76eb63cd97
commit 1532958a86
24 changed files with 296 additions and 46 deletions

38
Cargo.lock generated
View File

@@ -128,6 +128,7 @@ dependencies = [
"eui48", "eui48",
"google_home", "google_home",
"inventory", "inventory",
"lua_typed",
"mlua", "mlua",
"reqwest", "reqwest",
"rumqttc", "rumqttc",
@@ -153,6 +154,7 @@ dependencies = [
"hostname", "hostname",
"indexmap", "indexmap",
"inventory", "inventory",
"lua_typed",
"mlua", "mlua",
"rumqttc", "rumqttc",
"serde", "serde",
@@ -345,6 +347,15 @@ dependencies = [
"winnow", "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]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@@ -1094,6 +1105,27 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "lua_typed"
version = "0.1.0"
source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#d5d6fc1638bd108514899a792ee64335af50fc8b"
dependencies = [
"eui48",
"lua_typed_macro",
]
[[package]]
name = "lua_typed_macro"
version = "0.1.0"
source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#d5d6fc1638bd108514899a792ee64335af50fc8b"
dependencies = [
"convert_case",
"itertools",
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]] [[package]]
name = "luajit-src" name = "luajit-src"
version = "210.6.1+f9140a6" version = "210.6.1+f9140a6"
@@ -2256,6 +2288,12 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.9.0" version = "0.9.0"

View File

@@ -37,6 +37,7 @@ indexmap = { version = "2.11.0", features = ["serde"] }
inventory = "0.3.21" inventory = "0.3.21"
itertools = "0.14.0" itertools = "0.14.0"
json_value_merge = "2.0.1" json_value_merge = "2.0.1"
lua_typed = { git = "https://git.huizinga.dev/Dreaded_X/lua_typed" }
mlua = { version = "0.11.3", features = [ mlua = { version = "0.11.3", features = [
"lua54", "lua54",
"vendored", "vendored",

View File

@@ -14,6 +14,7 @@ dyn-clone = { workspace = true }
eui48 = { workspace = true } eui48 = { workspace = true }
google_home = { workspace = true } google_home = { workspace = true }
inventory = { workspace = true } inventory = { workspace = true }
lua_typed = { workspace = true }
mlua = { workspace = true } mlua = { workspace = true }
reqwest = { workspace = true } reqwest = { workspace = true }
rumqttc = { workspace = true } rumqttc = { workspace = true }

View File

@@ -9,15 +9,19 @@ use google_home::traits::{
TemperatureUnit, TemperatureUnit,
}; };
use google_home::types::Type; use google_home::types::Type;
use lua_typed::Typed;
use thiserror::Error; use thiserror::Error;
use tracing::{debug, trace}; use tracing::{debug, trace};
#[derive(Debug, Clone, LuaDeviceConfig)] #[derive(Debug, Clone, LuaDeviceConfig, Typed)]
#[typed(as = "AirFilterConfig")]
pub struct Config { pub struct Config {
#[device_config(flatten)] #[device_config(flatten)]
#[typed(flatten)]
pub info: InfoConfig, pub info: InfoConfig,
pub url: String, pub url: String,
} }
crate::register_type!(Config);
#[derive(Debug, Clone, Device)] #[derive(Debug, Clone, Device)]
#[device(traits(OnOff))] #[device(traits(OnOff))]

View File

@@ -13,35 +13,45 @@ use google_home::device;
use google_home::errors::{DeviceError, ErrorCode}; use google_home::errors::{DeviceError, ErrorCode};
use google_home::traits::OpenClose; use google_home::traits::OpenClose;
use google_home::types::Type; use google_home::types::Type;
use lua_typed::Typed;
use serde::Deserialize; use serde::Deserialize;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use tracing::{debug, error, trace}; use tracing::{debug, error, trace};
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy)] #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy, Typed)]
pub enum SensorType { pub enum SensorType {
Door, Door,
Drawer, Drawer,
Window, Window,
} }
crate::register_type!(SensorType);
#[derive(Debug, Clone, LuaDeviceConfig)] #[derive(Debug, Clone, LuaDeviceConfig, Typed)]
#[typed(as = "ContactSensorConfig")]
pub struct Config { pub struct Config {
#[device_config(flatten)] #[device_config(flatten)]
#[typed(flatten)]
pub info: InfoConfig, pub info: InfoConfig,
#[device_config(flatten)] #[device_config(flatten)]
#[typed(flatten)]
pub mqtt: MqttDeviceConfig, pub mqtt: MqttDeviceConfig,
#[device_config(default(SensorType::Window))] #[device_config(default(SensorType::Window))]
#[typed(default)]
pub sensor_type: SensorType, pub sensor_type: SensorType,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[typed(default)]
pub callback: ActionCallback<(ContactSensor, bool)>, pub callback: ActionCallback<(ContactSensor, bool)>,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[typed(default)]
pub battery_callback: ActionCallback<(ContactSensor, f32)>, pub battery_callback: ActionCallback<(ContactSensor, f32)>,
#[device_config(from_lua)] #[device_config(from_lua)]
#[typed(default)]
pub client: WrappedAsyncClient, pub client: WrappedAsyncClient,
} }
crate::register_type!(Config);
#[derive(Debug)] #[derive(Debug)]
struct State { struct State {

View File

@@ -4,6 +4,7 @@ use std::net::SocketAddr;
use async_trait::async_trait; use async_trait::async_trait;
use automation_lib::device::{Device, LuaDeviceCreate}; use automation_lib::device::{Device, LuaDeviceCreate};
use automation_macro::{Device, LuaDeviceConfig}; use automation_macro::{Device, LuaDeviceConfig};
use lua_typed::Typed;
use mlua::LuaSerdeExt; use mlua::LuaSerdeExt;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::{error, trace, warn}; use tracing::{error, trace, warn};
@@ -15,20 +16,24 @@ pub enum Flag {
Darkness, Darkness,
} }
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize, Typed)]
pub struct FlagIDs { pub struct FlagIDs {
presence: isize, presence: isize,
darkness: isize, darkness: isize,
} }
crate::register_type!(FlagIDs);
#[derive(Debug, LuaDeviceConfig, Clone)] #[derive(Debug, LuaDeviceConfig, Clone, Typed)]
#[typed(as = "HueBridgeConfig")]
pub struct Config { pub struct Config {
pub identifier: String, pub identifier: String,
#[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 80)))] #[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 80)))]
#[typed(as = "ip")]
pub addr: SocketAddr, pub addr: SocketAddr,
pub login: String, pub login: String,
pub flags: FlagIDs, pub flags: FlagIDs,
} }
crate::register_type!(Config);
#[derive(Debug, Clone, Device)] #[derive(Debug, Clone, Device)]
#[device(add_methods(Self::add_methods))] #[device(add_methods(Self::add_methods))]

View File

@@ -5,19 +5,23 @@ use async_trait::async_trait;
use automation_macro::{Device, LuaDeviceConfig}; use automation_macro::{Device, LuaDeviceConfig};
use google_home::errors::ErrorCode; use google_home::errors::ErrorCode;
use google_home::traits::OnOff; use google_home::traits::OnOff;
use lua_typed::Typed;
use tracing::{error, trace, warn}; use tracing::{error, trace, warn};
use super::{Device, LuaDeviceCreate}; use super::{Device, LuaDeviceCreate};
#[derive(Debug, Clone, LuaDeviceConfig)] #[derive(Debug, Clone, LuaDeviceConfig, Typed)]
#[typed(as = "HueGroupConfig")]
pub struct Config { pub struct Config {
pub identifier: String, pub identifier: String,
#[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 80)))] #[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 80)))]
#[typed(as = "ip")]
pub addr: SocketAddr, pub addr: SocketAddr,
pub login: String, pub login: String,
pub group_id: isize, pub group_id: isize,
pub scene_id: String, pub scene_id: String,
} }
crate::register_type!(Config);
#[derive(Debug, Clone, Device)] #[derive(Debug, Clone, Device)]
#[device(traits(OnOff))] #[device(traits(OnOff))]

View File

@@ -5,36 +5,46 @@ use automation_lib::device::{Device, LuaDeviceCreate};
use automation_lib::event::OnMqtt; use automation_lib::event::OnMqtt;
use automation_lib::mqtt::WrappedAsyncClient; use automation_lib::mqtt::WrappedAsyncClient;
use automation_macro::{Device, LuaDeviceConfig}; use automation_macro::{Device, LuaDeviceConfig};
use lua_typed::Typed;
use rumqttc::{Publish, matches}; use rumqttc::{Publish, matches};
use serde::Deserialize; use serde::Deserialize;
use tracing::{debug, trace, warn}; use tracing::{debug, trace, warn};
#[derive(Debug, Clone, LuaDeviceConfig)] #[derive(Debug, Clone, LuaDeviceConfig, Typed)]
#[typed(as = "HueSwitchConfig")]
pub struct Config { pub struct Config {
#[device_config(flatten)] #[device_config(flatten)]
#[typed(flatten)]
pub info: InfoConfig, pub info: InfoConfig,
#[device_config(flatten)] #[device_config(flatten)]
#[typed(flatten)]
pub mqtt: MqttDeviceConfig, pub mqtt: MqttDeviceConfig,
#[device_config(from_lua)] #[device_config(from_lua)]
pub client: WrappedAsyncClient, pub client: WrappedAsyncClient,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[typed(default)]
pub left_callback: ActionCallback<HueSwitch>, pub left_callback: ActionCallback<HueSwitch>,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[typed(default)]
pub right_callback: ActionCallback<HueSwitch>, pub right_callback: ActionCallback<HueSwitch>,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[typed(default)]
pub left_hold_callback: ActionCallback<HueSwitch>, pub left_hold_callback: ActionCallback<HueSwitch>,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[typed(default)]
pub right_hold_callback: ActionCallback<HueSwitch>, pub right_hold_callback: ActionCallback<HueSwitch>,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[typed(default)]
pub battery_callback: ActionCallback<(HueSwitch, f32)>, pub battery_callback: ActionCallback<(HueSwitch, f32)>,
} }
crate::register_type!(Config);
#[derive(Debug, Copy, Clone, Deserialize)] #[derive(Debug, Copy, Clone, Deserialize)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]

View File

@@ -6,28 +6,36 @@ use automation_lib::event::OnMqtt;
use automation_lib::messages::{RemoteAction, RemoteMessage}; use automation_lib::messages::{RemoteAction, RemoteMessage};
use automation_lib::mqtt::WrappedAsyncClient; use automation_lib::mqtt::WrappedAsyncClient;
use automation_macro::{Device, LuaDeviceConfig}; use automation_macro::{Device, LuaDeviceConfig};
use lua_typed::Typed;
use rumqttc::{Publish, matches}; use rumqttc::{Publish, matches};
use tracing::{debug, error, trace}; use tracing::{debug, error, trace};
#[derive(Debug, Clone, LuaDeviceConfig)] #[derive(Debug, Clone, LuaDeviceConfig, Typed)]
#[typed(as = "IkeaRemoteConfig")]
pub struct Config { pub struct Config {
#[device_config(flatten)] #[device_config(flatten)]
#[typed(flatten)]
pub info: InfoConfig, pub info: InfoConfig,
#[device_config(default)] #[device_config(default)]
#[typed(default)]
pub single_button: bool, pub single_button: bool,
#[device_config(flatten)] #[device_config(flatten)]
#[typed(flatten)]
pub mqtt: MqttDeviceConfig, pub mqtt: MqttDeviceConfig,
#[device_config(from_lua)] #[device_config(from_lua)]
pub client: WrappedAsyncClient, pub client: WrappedAsyncClient,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[typed(default)]
pub callback: ActionCallback<(IkeaRemote, bool)>, pub callback: ActionCallback<(IkeaRemote, bool)>,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[typed(default)]
pub battery_callback: ActionCallback<(IkeaRemote, f32)>, pub battery_callback: ActionCallback<(IkeaRemote, f32)>,
} }
crate::register_type!(Config);
#[derive(Debug, Clone, Device)] #[derive(Debug, Clone, Device)]
pub struct IkeaRemote { pub struct IkeaRemote {

View File

@@ -8,18 +8,22 @@ use automation_macro::{Device, LuaDeviceConfig};
use bytes::{Buf, BufMut}; use bytes::{Buf, BufMut};
use google_home::errors::{self, DeviceError}; use google_home::errors::{self, DeviceError};
use google_home::traits::OnOff; use google_home::traits::OnOff;
use lua_typed::Typed;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tracing::trace; use tracing::trace;
#[derive(Debug, Clone, LuaDeviceConfig)] #[derive(Debug, Clone, LuaDeviceConfig, Typed)]
#[typed(as = "KasaOutletConfig")]
pub struct Config { pub struct Config {
pub identifier: String, pub identifier: String,
#[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 9999)))] #[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 9999)))]
#[typed(as = "ip")]
pub addr: SocketAddr, pub addr: SocketAddr,
} }
crate::register_type!(Config);
#[derive(Debug, Clone, Device)] #[derive(Debug, Clone, Device)]
#[device(traits(OnOff))] #[device(traits(OnOff))]

View File

@@ -22,20 +22,22 @@ macro_rules! register_device {
stringify!($device), stringify!($device),
::mlua::Lua::create_proxy::<$device> ::mlua::Lua::create_proxy::<$device>
)); ));
crate::register_type!($device);
}; };
} }
pub(crate) use register_device; pub(crate) use register_device;
type RegisterFn = fn(lua: &mlua::Lua) -> mlua::Result<mlua::AnyUserData>; type RegisterDeviceFn = fn(lua: &mlua::Lua) -> mlua::Result<mlua::AnyUserData>;
pub struct RegisteredDevice { pub struct RegisteredDevice {
name: &'static str, name: &'static str,
register_fn: RegisterFn, register_fn: RegisterDeviceFn,
} }
impl RegisteredDevice { 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 } Self { name, register_fn }
} }
@@ -64,3 +66,28 @@ pub fn create_module(lua: &mlua::Lua) -> mlua::Result<mlua::Table> {
} }
inventory::submit! {Module::new("devices", create_module)} 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<String>;
pub struct RegisteredType(RegisterTypeFn);
inventory::collect!(RegisteredType);
pub fn generate_definitions() {
println!("---@meta\n\nlocal devices\n");
for ty in inventory::iter::<RegisteredType> {
let def = ty.0().unwrap();
println!("{def}");
}
println!("return devices")
}

View File

@@ -8,24 +8,29 @@ use automation_lib::event::OnMqtt;
use automation_lib::messages::BrightnessMessage; use automation_lib::messages::BrightnessMessage;
use automation_lib::mqtt::WrappedAsyncClient; use automation_lib::mqtt::WrappedAsyncClient;
use automation_macro::{Device, LuaDeviceConfig}; use automation_macro::{Device, LuaDeviceConfig};
use lua_typed::Typed;
use rumqttc::Publish; use rumqttc::Publish;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use tracing::{debug, trace, warn}; use tracing::{debug, trace, warn};
#[derive(Debug, Clone, LuaDeviceConfig)] #[derive(Debug, Clone, LuaDeviceConfig, Typed)]
#[typed(as = "LightSensorConfig")]
pub struct Config { pub struct Config {
pub identifier: String, pub identifier: String,
#[device_config(flatten)] #[device_config(flatten)]
#[typed(flatten)]
pub mqtt: MqttDeviceConfig, pub mqtt: MqttDeviceConfig,
pub min: isize, pub min: isize,
pub max: isize, pub max: isize,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[typed(default)]
pub callback: ActionCallback<(LightSensor, bool)>, pub callback: ActionCallback<(LightSensor, bool)>,
#[device_config(from_lua)] #[device_config(from_lua)]
pub client: WrappedAsyncClient, pub client: WrappedAsyncClient,
} }
crate::register_type!(Config);
const DEFAULT: bool = false; const DEFAULT: bool = false;

View File

@@ -4,14 +4,16 @@ use std::convert::Infallible;
use async_trait::async_trait; use async_trait::async_trait;
use automation_lib::device::{Device, LuaDeviceCreate}; use automation_lib::device::{Device, LuaDeviceCreate};
use automation_macro::{Device, LuaDeviceConfig}; use automation_macro::{Device, LuaDeviceConfig};
use lua_typed::Typed;
use mlua::LuaSerdeExt; use mlua::LuaSerdeExt;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::*; use serde_repr::*;
use tracing::{error, trace, warn}; use tracing::{error, trace, warn};
#[derive(Debug, Serialize_repr, Deserialize, Clone, Copy)] #[derive(Debug, Serialize_repr, Deserialize, Clone, Copy, Typed)]
#[repr(u8)] #[repr(u8)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
#[typed(rename_all = "snake_case")]
pub enum Priority { pub enum Priority {
Min = 1, Min = 1,
Low, Low,
@@ -19,44 +21,54 @@ pub enum Priority {
High, High,
Max, Max,
} }
crate::register_type!(Priority);
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone, Typed)]
#[serde(rename_all = "snake_case", tag = "action")] #[serde(rename_all = "snake_case", tag = "action")]
#[typed(rename_all = "snake_case", tag = "action")]
pub enum ActionType { pub enum ActionType {
Broadcast { Broadcast {
#[serde(skip_serializing_if = "HashMap::is_empty")] #[serde(skip_serializing_if = "HashMap::is_empty")]
#[serde(default)]
#[typed(default)]
extras: HashMap<String, String>, extras: HashMap<String, String>,
}, },
// View, // View,
// Http // Http
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone, Typed)]
pub struct Action { pub struct Action {
#[serde(flatten)] #[serde(flatten)]
#[typed(flatten)]
pub action: ActionType, pub action: ActionType,
pub label: String, pub label: String,
pub clear: Option<bool>, pub clear: Option<bool>,
} }
crate::register_type!(Action);
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Typed)]
struct NotificationFinal { struct NotificationFinal {
topic: String, topic: String,
#[serde(flatten)] #[serde(flatten)]
#[typed(flatten)]
inner: Notification, inner: Notification,
} }
#[derive(Debug, Serialize, Clone, Deserialize)] #[derive(Debug, Serialize, Clone, Deserialize, Typed)]
pub struct Notification { pub struct Notification {
title: String, title: String,
message: Option<String>, message: Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")] #[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")]
#[typed(default)]
tags: Vec<String>, tags: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
priority: Option<Priority>, priority: Option<Priority>,
#[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")] #[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")]
#[typed(default)]
actions: Vec<Action>, actions: Vec<Action>,
} }
crate::register_type!(Notification);
impl Notification { impl Notification {
fn finalize(self, topic: &str) -> NotificationFinal { fn finalize(self, topic: &str) -> NotificationFinal {
@@ -67,12 +79,15 @@ impl Notification {
} }
} }
#[derive(Debug, Clone, LuaDeviceConfig)] #[derive(Debug, Clone, LuaDeviceConfig, Typed)]
#[typed(as = "NtfyConfig")]
pub struct Config { pub struct Config {
#[device_config(default("https://ntfy.sh".into()))] #[device_config(default("https://ntfy.sh".into()))]
#[typed(default)]
pub url: String, pub url: String,
pub topic: String, pub topic: String,
} }
crate::register_type!(Config);
#[derive(Debug, Clone, Device)] #[derive(Debug, Clone, Device)]
#[device(add_methods(Self::add_methods))] #[device(add_methods(Self::add_methods))]

View File

@@ -9,21 +9,26 @@ use automation_lib::event::OnMqtt;
use automation_lib::messages::PresenceMessage; use automation_lib::messages::PresenceMessage;
use automation_lib::mqtt::WrappedAsyncClient; use automation_lib::mqtt::WrappedAsyncClient;
use automation_macro::{Device, LuaDeviceConfig}; use automation_macro::{Device, LuaDeviceConfig};
use lua_typed::Typed;
use rumqttc::Publish; use rumqttc::Publish;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use tracing::{debug, trace, warn}; use tracing::{debug, trace, warn};
#[derive(Debug, Clone, LuaDeviceConfig)] #[derive(Debug, Clone, LuaDeviceConfig, Typed)]
#[typed(as = "PresenceConfig")]
pub struct Config { pub struct Config {
#[device_config(flatten)] #[device_config(flatten)]
#[typed(flatten)]
pub mqtt: MqttDeviceConfig, pub mqtt: MqttDeviceConfig,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[typed(default)]
pub callback: ActionCallback<(Presence, bool)>, pub callback: ActionCallback<(Presence, bool)>,
#[device_config(from_lua)] #[device_config(from_lua)]
pub client: WrappedAsyncClient, pub client: WrappedAsyncClient,
} }
crate::register_type!(Config);
pub const DEFAULT_PRESENCE: bool = false; pub const DEFAULT_PRESENCE: bool = false;

View File

@@ -12,21 +12,27 @@ use google_home::device;
use google_home::errors::ErrorCode; use google_home::errors::ErrorCode;
use google_home::traits::{self, Scene}; use google_home::traits::{self, Scene};
use google_home::types::Type; use google_home::types::Type;
use lua_typed::Typed;
use rumqttc::Publish; use rumqttc::Publish;
use tracing::{debug, error, trace}; use tracing::{debug, error, trace};
#[derive(Debug, Clone, LuaDeviceConfig)] #[derive(Debug, Clone, LuaDeviceConfig, Typed)]
#[typed(as = "WolConfig")]
pub struct Config { pub struct Config {
#[device_config(flatten)] #[device_config(flatten)]
#[typed(flatten)]
pub info: InfoConfig, pub info: InfoConfig,
#[device_config(flatten)] #[device_config(flatten)]
#[typed(flatten)]
pub mqtt: MqttDeviceConfig, pub mqtt: MqttDeviceConfig,
pub mac_address: MacAddress, pub mac_address: MacAddress,
#[device_config(default(Ipv4Addr::new(255, 255, 255, 255)))] #[device_config(default(Ipv4Addr::new(255, 255, 255, 255)))]
#[typed(default)]
pub broadcast_ip: Ipv4Addr, pub broadcast_ip: Ipv4Addr,
#[device_config(from_lua)] #[device_config(from_lua)]
pub client: WrappedAsyncClient, pub client: WrappedAsyncClient,
} }
crate::register_type!(Config);
#[derive(Debug, Clone, Device)] #[derive(Debug, Clone, Device)]
pub struct WakeOnLAN { pub struct WakeOnLAN {

View File

@@ -8,24 +8,29 @@ use automation_lib::event::OnMqtt;
use automation_lib::messages::PowerMessage; use automation_lib::messages::PowerMessage;
use automation_lib::mqtt::WrappedAsyncClient; use automation_lib::mqtt::WrappedAsyncClient;
use automation_macro::{Device, LuaDeviceConfig}; use automation_macro::{Device, LuaDeviceConfig};
use lua_typed::Typed;
use rumqttc::Publish; use rumqttc::Publish;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use tracing::{debug, error, trace}; use tracing::{debug, error, trace};
#[derive(Debug, Clone, LuaDeviceConfig)] #[derive(Debug, Clone, LuaDeviceConfig, Typed)]
#[typed(as = "WasherConfig")]
pub struct Config { pub struct Config {
pub identifier: String, pub identifier: String,
#[device_config(flatten)] #[device_config(flatten)]
#[typed(flatten)]
pub mqtt: MqttDeviceConfig, pub mqtt: MqttDeviceConfig,
// Power in Watt // Power in Watt
pub threshold: f32, pub threshold: f32,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[typed(default)]
pub done_callback: ActionCallback<Washer>, pub done_callback: ActionCallback<Washer>,
#[device_config(from_lua)] #[device_config(from_lua)]
pub client: WrappedAsyncClient, pub client: WrappedAsyncClient,
} }
crate::register_type!(Config);
#[derive(Debug)] #[derive(Debug)]
pub struct State { pub struct State {

View File

@@ -15,6 +15,7 @@ use google_home::device;
use google_home::errors::ErrorCode; use google_home::errors::ErrorCode;
use google_home::traits::{Brightness, Color, ColorSetting, ColorTemperatureRange, OnOff}; use google_home::traits::{Brightness, Color, ColorSetting, ColorTemperatureRange, OnOff};
use google_home::types::Type; use google_home::types::Type;
use lua_typed::Typed;
use rumqttc::{Publish, matches}; use rumqttc::{Publish, matches};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
@@ -22,33 +23,47 @@ use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use tracing::{debug, trace, warn}; use tracing::{debug, trace, warn};
pub trait LightState: pub trait LightState:
Debug + Clone + Default + Sync + Send + Serialize + Into<StateOnOff> + 'static Debug + Clone + Default + Sync + Send + Serialize + Into<StateOnOff> + Typed + 'static
{ {
} }
#[derive(Debug, Clone, LuaDeviceConfig)] #[derive(Debug, Clone, LuaDeviceConfig, Typed)]
pub struct Config<T: LightState> { #[typed(as = "ConfigLight")]
pub struct Config<T: LightState>
where
Light<T>: Typed,
{
#[device_config(flatten)] #[device_config(flatten)]
#[typed(flatten)]
pub info: InfoConfig, pub info: InfoConfig,
#[device_config(flatten)] #[device_config(flatten)]
#[typed(flatten)]
pub mqtt: MqttDeviceConfig, pub mqtt: MqttDeviceConfig,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[typed(default)]
pub callback: ActionCallback<(Light<T>, T)>, pub callback: ActionCallback<(Light<T>, T)>,
#[device_config(from_lua)] #[device_config(from_lua)]
#[typed(default)]
pub client: WrappedAsyncClient, pub client: WrappedAsyncClient,
} }
crate::register_type!(Config<StateOnOff>);
crate::register_type!(Config<StateBrightness>);
crate::register_type!(Config<StateColorTemperature>);
#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize, Typed)]
#[typed(as = "LightStateOnOff")]
pub struct StateOnOff { pub struct StateOnOff {
#[serde(deserialize_with = "state_deserializer")] #[serde(deserialize_with = "state_deserializer")]
state: bool, state: bool,
} }
impl LightState for StateOnOff {} 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 { pub struct StateBrightness {
#[serde(deserialize_with = "state_deserializer")] #[serde(deserialize_with = "state_deserializer")]
state: bool, state: bool,
@@ -56,6 +71,7 @@ pub struct StateBrightness {
} }
impl LightState for StateBrightness {} impl LightState for StateBrightness {}
crate::register_type!(StateBrightness);
impl From<StateBrightness> for StateOnOff { impl From<StateBrightness> for StateOnOff {
fn from(state: StateBrightness) -> Self { fn from(state: StateBrightness) -> Self {
@@ -63,13 +79,15 @@ impl From<StateBrightness> for StateOnOff {
} }
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize, Typed)]
#[typed(as = "LightStateColorTemperature")]
pub struct StateColorTemperature { pub struct StateColorTemperature {
#[serde(deserialize_with = "state_deserializer")] #[serde(deserialize_with = "state_deserializer")]
state: bool, state: bool,
brightness: f32, brightness: f32,
color_temp: u32, color_temp: u32,
} }
crate::register_type!(StateColorTemperature);
impl LightState for StateColorTemperature {} impl LightState for StateColorTemperature {}
@@ -92,7 +110,10 @@ impl From<StateColorTemperature> for StateBrightness {
#[device(traits(OnOff for LightOnOff, LightBrightness, LightColorTemperature))] #[device(traits(OnOff for LightOnOff, LightBrightness, LightColorTemperature))]
#[device(traits(Brightness for LightBrightness, LightColorTemperature))] #[device(traits(Brightness for LightBrightness, LightColorTemperature))]
#[device(traits(ColorSetting for LightColorTemperature))] #[device(traits(ColorSetting for LightColorTemperature))]
pub struct Light<T: LightState> { pub struct Light<T: LightState>
where
Light<T>: Typed,
{
config: Config<T>, config: Config<T>,
state: Arc<RwLock<T>>, state: Arc<RwLock<T>>,
@@ -107,7 +128,10 @@ crate::register_device!(LightBrightness);
pub type LightColorTemperature = Light<StateColorTemperature>; pub type LightColorTemperature = Light<StateColorTemperature>;
crate::register_device!(LightColorTemperature); crate::register_device!(LightColorTemperature);
impl<T: LightState> Light<T> { impl<T: LightState> Light<T>
where
Light<T>: Typed,
{
async fn state(&self) -> RwLockReadGuard<'_, T> { async fn state(&self) -> RwLockReadGuard<'_, T> {
self.state.read().await self.state.read().await
} }
@@ -118,7 +142,10 @@ impl<T: LightState> Light<T> {
} }
#[async_trait] #[async_trait]
impl<T: LightState> LuaDeviceCreate for Light<T> { impl<T: LightState> LuaDeviceCreate for Light<T>
where
Light<T>: Typed,
{
type Config = Config<T>; type Config = Config<T>;
type Error = rumqttc::ClientError; type Error = rumqttc::ClientError;
@@ -137,7 +164,10 @@ impl<T: LightState> LuaDeviceCreate for Light<T> {
} }
} }
impl<T: LightState> Device for Light<T> { impl<T: LightState> Device for Light<T>
where
Light<T>: Typed,
{
fn get_id(&self) -> String { fn get_id(&self) -> String {
self.config.info.identifier() self.config.info.identifier()
} }
@@ -257,7 +287,10 @@ impl OnMqtt for LightColorTemperature {
} }
#[async_trait] #[async_trait]
impl<T: LightState> google_home::Device for Light<T> { impl<T: LightState> google_home::Device for Light<T>
where
Light<T>: Typed,
{
fn get_device_type(&self) -> Type { fn get_device_type(&self) -> Type {
Type::Light Type::Light
} }
@@ -288,6 +321,7 @@ impl<T: LightState> google_home::Device for Light<T> {
impl<T> OnOff for Light<T> impl<T> OnOff for Light<T>
where where
T: LightState, T: LightState,
Light<T>: Typed,
{ {
async fn on(&self) -> Result<bool, ErrorCode> { async fn on(&self) -> Result<bool, ErrorCode> {
let state = self.state().await; let state = self.state().await;
@@ -327,6 +361,7 @@ impl<T> Brightness for Light<T>
where where
T: LightState, T: LightState,
T: Into<StateBrightness>, T: Into<StateBrightness>,
Light<T>: Typed,
{ {
async fn brightness(&self) -> Result<u8, ErrorCode> { async fn brightness(&self) -> Result<u8, ErrorCode> {
let state = self.state().await; let state = self.state().await;
@@ -368,6 +403,7 @@ impl<T> ColorSetting for Light<T>
where where
T: LightState, T: LightState,
T: Into<StateColorTemperature>, T: Into<StateColorTemperature>,
Light<T>: Typed,
{ {
fn color_temperature_range(&self) -> ColorTemperatureRange { fn color_temperature_range(&self) -> ColorTemperatureRange {
ColorTemperatureRange { ColorTemperatureRange {

View File

@@ -15,6 +15,7 @@ use google_home::device;
use google_home::errors::ErrorCode; use google_home::errors::ErrorCode;
use google_home::traits::OnOff; use google_home::traits::OnOff;
use google_home::types::Type; use google_home::types::Type;
use lua_typed::Typed;
use rumqttc::{Publish, matches}; use rumqttc::{Publish, matches};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
@@ -22,15 +23,16 @@ use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use tracing::{debug, trace, warn}; use tracing::{debug, trace, warn};
pub trait OutletState: pub trait OutletState:
Debug + Clone + Default + Sync + Send + Serialize + Into<StateOnOff> + 'static Debug + Clone + Default + Sync + Send + Serialize + Into<StateOnOff> + Typed + 'static
{ {
} }
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy)] #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy, Typed)]
pub enum OutletType { pub enum OutletType {
Outlet, Outlet,
Kettle, Kettle,
} }
crate::register_type!(OutletType);
impl From<OutletType> for Type { impl From<OutletType> for Type {
fn from(outlet: OutletType) -> Self { fn from(outlet: OutletType) -> Self {
@@ -41,36 +43,50 @@ impl From<OutletType> for Type {
} }
} }
#[derive(Debug, Clone, LuaDeviceConfig)] #[derive(Debug, Clone, LuaDeviceConfig, Typed)]
pub struct Config<T: OutletState> { #[typed(as = "ConfigOutlet")]
pub struct Config<T: OutletState>
where
Outlet<T>: Typed,
{
#[device_config(flatten)] #[device_config(flatten)]
#[typed(flatten)]
pub info: InfoConfig, pub info: InfoConfig,
#[device_config(flatten)] #[device_config(flatten)]
#[typed(flatten)]
pub mqtt: MqttDeviceConfig, pub mqtt: MqttDeviceConfig,
#[device_config(default(OutletType::Outlet))] #[device_config(default(OutletType::Outlet))]
#[typed(default)]
pub outlet_type: OutletType, pub outlet_type: OutletType,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[typed(default)]
pub callback: ActionCallback<(Outlet<T>, T)>, pub callback: ActionCallback<(Outlet<T>, T)>,
#[device_config(from_lua)] #[device_config(from_lua)]
pub client: WrappedAsyncClient, pub client: WrappedAsyncClient,
} }
crate::register_type!(Config<StateOnOff>);
crate::register_type!(Config<StatePower>);
#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize, Typed)]
#[typed(as = "OutletStateOnOff")]
pub struct StateOnOff { pub struct StateOnOff {
#[serde(deserialize_with = "state_deserializer")] #[serde(deserialize_with = "state_deserializer")]
state: bool, state: bool,
} }
crate::register_type!(StateOnOff);
impl OutletState for 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 { pub struct StatePower {
#[serde(deserialize_with = "state_deserializer")] #[serde(deserialize_with = "state_deserializer")]
state: bool, state: bool,
power: f64, power: f64,
} }
crate::register_type!(StatePower);
impl OutletState for StatePower {} impl OutletState for StatePower {}
@@ -82,7 +98,10 @@ impl From<StatePower> for StateOnOff {
#[derive(Debug, Clone, Device)] #[derive(Debug, Clone, Device)]
#[device(traits(OnOff for OutletOnOff, OutletPower))] #[device(traits(OnOff for OutletOnOff, OutletPower))]
pub struct Outlet<T: OutletState> { pub struct Outlet<T: OutletState>
where
Outlet<T>: Typed,
{
config: Config<T>, config: Config<T>,
state: Arc<RwLock<T>>, state: Arc<RwLock<T>>,
@@ -94,7 +113,10 @@ crate::register_device!(OutletOnOff);
pub type OutletPower = Outlet<StatePower>; pub type OutletPower = Outlet<StatePower>;
crate::register_device!(OutletPower); crate::register_device!(OutletPower);
impl<T: OutletState> Outlet<T> { impl<T: OutletState> Outlet<T>
where
Outlet<T>: Typed,
{
async fn state(&self) -> RwLockReadGuard<'_, T> { async fn state(&self) -> RwLockReadGuard<'_, T> {
self.state.read().await self.state.read().await
} }
@@ -105,7 +127,10 @@ impl<T: OutletState> Outlet<T> {
} }
#[async_trait] #[async_trait]
impl<T: OutletState> LuaDeviceCreate for Outlet<T> { impl<T: OutletState> LuaDeviceCreate for Outlet<T>
where
Outlet<T>: Typed,
{
type Config = Config<T>; type Config = Config<T>;
type Error = rumqttc::ClientError; type Error = rumqttc::ClientError;
@@ -124,7 +149,10 @@ impl<T: OutletState> LuaDeviceCreate for Outlet<T> {
} }
} }
impl<T: OutletState> Device for Outlet<T> { impl<T: OutletState> Device for Outlet<T>
where
Outlet<T>: Typed,
{
fn get_id(&self) -> String { fn get_id(&self) -> String {
self.config.info.identifier() self.config.info.identifier()
} }
@@ -201,7 +229,10 @@ impl OnMqtt for OutletPower {
} }
#[async_trait] #[async_trait]
impl<T: OutletState> google_home::Device for Outlet<T> { impl<T: OutletState> google_home::Device for Outlet<T>
where
Outlet<T>: Typed,
{
fn get_device_type(&self) -> Type { fn get_device_type(&self) -> Type {
self.config.outlet_type.into() self.config.outlet_type.into()
} }
@@ -232,6 +263,7 @@ impl<T: OutletState> google_home::Device for Outlet<T> {
impl<T> OnOff for Outlet<T> impl<T> OnOff for Outlet<T>
where where
T: OutletState, T: OutletState,
Outlet<T>: Typed,
{ {
async fn on(&self) -> Result<bool, ErrorCode> { async fn on(&self) -> Result<bool, ErrorCode> {
let state = self.state().await; let state = self.state().await;

View File

@@ -13,6 +13,7 @@ google_home = { workspace = true }
hostname = { workspace = true } hostname = { workspace = true }
indexmap = { workspace = true } indexmap = { workspace = true }
inventory = { workspace = true } inventory = { workspace = true }
lua_typed = { workspace = true }
mlua = { workspace = true } mlua = { workspace = true }
rumqttc = { workspace = true } rumqttc = { workspace = true }
serde = { workspace = true } serde = { workspace = true }

View File

@@ -1,6 +1,7 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use futures::future::try_join_all; use futures::future::try_join_all;
use lua_typed::Typed;
use mlua::{FromLua, IntoLuaMulti}; use mlua::{FromLua, IntoLuaMulti};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -9,6 +10,23 @@ pub struct ActionCallback<P> {
_parameters: PhantomData<P>, _parameters: PhantomData<P>,
} }
impl<A: Typed> Typed for ActionCallback<A> {
fn type_name() -> String {
let type_name = A::type_name();
format!("fun(_: {type_name}) | fun(_: {type_name})[]")
}
}
impl<A: Typed, B: Typed> 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 // 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. // requires all types part of P to implement default, even if they never actually get constructed.
// By manually implemented Default it works fine. // By manually implemented Default it works fine.

View File

@@ -1,6 +1,7 @@
use std::net::{Ipv4Addr, SocketAddr}; use std::net::{Ipv4Addr, SocketAddr};
use std::time::Duration; use std::time::Duration;
use lua_typed::Typed;
use rumqttc::{MqttOptions, Transport}; use rumqttc::{MqttOptions, Transport};
use serde::Deserialize; use serde::Deserialize;
@@ -52,7 +53,7 @@ fn default_fulfillment_port() -> u16 {
7878 7878
} }
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize, Typed)]
pub struct InfoConfig { pub struct InfoConfig {
pub name: String, pub name: String,
pub room: Option<String>, pub room: Option<String>,
@@ -68,7 +69,7 @@ impl InfoConfig {
} }
} }
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize, Typed)]
pub struct MqttDeviceConfig { pub struct MqttDeviceConfig {
pub topic: String, pub topic: String,
} }

View File

@@ -1,5 +1,6 @@
#![allow(incomplete_features)] #![allow(incomplete_features)]
#![feature(iterator_try_collect)] #![feature(iterator_try_collect)]
#![feature(with_negative_coherence)]
use tracing::debug; use tracing::debug;

View File

@@ -1,5 +1,6 @@
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use lua_typed::Typed;
use mlua::FromLua; use mlua::FromLua;
use rumqttc::{AsyncClient, Event, EventLoop, Incoming}; use rumqttc::{AsyncClient, Event, EventLoop, Incoming};
use tracing::{debug, warn}; use tracing::{debug, warn};
@@ -9,6 +10,12 @@ use crate::event::{self, EventChannel};
#[derive(Debug, Clone, FromLua)] #[derive(Debug, Clone, FromLua)]
pub struct WrappedAsyncClient(pub AsyncClient); pub struct WrappedAsyncClient(pub AsyncClient);
impl Typed for WrappedAsyncClient {
fn type_name() -> String {
"AsyncClient".into()
}
}
impl Deref for WrappedAsyncClient { impl Deref for WrappedAsyncClient {
type Target = AsyncClient; type Target = AsyncClient;

View File

@@ -163,6 +163,12 @@ impl quote::ToTokens for Implementation {
)* )*
} }
} }
impl ::lua_typed::Typed for #name {
fn type_name() -> String {
stringify!(#name).into()
}
}
}); });
} }
} }