Improved impl_cast and made all traits Sync + Send + 'static
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
65f76904dd
commit
b54c9512b9
|
@ -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)?;
|
||||||
|
|
|
@ -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);
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user