Compare commits

..

3 Commits

Author SHA1 Message Date
6504b1239e feat: WIP
Some checks failed
Build and deploy / Deploy container (push) Blocked by required conditions
Build and deploy / build (push) Has been cancelled
2025-10-10 03:56:34 +02:00
ad8db00c8f feat: Add proper type definition for devices
Depending on the implemented traits the lua class will inherit from the
associated interface class.

It also specifies the constructor function for each of the devices.
2025-10-10 03:56:34 +02:00
8010f7a404 feat: Added Typed impl for all automation devices
To accomplish this a basic implementation was also provided for some
types in automation_lib
2025-10-10 03:56:33 +02:00
32 changed files with 817 additions and 63 deletions

38
Cargo.lock generated
View File

@@ -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#513b36afdbc23efcf10847f070faff7fcd30f29d"
dependencies = [
"eui48",
"lua_typed_macro",
]
[[package]]
name = "lua_typed_macro"
version = "0.1.0"
source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#513b36afdbc23efcf10847f070faff7fcd30f29d"
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"

View File

@@ -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",

View File

@@ -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 }

View File

@@ -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)]
#[serde(flatten)]
pub info: InfoConfig,
pub url: String,
}
crate::register_type!(Config);
#[derive(Debug, Clone, Device)]
#[device(traits(OnOff))]

View File

@@ -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)]
#[serde(flatten)]
pub info: InfoConfig,
#[device_config(flatten)]
#[serde(flatten)]
pub mqtt: MqttDeviceConfig,
#[device_config(default(SensorType::Window))]
#[serde(default)]
pub sensor_type: SensorType,
#[device_config(from_lua, default)]
#[serde(default)]
pub callback: ActionCallback<(ContactSensor, bool)>,
#[device_config(from_lua, default)]
#[serde(default)]
pub battery_callback: ActionCallback<(ContactSensor, f32)>,
#[device_config(from_lua)]
#[serde(default)]
pub client: WrappedAsyncClient,
}
crate::register_type!(Config);
#[derive(Debug)]
struct State {

View File

@@ -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))]

View File

@@ -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))]

View File

@@ -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)]
#[serde(flatten)]
pub info: InfoConfig,
#[device_config(flatten)]
#[serde(flatten)]
pub mqtt: MqttDeviceConfig,
#[device_config(from_lua)]
pub client: WrappedAsyncClient,
#[device_config(from_lua, default)]
#[serde(default)]
pub left_callback: ActionCallback<HueSwitch>,
#[device_config(from_lua, default)]
#[serde(default)]
pub right_callback: ActionCallback<HueSwitch>,
#[device_config(from_lua, default)]
#[serde(default)]
pub left_hold_callback: ActionCallback<HueSwitch>,
#[device_config(from_lua, default)]
#[serde(default)]
pub right_hold_callback: ActionCallback<HueSwitch>,
#[device_config(from_lua, default)]
#[serde(default)]
pub battery_callback: ActionCallback<(HueSwitch, f32)>,
}
crate::register_type!(Config);
#[derive(Debug, Copy, Clone, Deserialize)]
#[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::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)]
#[serde(flatten)]
pub info: InfoConfig,
#[device_config(default)]
#[serde(default)]
pub single_button: bool,
#[device_config(flatten)]
#[serde(flatten)]
pub mqtt: MqttDeviceConfig,
#[device_config(from_lua)]
pub client: WrappedAsyncClient,
#[device_config(from_lua, default)]
#[serde(default)]
pub callback: ActionCallback<(IkeaRemote, bool)>,
#[device_config(from_lua, default)]
#[serde(default)]
pub battery_callback: ActionCallback<(IkeaRemote, f32)>,
}
crate::register_type!(Config);
#[derive(Debug, Clone, Device)]
pub struct IkeaRemote {

View File

@@ -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))]

View File

