feat: WIP
All checks were successful
Build and deploy / build (push) Successful in 11m41s
Build and deploy / Deploy container (push) Has been skipped

This commit is contained in:
2025-09-18 01:45:31 +02:00
parent 06b3154733
commit f1965d6eef
32 changed files with 778 additions and 52 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#d2de4b5830c53feebb0d06c987f93a22fa6f3b3d"
dependencies = [
"eui48",
"lua_typed_macro",
]
[[package]]
name = "lua_typed_macro"
version = "0.1.0"
source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#d2de4b5830c53feebb0d06c987f93a22fa6f3b3d"
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)]
#[serde(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)]
#[serde(flatten)]
pub info: InfoConfig, pub info: InfoConfig,
#[device_config(flatten)] #[device_config(flatten)]
#[serde(flatten)]
pub mqtt: MqttDeviceConfig, pub mqtt: MqttDeviceConfig,
#[device_config(default(SensorType::Window))] #[device_config(default(SensorType::Window))]
#[serde(default)]
pub sensor_type: SensorType, pub sensor_type: SensorType,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[serde(default)]
pub callback: ActionCallback<(ContactSensor, bool)>, pub callback: ActionCallback<(ContactSensor, bool)>,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[serde(default)]
pub battery_callback: ActionCallback<(ContactSensor, f32)>, pub battery_callback: ActionCallback<(ContactSensor, f32)>,
#[device_config(from_lua)] #[device_config(from_lua)]
#[serde(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)]
#[serde(flatten)]
pub info: InfoConfig, pub info: InfoConfig,
#[device_config(flatten)] #[device_config(flatten)]
#[serde(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)]
#[serde(default)]
pub left_callback: ActionCallback<HueSwitch>, pub left_callback: ActionCallback<HueSwitch>,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[serde(default)]
pub right_callback: ActionCallback<HueSwitch>, pub right_callback: ActionCallback<HueSwitch>,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[serde(default)]
pub left_hold_callback: ActionCallback<HueSwitch>, pub left_hold_callback: ActionCallback<HueSwitch>,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[serde(default)]
pub right_hold_callback: ActionCallback<HueSwitch>, pub right_hold_callback: ActionCallback<HueSwitch>,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[serde(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)]
#[serde(flatten)]
pub info: InfoConfig, pub info: InfoConfig,
#[device_config(default)] #[device_config(default)]
#[serde(default)]
pub single_button: bool, pub single_button: bool,
#[device_config(flatten)] #[device_config(flatten)]
#[serde(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)]
#[serde(default)]
pub callback: ActionCallback<(IkeaRemote, bool)>, pub callback: ActionCallback<(IkeaRemote, bool)>,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[serde(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)]
#[serde(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)]
#[serde(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,12 +4,13 @@ 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")]
pub enum Priority { pub enum Priority {
@@ -19,34 +20,37 @@ 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")]
pub enum ActionType { pub enum ActionType {
Broadcast { Broadcast {
#[serde(skip_serializing_if = "HashMap::is_empty")] #[serde(skip_serializing_if = "HashMap::is_empty")]
#[serde(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)]
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)]
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>,
@@ -57,6 +61,7 @@ pub struct Notification {
#[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")] #[serde(skip_serializing_if = "Vec::is_empty", default = "Default::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 +72,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()))]
#[serde(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))]
@@ -96,6 +104,24 @@ impl Ntfy {
} }
} }
// impl Typed for Ntfy {
// fn type_name() -> String {
// "Ntfy".into()
// }
//
// fn generate_header() -> Option<String> {
// let type_name = <Self as Typed>::type_name();
// Some(format!("---@class {type_name}\nlocal {type_name}\n"))
// }
//
// fn generate_members() -> Option<String> {
// Some(format!(
// "---@async\n---@param notification Notification\nfunction {}:send_notification(notification) end\n",
// <Self as Typed>::type_name(),
// ))
// }
// }
#[async_trait] #[async_trait]
impl LuaDeviceCreate for Ntfy { impl LuaDeviceCreate for Ntfy {
type Config = Config; type Config = Config;

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)]
#[serde(flatten)]
pub mqtt: MqttDeviceConfig, pub mqtt: MqttDeviceConfig,
#[device_config(from_lua, default)] #[device_config(from_lua, default)]
#[serde(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)]
#[serde(flatten)]
pub info: InfoConfig, pub info: InfoConfig,
#[device_config(flatten)] #[device_config(flatten)]
#[serde(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)))]
#[serde(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)]
#[serde(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)]
#[serde(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;
@@ -40,6 +41,13 @@ pub struct Config<T: LightState> {
pub client: WrappedAsyncClient, pub client: WrappedAsyncClient,
} }
// TODO: Fix the derive macro so this is supported
impl<T: LightState> Typed for Config<T> {
fn type_name() -> String {
"LightConfig".into()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize)]
pub struct StateOnOff { pub struct StateOnOff {
#[serde(deserialize_with = "state_deserializer")] #[serde(deserialize_with = "state_deserializer")]

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;
@@ -57,6 +58,13 @@ pub struct Config<T: OutletState> {
pub client: WrappedAsyncClient, pub client: WrappedAsyncClient,
} }
// TODO: Fix the derive macro so this is supported
impl<T: OutletState> Typed for Config<T> {
fn type_name() -> String {
"OutletConfig".into()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize)]
pub struct StateOnOff { pub struct StateOnOff {
#[serde(deserialize_with = "state_deserializer")] #[serde(deserialize_with = "state_deserializer")]

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,5 @@
#![allow(incomplete_features)]
#![feature(iterator_try_collect)] #![feature(iterator_try_collect)]
#![feature(with_negative_coherence)]
use tracing::debug; use tracing::debug;

View File

@@ -1,11 +1,18 @@
use std::marker::PhantomData;
use std::ops::Deref; use std::ops::Deref;
use lua_typed::Typed;
// TODO: Enable and disable functions based on query_only and command_only // TODO: Enable and disable functions based on query_only and command_only
pub trait OnOff { pub struct OnOff<T> {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) _phantom: PhantomData<T>,
}
impl<T: google_home::traits::OnOff + Typed> OnOff<T> {
pub fn add_methods<M: mlua::UserDataMethods<T>>(methods: &mut M)
where where
Self: Sized + google_home::traits::OnOff + 'static, T: Sized + 'static,
{ {
methods.add_async_method("set_on", async |_lua, this, on: bool| { methods.add_async_method("set_on", async |_lua, this, on: bool| {
this.deref().set_on(on).await.unwrap(); this.deref().set_on(on).await.unwrap();
@@ -17,13 +24,27 @@ pub trait OnOff {
Ok(this.deref().on().await.unwrap()) Ok(this.deref().on().await.unwrap())
}); });
} }
}
impl<T> OnOff for T where T: google_home::traits::OnOff {}
pub trait Brightness { pub fn generate_definitions() -> String {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) let type_name = T::type_name();
let mut output = String::new();
output +=
&format!("---@async\n---@param on boolean\nfunction {type_name}:set_on(on) end\n");
output += &format!("---@async\n---@return boolean\nfunction {type_name}:on() end\n");
output
}
}
pub struct Brightness<T> {
_phantom: PhantomData<T>,
}
impl<T: google_home::traits::Brightness + Typed> Brightness<T> {
pub fn add_methods<M: mlua::UserDataMethods<T>>(methods: &mut M)
where where
Self: Sized + google_home::traits::Brightness + 'static, T: Sized + 'static,
{ {
methods.add_async_method("set_brightness", async |_lua, this, brightness: u8| { methods.add_async_method("set_brightness", async |_lua, this, brightness: u8| {
this.set_brightness(brightness).await.unwrap(); this.set_brightness(brightness).await.unwrap();
@@ -35,13 +56,29 @@ pub trait Brightness {
Ok(this.brightness().await.unwrap()) Ok(this.brightness().await.unwrap())
}); });
} }
}
impl<T> Brightness for T where T: google_home::traits::Brightness {}
pub trait ColorSetting { pub fn generate_definitions() -> String {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) let type_name = T::type_name();
let mut output = String::new();
output += &format!(
"---@async\n---@param brightness integer\nfunction {type_name}:set_brightness(brightness) end\n"
);
output +=
&format!("---@async\n---@return integer\nfunction {type_name}:brightness() end\n");
output
}
}
pub struct ColorSetting<T> {
_phantom: PhantomData<T>,
}
impl<T: google_home::traits::ColorSetting + Typed> ColorSetting<T> {
pub fn add_methods<M: mlua::UserDataMethods<T>>(methods: &mut M)
where where
Self: Sized + google_home::traits::ColorSetting + 'static, T: Sized + 'static,
{ {
methods.add_async_method( methods.add_async_method(
"set_color_temperature", "set_color_temperature",
@@ -58,13 +95,30 @@ pub trait ColorSetting {
Ok(this.color().await.temperature) Ok(this.color().await.temperature)
}); });
} }
}
impl<T> ColorSetting for T where T: google_home::traits::ColorSetting {}
pub trait OpenClose { pub fn generate_definitions() -> String {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) let type_name = T::type_name();
let mut output = String::new();
output += &format!(
"---@async\n---@param temperature integer\nfunction {type_name}:set_color_temperature(temperature) end\n"
);
output += &format!(
"---@async\n---@return integer\nfunction {type_name}:color_temperature() end\n"
);
output
}
}
pub struct OpenClose<T> {
_phantom: PhantomData<T>,
}
impl<T: google_home::traits::OpenClose + Typed> OpenClose<T> {
pub fn add_methods<M: mlua::UserDataMethods<T>>(methods: &mut M)
where where
Self: Sized + google_home::traits::OpenClose + 'static, T: Sized + 'static,
{ {
methods.add_async_method("set_open_percent", async |_lua, this, open_percent: u8| { methods.add_async_method("set_open_percent", async |_lua, this, open_percent: u8| {
this.set_open_percent(open_percent).await.unwrap(); this.set_open_percent(open_percent).await.unwrap();
@@ -76,5 +130,17 @@ pub trait OpenClose {
Ok(this.open_percent().await.unwrap()) Ok(this.open_percent().await.unwrap())
}); });
} }
pub fn generate_definitions() -> String {
let type_name = T::type_name();
let mut output = String::new();
output += &format!(
"---@async\n---@param open_percent integer\nfunction {type_name}:set_open_percent(open_percent) end\n"
);
output +=
&format!("---@async\n---@return integer\nfunction {type_name}:open_percent() end\n");
output
}
} }
impl<T> OpenClose for T where T: google_home::traits::OpenClose {}

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

@@ -47,7 +47,7 @@ impl Parse for TraitAttr {
} }
} }
#[derive(Default)] #[derive(Default, Clone)]
struct Traits(Vec<syn::Ident>); struct Traits(Vec<syn::Ident>);
impl Traits { impl Traits {
@@ -65,13 +65,27 @@ impl Parse for Traits {
} }
} }
impl ToTokens for Traits { struct TraitsAddMethods(Traits);
impl ToTokens for TraitsAddMethods {
fn to_tokens(&self, tokens: &mut TokenStream2) { fn to_tokens(&self, tokens: &mut TokenStream2) {
let Self(traits) = &self; let Self(Traits(traits)) = &self;
tokens.extend(quote! { tokens.extend(quote! {
#( #(
::automation_lib::lua::traits::#traits::add_methods(methods); ::automation_lib::lua::traits::#traits::<Self>::add_methods(methods);
)*
});
}
}
struct TraitsGenerateDefinitions(Traits);
impl ToTokens for TraitsGenerateDefinitions {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let Self(Traits(traits)) = &self;
tokens.extend(quote! {
#(
output += &::automation_lib::lua::traits::#traits::<Self>::generate_definitions();
)* )*
}); });
} }
@@ -138,9 +152,12 @@ impl quote::ToTokens for Implementation {
add_methods, add_methods,
} = &self; } = &self;
let traits_add_methods = TraitsAddMethods(traits.clone());
let traits_generate_definitions = TraitsGenerateDefinitions(traits.clone());
tokens.extend(quote! { tokens.extend(quote! {
impl mlua::UserData for #name { impl ::mlua::UserData for #name {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: ::mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_async_function("new", async |_lua, config| { methods.add_async_function("new", async |_lua, config| {
let device: Self = LuaDeviceCreate::create(config) let device: Self = LuaDeviceCreate::create(config)
.await .await
@@ -156,13 +173,39 @@ impl quote::ToTokens for Implementation {
methods.add_async_method("get_id", async |_lua, this, _: ()| { Ok(this.get_id()) }); methods.add_async_method("get_id", async |_lua, this, _: ()| { Ok(this.get_id()) });
#traits #traits_add_methods
#( #(
#add_methods(methods); #add_methods(methods);
)* )*
} }
} }
impl ::lua_typed::Typed for #name {
fn type_name() -> String {
stringify!(#name).into()
}
fn generate_header() -> std::option::Option<::std::string::String> {
let type_name = <Self as ::lua_typed::Typed>::type_name();
Some(format!("---@class {type_name}\nlocal {type_name}\n"))
}
fn generate_members() -> Option<String> {
let mut output = String::new();
#traits_generate_definitions
let type_name = <Self as ::lua_typed::Typed>::type_name();
output += &format!("devices.{type_name} = {{}}\n");
let config_name = <<Self as ::automation_lib::device::LuaDeviceCreate>::Config as ::lua_typed::Typed>::type_name();
output += &format!("---@param config {config_name}\n");
output += &format!("---@return {type_name}\n");
output += &format!("function devices.{type_name}.new(config) end\n");
Some(output)
}
}
}); });
} }
} }

