Improved impl_cast and made all traits Sync + Send + 'static
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Dreaded_X 2023-04-10 23:51:22 +02:00
parent 65f76904dd
commit b54c9512b9
Signed by: Dreaded_X
GPG Key ID: FA5F485356B0D2D4
9 changed files with 77 additions and 66 deletions

View File

@ -4,11 +4,11 @@ use crate::{
errors::{DeviceError, ErrorCode}, errors::{DeviceError, ErrorCode},
request::execute::CommandType, request::execute::CommandType,
response, response,
traits::{AsOnOff, AsScene, Trait}, traits::{As, OnOff, Scene, Trait},
types::Type, types::Type,
}; };
pub trait GoogleHomeDevice: AsOnOff + AsScene { pub trait GoogleHomeDevice: As<dyn OnOff> + As<dyn Scene> + 'static {
fn get_device_type(&self) -> Type; fn get_device_type(&self) -> Type;
fn get_device_name(&self) -> Name; fn get_device_name(&self) -> Name;
fn get_id(&self) -> &str; fn get_id(&self) -> &str;
@ -40,14 +40,14 @@ pub trait GoogleHomeDevice: AsOnOff + AsScene {
let mut traits = Vec::new(); let mut traits = Vec::new();
// OnOff // OnOff
if let Some(on_off) = AsOnOff::cast(self) { if let Some(on_off) = As::<dyn OnOff>::cast(self) {
traits.push(Trait::OnOff); traits.push(Trait::OnOff);
device.attributes.command_only_on_off = on_off.is_command_only(); device.attributes.command_only_on_off = on_off.is_command_only();
device.attributes.query_only_on_off = on_off.is_query_only(); device.attributes.query_only_on_off = on_off.is_query_only();
} }
// Scene // Scene
if let Some(scene) = AsScene::cast(self) { if let Some(scene) = As::<dyn Scene>::cast(self) {
traits.push(Trait::Scene); traits.push(Trait::Scene);
device.attributes.scene_reversible = scene.is_scene_reversible(); device.attributes.scene_reversible = scene.is_scene_reversible();
} }
@ -64,7 +64,7 @@ pub trait GoogleHomeDevice: AsOnOff + AsScene {
} }
// OnOff // OnOff
if let Some(on_off) = AsOnOff::cast(self) { if let Some(on_off) = As::<dyn OnOff>::cast(self) {
device.state.on = on_off.is_on().map_err(|err| device.set_error(err)).ok(); device.state.on = on_off.is_on().map_err(|err| device.set_error(err)).ok();
} }
@ -74,13 +74,13 @@ pub trait GoogleHomeDevice: AsOnOff + AsScene {
fn execute(&mut self, command: &CommandType) -> Result<(), ErrorCode> { fn execute(&mut self, command: &CommandType) -> Result<(), ErrorCode> {
match command { match command {
CommandType::OnOff { on } => { CommandType::OnOff { on } => {
let on_off = AsOnOff::cast_mut(self) let on_off = As::<dyn OnOff>::cast_mut(self)
.ok_or::<ErrorCode>(DeviceError::ActionNotAvailable.into())?; .ok_or::<ErrorCode>(DeviceError::ActionNotAvailable.into())?;
on_off.set_on(*on)?; on_off.set_on(*on)?;
} }
CommandType::ActivateScene { deactivate } => { CommandType::ActivateScene { deactivate } => {
let scene = AsScene::cast_mut(self) let scene = As::<dyn Scene>::cast_mut(self)
.ok_or::<ErrorCode>(DeviceError::ActionNotAvailable.into())?; .ok_or::<ErrorCode>(DeviceError::ActionNotAvailable.into())?;
scene.set_active(!deactivate)?; scene.set_active(!deactivate)?;

View File

@ -10,7 +10,11 @@ pub enum Trait {
Scene, Scene,
} }
pub trait OnOff: std::fmt::Debug { impl_cast::impl_setup!();
impl_cast::impl_cast!(GoogleHomeDevice, OnOff);
impl_cast::impl_cast!(GoogleHomeDevice, Scene);
pub trait OnOff: std::fmt::Debug + Sync + Send + 'static {
fn is_command_only(&self) -> Option<bool> { fn is_command_only(&self) -> Option<bool> {
None None
} }
@ -23,13 +27,11 @@ pub trait OnOff: std::fmt::Debug {
fn is_on(&self) -> Result<bool, ErrorCode>; fn is_on(&self) -> Result<bool, ErrorCode>;
fn set_on(&mut self, on: bool) -> Result<(), ErrorCode>; fn set_on(&mut self, on: bool) -> Result<(), ErrorCode>;
} }
impl_cast::impl_cast!(GoogleHomeDevice, OnOff);
pub trait Scene: std::fmt::Debug { pub trait Scene: std::fmt::Debug + Sync + Send + 'static {
fn is_scene_reversible(&self) -> Option<bool> { fn is_scene_reversible(&self) -> Option<bool> {
None None
} }
fn set_active(&self, activate: bool) -> Result<(), ErrorCode>; fn set_active(&self, activate: bool) -> Result<(), ErrorCode>;
} }
impl_cast::impl_cast!(GoogleHomeDevice, Scene);

View File

@ -1,38 +1,47 @@
pub extern crate paste; pub extern crate paste;
#[macro_export]
macro_rules! impl_setup {
() => {
pub trait As<T: ?Sized> {
fn consume(self: Box<Self>) -> Option<Box<T>>;
fn cast(&self) -> Option<&T>;
fn cast_mut(&mut self) -> Option<&mut T>;
}
};
}
#[macro_export] #[macro_export]
macro_rules! impl_cast { macro_rules! impl_cast {
($base:ident, $trait:ident) => { ($base:ident, $trait:ident) => {
$crate::paste::paste! { $crate::paste::paste! {
pub trait [< As $trait>] { impl<T: $base + $trait> As<dyn $trait> for T {
fn consume(self: Box<Self>) -> Option<Box<dyn $trait + Sync + Send>>; fn consume(self: Box<Self>) -> Option<Box<dyn $trait>> {
fn cast(&self) -> Option<&dyn $trait>;
fn cast_mut(&mut self) -> Option<&mut dyn $trait>;
}
impl<T: $base> [< As $trait>] for T {
default fn consume(self: Box<Self>) -> Option<Box<dyn $trait + Sync + Send>> {
None
}
default fn cast(&self) -> Option<&dyn $trait> {
None
}
default fn cast_mut(&mut self) -> Option<&mut dyn $trait> {
None
}
}
impl<T: $base + $trait + Sync + Send + 'static> [< As $trait>] for T {
fn consume(self: Box<Self>) -> Option<Box<dyn $trait + Sync + Send>> {
Some(self) Some(self)
} }
fn cast(&self) -> Option<&dyn $trait> { fn cast(&self) -> Option<&dyn $trait> {
Some(self) Some(self)
} }
fn cast_mut(&mut self) -> Option<&mut dyn $trait> { fn cast_mut(&mut self) -> Option<&mut dyn $trait> {
Some(self) Some(self)
} }
} }
impl<T: $base> As<dyn $trait> for T {
default fn consume(self: Box<Self>) -> Option<Box<dyn $trait>> {
None
}
default fn cast(&self) -> Option<&dyn $trait> {
None
}
default fn cast_mut(&mut self) -> Option<&mut dyn $trait> {
None
}
}
} }
}; };
} }

View File

@ -12,7 +12,7 @@ use serde::Deserialize;
use tracing::{debug, trace}; use tracing::{debug, trace};
use crate::{ use crate::{
devices::{self, AudioSetup, ContactSensor, DeviceBox, IkeaOutlet, KasaOutlet, WakeOnLAN}, devices::{self, AudioSetup, ContactSensor, IkeaOutlet, KasaOutlet, WakeOnLAN},
error::{ConfigParseError, DeviceCreationError, MissingEnv, MissingWildcard}, error::{ConfigParseError, DeviceCreationError, MissingEnv, MissingWildcard},
}; };
@ -255,10 +255,10 @@ impl Config {
// Quick helper function to box up the devices, // Quick helper function to box up the devices,
// passing in Box::new would be ideal, however the return type is incorrect // passing in Box::new would be ideal, however the return type is incorrect
// Maybe there is a better way to solve this? // Maybe there is a better way to solve this?
fn device_box<T: devices::Device + 'static>(device: T) -> DeviceBox { // fn device_box<T: devices::Device>(device: T) -> DeviceBox {
let a: DeviceBox = Box::new(device); // let a: DeviceBox = Box::new(device);
a // a
} // }
impl Device { impl Device {
#[async_recursion] #[async_recursion]
@ -267,8 +267,8 @@ impl Device {
identifier: &str, identifier: &str,
config: &Config, config: &Config,
client: AsyncClient, client: AsyncClient,
) -> Result<DeviceBox, DeviceCreationError> { ) -> Result<Box<dyn devices::Device>, DeviceCreationError> {
let device = match self { let device: Box<dyn devices::Device> = match self {
Device::IkeaOutlet { Device::IkeaOutlet {
info, info,
mqtt, mqtt,
@ -283,7 +283,7 @@ impl Device {
); );
IkeaOutlet::build(identifier, info, mqtt, outlet_type, timeout, client) IkeaOutlet::build(identifier, info, mqtt, outlet_type, timeout, client)
.await .await
.map(device_box)? .map(Box::new)?
} }
Device::WakeOnLAN { Device::WakeOnLAN {
info, info,
@ -299,11 +299,11 @@ impl Device {
); );
WakeOnLAN::build(identifier, info, mqtt, mac_address, broadcast_ip, client) WakeOnLAN::build(identifier, info, mqtt, mac_address, broadcast_ip, client)
.await .await
.map(device_box)? .map(Box::new)?
} }
Device::KasaOutlet { ip } => { Device::KasaOutlet { ip } => {
trace!(id = identifier, "KasaOutlet [{}]", identifier); trace!(id = identifier, "KasaOutlet [{}]", identifier);
device_box(KasaOutlet::new(identifier, ip)) Box::new(KasaOutlet::new(identifier, ip))
} }
Device::AudioSetup { Device::AudioSetup {
mqtt, mqtt,
@ -321,7 +321,7 @@ impl Device {
AudioSetup::build(identifier, mqtt, mixer, speakers, client) AudioSetup::build(identifier, mqtt, mixer, speakers, client)
.await .await
.map(device_box)? .map(Box::new)?
} }
Device::ContactSensor { mqtt, presence } => { Device::ContactSensor { mqtt, presence } => {
trace!(id = identifier, "ContactSensor [{}]", identifier); trace!(id = identifier, "ContactSensor [{}]", identifier);
@ -331,7 +331,7 @@ impl Device {
ContactSensor::build(identifier, mqtt, presence, client) ContactSensor::build(identifier, mqtt, presence, client)
.await .await
.map(device_box)? .map(Box::new)?
} }
}; };

View File

@ -25,6 +25,7 @@ use crate::{
presence::{self, OnPresence}, presence::{self, OnPresence},
}; };
impl_cast::impl_setup!();
impl_cast::impl_cast!(Device, OnMqtt); impl_cast::impl_cast!(Device, OnMqtt);
impl_cast::impl_cast!(Device, OnPresence); impl_cast::impl_cast!(Device, OnPresence);
impl_cast::impl_cast!(Device, OnDarkness); impl_cast::impl_cast!(Device, OnDarkness);
@ -32,14 +33,15 @@ impl_cast::impl_cast!(Device, GoogleHomeDevice);
impl_cast::impl_cast!(Device, OnOff); impl_cast::impl_cast!(Device, OnOff);
pub trait Device: pub trait Device:
AsGoogleHomeDevice As<dyn GoogleHomeDevice>
+ AsOnMqtt + As<dyn OnMqtt>
+ AsOnPresence + As<dyn OnPresence>
+ AsOnDarkness + As<dyn OnDarkness>
+ AsOnOff + As<dyn OnOff>
+ std::fmt::Debug + std::fmt::Debug
+ Sync + Sync
+ Send + Send
+ 'static
{ {
fn get_id(&self) -> &str; fn get_id(&self) -> &str;
} }
@ -47,7 +49,7 @@ pub trait Device:
// TODO: Add an inner type that we can wrap with Arc<RwLock<>> to make this type a little bit nicer // TODO: Add an inner type that we can wrap with Arc<RwLock<>> to make this type a little bit nicer
// to work with // to work with
struct Devices { struct Devices {
devices: HashMap<String, DeviceBox>, devices: HashMap<String, Box<dyn Device>>,
} }
macro_rules! get_cast { macro_rules! get_cast {
@ -57,7 +59,7 @@ macro_rules! get_cast {
self.devices self.devices
.iter_mut() .iter_mut()
.filter_map(|(id, device)| { .filter_map(|(id, device)| {
[< As $trait >]::cast_mut(device.as_mut()) As::<dyn $trait>::cast_mut(device.as_mut())
.map(|listener| (id.as_str(), listener)) .map(|listener| (id.as_str(), listener))
}).collect() }).collect()
} }
@ -73,13 +75,11 @@ pub enum Command {
tx: oneshot::Sender<Result<google_home::Response, FullfillmentError>>, tx: oneshot::Sender<Result<google_home::Response, FullfillmentError>>,
}, },
AddDevice { AddDevice {
device: DeviceBox, device: Box<dyn Device>,
tx: oneshot::Sender<()>, tx: oneshot::Sender<()>,
}, },
} }
pub type DeviceBox = Box<dyn Device>;
#[derive(Clone)] #[derive(Clone)]
pub struct DevicesHandle { pub struct DevicesHandle {
tx: mpsc::Sender<Command>, tx: mpsc::Sender<Command>,
@ -113,7 +113,7 @@ impl DevicesHandle {
Ok(rx.await??) Ok(rx.await??)
} }
pub async fn add_device(&self, device: DeviceBox) -> Result<(), DevicesError> { pub async fn add_device(&self, device: Box<dyn Device>) -> Result<(), DevicesError> {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
self.tx.send(Command::AddDevice { device, tx }).await?; self.tx.send(Command::AddDevice { device, tx }).await?;
Ok(rx.await?) Ok(rx.await?)
@ -176,7 +176,7 @@ impl Devices {
} }
} }
fn add_device(&mut self, device: DeviceBox) { fn add_device(&mut self, device: Box<dyn Device>) {
debug!(id = device.get_id(), "Adding device"); debug!(id = device.get_id(), "Adding device");
self.devices.insert(device.get_id().to_owned(), device); self.devices.insert(device.get_id().to_owned(), device);
} }

View File

@ -8,7 +8,7 @@ use crate::error::DeviceError;
use crate::mqtt::{OnMqtt, RemoteAction, RemoteMessage}; use crate::mqtt::{OnMqtt, RemoteAction, RemoteMessage};
use crate::presence::OnPresence; use crate::presence::OnPresence;
use super::{AsOnOff, Device, DeviceBox}; use super::{As, Device};
// TODO: Ideally we store am Arc to the childern devices, // TODO: Ideally we store am Arc to the childern devices,
// that way they hook into everything just like all other devices // that way they hook into everything just like all other devices
@ -16,25 +16,25 @@ use super::{AsOnOff, Device, DeviceBox};
pub struct AudioSetup { pub struct AudioSetup {
identifier: String, identifier: String,
mqtt: MqttDeviceConfig, mqtt: MqttDeviceConfig,
mixer: Box<dyn traits::OnOff + Sync + Send>, mixer: Box<dyn traits::OnOff>,
speakers: Box<dyn traits::OnOff + Sync + Send>, speakers: Box<dyn traits::OnOff>,
} }
impl AudioSetup { impl AudioSetup {
pub async fn build( pub async fn build(
identifier: &str, identifier: &str,
mqtt: MqttDeviceConfig, mqtt: MqttDeviceConfig,
mixer: DeviceBox, mixer: Box<dyn Device>,
speakers: DeviceBox, speakers: Box<dyn Device>,
client: AsyncClient, client: AsyncClient,
) -> Result<Self, DeviceError> { ) -> Result<Self, DeviceError> {
// We expect the children devices to implement the OnOff trait // We expect the children devices to implement the OnOff trait
let mixer_id = mixer.get_id().to_owned(); let mixer_id = mixer.get_id().to_owned();
let mixer = AsOnOff::consume(mixer).ok_or_else(|| DeviceError::OnOffExpected(mixer_id))?; let mixer = As::consume(mixer).ok_or_else(|| DeviceError::OnOffExpected(mixer_id))?;
let speakers_id = speakers.get_id().to_owned(); let speakers_id = speakers.get_id().to_owned();
let speakers = let speakers =
AsOnOff::consume(speakers).ok_or_else(|| DeviceError::OnOffExpected(speakers_id))?; As::consume(speakers).ok_or_else(|| DeviceError::OnOffExpected(speakers_id))?;
client client
.subscribe(mqtt.topic.clone(), rumqttc::QoS::AtLeastOnce) .subscribe(mqtt.topic.clone(), rumqttc::QoS::AtLeastOnce)

View File

@ -10,7 +10,7 @@ use crate::{
}; };
#[async_trait] #[async_trait]
pub trait OnDarkness { pub trait OnDarkness: Sync + Send + 'static {
async fn on_darkness(&mut self, dark: bool); async fn on_darkness(&mut self, dark: bool);
} }

View File

@ -10,7 +10,7 @@ use rumqttc::{Event, EventLoop, Incoming, Publish};
use tokio::sync::broadcast; use tokio::sync::broadcast;
#[async_trait] #[async_trait]
pub trait OnMqtt { pub trait OnMqtt: Sync + Send + 'static {
async fn on_mqtt(&mut self, message: &Publish); async fn on_mqtt(&mut self, message: &Publish);
} }

View File

@ -12,7 +12,7 @@ use crate::{
}; };
#[async_trait] #[async_trait]
pub trait OnPresence { pub trait OnPresence: Sync + Send + 'static {
async fn on_presence(&mut self, presence: bool); async fn on_presence(&mut self, presence: bool);
} }