@@ -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<mlua::AnyUserData>;
type RegisterDeviceFn = fn(lua: &mlua::Lua) -> mlua::Result<mlua::AnyUserData>;
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<mlua::Table> {
}
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::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)]
#[serde(flatten)]
pub mqtt: MqttDeviceConfig,
pub min: isize,
pub max: isize,
#[device_config(from_lua, default)]
#[serde(default)]
pub callback: ActionCallback<(LightSensor, bool)>,
#[device_config(from_lua)]
pub client: WrappedAsyncClient,
}
crate::register_type!(Config);
const DEFAULT: bool = false;

View File

@@ -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,37 @@ 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")]
pub enum ActionType {
Broadcast {
#[serde(skip_serializing_if = "HashMap::is_empty")]
#[serde(default)]
extras: HashMap<String, String>,
},
// View,
// Http
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone, Typed)]
pub struct Action {
#[serde(flatten)]
pub action: ActionType,
pub label: String,
pub clear: Option<bool>,
}
crate::register_type!(Action);
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Typed)]
struct NotificationFinal {
topic: String,
#[serde(flatten)]
inner: Notification,
}
#[derive(Debug, Serialize, Clone, Deserialize)]
#[derive(Debug, Serialize, Clone, Deserialize, Typed)]
pub struct Notification {
title: String,
message: Option<String>,
@@ -57,6 +61,7 @@ pub struct Notification {
#[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")]
actions: Vec<Action>,
}
crate::register_type!(Notification);
impl Notification {
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 {
#[device_config(default("https://ntfy.sh".into()))]
#[serde(default)]
pub url: String,
pub topic: String,
}
crate::register_type!(Config);
#[derive(Debug, Clone, Device)]
#[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]
impl LuaDeviceCreate for Ntfy {
type Config = Config;

View File

@@ -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)]
#[serde(flatten)]
pub mqtt: MqttDeviceConfig,
#[device_config(from_lua, default)]
#[serde(default)]
pub callback: ActionCallback<(Presence, bool)>,
#[device_config(from_lua)]
pub client: WrappedAsyncClient,
}
crate::register_type!(Config);
pub const DEFAULT_PRESENCE: bool = false;

View File

