From ffe06fde6e9530ae863bb904bd4aaf1732509b88 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Fri, 26 Apr 2024 04:53:45 +0200 Subject: [PATCH] Improved the internals of the LuaDeviceConfig macro and improve the usability of the macro --- Cargo.lock | 1 + automation_macro/Cargo.toml | 3 +- automation_macro/src/lib.rs | 398 +++++++++++++++++----------------- src/devices/air_filter.rs | 2 +- src/devices/audio_setup.rs | 4 +- src/devices/contact_sensor.rs | 13 +- src/devices/debug_bridge.rs | 2 +- src/devices/hue_bridge.rs | 3 +- src/devices/hue_light.rs | 3 +- src/devices/ikea_outlet.rs | 11 +- src/devices/kasa_outlet.rs | 3 +- src/devices/light_sensor.rs | 5 +- src/devices/wake_on_lan.rs | 6 +- src/devices/washer.rs | 5 +- src/helper.rs | 40 ---- src/lib.rs | 1 - 16 files changed, 226 insertions(+), 274 deletions(-) delete mode 100644 src/helper.rs diff --git a/Cargo.lock b/Cargo.lock index 34d99fd..2037bd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,6 +110,7 @@ dependencies = [ name = "automation_macro" version = "0.1.0" dependencies = [ + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.60", diff --git a/automation_macro/Cargo.toml b/automation_macro/Cargo.toml index 50f0fd7..656ba92 100644 --- a/automation_macro/Cargo.toml +++ b/automation_macro/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" proc-macro = true [dependencies] +itertools = "0.12.1" proc-macro2 = "1.0.81" quote = "1.0.36" -syn = { version = "2.0.60", features = ["extra-traits"] } +syn = { version = "2.0.60", features = ["extra-traits", "full"] } diff --git a/automation_macro/src/lib.rs b/automation_macro/src/lib.rs index 42c05e6..2caa40b 100644 --- a/automation_macro/src/lib.rs +++ b/automation_macro/src/lib.rs @@ -1,7 +1,11 @@ +use itertools::Itertools; use proc_macro2::TokenStream; -use quote::quote; +use quote::{quote, quote_spanned}; +use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; -use syn::{parse_macro_input, DeriveInput, Token}; +use syn::spanned::Spanned; +use syn::token::Paren; +use syn::{parenthesized, parse_macro_input, DeriveInput, Expr, LitStr, Result, Token}; #[proc_macro_derive(LuaDevice, attributes(config))] pub fn lua_device_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -53,129 +57,110 @@ fn impl_lua_device_macro(ast: &syn::DeriveInput) -> TokenStream { gen } -#[derive(Debug)] -enum Arg { - Flatten, - UserData, - Rename(String), - With(TokenStream), - Default(Option), -} - -impl syn::parse::Parse for Arg { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let arg = match input.parse::()?.to_string().as_str() { - "flatten" => Arg::Flatten, - "user_data" => Arg::UserData, - "rename" => { - input.parse::()?; - let lit = input.parse::()?; - if let syn::Lit::Str(lit_str) = lit { - Arg::Rename(lit_str.value()) - } else { - panic!("Expected literal string"); - } - } - "with" => { - input.parse::()?; - let lit = input.parse::()?; - 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::().is_ok() { - let func = input.parse::()?; - Arg::Default(Some(func)) - } else { - Arg::Default(None) - } - } - name => todo!("Handle unknown arg: {name}"), - }; - - Ok(arg) - } +mod kw { + syn::custom_keyword!(device_config); + syn::custom_keyword!(flatten); + syn::custom_keyword!(from_lua); + syn::custom_keyword!(rename); + syn::custom_keyword!(with); + syn::custom_keyword!(from); + syn::custom_keyword!(default); } #[derive(Debug)] -struct ArgsParser { - args: Punctuated, +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: syn::Type, + }, + Default { + _keyword: kw::default, + }, + DefaultExpr { + _keyword: kw::default, + _paren: Paren, + expr: Expr, + }, } -impl syn::parse::Parse for ArgsParser { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let args = input.parse_terminated(Arg::parse, Token![,])?; - - Ok(Self { args }) +impl Parse for Argument { + fn parse(input: ParseStream) -> Result { + 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 { - flatten: bool, - user_data: bool, - rename: Option, - with: Option, - default: Option>, + args: Punctuated, } -impl Args { - fn new(args: Vec) -> 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 +impl Parse for Args { + fn parse(input: ParseStream) -> Result { + Ok(Self { + args: input.parse_terminated(Argument::parse, Token![,])?, + }) } } @@ -186,12 +171,8 @@ pub fn lua_device_config_derive(input: proc_macro::TokenStream) -> proc_macro::T 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, .. }), .. @@ -199,72 +180,91 @@ fn impl_lua_device_config_macro(ast: &syn::DeriveInput) -> TokenStream { { named } else { - unimplemented!("Macro can only handle named structs"); + return quote_spanned! {ast.span() => compile_error!("This macro only works on named structs")}; }; - let fields: Vec<_> = fields + let field_names: 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(); + .map(|field| field.ident.clone().unwrap()) + .collect(); - let args = Args::new(args); + let fields: Vec<_> = fields + .iter() + .map(|field| { + let field_name = field.ident.clone().unwrap(); + let (args, errors): (Vec<_>, Vec<_>) = field + .attrs + .iter() + .filter_map(|attr| { + if attr.path().is_ident("device_config") { + Some(attr.parse_args::().map(|args| args.args)) + } else { + None + } + }) + .partition_result(); - let table_name = if let Some(name) = args.rename { - name - } else { - field_name.to_string() - }; + let errors: Vec<_> = errors + .iter() + .map(|error| error.to_compile_error()) + .collect(); - // 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 + 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::>() + .as_slice() + { + [] => field_name.to_string(), + [rename] => rename.to_owned(), + _ => return quote_spanned! {field.span() => compile_error!("Field contains duplicate 'rename'")}, }; - 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) } - }; + // 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::>() + .as_slice() + { + [] => quote! {panic!(#missing)}, + [default] => default.to_owned(), + _ => return quote_spanned! {field.span() => compile_error!("Field contains duplicate 'default'")}, + }; - 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 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::>() + .as_slice() { + [] => quote! { { let #field_name: mlua::Value = table.get(#table_name)?; if !#field_name.is_nil() { @@ -273,34 +273,40 @@ fn impl_lua_device_config_macro(ast: &syn::DeriveInput) -> TokenStream { #default } } - } + }, + [value] => value.to_owned(), + _ => return quote_spanned! {field.span() => compile_error!("Only one of either 'flatten' or 'from_lua' is allowed")}, }; - let value = if let Some(temp_type) = args.with { - if optional { - quote! { + let value = match args + .iter() + .filter_map(|arg| match arg { + Argument::From { ty, .. } => Some(quote! { { - let temp: #temp_type = #value; - temp.map(|v| v.into()) - } - } - } else { - quote! { - { - let temp: #temp_type = #value; + let temp: #ty = #value; temp.into() } - } - } - } else { - value + }), + Argument::With { expr, .. } => Some(quote! { + { + let temp = #value; + (#expr)(temp) + } + }), + _ => None, + }) + .collect::>() + .as_slice() { + [] => value, + [value] => value.to_owned(), + _ => return quote_spanned! {field.span() => compile_error!("Field contains duplicate 'as'")}, }; - quote! { - #field_name: #value - } - }) - .collect(); + quote! { #value } + }) + .zip(field_names) + .map(|(value, name)| quote! { #name: #value }) + .collect(); let gen = quote! { impl<'lua> mlua::FromLua<'lua> for #name { @@ -312,7 +318,7 @@ fn impl_lua_device_config_macro(ast: &syn::DeriveInput) -> TokenStream { Ok(#name { #(#fields,)* - }) + }) } } diff --git a/src/devices/air_filter.rs b/src/devices/air_filter.rs index b2b7576..168c287 100644 --- a/src/devices/air_filter.rs +++ b/src/devices/air_filter.rs @@ -22,7 +22,7 @@ pub struct AirFilterConfig { info: InfoConfig, #[device_config(flatten)] mqtt: MqttDeviceConfig, - #[device_config(user_data)] + #[device_config(from_lua)] client: WrappedAsyncClient, } diff --git a/src/devices/audio_setup.rs b/src/devices/audio_setup.rs index d4427f8..89105f1 100644 --- a/src/devices/audio_setup.rs +++ b/src/devices/audio_setup.rs @@ -15,9 +15,9 @@ use crate::messages::{RemoteAction, RemoteMessage}; pub struct AudioSetupConfig { #[device_config(flatten)] mqtt: MqttDeviceConfig, - #[device_config(user_data)] + #[device_config(from_lua)] mixer: WrappedDevice, - #[device_config(user_data)] + #[device_config(from_lua)] speakers: WrappedDevice, } diff --git a/src/devices/contact_sensor.rs b/src/devices/contact_sensor.rs index 09c19fe..fdfe667 100644 --- a/src/devices/contact_sensor.rs +++ b/src/devices/contact_sensor.rs @@ -13,7 +13,6 @@ use crate::device_manager::{DeviceConfig, WrappedDevice}; use crate::devices::{As, DEFAULT_PRESENCE}; use crate::error::DeviceConfigError; use crate::event::{OnMqtt, OnPresence}; -use crate::helper::DurationSeconds; use crate::messages::{ContactMessage, PresenceMessage}; use crate::mqtt::WrappedAsyncClient; use crate::traits::Timeout; @@ -23,7 +22,7 @@ use crate::traits::Timeout; pub struct PresenceDeviceConfig { #[device_config(flatten)] pub mqtt: MqttDeviceConfig, - #[device_config(with = "DurationSeconds")] + #[device_config(with(Duration::from_secs))] pub timeout: Duration, } @@ -44,9 +43,9 @@ impl From for Vec<(WrappedDevice, bool)> { #[derive(Debug, Clone, LuaDeviceConfig)] pub struct TriggerConfig { - #[device_config(user_data, with = "TriggerDevicesHelper")] + #[device_config(from_lua, from(TriggerDevicesHelper))] devices: Vec<(WrappedDevice, bool)>, - #[device_config(with = "Option")] + #[device_config(default, with(|t: Option<_>| t.map(Duration::from_secs)))] pub timeout: Option, } @@ -54,11 +53,11 @@ pub struct TriggerConfig { pub struct ContactSensorConfig { #[device_config(flatten)] mqtt: MqttDeviceConfig, - #[device_config(user_data)] + #[device_config(from_lua)] presence: Option, - #[device_config(user_data)] + #[device_config(from_lua)] trigger: Option, - #[device_config(user_data)] + #[device_config(from_lua)] client: WrappedAsyncClient, } diff --git a/src/devices/debug_bridge.rs b/src/devices/debug_bridge.rs index a13d101..2b22da6 100644 --- a/src/devices/debug_bridge.rs +++ b/src/devices/debug_bridge.rs @@ -14,7 +14,7 @@ use crate::mqtt::WrappedAsyncClient; pub struct DebugBridgeConfig { #[device_config(flatten)] pub mqtt: MqttDeviceConfig, - #[device_config(user_data)] + #[device_config(from_lua)] client: WrappedAsyncClient, } diff --git a/src/devices/hue_bridge.rs b/src/devices/hue_bridge.rs index f5fb870..c2b2a0f 100644 --- a/src/devices/hue_bridge.rs +++ b/src/devices/hue_bridge.rs @@ -9,7 +9,6 @@ use crate::device_manager::DeviceConfig; use crate::devices::Device; use crate::error::DeviceConfigError; use crate::event::{OnDarkness, OnPresence}; -use crate::helper::Ipv4SocketAddr; #[derive(Debug)] pub enum Flag { @@ -25,7 +24,7 @@ pub struct FlagIDs { #[derive(Debug, LuaDeviceConfig, Clone)] pub struct HueBridgeConfig { - #[device_config(rename = "ip", with = "Ipv4SocketAddr<80>")] + #[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 80)))] pub addr: SocketAddr, pub login: String, pub flags: FlagIDs, diff --git a/src/devices/hue_light.rs b/src/devices/hue_light.rs index 3856135..8bd60f5 100644 --- a/src/devices/hue_light.rs +++ b/src/devices/hue_light.rs @@ -14,13 +14,12 @@ use crate::config::MqttDeviceConfig; use crate::device_manager::DeviceConfig; use crate::error::DeviceConfigError; use crate::event::OnMqtt; -use crate::helper::Ipv4SocketAddr; use crate::messages::{RemoteAction, RemoteMessage}; use crate::traits::Timeout; #[derive(Debug, Clone, LuaDeviceConfig)] pub struct HueGroupConfig { - #[device_config(rename = "ip", with = "Ipv4SocketAddr<80>")] + #[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 80)))] pub addr: SocketAddr, pub login: String, pub group_id: isize, diff --git a/src/devices/ikea_outlet.rs b/src/devices/ikea_outlet.rs index 7c5cd7a..23f9511 100644 --- a/src/devices/ikea_outlet.rs +++ b/src/devices/ikea_outlet.rs @@ -17,7 +17,6 @@ use crate::device_manager::DeviceConfig; use crate::devices::Device; use crate::error::DeviceConfigError; use crate::event::{OnMqtt, OnPresence}; -use crate::helper::DurationSeconds; use crate::messages::{OnOffMessage, RemoteAction, RemoteMessage}; use crate::mqtt::WrappedAsyncClient; use crate::traits::Timeout; @@ -36,21 +35,17 @@ pub struct IkeaOutletConfig { info: InfoConfig, #[device_config(flatten)] mqtt: MqttDeviceConfig, - #[device_config(default = default_outlet_type)] + #[device_config(default(OutletType::Outlet))] outlet_type: OutletType, - #[device_config(with = "Option")] + #[device_config(default, with(|t: Option<_>| t.map(Duration::from_secs)))] timeout: Option, #[device_config(default)] pub remotes: Vec, - #[device_config(user_data)] + #[device_config(from_lua)] client: WrappedAsyncClient, } -fn default_outlet_type() -> OutletType { - OutletType::Outlet -} - #[async_trait] impl DeviceConfig for IkeaOutletConfig { async fn create(&self, identifier: &str) -> Result, DeviceConfigError> { diff --git a/src/devices/kasa_outlet.rs b/src/devices/kasa_outlet.rs index 4f3ec3f..796ab97 100644 --- a/src/devices/kasa_outlet.rs +++ b/src/devices/kasa_outlet.rs @@ -15,11 +15,10 @@ use tracing::trace; use super::Device; use crate::device_manager::DeviceConfig; use crate::error::DeviceConfigError; -use crate::helper::Ipv4SocketAddr; #[derive(Debug, Clone, LuaDeviceConfig)] pub struct KasaOutletConfig { - #[device_config(rename = "ip", with = "Ipv4SocketAddr<9999>")] + #[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 9999)))] addr: SocketAddr, } diff --git a/src/devices/light_sensor.rs b/src/devices/light_sensor.rs index 23ce9a1..a0e51ba 100644 --- a/src/devices/light_sensor.rs +++ b/src/devices/light_sensor.rs @@ -7,8 +7,7 @@ use crate::config::MqttDeviceConfig; use crate::device_manager::DeviceConfig; use crate::devices::Device; use crate::error::DeviceConfigError; -use crate::event::{self, Event, OnMqtt}; -use crate::helper::TxHelper; +use crate::event::{self, Event, EventChannel, OnMqtt}; use crate::messages::BrightnessMessage; #[derive(Debug, Clone, LuaDeviceConfig)] @@ -17,7 +16,7 @@ pub struct LightSensorConfig { pub mqtt: MqttDeviceConfig, pub min: isize, pub max: isize, - #[device_config(rename = "event_channel", user_data, with = "TxHelper")] + #[device_config(rename("event_channel"), from_lua, with(|ec: EventChannel| ec.get_tx()))] pub tx: event::Sender, } diff --git a/src/devices/wake_on_lan.rs b/src/devices/wake_on_lan.rs index 0162957..bddd7f9 100644 --- a/src/devices/wake_on_lan.rs +++ b/src/devices/wake_on_lan.rs @@ -24,14 +24,10 @@ pub struct WakeOnLANConfig { #[device_config(flatten)] mqtt: MqttDeviceConfig, mac_address: MacAddress, - #[device_config(default = default_broadcast_ip)] + #[device_config(default(Ipv4Addr::new(255, 255, 255, 255)))] broadcast_ip: Ipv4Addr, } -fn default_broadcast_ip() -> Ipv4Addr { - Ipv4Addr::new(255, 255, 255, 255) -} - #[async_trait] impl DeviceConfig for WakeOnLANConfig { async fn create(&self, identifier: &str) -> Result, DeviceConfigError> { diff --git a/src/devices/washer.rs b/src/devices/washer.rs index 67fae95..6abe4cb 100644 --- a/src/devices/washer.rs +++ b/src/devices/washer.rs @@ -8,8 +8,7 @@ use super::{Device, Notification}; use crate::config::MqttDeviceConfig; use crate::device_manager::DeviceConfig; use crate::error::DeviceConfigError; -use crate::event::{self, Event, OnMqtt}; -use crate::helper::TxHelper; +use crate::event::{self, Event, EventChannel, OnMqtt}; use crate::messages::PowerMessage; #[derive(Debug, Clone, LuaDeviceConfig)] @@ -18,7 +17,7 @@ pub struct WasherConfig { mqtt: MqttDeviceConfig, // Power in Watt threshold: f32, - #[device_config(rename = "event_channel", user_data, with = "TxHelper")] + #[device_config(rename("event_channel"), from_lua, with(|ec: EventChannel| ec.get_tx()))] pub tx: event::Sender, } diff --git a/src/helper.rs b/src/helper.rs deleted file mode 100644 index 0c8537c..0000000 --- a/src/helper.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::net::{Ipv4Addr, SocketAddr}; -use std::time::Duration; - -use mlua::FromLua; -use serde::Deserialize; - -use crate::event::{self, EventChannel}; - -#[derive(Debug, Deserialize)] -pub struct DurationSeconds(u64); - -impl From for Duration { - fn from(value: DurationSeconds) -> Self { - Self::from_secs(value.0) - } -} - -#[derive(Debug, Deserialize)] -pub struct Ipv4SocketAddr(Ipv4Addr); - -impl From> for SocketAddr { - fn from(ip: Ipv4SocketAddr) -> Self { - Self::from((ip.0, PORT)) - } -} - -#[derive(Debug, Clone)] -pub struct TxHelper(EventChannel); - -impl<'lua> FromLua<'lua> for TxHelper { - fn from_lua(value: mlua::Value<'lua>, lua: &'lua mlua::Lua) -> mlua::Result { - Ok(TxHelper(mlua::FromLua::from_lua(value, lua)?)) - } -} - -impl From for event::Sender { - fn from(value: TxHelper) -> Self { - value.0.get_tx() - } -} diff --git a/src/lib.rs b/src/lib.rs index 8d8b7e2..931fcb5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,6 @@ pub mod device_manager; pub mod devices; pub mod error; pub mod event; -pub mod helper; pub mod messages; pub mod mqtt; pub mod schedule;