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.
This commit is contained in:
parent
cde9654a78
commit
3689a52afd
16
Cargo.lock
generated
16
Cargo.lock
generated
|
@ -76,6 +76,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"automation_cast",
|
||||||
"axum",
|
"axum",
|
||||||
"bytes",
|
"bytes",
|
||||||
"console-subscriber",
|
"console-subscriber",
|
||||||
|
@ -84,7 +85,6 @@ dependencies = [
|
||||||
"eui48",
|
"eui48",
|
||||||
"futures",
|
"futures",
|
||||||
"google-home",
|
"google-home",
|
||||||
"impl_cast",
|
|
||||||
"indexmap 2.0.0",
|
"indexmap 2.0.0",
|
||||||
"paste",
|
"paste",
|
||||||
"pollster",
|
"pollster",
|
||||||
|
@ -104,6 +104,10 @@ dependencies = [
|
||||||
"wakey",
|
"wakey",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "automation_cast"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum"
|
name = "axum"
|
||||||
version = "0.6.20"
|
version = "0.6.20"
|
||||||
|
@ -568,8 +572,8 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"automation_cast",
|
||||||
"futures",
|
"futures",
|
||||||
"impl_cast",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
@ -761,14 +765,6 @@ dependencies = [
|
||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "impl_cast"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.28",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.3"
|
version = "1.9.3"
|
||||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -4,20 +4,20 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["impl_cast", "google-home"]
|
members = ["google-home", "automation_cast"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
automation_cast = { path = "./automation_cast/" }
|
||||||
rumqttc = "0.18"
|
rumqttc = "0.18"
|
||||||
serde = { version = "1.0.149", features = ["derive"] }
|
serde = { version = "1.0.149", features = ["derive"] }
|
||||||
serde_json = "1.0.89"
|
serde_json = "1.0.89"
|
||||||
impl_cast = { path = "./impl_cast", features = ["debug"] }
|
|
||||||
google-home = { path = "./google-home" }
|
google-home = { path = "./google-home" }
|
||||||
paste = "1.0.10"
|
paste = "1.0.10"
|
||||||
tokio = { version = "1", features = ["rt-multi-thread"] }
|
tokio = { version = "1", features = ["rt-multi-thread"] }
|
||||||
dotenvy = "0.15.0"
|
dotenvy = "0.15.0"
|
||||||
reqwest = { version = "0.11.13", features = [
|
reqwest = { version = "0.11.13", features = [
|
||||||
"json",
|
"json",
|
||||||
"rustls-tls",
|
"rustls-tls",
|
||||||
], default-features = false } # Use rustls, since the other packages also use rustls
|
], default-features = false } # Use rustls, since the other packages also use rustls
|
||||||
axum = "0.6.1"
|
axum = "0.6.1"
|
||||||
serde_repr = "0.1.10"
|
serde_repr = "0.1.10"
|
||||||
|
@ -28,8 +28,8 @@ regex = "1.7.0"
|
||||||
async-trait = "0.1.61"
|
async-trait = "0.1.61"
|
||||||
futures = "0.3.25"
|
futures = "0.3.25"
|
||||||
eui48 = { version = "1.1.0", default-features = false, features = [
|
eui48 = { version = "1.1.0", default-features = false, features = [
|
||||||
"disp_hexstring",
|
"disp_hexstring",
|
||||||
"serde",
|
"serde",
|
||||||
] }
|
] }
|
||||||
thiserror = "1.0.38"
|
thiserror = "1.0.38"
|
||||||
anyhow = "1.0.68"
|
anyhow = "1.0.68"
|
||||||
|
|
8
automation_cast/Cargo.toml
Normal file
8
automation_cast/Cargo.toml
Normal file
|
@ -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]
|
37
automation_cast/src/lib.rs
Normal file
37
automation_cast/src/lib.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
#![feature(specialization)]
|
||||||
|
#![feature(unsize)]
|
||||||
|
|
||||||
|
use std::marker::Unsize;
|
||||||
|
|
||||||
|
pub trait Cast<P: ?Sized> {
|
||||||
|
fn cast(&self) -> Option<&P>;
|
||||||
|
fn cast_mut(&mut self) -> Option<&mut P>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, P> Cast<P> for D
|
||||||
|
where
|
||||||
|
P: ?Sized,
|
||||||
|
{
|
||||||
|
default fn cast(&self) -> Option<&P> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
default fn cast_mut(&mut self) -> Option<&mut P> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, P> Cast<P> for D
|
||||||
|
where
|
||||||
|
D: Unsize<P>,
|
||||||
|
P: ?Sized,
|
||||||
|
{
|
||||||
|
fn cast(&self) -> Option<&P> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast_mut(&mut self) -> Option<&mut P> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
impl_cast = { path = "../impl_cast" }
|
automation_cast = { path = "../automation_cast/" }
|
||||||
serde = { version = "1.0.149", features = ["derive"] }
|
serde = { version = "1.0.149", features = ["derive"] }
|
||||||
serde_json = "1.0.89"
|
serde_json = "1.0.89"
|
||||||
thiserror = "1.0.37"
|
thiserror = "1.0.37"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use automation_cast::Cast;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::errors::{DeviceError, ErrorCode};
|
use crate::errors::{DeviceError, ErrorCode};
|
||||||
|
@ -7,43 +8,10 @@ use crate::response;
|
||||||
use crate::traits::{FanSpeed, HumiditySetting, OnOff, Scene, Trait};
|
use crate::traits::{FanSpeed, HumiditySetting, OnOff, Scene, Trait};
|
||||||
use crate::types::Type;
|
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<T> 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<T> 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]
|
#[async_trait]
|
||||||
#[impl_cast::device(As: OnOff + Scene + FanSpeed + HumiditySetting)]
|
pub trait GoogleHomeDevice:
|
||||||
pub trait GoogleHomeDevice: AsGoogleHomeDevice + Sync + Send + 'static {
|
Sync + Send + Cast<dyn OnOff> + Cast<dyn Scene> + Cast<dyn FanSpeed> + Cast<dyn HumiditySetting>
|
||||||
|
{
|
||||||
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;
|
||||||
|
@ -76,26 +44,26 @@ pub trait GoogleHomeDevice: AsGoogleHomeDevice + Sync + Send + 'static {
|
||||||
let mut traits = Vec::new();
|
let mut traits = Vec::new();
|
||||||
|
|
||||||
// OnOff
|
// OnOff
|
||||||
if let Some(on_off) = As::<dyn OnOff>::cast(self) {
|
if let Some(on_off) = self.cast() as Option<&dyn OnOff> {
|
||||||
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) = As::<dyn Scene>::cast(self) {
|
if let Some(scene) = self.cast() as Option<&dyn Scene> {
|
||||||
traits.push(Trait::Scene);
|
traits.push(Trait::Scene);
|
||||||
device.attributes.scene_reversible = scene.is_scene_reversible();
|
device.attributes.scene_reversible = scene.is_scene_reversible();
|
||||||
}
|
}
|
||||||
|
|
||||||
// FanSpeed
|
// FanSpeed
|
||||||
if let Some(fan_speed) = As::<dyn FanSpeed>::cast(self) {
|
if let Some(fan_speed) = self.cast() as Option<&dyn FanSpeed> {
|
||||||
traits.push(Trait::FanSpeed);
|
traits.push(Trait::FanSpeed);
|
||||||
device.attributes.command_only_fan_speed = fan_speed.command_only_fan_speed();
|
device.attributes.command_only_fan_speed = fan_speed.command_only_fan_speed();
|
||||||
device.attributes.available_fan_speeds = Some(fan_speed.available_speeds());
|
device.attributes.available_fan_speeds = Some(fan_speed.available_speeds());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(humidity_setting) = As::<dyn HumiditySetting>::cast(self) {
|
if let Some(humidity_setting) = self.cast() as Option<&dyn HumiditySetting> {
|
||||||
traits.push(Trait::HumiditySetting);
|
traits.push(Trait::HumiditySetting);
|
||||||
device.attributes.query_only_humidity_setting =
|
device.attributes.query_only_humidity_setting =
|
||||||
humidity_setting.query_only_humidity_setting();
|
humidity_setting.query_only_humidity_setting();
|
||||||
|
@ -113,7 +81,7 @@ pub trait GoogleHomeDevice: AsGoogleHomeDevice + Sync + Send + 'static {
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnOff
|
// OnOff
|
||||||
if let Some(on_off) = As::<dyn OnOff>::cast(self) {
|
if let Some(on_off) = self.cast() as Option<&dyn OnOff> {
|
||||||
device.state.on = on_off
|
device.state.on = on_off
|
||||||
.is_on()
|
.is_on()
|
||||||
.await
|
.await
|
||||||
|
@ -122,11 +90,11 @@ pub trait GoogleHomeDevice: AsGoogleHomeDevice + Sync + Send + 'static {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FanSpeed
|
// FanSpeed
|
||||||
if let Some(fan_speed) = As::<dyn FanSpeed>::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);
|
device.state.current_fan_speed_setting = Some(fan_speed.current_speed().await);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(humidity_setting) = As::<dyn HumiditySetting>::cast(self) {
|
if let Some(humidity_setting) = self.cast() as Option<&dyn HumiditySetting> {
|
||||||
device.state.humidity_ambient_percent =
|
device.state.humidity_ambient_percent =
|
||||||
Some(humidity_setting.humidity_ambient_percent().await);
|
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> {
|
async fn execute(&mut self, command: &CommandType) -> Result<(), ErrorCode> {
|
||||||
match command {
|
match command {
|
||||||
CommandType::OnOff { on } => {
|
CommandType::OnOff { on } => {
|
||||||
if let Some(t) = As::<dyn OnOff>::cast_mut(self) {
|
if let Some(t) = self.cast_mut() as Option<&mut dyn OnOff> {
|
||||||
t.set_on(*on).await?;
|
t.set_on(*on).await?;
|
||||||
} else {
|
} else {
|
||||||
return Err(DeviceError::ActionNotAvailable.into());
|
return Err(DeviceError::ActionNotAvailable.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CommandType::ActivateScene { deactivate } => {
|
CommandType::ActivateScene { deactivate } => {
|
||||||
if let Some(t) = As::<dyn Scene>::cast(self) {
|
if let Some(t) = self.cast_mut() as Option<&mut dyn Scene> {
|
||||||
t.set_active(!deactivate).await?;
|
t.set_active(!deactivate).await?;
|
||||||
} else {
|
} else {
|
||||||
return Err(DeviceError::ActionNotAvailable.into());
|
return Err(DeviceError::ActionNotAvailable.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CommandType::SetFanSpeed { fan_speed } => {
|
CommandType::SetFanSpeed { fan_speed } => {
|
||||||
if let Some(t) = As::<dyn FanSpeed>::cast(self) {
|
if let Some(t) = self.cast_mut() as Option<&mut dyn FanSpeed> {
|
||||||
t.set_speed(fan_speed).await?;
|
t.set_speed(fan_speed).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use automation_cast::Cast;
|
||||||
use futures::future::{join_all, OptionFuture};
|
use futures::future::{join_all, OptionFuture};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::sync::{Mutex, RwLock};
|
use tokio::sync::{Mutex, RwLock};
|
||||||
|
|
||||||
use crate::device::AsGoogleHomeDevice;
|
|
||||||
use crate::errors::{DeviceError, ErrorCode};
|
use crate::errors::{DeviceError, ErrorCode};
|
||||||
use crate::request::{self, Intent, Request};
|
use crate::request::{self, Intent, Request};
|
||||||
use crate::response::{self, execute, query, sync, Response, ResponsePayload, State};
|
use crate::response::{self, execute, query, sync, Response, ResponsePayload, State};
|
||||||
|
use crate::GoogleHomeDevice;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GoogleHome {
|
pub struct GoogleHome {
|
||||||
|
@ -29,7 +30,7 @@ impl GoogleHome {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_request<T: AsGoogleHomeDevice + ?Sized + 'static>(
|
pub async fn handle_request<T: Cast<dyn GoogleHomeDevice> + ?Sized + 'static>(
|
||||||
&self,
|
&self,
|
||||||
request: Request,
|
request: Request,
|
||||||
devices: &HashMap<String, Arc<RwLock<Box<T>>>>,
|
devices: &HashMap<String, Arc<RwLock<Box<T>>>>,
|
||||||
|
@ -58,7 +59,7 @@ impl GoogleHome {
|
||||||
.map(|payload| Response::new(&request.request_id, payload))
|
.map(|payload| Response::new(&request.request_id, payload))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sync<T: AsGoogleHomeDevice + ?Sized + 'static>(
|
async fn sync<T: Cast<dyn GoogleHomeDevice> + ?Sized + 'static>(
|
||||||
&self,
|
&self,
|
||||||
devices: &HashMap<String, Arc<RwLock<Box<T>>>>,
|
devices: &HashMap<String, Arc<RwLock<Box<T>>>>,
|
||||||
) -> sync::Payload {
|
) -> sync::Payload {
|
||||||
|
@ -75,7 +76,7 @@ impl GoogleHome {
|
||||||
resp_payload
|
resp_payload
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn query<T: AsGoogleHomeDevice + ?Sized + 'static>(
|
async fn query<T: Cast<dyn GoogleHomeDevice> + ?Sized + 'static>(
|
||||||
&self,
|
&self,
|
||||||
payload: request::query::Payload,
|
payload: request::query::Payload,
|
||||||
devices: &HashMap<String, Arc<RwLock<Box<T>>>>,
|
devices: &HashMap<String, Arc<RwLock<Box<T>>>>,
|
||||||
|
@ -107,7 +108,7 @@ impl GoogleHome {
|
||||||
resp_payload
|
resp_payload
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute<T: AsGoogleHomeDevice + ?Sized + 'static>(
|
async fn execute<T: Cast<dyn GoogleHomeDevice> + ?Sized + 'static>(
|
||||||
&self,
|
&self,
|
||||||
payload: request::execute::Payload,
|
payload: request::execute::Payload,
|
||||||
devices: &HashMap<String, Arc<RwLock<Box<T>>>>,
|
devices: &HashMap<String, Arc<RwLock<Box<T>>>>,
|
||||||
|
|
|
@ -16,8 +16,7 @@ pub enum Trait {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
#[impl_cast::device_trait]
|
pub trait OnOff: Sync + Send {
|
||||||
pub trait OnOff {
|
|
||||||
fn is_command_only(&self) -> Option<bool> {
|
fn is_command_only(&self) -> Option<bool> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -32,8 +31,7 @@ pub trait OnOff {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
#[impl_cast::device_trait]
|
pub trait Scene: Sync + Send {
|
||||||
pub trait Scene {
|
|
||||||
fn is_scene_reversible(&self) -> Option<bool> {
|
fn is_scene_reversible(&self) -> Option<bool> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -60,8 +58,7 @@ pub struct AvailableSpeeds {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
#[impl_cast::device_trait]
|
pub trait FanSpeed: Sync + Send {
|
||||||
pub trait FanSpeed {
|
|
||||||
fn reversible(&self) -> Option<bool> {
|
fn reversible(&self) -> Option<bool> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -76,8 +73,7 @@ pub trait FanSpeed {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
#[impl_cast::device_trait]
|
pub trait HumiditySetting: Sync + Send {
|
||||||
pub trait HumiditySetting {
|
|
||||||
// TODO: This implementation is not complete, I have only implemented what I need right now
|
// TODO: This implementation is not complete, I have only implemented what I need right now
|
||||||
fn query_only_humidity_setting(&self) -> Option<bool> {
|
fn query_only_humidity_setting(&self) -> Option<bool> {
|
||||||
None
|
None
|
||||||
|
|
|
@ -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
|
|
|
@ -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<Path>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Attr {
|
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
let mut traits = Vec::new();
|
|
||||||
|
|
||||||
let name = input.parse::<Ident>()?;
|
|
||||||
input.parse::<Token![:]>()?;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let ty = input.parse()?;
|
|
||||||
traits.push(ty);
|
|
||||||
|
|
||||||
if input.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
input.parse::<Token![+]>()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
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<dyn Device> = Box::new(ExampleDevice {});
|
|
||||||
///
|
|
||||||
/// // Cast to the OnOff trait, which is implemented
|
|
||||||
/// let as_on_off = As::<dyn OnOff>::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::<dyn Brightness>::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::<dyn OnOff>::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<T: ?Sized + 'static> {
|
|
||||||
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<dyn #device_trait>
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
let interface_ident = format_ident!("{}", interface.ident);
|
|
||||||
let impls = traits
|
|
||||||
.iter()
|
|
||||||
.map(|device_trait| {
|
|
||||||
quote! {
|
|
||||||
// Default impl
|
|
||||||
impl<T> #name<dyn #device_trait> 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<T> #name<dyn #device_trait> 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()
|
|
||||||
}
|
|
|
@ -12,7 +12,7 @@ use tokio_cron_scheduler::{Job, JobScheduler};
|
||||||
use tracing::{debug, error, instrument, trace};
|
use tracing::{debug, error, instrument, trace};
|
||||||
|
|
||||||
use crate::devices::{
|
use crate::devices::{
|
||||||
AirFilterConfig, As, AudioSetupConfig, ContactSensorConfig, DebugBridgeConfig, Device,
|
AirFilterConfig, AudioSetupConfig, ContactSensorConfig, DebugBridgeConfig, Device,
|
||||||
HueBridgeConfig, HueGroupConfig, IkeaOutletConfig, KasaOutletConfig, LightSensorConfig,
|
HueBridgeConfig, HueGroupConfig, IkeaOutletConfig, KasaOutletConfig, LightSensorConfig,
|
||||||
WakeOnLANConfig, WasherConfig,
|
WakeOnLANConfig, WasherConfig,
|
||||||
};
|
};
|
||||||
|
@ -106,22 +106,22 @@ impl DeviceManager {
|
||||||
let device = manager.get(&target).await.unwrap();
|
let device = manager.get(&target).await.unwrap();
|
||||||
match action {
|
match action {
|
||||||
Action::On => {
|
Action::On => {
|
||||||
As::<dyn OnOff>::cast_mut(
|
let mut device = device.write().await;
|
||||||
device.write().await.as_mut(),
|
let device: Option<&mut dyn OnOff> =
|
||||||
)
|
device.as_mut().cast_mut();
|
||||||
.unwrap()
|
|
||||||
.set_on(true)
|
if let Some(device) = device {
|
||||||
.await
|
device.set_on(true).await.unwrap();
|
||||||
.unwrap();
|
}
|
||||||
}
|
}
|
||||||
Action::Off => {
|
Action::Off => {
|
||||||
As::<dyn OnOff>::cast_mut(
|
let mut device = device.write().await;
|
||||||
device.write().await.as_mut(),
|
let device: Option<&mut dyn OnOff> =
|
||||||
)
|
device.as_mut().cast_mut();
|
||||||
.unwrap()
|
|
||||||
.set_on(false)
|
if let Some(device) = device {
|
||||||
.await
|
device.set_on(false).await.unwrap();
|
||||||
.unwrap();
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,14 +142,17 @@ impl DeviceManager {
|
||||||
|
|
||||||
debug!(id, "Adding device");
|
debug!(id, "Adding device");
|
||||||
|
|
||||||
// If the device listens to mqtt, subscribe to the topics
|
{
|
||||||
if let Some(device) = As::<dyn OnMqtt>::cast(device.as_ref()) {
|
// If the device listens to mqtt, subscribe to the topics
|
||||||
for topic in device.topics() {
|
let device: Option<&dyn OnMqtt> = device.as_ref().cast();
|
||||||
trace!(id, topic, "Subscribing to topic");
|
if let Some(device) = device {
|
||||||
if let Err(err) = self.client.subscribe(topic, QoS::AtLeastOnce).await {
|
for topic in device.topics() {
|
||||||
// NOTE: Pretty sure that this can only happen if the mqtt client if no longer
|
trace!(id, topic, "Subscribing to topic");
|
||||||
// running
|
if let Err(err) = self.client.subscribe(topic, QoS::AtLeastOnce).await {
|
||||||
error!(id, topic, "Failed to subscribe to topic: {err}");
|
// 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();
|
let message = message.clone();
|
||||||
async move {
|
async move {
|
||||||
let mut device = device.write().await;
|
let mut device = device.write().await;
|
||||||
let device = device.as_mut();
|
let device: Option<&mut dyn OnMqtt> = device.as_mut().cast_mut();
|
||||||
if let Some(device) = As::<dyn OnMqtt>::cast_mut(device) {
|
if let Some(device) = device {
|
||||||
let subscribed = device
|
let subscribed = device
|
||||||
.topics()
|
.topics()
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -220,8 +223,8 @@ impl DeviceManager {
|
||||||
let devices = self.devices.read().await;
|
let devices = self.devices.read().await;
|
||||||
let iter = devices.iter().map(|(id, device)| async move {
|
let iter = devices.iter().map(|(id, device)| async move {
|
||||||
let mut device = device.write().await;
|
let mut device = device.write().await;
|
||||||
let device = device.as_mut();
|
let device: Option<&mut dyn OnDarkness> = device.as_mut().cast_mut();
|
||||||
if let Some(device) = As::<dyn OnDarkness>::cast_mut(device) {
|
if let Some(device) = device {
|
||||||
trace!(id, "Handling");
|
trace!(id, "Handling");
|
||||||
device.on_darkness(dark).await;
|
device.on_darkness(dark).await;
|
||||||
}
|
}
|
||||||
|
@ -233,8 +236,8 @@ impl DeviceManager {
|
||||||
let devices = self.devices.read().await;
|
let devices = self.devices.read().await;
|
||||||
let iter = devices.iter().map(|(id, device)| async move {
|
let iter = devices.iter().map(|(id, device)| async move {
|
||||||
let mut device = device.write().await;
|
let mut device = device.write().await;
|
||||||
let device = device.as_mut();
|
let device: Option<&mut dyn OnPresence> = device.as_mut().cast_mut();
|
||||||
if let Some(device) = As::<dyn OnPresence>::cast_mut(device) {
|
if let Some(device) = device {
|
||||||
trace!(id, "Handling");
|
trace!(id, "Handling");
|
||||||
device.on_presence(presence).await;
|
device.on_presence(presence).await;
|
||||||
}
|
}
|
||||||
|
@ -248,8 +251,8 @@ impl DeviceManager {
|
||||||
let notification = notification.clone();
|
let notification = notification.clone();
|
||||||
async move {
|
async move {
|
||||||
let mut device = device.write().await;
|
let mut device = device.write().await;
|
||||||
let device = device.as_mut();
|
let device: Option<&mut dyn OnNotification> = device.as_mut().cast_mut();
|
||||||
if let Some(device) = As::<dyn OnNotification>::cast_mut(device) {
|
if let Some(device) = device {
|
||||||
trace!(id, "Handling");
|
trace!(id, "Handling");
|
||||||
device.on_notification(notification).await;
|
device.on_notification(notification).await;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ use tracing::{debug, error, trace, warn};
|
||||||
use super::Device;
|
use super::Device;
|
||||||
use crate::config::MqttDeviceConfig;
|
use crate::config::MqttDeviceConfig;
|
||||||
use crate::device_manager::{ConfigExternal, DeviceConfig, WrappedDevice};
|
use crate::device_manager::{ConfigExternal, DeviceConfig, WrappedDevice};
|
||||||
use crate::devices::As;
|
|
||||||
use crate::error::DeviceConfigError;
|
use crate::error::DeviceConfigError;
|
||||||
use crate::event::{OnMqtt, OnPresence};
|
use crate::event::{OnMqtt, OnPresence};
|
||||||
use crate::messages::{RemoteAction, RemoteMessage};
|
use crate::messages::{RemoteAction, RemoteMessage};
|
||||||
|
@ -39,8 +38,11 @@ impl DeviceConfig for AudioSetupConfig {
|
||||||
self.mixer.clone(),
|
self.mixer.clone(),
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
if !As::<dyn OnOff>::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 =
|
let speakers =
|
||||||
|
@ -52,11 +54,11 @@ impl DeviceConfig for AudioSetupConfig {
|
||||||
self.speakers.clone(),
|
self.speakers.clone(),
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
if !As::<dyn OnOff>::is(speakers.read().await.as_ref()) {
|
{
|
||||||
return Err(DeviceConfigError::MissingTrait(
|
let speakers = speakers.read().await;
|
||||||
self.speakers,
|
if (speakers.as_ref().cast() as Option<&dyn OnOff>).is_none() {
|
||||||
"OnOff".into(),
|
return Err(DeviceConfigError::MissingTrait(self.mixer, "OnOff".into()));
|
||||||
));
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let device = AudioSetup {
|
let device = AudioSetup {
|
||||||
|
@ -103,8 +105,8 @@ impl OnMqtt for AudioSetup {
|
||||||
let mut mixer = self.mixer.write().await;
|
let mut mixer = self.mixer.write().await;
|
||||||
let mut speakers = self.speakers.write().await;
|
let mut speakers = self.speakers.write().await;
|
||||||
if let (Some(mixer), Some(speakers)) = (
|
if let (Some(mixer), Some(speakers)) = (
|
||||||
As::<dyn OnOff>::cast_mut(mixer.as_mut()),
|
mixer.as_mut().cast_mut() as Option<&mut dyn OnOff>,
|
||||||
As::<dyn OnOff>::cast_mut(speakers.as_mut()),
|
speakers.as_mut().cast_mut() as Option<&mut dyn OnOff>,
|
||||||
) {
|
) {
|
||||||
match action {
|
match action {
|
||||||
RemoteAction::On => {
|
RemoteAction::On => {
|
||||||
|
@ -137,10 +139,9 @@ impl OnPresence for AudioSetup {
|
||||||
async fn on_presence(&mut self, presence: bool) {
|
async fn on_presence(&mut self, presence: bool) {
|
||||||
let mut mixer = self.mixer.write().await;
|
let mut mixer = self.mixer.write().await;
|
||||||
let mut speakers = self.speakers.write().await;
|
let mut speakers = self.speakers.write().await;
|
||||||
|
|
||||||
if let (Some(mixer), Some(speakers)) = (
|
if let (Some(mixer), Some(speakers)) = (
|
||||||
As::<dyn OnOff>::cast_mut(mixer.as_mut()),
|
mixer.as_mut().cast_mut() as Option<&mut dyn OnOff>,
|
||||||
As::<dyn OnOff>::cast_mut(speakers.as_mut()),
|
speakers.as_mut().cast_mut() as Option<&mut dyn OnOff>,
|
||||||
) {
|
) {
|
||||||
// Turn off the audio setup when we leave the house
|
// Turn off the audio setup when we leave the house
|
||||||
if !presence {
|
if !presence {
|
||||||
|
|
|
@ -11,7 +11,7 @@ use tracing::{debug, error, trace, warn};
|
||||||
use super::Device;
|
use super::Device;
|
||||||
use crate::config::MqttDeviceConfig;
|
use crate::config::MqttDeviceConfig;
|
||||||
use crate::device_manager::{ConfigExternal, DeviceConfig, WrappedDevice};
|
use crate::device_manager::{ConfigExternal, DeviceConfig, WrappedDevice};
|
||||||
use crate::devices::{As, DEFAULT_PRESENCE};
|
use crate::devices::DEFAULT_PRESENCE;
|
||||||
use crate::error::DeviceConfigError;
|
use crate::error::DeviceConfigError;
|
||||||
use crate::event::{OnMqtt, OnPresence};
|
use crate::event::{OnMqtt, OnPresence};
|
||||||
use crate::messages::{ContactMessage, PresenceMessage};
|
use crate::messages::{ContactMessage, PresenceMessage};
|
||||||
|
@ -60,20 +60,23 @@ impl DeviceConfig for ContactSensorConfig {
|
||||||
DeviceConfigError::MissingChild(device_name.into(), "OnOff".into()),
|
DeviceConfigError::MissingChild(device_name.into(), "OnOff".into()),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if !As::<dyn OnOff>::is(device.read().await.as_ref()) {
|
|
||||||
return Err(DeviceConfigError::MissingTrait(
|
|
||||||
device_name.into(),
|
|
||||||
"OnOff".into(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !trigger_config.timeout.is_zero()
|
|
||||||
&& !As::<dyn Timeout>::is(device.read().await.as_ref())
|
|
||||||
{
|
{
|
||||||
return Err(DeviceConfigError::MissingTrait(
|
let device = device.read().await;
|
||||||
device_name.into(),
|
if (device.as_ref().cast() as Option<&dyn OnOff>).is_none() {
|
||||||
"Timeout".into(),
|
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));
|
devices.push((device, false));
|
||||||
|
@ -161,7 +164,7 @@ impl OnMqtt for ContactSensor {
|
||||||
if !self.is_closed {
|
if !self.is_closed {
|
||||||
for (light, previous) 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) = light.as_mut().cast_mut() as Option<&mut dyn OnOff> {
|
||||||
*previous = light.is_on().await.unwrap();
|
*previous = light.is_on().await.unwrap();
|
||||||
light.set_on(true).await.ok();
|
light.set_on(true).await.ok();
|
||||||
}
|
}
|
||||||
|
@ -172,10 +175,12 @@ impl OnMqtt for ContactSensor {
|
||||||
if !previous {
|
if !previous {
|
||||||
// If the timeout is zero just turn the light off directly
|
// If the timeout is zero just turn the light off directly
|
||||||
if trigger.timeout.is_zero()
|
if trigger.timeout.is_zero()
|
||||||
&& let Some(light) = As::<dyn OnOff>::cast_mut(light.as_mut())
|
&& let Some(light) = light.as_mut().cast_mut() as Option<&mut dyn OnOff>
|
||||||
{
|
{
|
||||||
light.set_on(false).await.ok();
|
light.set_on(false).await.ok();
|
||||||
} else if let Some(light) = As::<dyn Timeout>::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();
|
light.start_timeout(trigger.timeout).await.unwrap();
|
||||||
}
|
}
|
||||||
// TODO: Put a warning/error on creation if either of this has to option to fail
|
// TODO: Put a warning/error on creation if either of this has to option to fail
|
||||||
|
|
|
@ -12,8 +12,11 @@ mod presence;
|
||||||
mod wake_on_lan;
|
mod wake_on_lan;
|
||||||
mod washer;
|
mod washer;
|
||||||
|
|
||||||
use google_home::device::AsGoogleHomeDevice;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use automation_cast::Cast;
|
||||||
use google_home::traits::OnOff;
|
use google_home::traits::OnOff;
|
||||||
|
use google_home::GoogleHomeDevice;
|
||||||
|
|
||||||
pub use self::air_filter::AirFilterConfig;
|
pub use self::air_filter::AirFilterConfig;
|
||||||
pub use self::audio_setup::AudioSetupConfig;
|
pub use self::audio_setup::AudioSetupConfig;
|
||||||
|
@ -31,7 +34,18 @@ pub use self::washer::WasherConfig;
|
||||||
use crate::event::{OnDarkness, OnMqtt, OnNotification, OnPresence};
|
use crate::event::{OnDarkness, OnMqtt, OnNotification, OnPresence};
|
||||||
use crate::traits::Timeout;
|
use crate::traits::Timeout;
|
||||||
|
|
||||||
#[impl_cast::device(As: OnMqtt + OnPresence + OnDarkness + OnNotification + OnOff + Timeout)]
|
pub trait Device:
|
||||||
pub trait Device: AsGoogleHomeDevice + std::fmt::Debug + Sync + Send {
|
Debug
|
||||||
|
+ Sync
|
||||||
|
+ Send
|
||||||
|
+ Cast<dyn GoogleHomeDevice>
|
||||||
|
+ Cast<dyn OnMqtt>
|
||||||
|
+ Cast<dyn OnMqtt>
|
||||||
|
+ Cast<dyn OnPresence>
|
||||||
|
+ Cast<dyn OnDarkness>
|
||||||
|
+ Cast<dyn OnNotification>
|
||||||
|
+ Cast<dyn OnOff>
|
||||||
|
+ Cast<dyn Timeout>
|
||||||
|
{
|
||||||
fn get_id(&self) -> &str;
|
fn get_id(&self) -> &str;
|
||||||
}
|
}
|
||||||
|
|
13
src/event.rs
13
src/event.rs
|
@ -1,5 +1,4 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use impl_cast::device_trait;
|
|
||||||
use rumqttc::Publish;
|
use rumqttc::Publish;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
@ -32,26 +31,22 @@ impl EventChannel {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
#[device_trait]
|
pub trait OnMqtt: Sync + Send {
|
||||||
pub trait OnMqtt {
|
|
||||||
fn topics(&self) -> Vec<&str>;
|
fn topics(&self) -> Vec<&str>;
|
||||||
async fn on_mqtt(&mut self, message: Publish);
|
async fn on_mqtt(&mut self, message: Publish);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
#[device_trait]
|
pub trait OnPresence: Sync + Send {
|
||||||
pub trait OnPresence {
|
|
||||||
async fn on_presence(&mut self, presence: bool);
|
async fn on_presence(&mut self, presence: bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
#[device_trait]
|
pub trait OnDarkness: Sync + Send {
|
||||||
pub trait OnDarkness {
|
|
||||||
async fn on_darkness(&mut self, dark: bool);
|
async fn on_darkness(&mut self, dark: bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
#[device_trait]
|
pub trait OnNotification: Sync + Send {
|
||||||
pub trait OnNotification {
|
|
||||||
async fn on_notification(&mut self, notification: Notification);
|
async fn on_notification(&mut self, notification: Notification);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,9 @@ use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use impl_cast::device_trait;
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
#[device_trait]
|
pub trait Timeout: Sync + Send {
|
||||||
pub trait Timeout {
|
|
||||||
async fn start_timeout(&mut self, _timeout: Duration) -> Result<()>;
|
async fn start_timeout(&mut self, _timeout: Duration) -> Result<()>;
|
||||||
async fn stop_timeout(&mut self) -> Result<()>;
|
async fn stop_timeout(&mut self) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user