Compare commits

..

2 Commits

Author SHA1 Message Date
5aaa8b43e9 feat: Add proper type definition for devices
All checks were successful
Build and deploy / build (push) Successful in 10m19s
Build and deploy / Deploy container (push) Has been skipped
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-11 00:44:18 +02:00
c1475370a9 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-11 00:44:00 +02:00
9 changed files with 136 additions and 213 deletions

4
Cargo.lock generated
View File

@@ -1108,7 +1108,7 @@ dependencies = [
[[package]] [[package]]
name = "lua_typed" name = "lua_typed"
version = "0.1.0" version = "0.1.0"
source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#d5d6fc1638bd108514899a792ee64335af50fc8b" source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#d30a01aada8f5fc49ad3a296ddbf6e369e08d1f4"
dependencies = [ dependencies = [
"eui48", "eui48",
"lua_typed_macro", "lua_typed_macro",
@@ -1117,7 +1117,7 @@ dependencies = [
[[package]] [[package]]
name = "lua_typed_macro" name = "lua_typed_macro"
version = "0.1.0" version = "0.1.0"
source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#d5d6fc1638bd108514899a792ee64335af50fc8b" source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#d30a01aada8f5fc49ad3a296ddbf6e369e08d1f4"
dependencies = [ dependencies = [
"convert_case", "convert_case",
"itertools", "itertools",

View File

@@ -3,21 +3,18 @@ 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_lib::lua::traits::PartialUserData;
use automation_macro::{Device, LuaDeviceConfig}; use automation_macro::{Device, LuaDeviceConfig};
use lua_typed::Typed; 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};
#[derive(Debug, Deserialize, Typed)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
#[typed(rename_all = "snake_case")]
pub enum Flag { pub enum Flag {
Presence, Presence,
Darkness, Darkness,
} }
crate::register_type!(Flag);
#[derive(Debug, Clone, Deserialize, Typed)] #[derive(Debug, Clone, Deserialize, Typed)]
pub struct FlagIDs { pub struct FlagIDs {
@@ -39,36 +36,12 @@ pub struct Config {
crate::register_type!(Config); crate::register_type!(Config);
#[derive(Debug, Clone, Device)] #[derive(Debug, Clone, Device)]
#[device(extra_user_data = SetFlag)] #[device(add_methods(Self::add_methods))]
pub struct HueBridge { pub struct HueBridge {
config: Config, config: Config,
} }
crate::register_device!(HueBridge); crate::register_device!(HueBridge);
struct SetFlag;
impl PartialUserData<HueBridge> for SetFlag {
fn add_methods<M: mlua::UserDataMethods<HueBridge>>(methods: &mut M) {
methods.add_async_method(
"set_flag",
async |lua, this, (flag, value): (mlua::Value, bool)| {
let flag: Flag = lua.from_value(flag)?;
this.set_flag(flag, value).await;
Ok(())
},
);
}
fn definitions() -> Option<String> {
Some(format!(
"---@async\n---@param flag {}\n---@param value boolean\nfunction {}:set_flag(flag, value) end\n",
<Flag as Typed>::type_name(),
<HueBridge as Typed>::type_name(),
))
}
}
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
struct FlagMessage { struct FlagMessage {
flag: bool, flag: bool,
@@ -116,6 +89,19 @@ impl HueBridge {
} }
} }
} }
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_async_method(
"set_flag",
async |lua, this, (flag, value): (mlua::Value, bool)| {
let flag: Flag = lua.from_value(flag)?;
this.set_flag(flag, value).await;
Ok(())
},
);
}
} }
impl Device for HueBridge { impl Device for HueBridge {

View File

@@ -1,4 +1,3 @@
#![feature(iter_intersperse)]
mod air_filter; mod air_filter;
mod contact_sensor; mod contact_sensor;
mod hue_bridge; mod hue_bridge;

View File

@@ -3,7 +3,6 @@ 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_lib::lua::traits::PartialUserData;
use automation_macro::{Device, LuaDeviceConfig}; use automation_macro::{Device, LuaDeviceConfig};
use lua_typed::Typed; use lua_typed::Typed;
use mlua::LuaSerdeExt; use mlua::LuaSerdeExt;
@@ -14,7 +13,6 @@ use tracing::{error, trace, warn};
#[derive(Debug, Serialize_repr, Deserialize, Clone, Copy, Typed)] #[derive(Debug, Serialize_repr, Deserialize, Clone, Copy, Typed)]
#[repr(u8)] #[repr(u8)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
#[typed(rename_all = "snake_case")]
pub enum Priority { pub enum Priority {
Min = 1, Min = 1,
Low, Low,
@@ -61,11 +59,9 @@ pub struct Notification {
title: String, title: String,
message: Option<String>, message: Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")] #[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")]
#[typed(default)]
tags: Vec<String>, tags: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
priority: Option<Priority>, priority: Option<Priority>,
#[typed(default)]
#[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>,
} }
@@ -91,15 +87,14 @@ pub struct Config {
crate::register_type!(Config); crate::register_type!(Config);
#[derive(Debug, Clone, Device)] #[derive(Debug, Clone, Device)]
#[device(extra_user_data = SendNotification)] #[device(add_methods(Self::add_methods))]
pub struct Ntfy { pub struct Ntfy {
config: Config, config: Config,
} }
crate::register_device!(Ntfy); crate::register_device!(Ntfy);
struct SendNotification; impl Ntfy {
impl PartialUserData<Ntfy> for SendNotification { fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
fn add_methods<M: mlua::UserDataMethods<Ntfy>>(methods: &mut M) {
methods.add_async_method( methods.add_async_method(
"send_notification", "send_notification",
async |lua, this, notification: mlua::Value| { async |lua, this, notification: mlua::Value| {
@@ -111,14 +106,6 @@ impl PartialUserData<Ntfy> for SendNotification {
}, },
); );
} }
fn definitions() -> Option<String> {
Some(format!(
"---@async\n---@param notification {}\nfunction {}:send_notification(notification) end\n",
<Notification as Typed>::type_name(),
<Ntfy as Typed>::type_name(),
))
}
} }
#[async_trait] #[async_trait]

View File

@@ -6,7 +6,6 @@ use automation_lib::action_callback::ActionCallback;
use automation_lib::config::MqttDeviceConfig; use automation_lib::config::MqttDeviceConfig;
use automation_lib::device::{Device, LuaDeviceCreate}; use automation_lib::device::{Device, LuaDeviceCreate};
use automation_lib::event::OnMqtt; use automation_lib::event::OnMqtt;
use automation_lib::lua::traits::PartialUserData;
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};
@@ -40,29 +39,13 @@ pub struct State {
} }
#[derive(Debug, Clone, Device)] #[derive(Debug, Clone, Device)]
#[device(extra_user_data = OverallPresence)] #[device(add_methods(Self::add_methods))]
pub struct Presence { pub struct Presence {
config: Config, config: Config,
state: Arc<RwLock<State>>, state: Arc<RwLock<State>>,
} }
crate::register_device!(Presence); crate::register_device!(Presence);
struct OverallPresence;
impl PartialUserData<Presence> for OverallPresence {
fn add_methods<M: mlua::UserDataMethods<Presence>>(methods: &mut M) {
methods.add_async_method("overall_presence", async |_lua, this, ()| {
Ok(this.state().await.current_overall_presence)
});
}
fn definitions() -> Option<String> {
Some(format!(
"---@async\n---@return boolean\nfunction {}:overall_presence() end\n",
<Presence as Typed>::type_name(),
))
}
}
impl Presence { impl Presence {
async fn state(&self) -> RwLockReadGuard<'_, State> { async fn state(&self) -> RwLockReadGuard<'_, State> {
self.state.read().await self.state.read().await
@@ -71,6 +54,12 @@ impl Presence {
async fn state_mut(&self) -> RwLockWriteGuard<'_, State> { async fn state_mut(&self) -> RwLockWriteGuard<'_, State> {
self.state.write().await self.state.write().await
} }
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_async_method("overall_presence", async |_lua, this, ()| {
Ok(this.state().await.current_overall_presence)
});
}
} }
#[async_trait] #[async_trait]

View File

@@ -2,40 +2,21 @@ use std::ops::Deref;
// 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 PartialUserData<T> { pub trait Device {
fn add_methods<M: mlua::UserDataMethods<T>>(methods: &mut M); fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M)
where
fn interface_name() -> Option<&'static str> { Self: Sized + crate::device::Device + 'static,
None {
}
fn definitions() -> Option<String> {
None
}
}
pub struct Device;
impl<T> PartialUserData<T> for Device
where
T: crate::device::Device + 'static,
{
fn add_methods<M: mlua::UserDataMethods<T>>(methods: &mut M) {
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()));
} }
fn interface_name() -> Option<&'static str> {
Some("DeviceInterface")
}
} }
impl<T> Device for T where T: crate::device::Device {}
pub struct OnOff; pub trait OnOff {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M)
impl<T> PartialUserData<T> for OnOff where
where Self: Sized + google_home::traits::OnOff + 'static,
T: google_home::traits::OnOff + 'static, {
{
fn add_methods<M: mlua::UserDataMethods<T>>(methods: &mut M) {
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();
@@ -46,19 +27,14 @@ where
Ok(this.deref().on().await.unwrap()) Ok(this.deref().on().await.unwrap())
}); });
} }
fn interface_name() -> Option<&'static str> {
Some("OnOffInterface")
}
} }
impl<T> OnOff for T where T: google_home::traits::OnOff {}
pub struct Brightness; pub trait Brightness {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M)
impl<T> PartialUserData<T> for Brightness where
where Self: Sized + google_home::traits::Brightness + 'static,
T: google_home::traits::Brightness + 'static, {
{
fn add_methods<M: mlua::UserDataMethods<T>>(methods: &mut M) {
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();
@@ -69,19 +45,14 @@ where
Ok(this.brightness().await.unwrap()) Ok(this.brightness().await.unwrap())
}); });
} }
fn interface_name() -> Option<&'static str> {
Some("BrightnessInterface")
}
} }
impl<T> Brightness for T where T: google_home::traits::Brightness {}
pub struct ColorSetting; pub trait ColorSetting {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M)
impl<T> PartialUserData<T> for ColorSetting where
where Self: Sized + google_home::traits::ColorSetting + 'static,
T: google_home::traits::ColorSetting + 'static, {
{
fn add_methods<M: mlua::UserDataMethods<T>>(methods: &mut M) {
methods.add_async_method( methods.add_async_method(
"set_color_temperature", "set_color_temperature",
async |_lua, this, temperature: u32| { async |_lua, this, temperature: u32| {
@@ -97,19 +68,14 @@ where
Ok(this.color().await.temperature) Ok(this.color().await.temperature)
}); });
} }
fn interface_name() -> Option<&'static str> {
Some("ColorSettingInterface")
}
} }
impl<T> ColorSetting for T where T: google_home::traits::ColorSetting {}
pub struct OpenClose; pub trait OpenClose {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M)
impl<T> PartialUserData<T> for OpenClose where
where Self: Sized + google_home::traits::OpenClose + 'static,
T: google_home::traits::OpenClose + 'static, {
{
fn add_methods<M: mlua::UserDataMethods<T>>(methods: &mut M) {
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();
@@ -120,8 +86,5 @@ where
Ok(this.open_percent().await.unwrap()) Ok(this.open_percent().await.unwrap())
}); });
} }
fn interface_name() -> Option<&'static str> {
Some("OpenCloseInterface")
}
} }
impl<T> OpenClose for T where T: google_home::traits::OpenClose {}

