Compare commits
3 Commits
6d2dbd37f1
...
c90cd00cfa
Author | SHA1 | Date | |
---|---|---|---|
c90cd00cfa | |||
31169d32eb | |||
396ac4a9f1 |
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -110,6 +110,7 @@ dependencies = [
|
||||||
name = "automation_macro"
|
name = "automation_macro"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"itertools 0.12.1",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.60",
|
"syn 2.0.60",
|
||||||
|
|
|
@ -7,6 +7,7 @@ edition = "2021"
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
itertools = "0.12.1"
|
||||||
proc-macro2 = "1.0.81"
|
proc-macro2 = "1.0.81"
|
||||||
quote = "1.0.36"
|
quote = "1.0.36"
|
||||||
syn = { version = "2.0.60", features = ["extra-traits"] }
|
syn = { version = "2.0.60", features = ["extra-traits", "full"] }
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
use proc_macro2::TokenStream;
|
mod lua_device;
|
||||||
use quote::quote;
|
mod lua_device_config;
|
||||||
use syn::punctuated::Punctuated;
|
|
||||||
use syn::{parse_macro_input, DeriveInput, Token};
|
use lua_device::impl_lua_device_macro;
|
||||||
|
use lua_device_config::impl_lua_device_config_macro;
|
||||||
|
use syn::{parse_macro_input, DeriveInput};
|
||||||
|
|
||||||
#[proc_macro_derive(LuaDevice, attributes(config))]
|
#[proc_macro_derive(LuaDevice, attributes(config))]
|
||||||
pub fn lua_device_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub fn lua_device_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
@ -10,313 +12,9 @@ pub fn lua_device_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStr
|
||||||
impl_lua_device_macro(&ast).into()
|
impl_lua_device_macro(&ast).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn impl_lua_device_macro(ast: &syn::DeriveInput) -> TokenStream {
|
|
||||||
let name = &ast.ident;
|
|
||||||
// TODO: Handle errors properly
|
|
||||||
// This includes making sure one, and only one config is specified
|
|
||||||
let config = if let syn::Data::Struct(syn::DataStruct {
|
|
||||||
fields: syn::Fields::Named(syn::FieldsNamed { ref named, .. }),
|
|
||||||
..
|
|
||||||
}) = ast.data
|
|
||||||
{
|
|
||||||
named
|
|
||||||
.iter()
|
|
||||||
.find(|&field| {
|
|
||||||
field
|
|
||||||
.attrs
|
|
||||||
.iter()
|
|
||||||
.any(|attr| attr.path().is_ident("config"))
|
|
||||||
})
|
|
||||||
.map(|field| field.ty.clone())
|
|
||||||
.unwrap()
|
|
||||||
} else {
|
|
||||||
unimplemented!()
|
|
||||||
};
|
|
||||||
|
|
||||||
let gen = quote! {
|
|
||||||
impl #name {
|
|
||||||
pub fn register_with_lua(lua: &mlua::Lua) -> mlua::Result<()> {
|
|
||||||
lua.globals().set(stringify!(#name), lua.create_proxy::<#name>()?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl mlua::UserData for #name {
|
|
||||||
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
|
|
||||||
methods.add_function("new", |lua, config: mlua::Value| {
|
|
||||||
let config: #config = mlua::FromLua::from_lua(config, lua)?;
|
|
||||||
let config: Box<dyn crate::device_manager::DeviceConfig> = Box::new(config);
|
|
||||||
Ok(config)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
gen
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Arg {
|
|
||||||
Flatten,
|
|
||||||
UserData,
|
|
||||||
Rename(String),
|
|
||||||
With(TokenStream),
|
|
||||||
Default(Option<syn::Ident>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl syn::parse::Parse for Arg {
|
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
let arg = match input.parse::<syn::Ident>()?.to_string().as_str() {
|
|
||||||
"flatten" => Arg::Flatten,
|
|
||||||
"user_data" => Arg::UserData,
|
|
||||||
"rename" => {
|
|
||||||
input.parse::<Token![=]>()?;
|
|
||||||
let lit = input.parse::<syn::Lit>()?;
|
|
||||||
if let syn::Lit::Str(lit_str) = lit {
|
|
||||||
Arg::Rename(lit_str.value())
|
|
||||||
} else {
|
|
||||||
panic!("Expected literal string");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"with" => {
|
|
||||||
input.parse::<Token![=]>()?;
|
|
||||||
let lit = input.parse::<syn::Lit>()?;
|
|
||||||
if let syn::Lit::Str(lit_str) = lit {
|
|
||||||
let token_stream: TokenStream = lit_str.parse()?;
|
|
||||||
Arg::With(token_stream)
|
|
||||||
} else {
|
|
||||||
panic!("Expected literal string");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"default" => {
|
|
||||||
if input.parse::<Token![=]>().is_ok() {
|
|
||||||
let func = input.parse::<syn::Ident>()?;
|
|
||||||
Arg::Default(Some(func))
|
|
||||||
} else {
|
|
||||||
Arg::Default(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
name => todo!("Handle unknown arg: {name}"),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct ArgsParser {
|
|
||||||
args: Punctuated<Arg, Token![,]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl syn::parse::Parse for ArgsParser {
|
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
let args = input.parse_terminated(Arg::parse, Token![,])?;
|
|
||||||
|
|
||||||
Ok(Self { args })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Args {
|
|
||||||
flatten: bool,
|
|
||||||
user_data: bool,
|
|
||||||
rename: Option<String>,
|
|
||||||
with: Option<TokenStream>,
|
|
||||||
default: Option<Option<syn::Ident>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Args {
|
|
||||||
fn new(args: Vec<Arg>) -> Self {
|
|
||||||
let mut result = Args {
|
|
||||||
flatten: false,
|
|
||||||
user_data: false,
|
|
||||||
rename: None,
|
|
||||||
with: None,
|
|
||||||
default: None,
|
|
||||||
};
|
|
||||||
for arg in args {
|
|
||||||
match arg {
|
|
||||||
Arg::Flatten => {
|
|
||||||
if result.flatten {
|
|
||||||
panic!("Option 'flatten' is already set")
|
|
||||||
}
|
|
||||||
result.flatten = true
|
|
||||||
}
|
|
||||||
Arg::UserData => {
|
|
||||||
if result.flatten {
|
|
||||||
panic!("Option 'user_data' is already set")
|
|
||||||
}
|
|
||||||
result.user_data = true
|
|
||||||
}
|
|
||||||
Arg::Rename(name) => {
|
|
||||||
if result.rename.is_some() {
|
|
||||||
panic!("Option 'rename' is already set")
|
|
||||||
}
|
|
||||||
result.rename = Some(name)
|
|
||||||
}
|
|
||||||
Arg::With(ty) => {
|
|
||||||
if result.with.is_some() {
|
|
||||||
panic!("Option 'with' is already set")
|
|
||||||
}
|
|
||||||
result.with = Some(ty)
|
|
||||||
}
|
|
||||||
Arg::Default(func) => {
|
|
||||||
if result.default.is_some() {
|
|
||||||
panic!("Option 'default' is already set")
|
|
||||||
}
|
|
||||||
result.default = Some(func)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.flatten && result.user_data {
|
|
||||||
panic!("The options 'flatten' and 'user_data' conflict with each other")
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.flatten && result.default.is_some() {
|
|
||||||
panic!("The options 'flatten' and 'default' conflict with each other")
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[proc_macro_derive(LuaDeviceConfig, attributes(device_config))]
|
#[proc_macro_derive(LuaDeviceConfig, attributes(device_config))]
|
||||||
pub fn lua_device_config_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub fn lua_device_config_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
let ast = parse_macro_input!(input as DeriveInput);
|
let ast = parse_macro_input!(input as DeriveInput);
|
||||||
|
|
||||||
impl_lua_device_config_macro(&ast).into()
|
impl_lua_device_config_macro(&ast).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
// struct Args
|
|
||||||
|
|
||||||
fn impl_lua_device_config_macro(ast: &syn::DeriveInput) -> TokenStream {
|
|
||||||
let name = &ast.ident;
|
|
||||||
// TODO: Handle errors properly
|
|
||||||
// This includes making sure one, and only one config is specified
|
|
||||||
let fields = if let syn::Data::Struct(syn::DataStruct {
|
|
||||||
fields: syn::Fields::Named(syn::FieldsNamed { ref named, .. }),
|
|
||||||
..
|
|
||||||
}) = ast.data
|
|
||||||
{
|
|
||||||
named
|
|
||||||
} else {
|
|
||||||
unimplemented!("Macro can only handle named structs");
|
|
||||||
};
|
|
||||||
|
|
||||||
let fields: Vec<_> = fields
|
|
||||||
.iter()
|
|
||||||
.map(|field| {
|
|
||||||
let field_name = field.ident.clone().unwrap();
|
|
||||||
let args: Vec<_> = field
|
|
||||||
.attrs
|
|
||||||
.iter()
|
|
||||||
.filter_map(|attr| {
|
|
||||||
if attr.path().is_ident("device_config") {
|
|
||||||
let args: ArgsParser = attr.parse_args().unwrap();
|
|
||||||
Some(args.args)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let args = Args::new(args);
|
|
||||||
|
|
||||||
let table_name = if let Some(name) = args.rename {
|
|
||||||
name
|
|
||||||
} else {
|
|
||||||
field_name.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Improve how optional fields are detected
|
|
||||||
let optional = if let syn::Type::Path(path) = field.ty.clone() {
|
|
||||||
path.path.segments.first().unwrap().ident == "Option"
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
let default = if optional {
|
|
||||||
quote! { None }
|
|
||||||
} else if let Some(func) = args.default {
|
|
||||||
if func.is_some() {
|
|
||||||
quote! { #func() }
|
|
||||||
} else {
|
|
||||||
quote! { Default::default() }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let missing = format!("Missing field '{table_name}'");
|
|
||||||
quote! { panic!(#missing) }
|
|
||||||
};
|
|
||||||
|
|
||||||
let value = if args.flatten {
|
|
||||||
// println!("ValueFlatten: {}", field_name);
|
|
||||||
quote! {
|
|
||||||
mlua::LuaSerdeExt::from_value_with(lua, value.clone(), mlua::DeserializeOptions::new().deny_unsupported_types(false))?
|
|
||||||
}
|
|
||||||
} else if args.user_data {
|
|
||||||
// println!("UserData: {}", field_name);
|
|
||||||
quote! {
|
|
||||||
if table.contains_key(#table_name)? {
|
|
||||||
table.get(#table_name)?
|
|
||||||
} else {
|
|
||||||
#default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// println!("Value: {}", field_name);
|
|
||||||
quote! {
|
|
||||||
{
|
|
||||||
let #field_name: mlua::Value = table.get(#table_name)?;
|
|
||||||
if !#field_name.is_nil() {
|
|
||||||
mlua::LuaSerdeExt::from_value(lua, #field_name)?
|
|
||||||
} else {
|
|
||||||
#default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let value = if let Some(temp_type) = args.with {
|
|
||||||
if optional {
|
|
||||||
quote! {
|
|
||||||
{
|
|
||||||
let temp: #temp_type = #value;
|
|
||||||
temp.map(|v| v.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
{
|
|
||||||
let temp: #temp_type = #value;
|
|
||||||
temp.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
value
|
|
||||||
};
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
#field_name: #value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let gen = quote! {
|
|
||||||
impl<'lua> mlua::FromLua<'lua> for #name {
|
|
||||||
fn from_lua(value: mlua::Value<'lua>, lua: &'lua mlua::Lua) -> mlua::Result<Self> {
|
|
||||||
if !value.is_table() {
|
|
||||||
panic!("Expected table");
|
|
||||||
}
|
|
||||||
let table = value.as_table().unwrap();
|
|
||||||
|
|
||||||
Ok(#name {
|
|
||||||
#(#fields,)*
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
gen
|
|
||||||
}
|
|
||||||
|
|
46
automation_macro/src/lua_device.rs
Normal file
46
automation_macro/src/lua_device.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{Data, DataStruct, DeriveInput, Fields, FieldsNamed};
|
||||||
|
|
||||||
|
pub fn impl_lua_device_macro(ast: &DeriveInput) -> TokenStream {
|
||||||
|
let name = &ast.ident;
|
||||||
|
// TODO: Handle errors properly
|
||||||
|
// This includes making sure one, and only one config is specified
|
||||||
|
let config = if let Data::Struct(DataStruct {
|
||||||
|
fields: Fields::Named(FieldsNamed { ref named, .. }),
|
||||||
|
..
|
||||||
|
}) = ast.data
|
||||||
|
{
|
||||||
|
named
|
||||||
|
.iter()
|
||||||
|
.find(|&field| {
|
||||||
|
field
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.any(|attr| attr.path().is_ident("config"))
|
||||||
|
})
|
||||||
|
.map(|field| field.ty.clone())
|
||||||
|
.unwrap()
|
||||||
|
} else {
|
||||||
|
unimplemented!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let gen = quote! {
|
||||||
|
impl #name {
|
||||||
|
pub fn register_with_lua(lua: &mlua::Lua) -> mlua::Result<()> {
|
||||||
|
lua.globals().set(stringify!(#name), lua.create_proxy::<#name>()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl mlua::UserData for #name {
|
||||||
|
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||||
|
methods.add_function("new", |lua, config: mlua::Value| {
|
||||||
|
let config: #config = mlua::FromLua::from_lua(config, lua)?;
|
||||||
|
let config: Box<dyn crate::device_manager::DeviceConfig> = Box::new(config);
|
||||||
|
Ok(config)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
gen
|
||||||
|
}
|
280
automation_macro/src/lua_device_config.rs
Normal file
280
automation_macro/src/lua_device_config.rs
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
use itertools::Itertools;
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{quote, quote_spanned};
|
||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::token::Paren;
|
||||||
|
use syn::{
|
||||||
|
parenthesized, Data, DataStruct, DeriveInput, Expr, Field, Fields, FieldsNamed, LitStr, Result,
|
||||||
|
Token, Type,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod kw {
|
||||||
|
use syn::custom_keyword;
|
||||||
|
|
||||||
|
custom_keyword!(device_config);
|
||||||
|
custom_keyword!(flatten);
|
||||||
|
custom_keyword!(from_lua);
|
||||||
|
custom_keyword!(rename);
|
||||||
|
custom_keyword!(with);
|
||||||
|
custom_keyword!(from);
|
||||||
|
custom_keyword!(default);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Argument {
|
||||||
|
Flatten {
|
||||||
|
_keyword: kw::flatten,
|
||||||
|
},
|
||||||
|
FromLua {
|
||||||
|
_keyword: kw::from_lua,
|
||||||
|
},
|
||||||
|
Rename {
|
||||||
|
_keyword: kw::rename,
|
||||||
|
_paren: Paren,
|
||||||
|
ident: LitStr,
|
||||||
|
},
|
||||||
|
With {
|
||||||
|
_keyword: kw::with,
|
||||||
|
_paren: Paren,
|
||||||
|
// TODO: Ideally we capture this better
|
||||||
|
expr: Expr,
|
||||||
|
},
|
||||||
|
From {
|
||||||
|
_keyword: kw::from,
|
||||||
|
_paren: Paren,
|
||||||
|
ty: Type,
|
||||||
|
},
|
||||||
|
Default {
|
||||||
|
_keyword: kw::default,
|
||||||
|
},
|
||||||
|
DefaultExpr {
|
||||||
|
_keyword: kw::default,
|
||||||
|
_paren: Paren,
|
||||||
|
expr: Expr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Argument {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let lookahead = input.lookahead1();
|
||||||
|
if lookahead.peek(kw::flatten) {
|
||||||
|
Ok(Self::Flatten {
|
||||||
|
_keyword: input.parse()?,
|
||||||
|
})
|
||||||
|
} else if lookahead.peek(kw::from_lua) {
|
||||||
|
Ok(Self::FromLua {
|
||||||
|
_keyword: input.parse()?,
|
||||||
|
})
|
||||||
|
} else if lookahead.peek(kw::rename) {
|
||||||
|
let content;
|
||||||
|
Ok(Self::Rename {
|
||||||
|
_keyword: input.parse()?,
|
||||||
|
_paren: parenthesized!(content in input),
|
||||||
|
ident: content.parse()?,
|
||||||
|
})
|
||||||
|
} else if lookahead.peek(kw::with) {
|
||||||
|
let content;
|
||||||
|
Ok(Self::With {
|
||||||
|
_keyword: input.parse()?,
|
||||||
|
_paren: parenthesized!(content in input),
|
||||||
|
expr: content.parse()?,
|
||||||
|
})
|
||||||
|
} else if lookahead.peek(kw::from) {
|
||||||
|
let content;
|
||||||
|
Ok(Self::From {
|
||||||
|
_keyword: input.parse()?,
|
||||||
|
_paren: parenthesized!(content in input),
|
||||||
|
ty: content.parse()?,
|
||||||
|
})
|
||||||
|
} else if lookahead.peek(kw::default) {
|
||||||
|
let keyword = input.parse()?;
|
||||||
|
if input.peek(Paren) {
|
||||||
|
let content;
|
||||||
|
Ok(Self::DefaultExpr {
|
||||||
|
_keyword: keyword,
|
||||||
|
_paren: parenthesized!(content in input),
|
||||||
|
expr: content.parse()?,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(Self::Default { _keyword: keyword })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(lookahead.error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Args {
|
||||||
|
args: Punctuated<Argument, Token![,]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Args {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
args: input.parse_terminated(Argument::parse, Token![,])?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn field_from_lua(field: &Field) -> TokenStream {
|
||||||
|
let (args, errors): (Vec<_>, Vec<_>) = field
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|attr| {
|
||||||
|
if attr.path().is_ident("device_config") {
|
||||||
|
Some(attr.parse_args::<Args>().map(|args| args.args))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.partition_result();
|
||||||
|
|
||||||
|
let errors: Vec<_> = errors
|
||||||
|
.iter()
|
||||||
|
.map(|error| error.to_compile_error())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if !errors.is_empty() {
|
||||||
|
return quote! { #(#errors)* };
|
||||||
|
}
|
||||||
|
|
||||||
|
let args: Vec<_> = args.into_iter().flatten().collect();
|
||||||
|
|
||||||
|
let table_name = match args
|
||||||
|
.iter()
|
||||||
|
.filter_map(|arg| match arg {
|
||||||
|
Argument::Rename { ident, .. } => Some(ident.value()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.as_slice()
|
||||||
|
{
|
||||||
|
[] => field.ident.clone().unwrap().to_string(),
|
||||||
|
[rename] => rename.to_owned(),
|
||||||
|
_ => {
|
||||||
|
return quote_spanned! {field.span() => compile_error!("Field contains duplicate 'rename'")}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Detect Option<_> properly and use Default::default() as fallback automatically
|
||||||
|
let missing = format!("Missing field '{table_name}'");
|
||||||
|
let default = match args
|
||||||
|
.iter()
|
||||||
|
.filter_map(|arg| match arg {
|
||||||
|
Argument::Default { .. } => Some(quote! { Default::default() }),
|
||||||
|
Argument::DefaultExpr { expr, .. } => Some(quote! { (#expr) }),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.as_slice()
|
||||||
|
{
|
||||||
|
[] => quote! {panic!(#missing)},
|
||||||
|
[default] => default.to_owned(),
|
||||||
|
_ => {
|
||||||
|
return quote_spanned! {field.span() => compile_error!("Field contains duplicate 'default'")}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = match args
|
||||||
|
.iter()
|
||||||
|
.filter_map(|arg| match arg {
|
||||||
|
Argument::Flatten { .. } => Some(quote! {
|
||||||
|
mlua::LuaSerdeExt::from_value_with(lua, value.clone(), mlua::DeserializeOptions::new().deny_unsupported_types(false))?
|
||||||
|
}),
|
||||||
|
Argument::FromLua { .. } => Some(quote! {
|
||||||
|
if table.contains_key(#table_name)? {
|
||||||
|
table.get(#table_name)?
|
||||||
|
} else {
|
||||||
|
#default
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.as_slice() {
|
||||||
|
[] => quote! {
|
||||||
|
{
|
||||||
|
let value: mlua::Value = table.get(#table_name)?;
|
||||||
|
if !value.is_nil() {
|
||||||
|
mlua::LuaSerdeExt::from_value(lua, value)?
|
||||||
|
} else {
|
||||||
|
#default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[value] => value.to_owned(),
|
||||||
|
_ => return quote_spanned! {field.span() => compile_error!("Only one of either 'flatten' or 'from_lua' is allowed")},
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = match args
|
||||||
|
.iter()
|
||||||
|
.filter_map(|arg| match arg {
|
||||||
|
Argument::From { ty, .. } => Some(quote! {
|
||||||
|
{
|
||||||
|
let temp: #ty = #value;
|
||||||
|
temp.into()
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Argument::With { expr, .. } => Some(quote! {
|
||||||
|
{
|
||||||
|
let temp = #value;
|
||||||
|
(#expr)(temp)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.as_slice()
|
||||||
|
{
|
||||||
|
[] => value,
|
||||||
|
[value] => value.to_owned(),
|
||||||
|
_ => {
|
||||||
|
return quote_spanned! {field.span() => compile_error!("Only one of either 'from' or 'with' is allowed")}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! { #value }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn impl_lua_device_config_macro(ast: &DeriveInput) -> TokenStream {
|
||||||
|
let name = &ast.ident;
|
||||||
|
let fields = if let Data::Struct(DataStruct {
|
||||||
|
fields: Fields::Named(FieldsNamed { ref named, .. }),
|
||||||
|
..
|
||||||
|
}) = ast.data
|
||||||
|
{
|
||||||
|
named
|
||||||
|
} else {
|
||||||
|
return quote_spanned! {ast.span() => compile_error!("This macro only works on named structs")};
|
||||||
|
};
|
||||||
|
|
||||||
|
let lua_fields: Vec<_> = fields
|
||||||
|
.iter()
|
||||||
|
.map(|field| {
|
||||||
|
let name = field.ident.clone().unwrap();
|
||||||
|
let value = field_from_lua(field);
|
||||||
|
quote! { #name: #value }
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let impl_from_lua = quote! {
|
||||||
|
impl<'lua> mlua::FromLua<'lua> for #name {
|
||||||
|
fn from_lua(value: mlua::Value<'lua>, lua: &'lua mlua::Lua) -> mlua::Result<Self> {
|
||||||
|
if !value.is_table() {
|
||||||
|
panic!("Expected table");
|
||||||
|
}
|
||||||
|
let table = value.as_table().unwrap();
|
||||||
|
|
||||||
|
Ok(#name {
|
||||||
|
#(#lua_fields,)*
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
impl_from_lua
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ pub struct AirFilterConfig {
|
||||||
info: InfoConfig,
|
info: InfoConfig,
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
mqtt: MqttDeviceConfig,
|
mqtt: MqttDeviceConfig,
|
||||||
#[device_config(user_data)]
|
#[device_config(from_lua)]
|
||||||
client: WrappedAsyncClient,
|
client: WrappedAsyncClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,9 @@ use crate::messages::{RemoteAction, RemoteMessage};
|
||||||
pub struct AudioSetupConfig {
|
pub struct AudioSetupConfig {
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
mqtt: MqttDeviceConfig,
|
mqtt: MqttDeviceConfig,
|
||||||
#[device_config(user_data)]
|
#[device_config(from_lua)]
|
||||||
mixer: WrappedDevice,
|
mixer: WrappedDevice,
|
||||||
#[device_config(user_data)]
|
#[device_config(from_lua)]
|
||||||
speakers: WrappedDevice,
|
speakers: WrappedDevice,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::time::Duration;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
||||||
use google_home::traits::OnOff;
|
use google_home::traits::OnOff;
|
||||||
|
use mlua::FromLua;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use tracing::{debug, error, trace, warn};
|
use tracing::{debug, error, trace, warn};
|
||||||
|
|
||||||
|
@ -12,7 +13,6 @@ use crate::device_manager::{DeviceConfig, WrappedDevice};
|
||||||
use crate::devices::{As, DEFAULT_PRESENCE};
|
use crate::devices::{As, DEFAULT_PRESENCE};
|
||||||
use crate::error::DeviceConfigError;
|
use crate::error::DeviceConfigError;
|
||||||
use crate::event::{OnMqtt, OnPresence};
|
use crate::event::{OnMqtt, OnPresence};
|
||||||
use crate::helper::DurationSeconds;
|
|
||||||
use crate::messages::{ContactMessage, PresenceMessage};
|
use crate::messages::{ContactMessage, PresenceMessage};
|
||||||
use crate::mqtt::WrappedAsyncClient;
|
use crate::mqtt::WrappedAsyncClient;
|
||||||
use crate::traits::Timeout;
|
use crate::traits::Timeout;
|
||||||
|
@ -22,15 +22,30 @@ use crate::traits::Timeout;
|
||||||
pub struct PresenceDeviceConfig {
|
pub struct PresenceDeviceConfig {
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
#[device_config(with = "DurationSeconds")]
|
#[device_config(with(Duration::from_secs))]
|
||||||
pub timeout: Duration,
|
pub timeout: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct TriggerDevicesHelper(Vec<WrappedDevice>);
|
||||||
|
|
||||||
|
impl<'lua> FromLua<'lua> for TriggerDevicesHelper {
|
||||||
|
fn from_lua(value: mlua::Value<'lua>, lua: &'lua mlua::Lua) -> mlua::Result<Self> {
|
||||||
|
Ok(TriggerDevicesHelper(mlua::FromLua::from_lua(value, lua)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TriggerDevicesHelper> for Vec<(WrappedDevice, bool)> {
|
||||||
|
fn from(value: TriggerDevicesHelper) -> Self {
|
||||||
|
value.0.into_iter().map(|device| (device, false)).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig)]
|
||||||
pub struct TriggerConfig {
|
pub struct TriggerConfig {
|
||||||
#[device_config(user_data)]
|
#[device_config(from_lua, from(TriggerDevicesHelper))]
|
||||||
devices: Vec<WrappedDevice>,
|
devices: Vec<(WrappedDevice, bool)>,
|
||||||
#[device_config(with = "Option<DurationSeconds>")]
|
#[device_config(default, with(|t: Option<_>| t.map(Duration::from_secs)))]
|
||||||
pub timeout: Option<Duration>,
|
pub timeout: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,11 +53,11 @@ pub struct TriggerConfig {
|
||||||
pub struct ContactSensorConfig {
|
pub struct ContactSensorConfig {
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
mqtt: MqttDeviceConfig,
|
mqtt: MqttDeviceConfig,
|
||||||
#[device_config(user_data)]
|
#[device_config(from_lua)]
|
||||||
presence: Option<PresenceDeviceConfig>,
|
presence: Option<PresenceDeviceConfig>,
|
||||||
#[device_config(user_data)]
|
#[device_config(from_lua)]
|
||||||
trigger: Option<TriggerConfig>,
|
trigger: Option<TriggerConfig>,
|
||||||
#[device_config(user_data)]
|
#[device_config(from_lua)]
|
||||||
client: WrappedAsyncClient,
|
client: WrappedAsyncClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,30 +66,20 @@ impl DeviceConfig for ContactSensorConfig {
|
||||||
async fn create(&self, identifier: &str) -> Result<Box<dyn Device>, DeviceConfigError> {
|
async fn create(&self, identifier: &str) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||||
trace!(id = identifier, "Setting up ContactSensor");
|
trace!(id = identifier, "Setting up ContactSensor");
|
||||||
|
|
||||||
let trigger = if let Some(trigger_config) = &self.trigger {
|
// Make sure the devices implement the required traits
|
||||||
let mut devices = Vec::new();
|
if let Some(trigger) = &self.trigger {
|
||||||
for device in &trigger_config.devices {
|
for (device, _) in &trigger.devices {
|
||||||
let id = device.read().await.get_id().to_owned();
|
let id = device.read().await.get_id().to_owned();
|
||||||
if !As::<dyn OnOff>::is(device.read().await.as_ref()) {
|
if !As::<dyn OnOff>::is(device.read().await.as_ref()) {
|
||||||
return Err(DeviceConfigError::MissingTrait(id, "OnOff".into()));
|
return Err(DeviceConfigError::MissingTrait(id, "OnOff".into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if trigger_config.timeout.is_none()
|
if trigger.timeout.is_none() && !As::<dyn Timeout>::is(device.read().await.as_ref())
|
||||||
&& !As::<dyn Timeout>::is(device.read().await.as_ref())
|
|
||||||
{
|
{
|
||||||
return Err(DeviceConfigError::MissingTrait(id, "Timeout".into()));
|
return Err(DeviceConfigError::MissingTrait(id, "Timeout".into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
devices.push((device.clone(), false));
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Some(Trigger {
|
|
||||||
devices,
|
|
||||||
timeout: trigger_config.timeout,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let device = ContactSensor {
|
let device = ContactSensor {
|
||||||
identifier: identifier.into(),
|
identifier: identifier.into(),
|
||||||
|
@ -82,19 +87,12 @@ impl DeviceConfig for ContactSensorConfig {
|
||||||
overall_presence: DEFAULT_PRESENCE,
|
overall_presence: DEFAULT_PRESENCE,
|
||||||
is_closed: true,
|
is_closed: true,
|
||||||
handle: None,
|
handle: None,
|
||||||
trigger,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Box::new(device))
|
Ok(Box::new(device))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Trigger {
|
|
||||||
devices: Vec<(WrappedDevice, bool)>,
|
|
||||||
timeout: Option<Duration>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, LuaDevice)]
|
#[derive(Debug, LuaDevice)]
|
||||||
pub struct ContactSensor {
|
pub struct ContactSensor {
|
||||||
identifier: String,
|
identifier: String,
|
||||||
|
@ -104,8 +102,6 @@ pub struct ContactSensor {
|
||||||
overall_presence: bool,
|
overall_presence: bool,
|
||||||
is_closed: bool,
|
is_closed: bool,
|
||||||
handle: Option<JoinHandle<()>>,
|
handle: Option<JoinHandle<()>>,
|
||||||
|
|
||||||
trigger: Option<Trigger>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Device for ContactSensor {
|
impl Device for ContactSensor {
|
||||||
|
@ -143,11 +139,12 @@ impl OnMqtt for ContactSensor {
|
||||||
debug!(id = self.identifier, "Updating state to {is_closed}");
|
debug!(id = self.identifier, "Updating state to {is_closed}");
|
||||||
self.is_closed = is_closed;
|
self.is_closed = is_closed;
|
||||||
|
|
||||||
if let Some(trigger) = &mut self.trigger {
|
if let Some(trigger) = &mut self.config.trigger {
|
||||||
if !self.is_closed {
|
if !self.is_closed {
|
||||||
for (light, _) in &mut trigger.devices {
|
for (light, previous) in &mut trigger.devices {
|
||||||
let mut light = light.write().await;
|
let mut light = light.write().await;
|
||||||
if let Some(light) = As::<dyn OnOff>::cast_mut(light.as_mut()) {
|
if let Some(light) = As::<dyn OnOff>::cast_mut(light.as_mut()) {
|
||||||
|
*previous = light.is_on().await.unwrap();
|
||||||
light.set_on(true).await.ok();
|
light.set_on(true).await.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ use crate::mqtt::WrappedAsyncClient;
|
||||||
pub struct DebugBridgeConfig {
|
pub struct DebugBridgeConfig {
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
#[device_config(user_data)]
|
#[device_config(from_lua)]
|
||||||
client: WrappedAsyncClient,
|
client: WrappedAsyncClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ use crate::device_manager::DeviceConfig;
|
||||||
use crate::devices::Device;
|
use crate::devices::Device;
|
||||||
use crate::error::DeviceConfigError;
|
use crate::error::DeviceConfigError;
|
||||||
use crate::event::{OnDarkness, OnPresence};
|
use crate::event::{OnDarkness, OnPresence};
|
||||||
use crate::helper::Ipv4SocketAddr;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Flag {
|
pub enum Flag {
|
||||||
|
@ -25,7 +24,7 @@ pub struct FlagIDs {
|
||||||
|
|
||||||
#[derive(Debug, LuaDeviceConfig, Clone)]
|
#[derive(Debug, LuaDeviceConfig, Clone)]
|
||||||
pub struct HueBridgeConfig {
|
pub struct HueBridgeConfig {
|
||||||
#[device_config(rename = "ip", with = "Ipv4SocketAddr<80>")]
|
#[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 80)))]
|
||||||
pub addr: SocketAddr,
|
pub addr: SocketAddr,
|
||||||
pub login: String,
|
pub login: String,
|
||||||
pub flags: FlagIDs,
|
pub flags: FlagIDs,
|
||||||
|
|
|
@ -14,13 +14,12 @@ use crate::config::MqttDeviceConfig;
|
||||||
use crate::device_manager::DeviceConfig;
|
use crate::device_manager::DeviceConfig;
|
||||||
use crate::error::DeviceConfigError;
|
use crate::error::DeviceConfigError;
|
||||||
use crate::event::OnMqtt;
|
use crate::event::OnMqtt;
|
||||||
use crate::helper::Ipv4SocketAddr;
|
|
||||||
use crate::messages::{RemoteAction, RemoteMessage};
|
use crate::messages::{RemoteAction, RemoteMessage};
|
||||||
use crate::traits::Timeout;
|
use crate::traits::Timeout;
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig)]
|
||||||
pub struct HueGroupConfig {
|
pub struct HueGroupConfig {
|
||||||
#[device_config(rename = "ip", with = "Ipv4SocketAddr<80>")]
|
#[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 80)))]
|
||||||
pub addr: SocketAddr,
|
pub addr: SocketAddr,
|
||||||
pub login: String,
|
pub login: String,
|
||||||
pub group_id: isize,
|
pub group_id: isize,
|
||||||
|
|
|
@ -17,7 +17,6 @@ use crate::device_manager::DeviceConfig;
|
||||||
use crate::devices::Device;
|
use crate::devices::Device;
|
||||||
use crate::error::DeviceConfigError;
|
use crate::error::DeviceConfigError;
|
||||||
use crate::event::{OnMqtt, OnPresence};
|
use crate::event::{OnMqtt, OnPresence};
|
||||||
use crate::helper::DurationSeconds;
|
|
||||||
use crate::messages::{OnOffMessage, RemoteAction, RemoteMessage};
|
use crate::messages::{OnOffMessage, RemoteAction, RemoteMessage};
|
||||||
use crate::mqtt::WrappedAsyncClient;
|
use crate::mqtt::WrappedAsyncClient;
|
||||||
use crate::traits::Timeout;
|
use crate::traits::Timeout;
|
||||||
|
@ -36,21 +35,17 @@ pub struct IkeaOutletConfig {
|
||||||
info: InfoConfig,
|
info: InfoConfig,
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
mqtt: MqttDeviceConfig,
|
mqtt: MqttDeviceConfig,
|
||||||
#[device_config(default = default_outlet_type)]
|
#[device_config(default(OutletType::Outlet))]
|
||||||
outlet_type: OutletType,
|
outlet_type: OutletType,
|
||||||
#[device_config(with = "Option<DurationSeconds>")]
|
#[device_config(default, with(|t: Option<_>| t.map(Duration::from_secs)))]
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
#[device_config(default)]
|
#[device_config(default)]
|
||||||
pub remotes: Vec<MqttDeviceConfig>,
|
pub remotes: Vec<MqttDeviceConfig>,
|
||||||
|
|
||||||
#[device_config(user_data)]
|
#[device_config(from_lua)]
|
||||||
client: WrappedAsyncClient,
|
client: WrappedAsyncClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_outlet_type() -> OutletType {
|
|
||||||
OutletType::Outlet
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl DeviceConfig for IkeaOutletConfig {
|
impl DeviceConfig for IkeaOutletConfig {
|
||||||
async fn create(&self, identifier: &str) -> Result<Box<dyn Device>, DeviceConfigError> {
|
async fn create(&self, identifier: &str) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||||
|
|
|
@ -15,11 +15,10 @@ use tracing::trace;
|
||||||
use super::Device;
|
use super::Device;
|
||||||
use crate::device_manager::DeviceConfig;
|
use crate::device_manager::DeviceConfig;
|
||||||
use crate::error::DeviceConfigError;
|
use crate::error::DeviceConfigError;
|
||||||
use crate::helper::Ipv4SocketAddr;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig)]
|
||||||
pub struct KasaOutletConfig {
|
pub struct KasaOutletConfig {
|
||||||
#[device_config(rename = "ip", with = "Ipv4SocketAddr<9999>")]
|
#[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 9999)))]
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,8 @@ pub struct LightSensorConfig {
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
pub min: isize,
|
pub min: isize,
|
||||||
pub max: isize,
|
pub max: isize,
|
||||||
#[device_config(user_data)]
|
#[device_config(rename("event_channel"), from_lua, with(|ec: EventChannel| ec.get_tx()))]
|
||||||
pub event_channel: EventChannel,
|
pub tx: event::Sender,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const DEFAULT: bool = false;
|
pub const DEFAULT: bool = false;
|
||||||
|
@ -30,7 +30,6 @@ impl DeviceConfig for LightSensorConfig {
|
||||||
let device = LightSensor {
|
let device = LightSensor {
|
||||||
identifier: identifier.into(),
|
identifier: identifier.into(),
|
||||||
// Add helper type that does this conversion for us
|
// Add helper type that does this conversion for us
|
||||||
tx: self.event_channel.get_tx(),
|
|
||||||
config: self.clone(),
|
config: self.clone(),
|
||||||
is_dark: DEFAULT,
|
is_dark: DEFAULT,
|
||||||
};
|
};
|
||||||
|
@ -45,7 +44,6 @@ pub struct LightSensor {
|
||||||
#[config]
|
#[config]
|
||||||
config: LightSensorConfig,
|
config: LightSensorConfig,
|
||||||
|
|
||||||
tx: event::Sender,
|
|
||||||
is_dark: bool,
|
is_dark: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +89,7 @@ impl OnMqtt for LightSensor {
|
||||||
debug!("Dark state has changed: {is_dark}");
|
debug!("Dark state has changed: {is_dark}");
|
||||||
self.is_dark = is_dark;
|
self.is_dark = is_dark;
|
||||||
|
|
||||||
if self.tx.send(Event::Darkness(is_dark)).await.is_err() {
|
if self.config.tx.send(Event::Darkness(is_dark)).await.is_err() {
|
||||||
warn!("There are no receivers on the event channel");
|
warn!("There are no receivers on the event channel");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,14 +24,10 @@ pub struct WakeOnLANConfig {
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
mqtt: MqttDeviceConfig,
|
mqtt: MqttDeviceConfig,
|
||||||
mac_address: MacAddress,
|
mac_address: MacAddress,
|
||||||
#[device_config(default = default_broadcast_ip)]
|
#[device_config(default(Ipv4Addr::new(255, 255, 255, 255)))]
|
||||||
broadcast_ip: Ipv4Addr,
|
broadcast_ip: Ipv4Addr,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_broadcast_ip() -> Ipv4Addr {
|
|
||||||
Ipv4Addr::new(255, 255, 255, 255)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl DeviceConfig for WakeOnLANConfig {
|
impl DeviceConfig for WakeOnLANConfig {
|
||||||
async fn create(&self, identifier: &str) -> Result<Box<dyn Device>, DeviceConfigError> {
|
async fn create(&self, identifier: &str) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||||
|
|
|
@ -8,7 +8,7 @@ use super::{Device, Notification};
|
||||||
use crate::config::MqttDeviceConfig;
|
use crate::config::MqttDeviceConfig;
|
||||||
use crate::device_manager::DeviceConfig;
|
use crate::device_manager::DeviceConfig;
|
||||||
use crate::error::DeviceConfigError;
|
use crate::error::DeviceConfigError;
|
||||||
use crate::event::{Event, EventChannel, OnMqtt};
|
use crate::event::{self, Event, EventChannel, OnMqtt};
|
||||||
use crate::messages::PowerMessage;
|
use crate::messages::PowerMessage;
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig)]
|
||||||
|
@ -17,8 +17,8 @@ pub struct WasherConfig {
|
||||||
mqtt: MqttDeviceConfig,
|
mqtt: MqttDeviceConfig,
|
||||||
// Power in Watt
|
// Power in Watt
|
||||||
threshold: f32,
|
threshold: f32,
|
||||||
#[device_config(user_data)]
|
#[device_config(rename("event_channel"), from_lua, with(|ec: EventChannel| ec.get_tx()))]
|
||||||
event_channel: EventChannel,
|
pub tx: event::Sender,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
@ -91,8 +91,7 @@ impl OnMqtt for Washer {
|
||||||
|
|
||||||
if self
|
if self
|
||||||
.config
|
.config
|
||||||
.event_channel
|
.tx
|
||||||
.get_tx()
|
|
||||||
.send(Event::Ntfy(notification))
|
.send(Event::Ntfy(notification))
|
||||||
.await
|
.await
|
||||||
.is_err()
|
.is_err()
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
use std::net::{Ipv4Addr, SocketAddr};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct DurationSeconds(u64);
|
|
||||||
|
|
||||||
impl From<DurationSeconds> for Duration {
|
|
||||||
fn from(value: DurationSeconds) -> Self {
|
|
||||||
Self::from_secs(value.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct Ipv4SocketAddr<const PORT: u16>(Ipv4Addr);
|
|
||||||
|
|
||||||
impl<const PORT: u16> From<Ipv4SocketAddr<PORT>> for SocketAddr {
|
|
||||||
fn from(ip: Ipv4SocketAddr<PORT>) -> Self {
|
|
||||||
Self::from((ip.0, PORT))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,7 +7,6 @@ pub mod device_manager;
|
||||||
pub mod devices;
|
pub mod devices;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
pub mod helper;
|
|
||||||
pub mod messages;
|
pub mod messages;
|
||||||
pub mod mqtt;
|
pub mod mqtt;
|
||||||
pub mod schedule;
|
pub mod schedule;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user