View File

@@ -2,17 +2,21 @@ local devices = require("automation:devices")
local device_manager = require("automation:device_manager") local device_manager = require("automation:device_manager")
local utils = require("automation:utils") local utils = require("automation:utils")
local secrets = require("automation:secrets") local secrets = require("automation:secrets")
local debug = require("automation:variables").debug or false local debug = require("automation:variables").debug and true or false
print(_VERSION) print(_VERSION)
local host = utils.get_hostname() local host = utils.get_hostname()
print("Running @" .. host) print("Running @" .. host)
--- @param topic string
--- @return string
local function mqtt_z2m(topic) local function mqtt_z2m(topic)
return "zigbee2mqtt/" .. topic return "zigbee2mqtt/" .. topic
end end
--- @param topic string
--- @return string
local function mqtt_automation(topic) local function mqtt_automation(topic)
return "automation/" .. topic return "automation/" .. topic
end end
@@ -72,6 +76,7 @@ local on_presence = {
end, end,
} }
--- @type Presence
local presence_system = devices.Presence.new({ local presence_system = devices.Presence.new({
topic = mqtt_automation("presence/+/#"), topic = mqtt_automation("presence/+/#"),
client = mqtt_client, client = mqtt_client,
@@ -272,6 +277,7 @@ local function kettle_timeout()
end end
end end
--- @type OutletPower
local kettle = devices.OutletPower.new({ local kettle = devices.OutletPower.new({
outlet_type = "Kettle", outlet_type = "Kettle",
name = "Kettle", name = "Kettle",
@@ -362,6 +368,7 @@ local workbench_outlet = devices.OutletOnOff.new({
turn_off_when_away(workbench_outlet) turn_off_when_away(workbench_outlet)
device_manager:add(workbench_outlet) device_manager:add(workbench_outlet)
--- @type LightColorTemperature
local workbench_light = devices.LightColorTemperature.new({ local workbench_light = devices.LightColorTemperature.new({
name = "Light", name = "Light",
room = "Workbench", room = "Workbench",

View File

@@ -0,0 +1,42 @@
---@meta
local devices
---@class Action
---@field action
---| "broadcast"
---| "view"
---@field extras table<string, string> | nil
---@field label string | nil
---@field clear boolean|nil
---@alias Priority
---| "min"
---| "low"
---| "default"
---| "high"
---| "max"
---@class Notification
---@field title string
---@field message string | nil
-- NOTE: It might be possible to specify this down to the actual possible values
---@field tags string[] | nil
---@field priority Priority | nil
---@field actions Action[] | nil
---@class Ntfy
local Ntfy
---@async
---@param notification Notification
function Ntfy:send_notification(notification) end
---@class NtfyConfig
---@field topic string
devices.Ntfy = {}
---@param config NtfyConfig
---@return Ntfy
function devices.Ntfy.new(config) end
return devices

View File

@@ -0,0 +1,6 @@
---@meta
---@type table<string, string|nil>
local secrets
return secrets

View File

@@ -0,0 +1,27 @@
---@meta
local utils
---@class Timeout
local Timeout
---@async
---@param timeout number
---@param callback fun()
function Timeout:start(timeout, callback) end
---@async
function Timeout:cancel() end
---@async
---@return boolean
function Timeout:is_waiting() end
utils.Timeout = {}
---@return Timeout
function utils.Timeout.new() end
--- @return string hostname
function utils.get_hostname() end
--- @return number epoch
function utils.get_epoch() end
return utils

View File

@@ -0,0 +1,6 @@
---@meta
---@type table<string, string|nil>
local variables
return variables

View File

@@ -0,0 +1,324 @@
---@meta
local devices
---@class OutletOnOff
local OutletOnOff
---@async
---@param on boolean
function OutletOnOff:set_on(on) end
---@async
---@return boolean
function OutletOnOff:on() end
devices.OutletOnOff = {}
---@param config OutletConfig
---@return OutletOnOff
function devices.OutletOnOff.new(config) end
---@class OutletPower
local OutletPower
---@async
---@param on boolean
function OutletPower:set_on(on) end
---@async
---@return boolean
function OutletPower:on() end
devices.OutletPower = {}
---@param config OutletConfig
---@return OutletPower
function devices.OutletPower.new(config) end
---@class AirFilterConfig
---@field name string
---@field room string?
---@field url string
local AirFilterConfig
---@class AirFilter
local AirFilter
---@async
---@param on boolean
function AirFilter:set_on(on) end
---@async
---@return boolean
function AirFilter:on() end
devices.AirFilter = {}
---@param config AirFilterConfig
---@return AirFilter
function devices.AirFilter.new(config) end
---@class PresenceConfig
---@field topic string
---@field callback fun(_: Presence, _: boolean) | fun(_: Presence, _: boolean)[]?
---@field client AsyncClient
local PresenceConfig
---@class Presence
local Presence
devices.Presence = {}
---@param config PresenceConfig
---@return Presence
function devices.Presence.new(config) end
---@class WolConfig
---@field name string
---@field room string?
---@field topic string
---@field mac_address string
---@field broadcast_ip string?
---@field client AsyncClient
local WolConfig
---@class WakeOnLAN
local WakeOnLAN
devices.WakeOnLAN = {}
---@param config WolConfig
---@return WakeOnLAN
function devices.WakeOnLAN.new(config) end
---@class LightSensor
local LightSensor
devices.LightSensor = {}
---@param config LightSensorConfig
---@return LightSensor
function devices.LightSensor.new(config) end
---@class LightSensorConfig
---@field identifier string
---@field topic string
---@field min integer
---@field max integer
---@field callback fun(_: LightSensor, _: boolean) | fun(_: LightSensor, _: boolean)[]?
---@field client AsyncClient
local LightSensorConfig
---@class ContactSensor
local ContactSensor
---@async
---@param open_percent integer
function ContactSensor:set_open_percent(open_percent) end
---@async
---@return integer
function ContactSensor:open_percent() end
devices.ContactSensor = {}
---@param config ContactSensorConfig
---@return ContactSensor
function devices.ContactSensor.new(config) end
---@alias SensorType
---| "Door"
---| "Drawer"
---| "Window"
---@class ContactSensorConfig
---@field name string
---@field room string?
---@field topic string
---@field sensor_type SensorType?
---@field callback fun(_: ContactSensor, _: boolean) | fun(_: ContactSensor, _: boolean)[]?
---@field battery_callback fun(_: ContactSensor, _: number) | fun(_: ContactSensor, _: number)[]?
---@field client AsyncClient?
local ContactSensorConfig
---@class KasaOutlet
local KasaOutlet
---@async
---@param on boolean
function KasaOutlet:set_on(on) end
---@async
---@return boolean
function KasaOutlet:on() end
devices.KasaOutlet = {}
---@param config KasaOutletConfig
---@return KasaOutlet
function devices.KasaOutlet.new(config) end
---@class KasaOutletConfig
---@field identifier string
---@field ip string
local KasaOutletConfig
---@class LightOnOff
local LightOnOff
---@async
---@param on boolean
function LightOnOff:set_on(on) end
---@async
---@return boolean
function LightOnOff:on() end
devices.LightOnOff = {}
---@param config LightConfig
---@return LightOnOff
function devices.LightOnOff.new(config) end
---@class LightColorTemperature
local LightColorTemperature
---@async
---@param on boolean
function LightColorTemperature:set_on(on) end
---@async
---@return boolean
function LightColorTemperature:on() end
---@async
---@param brightness integer
function LightColorTemperature:set_brightness(brightness) end
---@async
---@return integer
function LightColorTemperature:brightness() end
---@async
---@param temperature integer
function LightColorTemperature:set_color_temperature(temperature) end
---@async
---@return integer
function LightColorTemperature:color_temperature() end
devices.LightColorTemperature = {}
---@param config LightConfig
---@return LightColorTemperature
function devices.LightColorTemperature.new(config) end
---@class LightBrightness
local LightBrightness
---@async
---@param on boolean
function LightBrightness:set_on(on) end
---@async
---@return boolean
function LightBrightness:on() end
---@async
---@param brightness integer
function LightBrightness:set_brightness(brightness) end
---@async
---@return integer
function LightBrightness:brightness() end
devices.LightBrightness = {}
---@param config LightConfig
---@return LightBrightness
function devices.LightBrightness.new(config) end
---@class HueSwitch
local HueSwitch
devices.HueSwitch = {}
---@param config HueSwitchConfig
---@return HueSwitch
function devices.HueSwitch.new(config) end
---@class HueSwitchConfig
---@field name string
---@field room string?
---@field topic string
---@field client AsyncClient
---@field left_callback fun(_: HueSwitch) | fun(_: HueSwitch)[]?
---@field right_callback fun(_: HueSwitch) | fun(_: HueSwitch)[]?
---@field left_hold_callback fun(_: HueSwitch) | fun(_: HueSwitch)[]?
---@field right_hold_callback fun(_: HueSwitch) | fun(_: HueSwitch)[]?
---@field battery_callback fun(_: HueSwitch, _: number) | fun(_: HueSwitch, _: number)[]?
local HueSwitchConfig
---@class HueBridge
local HueBridge
devices.HueBridge = {}
---@param config HueBridgeConfig
---@return HueBridge
function devices.HueBridge.new(config) end
---@class HueBridgeConfig
---@field identifier string
---@field ip string
---@field login string
---@field flags FlagIDs
local HueBridgeConfig
---@class FlagIDs
---@field presence integer
---@field darkness integer
local FlagIDs
---@class IkeaRemoteConfig
---@field name string
---@field room string?
---@field single_button boolean?
---@field topic string
---@field client AsyncClient
---@field callback fun(_: IkeaRemote, _: boolean) | fun(_: IkeaRemote, _: boolean)[]?
---@field battery_callback fun(_: IkeaRemote, _: number) | fun(_: IkeaRemote, _: number)[]?
local IkeaRemoteConfig
---@class IkeaRemote
local IkeaRemote
devices.IkeaRemote = {}
---@param config IkeaRemoteConfig
---@return IkeaRemote
function devices.IkeaRemote.new(config) end
---@alias Priority
---| "min"
---| "low"
---| "default"
---| "high"
---| "max"
---@class Ntfy
local Ntfy
devices.Ntfy = {}
---@param config NtfyConfig
---@return Ntfy
function devices.Ntfy.new(config) end
---@class NtfyConfig
---@field url string?
---@field topic string
local NtfyConfig
---@class Action
---@field action
---| "broadcast"
---@field extras table<string, string>?
---@field label string
---@field clear boolean?
local Action
---@class Notification
---@field title string
---@field message string?
---@field tags string[]?
---@field priority Priority?
---@field actions Action[]?
local Notification
---@class WasherConfig
---@field identifier string
---@field topic string
---@field threshold number
---@field done_callback fun(_: Washer) | fun(_: Washer)[]?
---@field client AsyncClient
local WasherConfig
---@class Washer
local Washer
devices.Washer = {}
---@param config WasherConfig
---@return Washer
function devices.Washer.new(config) end
---@class HueGroupConfig
---@field identifier string
---@field ip string
---@field login string
---@field group_id integer
---@field scene_id string
local HueGroupConfig
---@class HueGroup
local HueGroup
---@async
---@param on boolean
function HueGroup:set_on(on) end
---@async
---@return boolean
function HueGroup:on() end
devices.HueGroup = {}
---@param config HueGroupConfig
---@return HueGroup
function devices.HueGroup.new(config) end
return devices

View File

@@ -0,0 +1,3 @@
fn main() {
automation_devices::generate_definitions()
}