From 3689a52afd88801e2b4a7dadcec1c16837ee6fc0 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Sun, 5 May 2024 00:33:21 +0200 Subject: [PATCH] Replaced impl_cast with a new and improved trait With this trait the impl_cast macros are no longer needed, simplifying everything. This commit also improved how the actual casting itself is handled. --- Cargo.lock | 16 ++-- Cargo.toml | 12 +-- automation_cast/Cargo.toml | 8 ++ automation_cast/src/lib.rs | 37 +++++++ google-home/Cargo.toml | 2 +- google-home/src/device.rs | 60 +++--------- google-home/src/fullfillment.rs | 11 ++- google-home/src/traits.rs | 12 +-- impl_cast/Cargo.toml | 15 --- impl_cast/src/lib.rs | 165 -------------------------------- src/device_manager.rs | 65 +++++++------ src/devices/audio_setup.rs | 27 +++--- src/devices/contact_sensor.rs | 39 ++++---- src/devices/mod.rs | 20 +++- src/event.rs | 13 +-- src/traits.rs | 4 +- 16 files changed, 174 insertions(+), 332 deletions(-) create mode 100644 automation_cast/Cargo.toml create mode 100644 automation_cast/src/lib.rs delete mode 100644 impl_cast/Cargo.toml delete mode 100644 impl_cast/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 4c319e9..daa4110 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,6 +76,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "automation_cast", "axum", "bytes", "console-subscriber", @@ -84,7 +85,6 @@ dependencies = [ "eui48", "futures", "google-home", - "impl_cast", "indexmap 2.0.0", "paste", "pollster", @@ -104,6 +104,10 @@ dependencies = [ "wakey", ] +[[package]] +name = "automation_cast" +version = "0.1.0" + [[package]] name = "axum" version = "0.6.20" @@ -568,8 +572,8 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "automation_cast", "futures", - "impl_cast", "serde", "serde_json", "thiserror", @@ -761,14 +765,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "impl_cast" -version = "0.1.0" -dependencies = [ - "quote", - "syn 2.0.28", -] - [[package]] name = "indexmap" version = "1.9.3" diff --git a/Cargo.toml b/Cargo.toml index 9a78143..50790d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,20 +4,20 @@ version = "0.1.0" edition = "2021" [workspace] -members = ["impl_cast", "google-home"] +members = ["google-home", "automation_cast"] [dependencies] +automation_cast = { path = "./automation_cast/" } rumqttc = "0.18" serde = { version = "1.0.149", features = ["derive"] } serde_json = "1.0.89" -impl_cast = { path = "./impl_cast", features = ["debug"] } google-home = { path = "./google-home" } paste = "1.0.10" tokio = { version = "1", features = ["rt-multi-thread"] } dotenvy = "0.15.0" reqwest = { version = "0.11.13", features = [ - "json", - "rustls-tls", + "json", + "rustls-tls", ], default-features = false } # Use rustls, since the other packages also use rustls axum = "0.6.1" serde_repr = "0.1.10" @@ -28,8 +28,8 @@ regex = "1.7.0" async-trait = "0.1.61" futures = "0.3.25" eui48 = { version = "1.1.0", default-features = false, features = [ - "disp_hexstring", - "serde", + "disp_hexstring", + "serde", ] } thiserror = "1.0.38" anyhow = "1.0.68" diff --git a/automation_cast/Cargo.toml b/automation_cast/Cargo.toml new file mode 100644 index 0000000..fb7e7a3 --- /dev/null +++ b/automation_cast/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "automation_cast" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/automation_cast/src/lib.rs b/automation_cast/src/lib.rs new file mode 100644 index 0000000..0ffdc6a --- /dev/null +++ b/automation_cast/src/lib.rs @@ -0,0 +1,37 @@ +#![allow(incomplete_features)] +#![feature(specialization)] +#![feature(unsize)] + +use std::marker::Unsize; + +pub trait Cast { + fn cast(&self) -> Option<&P>; + fn cast_mut(&mut self) -> Option<&mut P>; +} + +impl Cast

for D +where + P: ?Sized, +{ + default fn cast(&self) -> Option<&P> { + None + } + + default fn cast_mut(&mut self) -> Option<&mut P> { + None + } +} + +impl Cast

for D +where + D: Unsize

, + P: ?Sized, +{ + fn cast(&self) -> Option<&P> { + Some(self) + } + + fn cast_mut(&mut self) -> Option<&mut P> { + Some(self) + } +} diff --git a/google-home/Cargo.toml b/google-home/Cargo.toml index a858d19..f2385c6 100644 --- a/google-home/Cargo.toml +++ b/google-home/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -impl_cast = { path = "../impl_cast" } +automation_cast = { path = "../automation_cast/" } serde = { version = "1.0.149", features = ["derive"] } serde_json = "1.0.89" thiserror = "1.0.37" diff --git a/google-home/src/device.rs b/google-home/src/device.rs index 0cbc6bc..597e0bb 100644 --- a/google-home/src/device.rs +++ b/google-home/src/device.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use automation_cast::Cast; use serde::Serialize; use crate::errors::{DeviceError, ErrorCode}; @@ -7,43 +8,10 @@ use crate::response; use crate::traits::{FanSpeed, HumiditySetting, OnOff, Scene, Trait}; use crate::types::Type; -// TODO: Find a more elegant way to do this -pub trait AsGoogleHomeDevice { - fn cast(&self) -> Option<&dyn GoogleHomeDevice>; - fn cast_mut(&mut self) -> Option<&mut dyn GoogleHomeDevice>; -} - -// Default impl -impl AsGoogleHomeDevice for T -where - T: 'static, -{ - default fn cast(&self) -> Option<&(dyn GoogleHomeDevice + 'static)> { - None - } - - default fn cast_mut(&mut self) -> Option<&mut (dyn GoogleHomeDevice + 'static)> { - None - } -} - -// Specialization -impl AsGoogleHomeDevice for T -where - T: GoogleHomeDevice + 'static, -{ - fn cast(&self) -> Option<&(dyn GoogleHomeDevice + 'static)> { - Some(self) - } - - fn cast_mut(&mut self) -> Option<&mut (dyn GoogleHomeDevice + 'static)> { - Some(self) - } -} - #[async_trait] -#[impl_cast::device(As: OnOff + Scene + FanSpeed + HumiditySetting)] -pub trait GoogleHomeDevice: AsGoogleHomeDevice + Sync + Send + 'static { +pub trait GoogleHomeDevice: + Sync + Send + Cast + Cast + Cast + Cast +{ fn get_device_type(&self) -> Type; fn get_device_name(&self) -> Name; fn get_id(&self) -> &str; @@ -76,26 +44,26 @@ pub trait GoogleHomeDevice: AsGoogleHomeDevice + Sync + Send + 'static { let mut traits = Vec::new(); // OnOff - if let Some(on_off) = As::::cast(self) { + if let Some(on_off) = self.cast() as Option<&dyn OnOff> { traits.push(Trait::OnOff); device.attributes.command_only_on_off = on_off.is_command_only(); device.attributes.query_only_on_off = on_off.is_query_only(); } // Scene - if let Some(scene) = As::::cast(self) { + if let Some(scene) = self.cast() as Option<&dyn Scene> { traits.push(Trait::Scene); device.attributes.scene_reversible = scene.is_scene_reversible(); } // FanSpeed - if let Some(fan_speed) = As::::cast(self) { + if let Some(fan_speed) = self.cast() as Option<&dyn FanSpeed> { traits.push(Trait::FanSpeed); device.attributes.command_only_fan_speed = fan_speed.command_only_fan_speed(); device.attributes.available_fan_speeds = Some(fan_speed.available_speeds()); } - if let Some(humidity_setting) = As::::cast(self) { + if let Some(humidity_setting) = self.cast() as Option<&dyn HumiditySetting> { traits.push(Trait::HumiditySetting); device.attributes.query_only_humidity_setting = humidity_setting.query_only_humidity_setting(); @@ -113,7 +81,7 @@ pub trait GoogleHomeDevice: AsGoogleHomeDevice + Sync + Send + 'static { } // OnOff - if let Some(on_off) = As::::cast(self) { + if let Some(on_off) = self.cast() as Option<&dyn OnOff> { device.state.on = on_off .is_on() .await @@ -122,11 +90,11 @@ pub trait GoogleHomeDevice: AsGoogleHomeDevice + Sync + Send + 'static { } // FanSpeed - if let Some(fan_speed) = As::::cast(self) { + if let Some(fan_speed) = self.cast() as Option<&dyn FanSpeed> { device.state.current_fan_speed_setting = Some(fan_speed.current_speed().await); } - if let Some(humidity_setting) = As::::cast(self) { + if let Some(humidity_setting) = self.cast() as Option<&dyn HumiditySetting> { device.state.humidity_ambient_percent = Some(humidity_setting.humidity_ambient_percent().await); } @@ -137,21 +105,21 @@ pub trait GoogleHomeDevice: AsGoogleHomeDevice + Sync + Send + 'static { async fn execute(&mut self, command: &CommandType) -> Result<(), ErrorCode> { match command { CommandType::OnOff { on } => { - if let Some(t) = As::::cast_mut(self) { + if let Some(t) = self.cast_mut() as Option<&mut dyn OnOff> { t.set_on(*on).await?; } else { return Err(DeviceError::ActionNotAvailable.into()); } } CommandType::ActivateScene { deactivate } => { - if let Some(t) = As::::cast(self) { + if let Some(t) = self.cast_mut() as Option<&mut dyn Scene> { t.set_active(!deactivate).await?; } else { return Err(DeviceError::ActionNotAvailable.into()); } } CommandType::SetFanSpeed { fan_speed } => { - if let Some(t) = As::::cast(self) { + if let Some(t) = self.cast_mut() as Option<&mut dyn FanSpeed> { t.set_speed(fan_speed).await?; } } diff --git a/google-home/src/fullfillment.rs b/google-home/src/fullfillment.rs index 88c9699..521de9f 100644 --- a/google-home/src/fullfillment.rs +++ b/google-home/src/fullfillment.rs @@ -1,14 +1,15 @@ use std::collections::HashMap; use std::sync::Arc; +use automation_cast::Cast; use futures::future::{join_all, OptionFuture}; use thiserror::Error; use tokio::sync::{Mutex, RwLock}; -use crate::device::AsGoogleHomeDevice; use crate::errors::{DeviceError, ErrorCode}; use crate::request::{self, Intent, Request}; use crate::response::{self, execute, query, sync, Response, ResponsePayload, State}; +use crate::GoogleHomeDevice; #[derive(Debug)] pub struct GoogleHome { @@ -29,7 +30,7 @@ impl GoogleHome { } } - pub async fn handle_request( + pub async fn handle_request + ?Sized + 'static>( &self, request: Request, devices: &HashMap>>>, @@ -58,7 +59,7 @@ impl GoogleHome { .map(|payload| Response::new(&request.request_id, payload)) } - async fn sync( + async fn sync + ?Sized + 'static>( &self, devices: &HashMap>>>, ) -> sync::Payload { @@ -75,7 +76,7 @@ impl GoogleHome { resp_payload } - async fn query( + async fn query + ?Sized + 'static>( &self, payload: request::query::Payload, devices: &HashMap>>>, @@ -107,7 +108,7 @@ impl GoogleHome { resp_payload } - async fn execute( + async fn execute + ?Sized + 'static>( &self, payload: request::execute::Payload, devices: &HashMap>>>, diff --git a/google-home/src/traits.rs b/google-home/src/traits.rs index cae0340..a92c656 100644 --- a/google-home/src/traits.rs +++ b/google-home/src/traits.rs @@ -16,8 +16,7 @@ pub enum Trait { } #[async_trait] -#[impl_cast::device_trait] -pub trait OnOff { +pub trait OnOff: Sync + Send { fn is_command_only(&self) -> Option { None } @@ -32,8 +31,7 @@ pub trait OnOff { } #[async_trait] -#[impl_cast::device_trait] -pub trait Scene { +pub trait Scene: Sync + Send { fn is_scene_reversible(&self) -> Option { None } @@ -60,8 +58,7 @@ pub struct AvailableSpeeds { } #[async_trait] -#[impl_cast::device_trait] -pub trait FanSpeed { +pub trait FanSpeed: Sync + Send { fn reversible(&self) -> Option { None } @@ -76,8 +73,7 @@ pub trait FanSpeed { } #[async_trait] -#[impl_cast::device_trait] -pub trait HumiditySetting { +pub trait HumiditySetting: Sync + Send { // TODO: This implementation is not complete, I have only implemented what I need right now fn query_only_humidity_setting(&self) -> Option { None diff --git a/impl_cast/Cargo.toml b/impl_cast/Cargo.toml deleted file mode 100644 index 4fb7d65..0000000 --- a/impl_cast/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "impl_cast" -version = "0.1.0" -edition = "2021" - -[lib] -proc-macro = true - -[dependencies] -syn = { version = "2.0", features = ["extra-traits", "full"] } -quote = "1.0" - -[features] -debug = [ -] # If enabled it will add std::fmt::Debug as a trait bound to device_traits diff --git a/impl_cast/src/lib.rs b/impl_cast/src/lib.rs deleted file mode 100644 index 059465b..0000000 --- a/impl_cast/src/lib.rs +++ /dev/null @@ -1,165 +0,0 @@ -use proc_macro::TokenStream; -use quote::{format_ident, quote, ToTokens}; -use syn::parse::Parse; -use syn::{parse_macro_input, Ident, ItemTrait, Path, Token, TypeParamBound}; - -struct Attr { - name: Ident, - traits: Vec, -} - -impl Parse for Attr { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let mut traits = Vec::new(); - - let name = input.parse::()?; - input.parse::()?; - - loop { - let ty = input.parse()?; - traits.push(ty); - - if input.is_empty() { - break; - } - - input.parse::()?; - } - - Ok(Attr { name, traits }) - } -} - -/// This macro enables optional trait bounds on a trait with an appropriate cast trait to convert -/// to the optional traits -/// # Example -/// -/// ``` -/// #![feature(specialization)] -/// -/// // Create some traits -/// #[impl_cast::device_trait] -/// trait OnOff {} -/// #[impl_cast::device_trait] -/// trait Brightness {} -/// -/// // Create the main device trait -/// #[impl_cast::device(As: OnOff + Brightness)] -/// trait Device {} -/// -/// // Create an implementation -/// struct ExampleDevice {} -/// impl Device for ExampleDevice {} -/// impl OnOff for ExampleDevice {} -/// -/// // Creates a boxed instance of the example device -/// let example_device: Box = Box::new(ExampleDevice {}); -/// -/// // Cast to the OnOff trait, which is implemented -/// let as_on_off = As::::cast(example_device.as_ref()); -/// assert!(as_on_off.is_some()); -/// -/// // Cast to the Brightness trait, which is not implemented -/// let as_on_off = As::::cast(example_device.as_ref()); -/// assert!(as_on_off.is_none()); -/// -/// // Finally we are going to consume the example device into an instance of the OnOff trait -/// let consumed = As::::consume(example_device); -/// assert!(consumed.is_some()) -/// ``` -#[proc_macro_attribute] -pub fn device(attr: TokenStream, item: TokenStream) -> TokenStream { - let Attr { name, traits } = parse_macro_input!(attr); - let mut interface: ItemTrait = parse_macro_input!(item); - - let prefix = quote! { - pub trait #name { - fn is(&self) -> bool; - fn cast(&self) -> Option<&T>; - fn cast_mut(&mut self) -> Option<&mut T>; - } - }; - - traits.iter().for_each(|device_trait| { - interface.supertraits.push(TypeParamBound::Verbatim(quote! { - #name - })); - }); - - let interface_ident = format_ident!("{}", interface.ident); - let impls = traits - .iter() - .map(|device_trait| { - quote! { - // Default impl - impl #name for T - where - T: #interface_ident + 'static, - { - default fn is(&self) -> bool { - false - } - - default fn cast(&self) -> Option<&(dyn #device_trait + 'static)> { - None - } - - default fn cast_mut(&mut self) -> Option<&mut (dyn #device_trait + 'static)> { - None - } - } - - // Specialization, should not cause any unsoundness as we dispatch based on - // #device_trait - impl #name for T - where - T: #interface_ident + #device_trait + 'static, - { - fn is(&self) -> bool { - true - } - - fn cast(&self) -> Option<&(dyn #device_trait + 'static)> { - Some(self) - } - - fn cast_mut(&mut self) -> Option<&mut (dyn #device_trait + 'static)> { - Some(self) - } - } - } - }) - .fold(quote! {}, |acc, x| { - quote! { - // Not sure if this is the right way to do this - #acc - #x - } - }); - - let tokens = quote! { - #interface - #prefix - #impls - }; - - tokens.into() -} - -// TODO: Not sure if this makes sense to have? -/// This macro ensures that the device traits have the correct trait bounds -#[proc_macro_attribute] -pub fn device_trait(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut interface: ItemTrait = parse_macro_input!(item); - - interface.supertraits.push(TypeParamBound::Verbatim(quote! { - ::core::marker::Sync + ::core::marker::Send - })); - - #[cfg(feature = "debug")] - interface.supertraits.push(TypeParamBound::Verbatim(quote! { - ::std::fmt::Debug - })); - - interface.into_token_stream().into() -} diff --git a/src/device_manager.rs b/src/device_manager.rs index c760b7c..0a1833a 100644 --- a/src/device_manager.rs +++ b/src/device_manager.rs @@ -12,7 +12,7 @@ use tokio_cron_scheduler::{Job, JobScheduler}; use tracing::{debug, error, instrument, trace}; use crate::devices::{ - AirFilterConfig, As, AudioSetupConfig, ContactSensorConfig, DebugBridgeConfig, Device, + AirFilterConfig, AudioSetupConfig, ContactSensorConfig, DebugBridgeConfig, Device, HueBridgeConfig, HueGroupConfig, IkeaOutletConfig, KasaOutletConfig, LightSensorConfig, WakeOnLANConfig, WasherConfig, }; @@ -106,22 +106,22 @@ impl DeviceManager { let device = manager.get(&target).await.unwrap(); match action { Action::On => { - As::::cast_mut( - device.write().await.as_mut(), - ) - .unwrap() - .set_on(true) - .await - .unwrap(); + let mut device = device.write().await; + let device: Option<&mut dyn OnOff> = + device.as_mut().cast_mut(); + + if let Some(device) = device { + device.set_on(true).await.unwrap(); + } } Action::Off => { - As::::cast_mut( - device.write().await.as_mut(), - ) - .unwrap() - .set_on(false) - .await - .unwrap(); + let mut device = device.write().await; + let device: Option<&mut dyn OnOff> = + device.as_mut().cast_mut(); + + if let Some(device) = device { + device.set_on(false).await.unwrap(); + } } } } @@ -142,14 +142,17 @@ impl DeviceManager { debug!(id, "Adding device"); - // If the device listens to mqtt, subscribe to the topics - if let Some(device) = As::::cast(device.as_ref()) { - for topic in device.topics() { - trace!(id, topic, "Subscribing to topic"); - if let Err(err) = self.client.subscribe(topic, QoS::AtLeastOnce).await { - // NOTE: Pretty sure that this can only happen if the mqtt client if no longer - // running - error!(id, topic, "Failed to subscribe to topic: {err}"); + { + // If the device listens to mqtt, subscribe to the topics + let device: Option<&dyn OnMqtt> = device.as_ref().cast(); + if let Some(device) = device { + for topic in device.topics() { + trace!(id, topic, "Subscribing to topic"); + if let Err(err) = self.client.subscribe(topic, QoS::AtLeastOnce).await { + // NOTE: Pretty sure that this can only happen if the mqtt client if no longer + // running + error!(id, topic, "Failed to subscribe to topic: {err}"); + } } } } @@ -199,8 +202,8 @@ impl DeviceManager { let message = message.clone(); async move { let mut device = device.write().await; - let device = device.as_mut(); - if let Some(device) = As::::cast_mut(device) { + let device: Option<&mut dyn OnMqtt> = device.as_mut().cast_mut(); + if let Some(device) = device { let subscribed = device .topics() .iter() @@ -220,8 +223,8 @@ impl DeviceManager { let devices = self.devices.read().await; let iter = devices.iter().map(|(id, device)| async move { let mut device = device.write().await; - let device = device.as_mut(); - if let Some(device) = As::::cast_mut(device) { + let device: Option<&mut dyn OnDarkness> = device.as_mut().cast_mut(); + if let Some(device) = device { trace!(id, "Handling"); device.on_darkness(dark).await; } @@ -233,8 +236,8 @@ impl DeviceManager { let devices = self.devices.read().await; let iter = devices.iter().map(|(id, device)| async move { let mut device = device.write().await; - let device = device.as_mut(); - if let Some(device) = As::::cast_mut(device) { + let device: Option<&mut dyn OnPresence> = device.as_mut().cast_mut(); + if let Some(device) = device { trace!(id, "Handling"); device.on_presence(presence).await; } @@ -248,8 +251,8 @@ impl DeviceManager { let notification = notification.clone(); async move { let mut device = device.write().await; - let device = device.as_mut(); - if let Some(device) = As::::cast_mut(device) { + let device: Option<&mut dyn OnNotification> = device.as_mut().cast_mut(); + if let Some(device) = device { trace!(id, "Handling"); device.on_notification(notification).await; } diff --git a/src/devices/audio_setup.rs b/src/devices/audio_setup.rs index 65c8d2c..cc99adc 100644 --- a/src/devices/audio_setup.rs +++ b/src/devices/audio_setup.rs @@ -6,7 +6,6 @@ use tracing::{debug, error, trace, warn}; use super::Device; use crate::config::MqttDeviceConfig; use crate::device_manager::{ConfigExternal, DeviceConfig, WrappedDevice}; -use crate::devices::As; use crate::error::DeviceConfigError; use crate::event::{OnMqtt, OnPresence}; use crate::messages::{RemoteAction, RemoteMessage}; @@ -39,8 +38,11 @@ impl DeviceConfig for AudioSetupConfig { self.mixer.clone(), ))?; - if !As::::is(mixer.read().await.as_ref()) { - return Err(DeviceConfigError::MissingTrait(self.mixer, "OnOff".into())); + { + let mixer = mixer.read().await; + if (mixer.as_ref().cast() as Option<&dyn OnOff>).is_none() { + return Err(DeviceConfigError::MissingTrait(self.mixer, "OnOff".into())); + } } let speakers = @@ -52,11 +54,11 @@ impl DeviceConfig for AudioSetupConfig { self.speakers.clone(), ))?; - if !As::::is(speakers.read().await.as_ref()) { - return Err(DeviceConfigError::MissingTrait( - self.speakers, - "OnOff".into(), - )); + { + let speakers = speakers.read().await; + if (speakers.as_ref().cast() as Option<&dyn OnOff>).is_none() { + return Err(DeviceConfigError::MissingTrait(self.mixer, "OnOff".into())); + } } let device = AudioSetup { @@ -103,8 +105,8 @@ impl OnMqtt for AudioSetup { let mut mixer = self.mixer.write().await; let mut speakers = self.speakers.write().await; if let (Some(mixer), Some(speakers)) = ( - As::::cast_mut(mixer.as_mut()), - As::::cast_mut(speakers.as_mut()), + mixer.as_mut().cast_mut() as Option<&mut dyn OnOff>, + speakers.as_mut().cast_mut() as Option<&mut dyn OnOff>, ) { match action { RemoteAction::On => { @@ -137,10 +139,9 @@ impl OnPresence for AudioSetup { async fn on_presence(&mut self, presence: bool) { let mut mixer = self.mixer.write().await; let mut speakers = self.speakers.write().await; - if let (Some(mixer), Some(speakers)) = ( - As::::cast_mut(mixer.as_mut()), - As::::cast_mut(speakers.as_mut()), + mixer.as_mut().cast_mut() as Option<&mut dyn OnOff>, + speakers.as_mut().cast_mut() as Option<&mut dyn OnOff>, ) { // Turn off the audio setup when we leave the house if !presence { diff --git a/src/devices/contact_sensor.rs b/src/devices/contact_sensor.rs index 45ff35b..cc10f19 100644 --- a/src/devices/contact_sensor.rs +++ b/src/devices/contact_sensor.rs @@ -11,7 +11,7 @@ use tracing::{debug, error, trace, warn}; use super::Device; use crate::config::MqttDeviceConfig; use crate::device_manager::{ConfigExternal, DeviceConfig, WrappedDevice}; -use crate::devices::{As, DEFAULT_PRESENCE}; +use crate::devices::DEFAULT_PRESENCE; use crate::error::DeviceConfigError; use crate::event::{OnMqtt, OnPresence}; use crate::messages::{ContactMessage, PresenceMessage}; @@ -60,20 +60,23 @@ impl DeviceConfig for ContactSensorConfig { DeviceConfigError::MissingChild(device_name.into(), "OnOff".into()), )?; - if !As::::is(device.read().await.as_ref()) { - return Err(DeviceConfigError::MissingTrait( - device_name.into(), - "OnOff".into(), - )); - } - - if !trigger_config.timeout.is_zero() - && !As::::is(device.read().await.as_ref()) { - return Err(DeviceConfigError::MissingTrait( - device_name.into(), - "Timeout".into(), - )); + let device = device.read().await; + if (device.as_ref().cast() as Option<&dyn OnOff>).is_none() { + return Err(DeviceConfigError::MissingTrait( + device_name.into(), + "OnOff".into(), + )); + } + + if trigger_config.timeout.is_zero() + && (device.as_ref().cast() as Option<&dyn Timeout>).is_none() + { + return Err(DeviceConfigError::MissingTrait( + device_name.into(), + "Timeout".into(), + )); + } } devices.push((device, false)); @@ -161,7 +164,7 @@ impl OnMqtt for ContactSensor { if !self.is_closed { for (light, previous) in &mut trigger.devices { let mut light = light.write().await; - if let Some(light) = As::::cast_mut(light.as_mut()) { + if let Some(light) = light.as_mut().cast_mut() as Option<&mut dyn OnOff> { *previous = light.is_on().await.unwrap(); light.set_on(true).await.ok(); } @@ -172,10 +175,12 @@ impl OnMqtt for ContactSensor { if !previous { // If the timeout is zero just turn the light off directly if trigger.timeout.is_zero() - && let Some(light) = As::::cast_mut(light.as_mut()) + && let Some(light) = light.as_mut().cast_mut() as Option<&mut dyn OnOff> { light.set_on(false).await.ok(); - } else if let Some(light) = As::::cast_mut(light.as_mut()) { + } else if let Some(light) = + light.as_mut().cast_mut() as Option<&mut dyn Timeout> + { light.start_timeout(trigger.timeout).await.unwrap(); } // TODO: Put a warning/error on creation if either of this has to option to fail diff --git a/src/devices/mod.rs b/src/devices/mod.rs index 5d5e9f0..f214c94 100644 --- a/src/devices/mod.rs +++ b/src/devices/mod.rs @@ -12,8 +12,11 @@ mod presence; mod wake_on_lan; mod washer; -use google_home::device::AsGoogleHomeDevice; +use std::fmt::Debug; + +use automation_cast::Cast; use google_home::traits::OnOff; +use google_home::GoogleHomeDevice; pub use self::air_filter::AirFilterConfig; pub use self::audio_setup::AudioSetupConfig; @@ -31,7 +34,18 @@ pub use self::washer::WasherConfig; use crate::event::{OnDarkness, OnMqtt, OnNotification, OnPresence}; use crate::traits::Timeout; -#[impl_cast::device(As: OnMqtt + OnPresence + OnDarkness + OnNotification + OnOff + Timeout)] -pub trait Device: AsGoogleHomeDevice + std::fmt::Debug + Sync + Send { +pub trait Device: + Debug + + Sync + + Send + + Cast + + Cast + + Cast + + Cast + + Cast + + Cast + + Cast + + Cast +{ fn get_id(&self) -> &str; } diff --git a/src/event.rs b/src/event.rs index 9f5591e..a9e69b1 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,5 +1,4 @@ use async_trait::async_trait; -use impl_cast::device_trait; use rumqttc::Publish; use tokio::sync::mpsc; @@ -32,26 +31,22 @@ impl EventChannel { } #[async_trait] -#[device_trait] -pub trait OnMqtt { +pub trait OnMqtt: Sync + Send { fn topics(&self) -> Vec<&str>; async fn on_mqtt(&mut self, message: Publish); } #[async_trait] -#[device_trait] -pub trait OnPresence { +pub trait OnPresence: Sync + Send { async fn on_presence(&mut self, presence: bool); } #[async_trait] -#[device_trait] -pub trait OnDarkness { +pub trait OnDarkness: Sync + Send { async fn on_darkness(&mut self, dark: bool); } #[async_trait] -#[device_trait] -pub trait OnNotification { +pub trait OnNotification: Sync + Send { async fn on_notification(&mut self, notification: Notification); } diff --git a/src/traits.rs b/src/traits.rs index a2e5325..eaeff9f 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -2,11 +2,9 @@ use std::time::Duration; use anyhow::Result; use async_trait::async_trait; -use impl_cast::device_trait; #[async_trait] -#[device_trait] -pub trait Timeout { +pub trait Timeout: Sync + Send { async fn start_timeout(&mut self, _timeout: Duration) -> Result<()>; async fn stop_timeout(&mut self) -> Result<()>; }