@@ -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)]
#[serde(flatten)]
pub info: InfoConfig,
#[device_config(flatten)]
#[serde(flatten)]
pub mqtt: MqttDeviceConfig,
pub mac_address: MacAddress,
#[device_config(default(Ipv4Addr::new(255, 255, 255, 255)))]
#[serde(default)]
pub broadcast_ip: Ipv4Addr,
#[device_config(from_lua)]
pub client: WrappedAsyncClient,
}
crate::register_type!(Config);
#[derive(Debug, Clone, Device)]
pub struct WakeOnLAN {

View File

@@ -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)]
#[serde(flatten)]
pub mqtt: MqttDeviceConfig,
// Power in Watt
pub threshold: f32,
#[device_config(from_lua, default)]
#[serde(default)]
pub done_callback: ActionCallback<Washer>,
#[device_config(from_lua)]
pub client: WrappedAsyncClient,
}
crate::register_type!(Config);
#[derive(Debug)]
pub struct State {

View File

@@ -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<StateOnOff> + 'static
Debug + Clone + Default + Sync + Send + Serialize + Into<StateOnOff> + Typed + 'static
{
}
#[derive(Debug, Clone, LuaDeviceConfig)]
pub struct Config<T: LightState> {
#[derive(Debug, Clone, LuaDeviceConfig, Typed)]
#[typed(as = "ConfigLight")]
pub struct Config<T: LightState>
where
Light<T>: Typed,
{
#[device_config(flatten)]
#[serde(flatten)]
pub info: InfoConfig,
#[device_config(flatten)]
#[serde(flatten)]
pub mqtt: MqttDeviceConfig,
#[device_config(from_lua, default)]
#[serde(default)]
pub callback: ActionCallback<(Light<T>, T)>,
#[serde(default)]
#[device_config(from_lua)]
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 {
#[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<StateBrightness> for StateOnOff {
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 {
#[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<StateColorTemperature> for StateBrightness {
#[device(traits(OnOff for LightOnOff, LightBrightness, LightColorTemperature))]
#[device(traits(Brightness for LightBrightness, LightColorTemperature))]
#[device(traits(ColorSetting for LightColorTemperature))]
pub struct Light<T: LightState> {
pub struct Light<T: LightState>
where
Light<T>: Typed,
{
config: Config<T>,
state: Arc<RwLock<T>>,
@@ -107,7 +128,10 @@ crate::register_device!(LightBrightness);
pub type LightColorTemperature = Light<StateColorTemperature>;
crate::register_device!(LightColorTemperature);
impl<T: LightState> Light<T> {
impl<T: LightState> Light<T>
where
Light<T>: Typed,
{
async fn state(&self) -> RwLockReadGuard<'_, T> {
self.state.read().await
}
@@ -118,7 +142,10 @@ impl<T: LightState> Light<T> {
}
#[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 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 {
self.config.info.identifier()
}
@@ -257,7 +287,10 @@ impl OnMqtt for LightColorTemperature {
}
#[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 {
Type::Light
}
@@ -288,6 +321,7 @@ impl<T: LightState> google_home::Device for Light<T> {
impl<T> OnOff for Light<T>
where
T: LightState,
Light<T>: Typed,
{
async fn on(&self) -> Result<bool, ErrorCode> {
let state = self.state().await;
@@ -327,6 +361,7 @@ impl<T> Brightness for Light<T>
where
T: LightState,
T: Into<StateBrightness>,
Light<T>: Typed,
{
async fn brightness(&self) -> Result<u8, ErrorCode> {
let state = self.state().await;
@@ -368,6 +403,7 @@ impl<T> ColorSetting for Light<T>
where
T: LightState,
T: Into<StateColorTemperature>,
Light<T>: Typed,
{
fn color_temperature_range(&self) -> ColorTemperatureRange {
ColorTemperatureRange {

View File

@@ -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<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 {
Outlet,
Kettle,
}
crate::register_type!(OutletType);
impl From<OutletType> for Type {
fn from(outlet: OutletType) -> Self {
@@ -41,36 +43,50 @@ impl From<OutletType> for Type {
}
}
#[derive(Debug, Clone, LuaDeviceConfig)]
pub struct Config<T: OutletState> {
#[derive(Debug, Clone, LuaDeviceConfig, Typed)]
#[typed(as = "ConfigOutlet")]
pub struct Config<T: OutletState>
where
Outlet<T>: Typed,
{
#[device_config(flatten)]
#[serde(flatten)]
pub info: InfoConfig,
#[device_config(flatten)]
#[serde(flatten)]
pub mqtt: MqttDeviceConfig,
#[device_config(default(OutletType::Outlet))]
#[serde(default)]
pub outlet_type: OutletType,
#[device_config(from_lua, default)]
#[serde(default)]
pub callback: ActionCallback<(Outlet<T>, T)>,
#[device_config(from_lua)]
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 {
#[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<StatePower> for StateOnOff {
#[derive(Debug, Clone, Device)]
#[device(traits(OnOff for OutletOnOff, OutletPower))]
pub struct Outlet<T: OutletState> {
pub struct Outlet<T: OutletState>
where
Outlet<T>: Typed,
{
config: Config<T>,
state: Arc<RwLock<T>>,
@@ -94,7 +113,10 @@ crate::register_device!(OutletOnOff);
pub type OutletPower = Outlet<StatePower>;
crate::register_device!(OutletPower);
impl<T: OutletState> Outlet<T> {
impl<T: OutletState> Outlet<T>
where
Outlet<T>: Typed,
{
async fn state(&self) -> RwLockReadGuard<'_, T> {
self.state.read().await
}
@@ -105,7 +127,10 @@ impl<T: OutletState> Outlet<T> {
}
#[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 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 {
self.config.info.identifier()
}
@@ -201,7 +229,10 @@ impl OnMqtt for OutletPower {
}
#[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 {
self.config.outlet_type.into()
}
@@ -232,6 +263,7 @@ impl<T: OutletState> google_home::Device for Outlet<T> {
impl<T> OnOff for Outlet<T>
where
T: OutletState,
Outlet<T>: Typed,
{
async fn on(&self) -> Result<bool, ErrorCode> {
let state = self.state().await;

View File

@@ -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 }

View File

@@ -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<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
// requires all types part of P to implement default, even if they never actually get constructed.
// By manually implemented Default it works fine.

View File

@@ -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<String>,
@@ -68,7 +69,7 @@ impl InfoConfig {
}
}
#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Clone, Deserialize, Typed)]
pub struct MqttDeviceConfig {
pub topic: String,
}

View File

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

View File

@@ -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;

View File

@@ -138,6 +138,12 @@ impl quote::ToTokens for Implementation {
add_methods,
} = &self;
let interfaces: String = traits
.0
.iter()
.map(|tr| format!(", Interface{tr}"))
.collect();
tokens.extend(quote! {
impl mlua::UserData for #name {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
@@ -163,6 +169,31 @@ impl quote::ToTokens for Implementation {
)*
}
}
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}: InterfaceDevice{}\nlocal {type_name}\n", #interfaces))
}
fn generate_members() -> Option<String> {
let mut output = String::new();
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 utils = require("automation:utils")
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)
local host = utils.get_hostname()
print("Running @" .. host)
--- @param topic string
--- @return string
local function mqtt_z2m(topic)
return "zigbee2mqtt/" .. topic
end
--- @param topic string
--- @return string
local function mqtt_automation(topic)
return "automation/" .. topic
end
@@ -35,7 +39,10 @@ local ntfy = devices.Ntfy.new({
})
device_manager:add(ntfy)
--- @type {[string]: number}
local low_battery = {}
--- @param device InterfaceDevice
--- @param battery number
local function check_battery(device, battery)
local id = device:get_id()
if battery < 15 then
@@ -66,11 +73,13 @@ device_manager:schedule("0 0 21 */1 * *", function()
})
end)
local on_presence = {
add = function(self, f)
self[#self + 1] = f
end,
}
--- @class OnPresence
--- @field [integer] fun(presence: boolean)
local on_presence = {}
--- @param f fun(presence: boolean)
function on_presence:add(f)
self[#self + 1] = f
end
local presence_system = devices.Presence.new({
topic = mqtt_automation("presence/+/#"),
@@ -110,11 +119,13 @@ on_presence:add(function(presence)
})
end)
local window_sensors = {
add = function(self, f)
self[#self + 1] = f
end,
}
--- @class WindowSensor
--- @field [integer] InterfaceOpenClose
local window_sensors = {}
--- @param sensor InterfaceOpenClose
function window_sensors:add(sensor)
self[#self + 1] = sensor
end
on_presence:add(function(presence)
if not presence then
local open = {}
@@ -139,6 +150,7 @@ on_presence:add(function(presence)
end
end)
--- @param device InterfaceOnOff
local function turn_off_when_away(device)
on_presence:add(function(presence)
if not presence then
@@ -147,11 +159,13 @@ local function turn_off_when_away(device)
end)
end
local on_light = {
add = function(self, f)
self[#self + 1] = f
end,
}
--- @class OnLight
--- @field [integer] fun(light: boolean)
local on_light = {}
--- @param f fun(light: boolean)
function on_light:add(f)
self[#self + 1] = f
end
device_manager:add(devices.LightSensor.new({
identifier = "living_light_sensor",
topic = mqtt_z2m("living/light"),
@@ -290,6 +304,8 @@ device_manager:add(devices.IkeaRemote.new({
local function kettle_timeout()
local timeout = utils.Timeout.new()
--- @param self InterfaceOnOff
--- @param state StatePower
return function(self, state)
if state.state and state.power < 100 then
timeout:start(3, function()
@@ -301,6 +317,7 @@ local function kettle_timeout()
end
end
--- @type OutletPower
local kettle = devices.OutletPower.new({
outlet_type = "Kettle",
name = "Kettle",
@@ -312,6 +329,7 @@ local kettle = devices.OutletPower.new({
turn_off_when_away(kettle)
device_manager:add(kettle)
--- @param on boolean
local function set_kettle(_, on)
kettle:set_on(on)
end
@@ -336,9 +354,12 @@ device_manager:add(devices.IkeaRemote.new({
battery_callback = check_battery,
}))
--- @param duration number
local function off_timeout(duration)
local timeout = utils.Timeout.new()
--- @param self InterfaceOnOff
--- @param state {state: boolean}
return function(self, state)
if state.state then
timeout:start(duration, function()

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,322 @@
---@meta
local devices
---@class LightSensor: InterfaceDevice
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
---@alias Priority
---| "min"
---| "low"
---| "default"
---| "high"
---| "max"
---@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 NtfyConfig
---@field url string?
---@field topic string
local NtfyConfig
---@class Ntfy: InterfaceDevice
local Ntfy
devices.Ntfy = {}
---@param config NtfyConfig
---@return Ntfy
function devices.Ntfy.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 HueSwitch: InterfaceDevice
local HueSwitch
devices.HueSwitch = {}
---@param config HueSwitchConfig
---@return HueSwitch
function devices.HueSwitch.new(config) end
---@class AirFilterConfig
---@field name string
---@field room string?
---@field url string
local AirFilterConfig
---@class AirFilter: InterfaceDevice, InterfaceOnOff
local AirFilter
devices.AirFilter = {}
---@param config AirFilterConfig
---@return AirFilter
function devices.AirFilter.new(config) end
---@class HueBridge: InterfaceDevice
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 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: InterfaceDevice
local WakeOnLAN
devices.WakeOnLAN = {}
---@param config WolConfig
---@return WakeOnLAN
function devices.WakeOnLAN.new(config) end
---@class OutletStateOnOff
---@field state boolean
local OutletStateOnOff
---@class OutletOnOff: InterfaceDevice, InterfaceOnOff
local OutletOnOff
devices.OutletOnOff = {}
---@param config ConfigOutletOutletStateOnOff
---@return OutletOnOff
function devices.OutletOnOff.new(config) end
---@class OutletPower: InterfaceDevice, InterfaceOnOff
local OutletPower
devices.OutletPower = {}
---@param config ConfigOutletOutletStatePower
---@return OutletPower
function devices.OutletPower.new(config) end
---@class ConfigOutletOutletStatePower
---@field name string
---@field room string?
---@field topic string
---@field outlet_type OutletType?
---@field callback fun(_: OutletPower, _: OutletStatePower) | fun(_: OutletPower, _: OutletStatePower)[]?
---@field client AsyncClient
local ConfigOutletOutletStatePower
---@alias OutletType
---| "Outlet"
---| "Kettle"
---@class ConfigOutletOutletStateOnOff
---@field name string
---@field room string?
---@field topic string
---@field outlet_type OutletType?
---@field callback fun(_: OutletOnOff, _: OutletStateOnOff) | fun(_: OutletOnOff, _: OutletStateOnOff)[]?
---@field client AsyncClient
local ConfigOutletOutletStateOnOff
---@class OutletStatePower
---@field state boolean
---@field power number
local OutletStatePower
---@class ConfigLightLightStateBrightness
---@field name string
---@field room string?
---@field topic string
---@field callback fun(_: LightBrightness, _: LightStateBrightness) | fun(_: LightBrightness, _: LightStateBrightness)[]
---@field client AsyncClient
local ConfigLightLightStateBrightness
---@class ConfigLightLightStateColorTemperature
---@field name string
---@field room string?
---@field topic string
---@field callback fun(_: LightColorTemperature, _: LightStateColorTemperature) | fun(_: LightColorTemperature, _: LightStateColorTemperature)[]
---@field client AsyncClient
local ConfigLightLightStateColorTemperature
---@class LightOnOff: InterfaceDevice, InterfaceOnOff
local LightOnOff
devices.LightOnOff = {}
---@param config ConfigLightLightStateOnOff
---@return LightOnOff
function devices.LightOnOff.new(config) end
---@class LightColorTemperature: InterfaceDevice, InterfaceOnOff, InterfaceBrightness, InterfaceColorSetting
local LightColorTemperature
devices.LightColorTemperature = {}
---@param config ConfigLightLightStateColorTemperature
---@return LightColorTemperature
function devices.LightColorTemperature.new(config) end
---@class LightBrightness: InterfaceDevice, InterfaceOnOff, InterfaceBrightness
local LightBrightness
devices.LightBrightness = {}
---@param config ConfigLightLightStateBrightness
---@return LightBrightness
function devices.LightBrightness.new(config) end
---@class LightStateBrightness
---@field state boolean
---@field brightness number
local LightStateBrightness
---@class ConfigLightLightStateOnOff
---@field name string
---@field room string?
---@field topic string
---@field callback fun(_: LightOnOff, _: LightStateOnOff) | fun(_: LightOnOff, _: LightStateOnOff)[]
---@field client AsyncClient
local ConfigLightLightStateOnOff
---@class LightStateOnOff
---@field state boolean
local LightStateOnOff
---@class LightStateColorTemperature
---@field state boolean
---@field brightness number
---@field color_temp integer
local LightStateColorTemperature
---@class PresenceConfig
---@field topic string
---@field callback fun(_: Presence, _: boolean) | fun(_: Presence, _: boolean)[]?
---@field client AsyncClient
local PresenceConfig
---@class Presence: InterfaceDevice
local Presence
devices.Presence = {}
---@param config PresenceConfig
---@return Presence
function devices.Presence.new(config) end
---@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: InterfaceDevice
local IkeaRemote
devices.IkeaRemote = {}
---@param config IkeaRemoteConfig
---@return IkeaRemote
function devices.IkeaRemote.new(config) end
---@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: InterfaceDevice
local Washer
devices.Washer = {}
---@param config WasherConfig
---@return Washer
function devices.Washer.new(config) end
---@class HueGroup: InterfaceDevice, InterfaceOnOff
local HueGroup
devices.HueGroup = {}
---@param config HueGroupConfig
---@return HueGroup
function devices.HueGroup.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 KasaOutletConfig
---@field identifier string
---@field ip string
local KasaOutletConfig
---@class KasaOutlet: InterfaceDevice, InterfaceOnOff
local KasaOutlet
devices.KasaOutlet = {}
---@param config KasaOutletConfig
---@return KasaOutlet
function devices.KasaOutlet.new(config) end
---@class ContactSensor: InterfaceDevice, InterfaceOpenClose
local ContactSensor
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
return devices

View File

@@ -0,0 +1,42 @@
--- @meta
---@class InterfaceDevice
local InterfaceDevice
---@return string
function InterfaceDevice:get_id() end
---@class InterfaceOnOff: InterfaceDevice
local InterfaceOnOff
---@async
---@param on boolean
function InterfaceOnOff:set_on(on) end
---@async
---@return boolean
function InterfaceOnOff:on() end
---@class InterfaceBrightness: InterfaceDevice
local InterfaceBrightness
---@async
---@param brightness integer
function InterfaceBrightness:set_brightness(brightness) end
---@async
---@return integer
function InterfaceBrightness:brightness() end
---@class InterfaceColorSetting: InterfaceDevice
local InterfaceColorSetting
---@async
---@param temperature integer
function InterfaceColorSetting:set_color_temperature(temperature) end
---@async
---@return integer
function InterfaceColorSetting:color_temperature() end
---@class InterfaceOpenClose: InterfaceDevice
local InterfaceOpenClose
---@async
---@param open_percent integer
function InterfaceOpenClose:set_open_percent(open_percent) end
---@async
---@return integer
function InterfaceOpenClose:open_percent() end

View File

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