View File

@@ -1,36 +1,35 @@
use std::collections::HashMap; use std::collections::HashMap;
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
use quote::quote; use quote::{ToTokens, quote};
use syn::parse::{Parse, ParseStream}; use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated; use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{Attribute, DeriveInput, Token, parenthesized}; use syn::{Attribute, DeriveInput, Token, parenthesized};
enum Attr { enum Attr {
Trait(TraitAttr), Trait(TraitAttr),
ExtraUserData(ExtraUserDataAttr), AddMethods(AddMethodsAttr),
} }
impl Attr { impl Parse for Attr {
fn parse(attr: &Attribute) -> syn::Result<Self> { fn parse(input: ParseStream) -> syn::Result<Self> {
let mut parsed = None; let ident: syn::Ident = input.parse()?;
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("traits") { let attr;
let input; _ = parenthesized!(attr in input);
_ = parenthesized!(input in meta.input);
parsed = Some(Attr::Trait(input.parse()?)); let attr = match ident.to_string().as_str() {
} else if meta.path.is_ident("extra_user_data") { "traits" => Attr::Trait(attr.parse()?),
let value = meta.value()?; "add_methods" => Attr::AddMethods(attr.parse()?),
parsed = Some(Attr::ExtraUserData(value.parse()?)); _ => {
} else { return Err(syn::Error::new(
return Err(syn::Error::new(meta.path.span(), "Unknown attribute")); ident.span(),
"Expected 'traits' or 'add_methods'",
));
} }
};
Ok(()) Ok(attr)
})?;
Ok(parsed.expect("Parsed should be set"))
} }
} }
@@ -66,6 +65,18 @@ impl Parse for Traits {
} }
} }
impl ToTokens for Traits {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let Self(traits) = &self;
tokens.extend(quote! {
#(
::automation_lib::lua::traits::#traits::add_methods(methods);
)*
});
}
}
#[derive(Default)] #[derive(Default)]
struct Aliases(Vec<syn::Ident>); struct Aliases(Vec<syn::Ident>);
@@ -95,18 +106,28 @@ impl Parse for Aliases {
} }
#[derive(Clone)] #[derive(Clone)]
struct ExtraUserDataAttr(syn::Ident); struct AddMethodsAttr(syn::Path);
impl Parse for ExtraUserDataAttr { impl Parse for AddMethodsAttr {
fn parse(input: ParseStream) -> syn::Result<Self> { fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self(input.parse()?)) Ok(Self(input.parse()?))
} }
} }
impl ToTokens for AddMethodsAttr {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let Self(path) = self;
tokens.extend(quote! {
#path
});
}
}
struct Implementation { struct Implementation {
name: syn::Ident, name: syn::Ident,
traits: Traits, traits: Traits,
extra_user_data: Vec<ExtraUserDataAttr>, add_methods: Vec<AddMethodsAttr>,
} }
impl quote::ToTokens for Implementation { impl quote::ToTokens for Implementation {
@@ -114,10 +135,14 @@ impl quote::ToTokens for Implementation {
let Self { let Self {
name, name,
traits, traits,
extra_user_data, add_methods,
} = &self; } = &self;
let Traits(traits) = traits;
let extra_user_data: Vec<_> = extra_user_data.iter().map(|tr| tr.0.clone()).collect(); let interfaces: String = traits
.0
.iter()
.map(|tr| format!(", Interface{tr}"))
.collect();
tokens.extend(quote! { tokens.extend(quote! {
impl mlua::UserData for #name { impl mlua::UserData for #name {
@@ -135,14 +160,12 @@ impl quote::ToTokens for Implementation {
Ok(b) Ok(b)
}); });
<::automation_lib::lua::traits::Device as ::automation_lib::lua::traits::PartialUserData<#name>>::add_methods(methods); ::automation_lib::lua::traits::Device::add_methods(methods);
#traits
#( #(
<::automation_lib::lua::traits::#traits as ::automation_lib::lua::traits::PartialUserData<#name>>::add_methods(methods); #add_methods(methods);
)*
#(
<#extra_user_data as ::automation_lib::lua::traits::PartialUserData<#name>>::add_methods(methods);
)* )*
} }
} }
@@ -154,22 +177,7 @@ impl quote::ToTokens for Implementation {
fn generate_header() -> std::option::Option<::std::string::String> { fn generate_header() -> std::option::Option<::std::string::String> {
let type_name = <Self as ::lua_typed::Typed>::type_name(); let type_name = <Self as ::lua_typed::Typed>::type_name();
let mut output = String::new(); Some(format!("---@class {type_name}: InterfaceDevice{}\nlocal {type_name}\n", #interfaces))
let interfaces: String = [
<::automation_lib::lua::traits::Device as ::automation_lib::lua::traits::PartialUserData<#name>>::interface_name(),
#(
<::automation_lib::lua::traits::#traits as ::automation_lib::lua::traits::PartialUserData<#name>>::interface_name(),
)*
].into_iter().flatten().intersperse(", ").collect();
let interfaces = if interfaces.is_empty() {
"".into()
} else {
format!(": {interfaces}")
};
Some(format!("---@class {type_name}{interfaces}\nlocal {type_name}\n"))
} }
fn generate_members() -> Option<String> { fn generate_members() -> Option<String> {
@@ -182,15 +190,6 @@ impl quote::ToTokens for Implementation {
output += &format!("---@return {type_name}\n"); output += &format!("---@return {type_name}\n");
output += &format!("function devices.{type_name}.new(config) end\n"); output += &format!("function devices.{type_name}.new(config) end\n");
output += &<::automation_lib::lua::traits::Device as ::automation_lib::lua::traits::PartialUserData<#name>>::definitions().unwrap_or("".into());
#(
output += &<::automation_lib::lua::traits::#traits as ::automation_lib::lua::traits::PartialUserData<#name>>::definitions().unwrap_or("".into());
)*
#(
output += &<#extra_user_data as ::automation_lib::lua::traits::PartialUserData<#name>>::definitions().unwrap_or("".into());
)*
Some(output) Some(output)
} }
@@ -220,7 +219,7 @@ impl Implementations {
all.extend(&attribute.traits); all.extend(&attribute.traits);
} }
} }
Attr::ExtraUserData(attribute) => add_methods.push(attribute), Attr::AddMethods(attribute) => add_methods.push(attribute),
} }
} }
@@ -238,7 +237,7 @@ impl Implementations {
.map(|(alias, traits)| Implementation { .map(|(alias, traits)| Implementation {
name: alias.unwrap_or(name.clone()), name: alias.unwrap_or(name.clone()),
traits, traits,
extra_user_data: add_methods.clone(), add_methods: add_methods.clone(),
}) })
.collect(), .collect(),
) )
@@ -250,7 +249,7 @@ pub fn device(input: DeriveInput) -> TokenStream2 {
.attrs .attrs
.iter() .iter()
.filter(|attr| attr.path().is_ident("device")) .filter(|attr| attr.path().is_ident("device"))
.map(Attr::parse) .map(Attribute::parse_args)
.try_collect::<Vec<_>>() .try_collect::<Vec<_>>()
{ {
Ok(attr) => Implementations::from_attr(attr, input.ident), Ok(attr) => Implementations::from_attr(attr, input.ident),

View File

@@ -64,7 +64,7 @@ pub fn lua_serialize(input: proc_macro::TokenStream) -> proc_macro::TokenStream
/// ``` /// ```
/// It can then be registered with: /// It can then be registered with:
/// ```rust /// ```rust
/// #[device(add_methods = top_secret)] /// #[device(add_methods(top_secret))]
/// ``` /// ```
#[proc_macro_derive(Device, attributes(device))] #[proc_macro_derive(Device, attributes(device))]
pub fn device(input: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn device(input: proc_macro::TokenStream) -> proc_macro::TokenStream {

View File

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