Improved the way devices are instantiated from their respective configs
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2023-08-16 02:17:21 +02:00
parent ab5e47d1ff
commit b1506f8e63
15 changed files with 530 additions and 364 deletions

View File

@@ -5,6 +5,7 @@ use std::{
};
use async_trait::async_trait;
use enum_dispatch::enum_dispatch;
use regex::{Captures, Regex};
use rumqttc::{AsyncClient, MqttOptions, Transport};
use serde::{Deserialize, Deserializer};
@@ -14,10 +15,11 @@ use crate::{
auth::OpenIDConfig,
device_manager::DeviceManager,
devices::{
AudioSetup, ContactSensor, DebugBridgeConfig, Device, HueBridgeConfig, HueLight,
IkeaOutlet, KasaOutlet, LightSensorConfig, PresenceConfig, WakeOnLAN, Washer,
AudioSetupConfig, ContactSensorConfig, DebugBridgeConfig, Device, HueBridgeConfig,
HueLightConfig, IkeaOutletConfig, KasaOutletConfig, LightSensorConfig, PresenceConfig,
WakeOnLANConfig, WasherConfig,
},
error::{ConfigParseError, CreateDeviceError, MissingEnv},
error::{ConfigParseError, DeviceConfigError, MissingEnv},
event::EventChannel,
};
@@ -34,7 +36,7 @@ pub struct Config {
pub hue_bridge: Option<HueBridgeConfig>,
pub debug_bridge: Option<DebugBridgeConfig>,
#[serde(default, with = "tuple_vec_map")]
pub devices: Vec<(String, DeviceConfig)>,
pub devices: Vec<(String, DeviceConfigs)>,
}
#[derive(Debug, Clone, Deserialize)]
@@ -122,18 +124,6 @@ pub struct MqttDeviceConfig {
pub topic: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "type")]
pub enum DeviceConfig {
AudioSetup(<AudioSetup as CreateDevice>::Config),
ContactSensor(<ContactSensor as CreateDevice>::Config),
IkeaOutlet(<IkeaOutlet as CreateDevice>::Config),
KasaOutlet(<KasaOutlet as CreateDevice>::Config),
WakeOnLAN(<WakeOnLAN as CreateDevice>::Config),
Washer(<Washer as CreateDevice>::Config),
HueLight(<HueLight as CreateDevice>::Config),
}
impl Config {
pub fn parse_file(filename: &str) -> Result<Self, ConfigParseError> {
debug!("Loading config: {filename}");
@@ -162,50 +152,32 @@ impl Config {
}
}
pub struct ConfigExternal<'a> {
pub client: &'a AsyncClient,
pub device_manager: &'a DeviceManager,
pub presence_topic: &'a str,
pub event_channel: &'a EventChannel,
}
#[async_trait]
pub trait CreateDevice {
type Config;
#[enum_dispatch]
pub trait DeviceConfig {
async fn create(
identifier: &str,
config: Self::Config,
event_channel: &EventChannel,
client: &AsyncClient,
// TODO: Not a big fan of passing in the global config
presence_topic: &str,
devices: &DeviceManager,
) -> Result<Self, CreateDeviceError>
where
Self: Sized;
}
macro_rules! create {
(($self:ident, $id:ident, $event_channel:ident, $client:ident, $presence_topic:ident, $device_manager:ident), [ $( $Variant:ident ),* ]) => {
match $self {
$(DeviceConfig::$Variant(c) => Box::new($Variant::create($id, c, $event_channel, $client, $presence_topic, $device_manager).await?),)*
}
};
}
impl DeviceConfig {
pub async fn create(
self,
id: &str,
event_channel: &EventChannel,
client: &AsyncClient,
presence_topic: &str,
device_manager: &DeviceManager,
) -> Result<Box<dyn Device>, CreateDeviceError> {
Ok(create! {
(self, id, event_channel, client, presence_topic, device_manager), [
AudioSetup,
ContactSensor,
IkeaOutlet,
KasaOutlet,
WakeOnLAN,
Washer,
HueLight
]
})
}
identifier: &str,
ext: &ConfigExternal,
) -> Result<Box<dyn Device>, DeviceConfigError>;
}
#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
#[enum_dispatch(DeviceConfig)]
pub enum DeviceConfigs {
AudioSetup(AudioSetupConfig),
ContactSensor(ContactSensorConfig),
IkeaOutlet(IkeaOutletConfig),
KasaOutlet(KasaOutletConfig),
WakeOnLAN(WakeOnLANConfig),
Washer(WasherConfig),
HueLight(HueLightConfig),
}

View File

@@ -1,15 +1,13 @@
use async_trait::async_trait;
use google_home::traits::OnOff;
use rumqttc::AsyncClient;
use serde::Deserialize;
use tracing::{debug, error, trace, warn};
use crate::{
config::{CreateDevice, MqttDeviceConfig},
device_manager::{DeviceManager, WrappedDevice},
config::{ConfigExternal, DeviceConfig, MqttDeviceConfig},
device_manager::WrappedDevice,
devices::As,
error::CreateDeviceError,
event::EventChannel,
error::DeviceConfigError,
event::OnMqtt,
event::OnPresence,
messages::{RemoteAction, RemoteMessage},
@@ -25,63 +23,66 @@ pub struct AudioSetupConfig {
speakers: String,
}
#[async_trait]
impl DeviceConfig for AudioSetupConfig {
async fn create(
self,
identifier: &str,
ext: &ConfigExternal,
) -> Result<Box<dyn Device>, DeviceConfigError> {
trace!(id = identifier, "Setting up AudioSetup");
// TODO: Make sure they implement OnOff?
let mixer = ext
.device_manager
.get(&self.mixer)
.await
// NOTE: We need to clone to make the compiler happy, how ever if this clone happens the next one can never happen...
.ok_or(DeviceConfigError::MissingChild(
identifier.into(),
self.mixer.clone(),
))?;
if !As::<dyn OnOff>::is(mixer.read().await.as_ref()) {
return Err(DeviceConfigError::MissingTrait(self.mixer, "OnOff".into()));
}
let speakers =
ext.device_manager
.get(&self.speakers)
.await
.ok_or(DeviceConfigError::MissingChild(
identifier.into(),
self.speakers.clone(),
))?;
if !As::<dyn OnOff>::is(speakers.read().await.as_ref()) {
return Err(DeviceConfigError::MissingTrait(
self.speakers,
"OnOff".into(),
));
}
let device = AudioSetup {
identifier: identifier.to_owned(),
mqtt: self.mqtt,
mixer,
speakers,
};
Ok(Box::new(device))
}
}
// TODO: We need a better way to store the children devices
#[derive(Debug)]
pub struct AudioSetup {
struct AudioSetup {
identifier: String,
mqtt: MqttDeviceConfig,
mixer: WrappedDevice,
speakers: WrappedDevice,
}
#[async_trait]
impl CreateDevice for AudioSetup {
type Config = AudioSetupConfig;
async fn create(
identifier: &str,
config: Self::Config,
_event_channel: &EventChannel,
_client: &AsyncClient,
_presence_topic: &str,
device_manager: &DeviceManager,
) -> Result<Self, CreateDeviceError> {
trace!(id = identifier, "Setting up AudioSetup");
// TODO: Make sure they implement OnOff?
let mixer = device_manager
.get(&config.mixer)
.await
// NOTE: We need to clone to make the compiler happy, how ever if this clone happens the next one can never happen...
.ok_or(CreateDeviceError::DeviceDoesNotExist(config.mixer.clone()))?;
{
let mixer = mixer.read().await;
if As::<dyn OnOff>::cast(mixer.as_ref()).is_none() {
return Err(CreateDeviceError::OnOffExpected(config.mixer));
}
}
let speakers = device_manager.get(&config.speakers).await.ok_or(
CreateDeviceError::DeviceDoesNotExist(config.speakers.clone()),
)?;
{
let speakers = speakers.read().await;
if As::<dyn OnOff>::cast(speakers.as_ref()).is_none() {
return Err(CreateDeviceError::OnOffExpected(config.speakers));
}
}
Ok(Self {
identifier: identifier.to_owned(),
mqtt: config.mqtt,
mixer,
speakers,
})
}
}
impl Device for AudioSetup {
fn get_id(&self) -> &str {
&self.identifier

View File

@@ -4,15 +4,15 @@ use async_trait::async_trait;
use google_home::traits::OnOff;
use rumqttc::{has_wildcards, AsyncClient};
use serde::Deserialize;
use serde_with::{serde_as, DurationSeconds};
use tokio::task::JoinHandle;
use tracing::{debug, error, trace, warn};
use crate::{
config::{CreateDevice, MqttDeviceConfig},
device_manager::{DeviceManager, WrappedDevice},
config::{ConfigExternal, DeviceConfig, MqttDeviceConfig},
device_manager::WrappedDevice,
devices::{As, DEFAULT_PRESENCE},
error::{CreateDeviceError, MissingWildcard},
event::EventChannel,
error::{DeviceConfigError, MissingWildcard},
event::OnMqtt,
event::OnPresence,
messages::{ContactMessage, PresenceMessage},
@@ -22,11 +22,13 @@ use crate::{
use super::Device;
// NOTE: If we add more presence devices we might need to move this out of here
#[serde_as]
#[derive(Debug, Clone, Deserialize)]
pub struct PresenceDeviceConfig {
#[serde(flatten)]
pub mqtt: Option<MqttDeviceConfig>,
pub timeout: u64, // Timeout in seconds
#[serde_as(as = "DurationSeconds")]
pub timeout: Duration,
}
impl PresenceDeviceConfig {
@@ -56,11 +58,13 @@ impl PresenceDeviceConfig {
}
}
#[serde_as]
#[derive(Debug, Clone, Deserialize)]
pub struct LightsConfig {
lights: Vec<String>,
#[serde(default)]
timeout: u64, // Timeout in seconds
#[serde_as(as = "DurationSeconds")]
pub timeout: Duration,
}
#[derive(Debug, Clone, Deserialize)]
@@ -71,14 +75,66 @@ pub struct ContactSensorConfig {
lights: Option<LightsConfig>,
}
#[async_trait]
impl DeviceConfig for ContactSensorConfig {
async fn create(
self,
identifier: &str,
ext: &ConfigExternal,
) -> Result<Box<dyn Device>, DeviceConfigError> {
trace!(id = identifier, "Setting up ContactSensor");
let presence = self
.presence
.map(|p| p.generate_topic("contact", identifier, ext.presence_topic))
.transpose()?;
let lights =
if let Some(lights_config) = self.lights {
let mut lights = Vec::new();
for name in lights_config.lights {
let light = ext.device_manager.get(&name).await.ok_or(
DeviceConfigError::MissingChild(name.clone(), "OnOff".into()),
)?;
if !As::<dyn OnOff>::is(light.read().await.as_ref()) {
return Err(DeviceConfigError::MissingTrait(name, "OnOff".into()));
}
lights.push((light, false));
}
Some(Lights {
lights,
timeout: lights_config.timeout,
})
} else {
None
};
let device = ContactSensor {
identifier: identifier.to_owned(),
mqtt: self.mqtt,
presence,
client: ext.client.clone(),
overall_presence: DEFAULT_PRESENCE,
is_closed: true,
handle: None,
lights,
};
Ok(Box::new(device))
}
}
#[derive(Debug)]
pub struct Lights {
struct Lights {
lights: Vec<(WrappedDevice, bool)>,
timeout: Duration, // Timeout in seconds
}
#[derive(Debug)]
pub struct ContactSensor {
struct ContactSensor {
identifier: String,
mqtt: MqttDeviceConfig,
presence: Option<PresenceDeviceConfig>,
@@ -91,64 +147,6 @@ pub struct ContactSensor {
lights: Option<Lights>,
}
#[async_trait]
impl CreateDevice for ContactSensor {
type Config = ContactSensorConfig;
async fn create(
identifier: &str,
config: Self::Config,
_event_channel: &EventChannel,
client: &AsyncClient,
presence_topic: &str,
device_manager: &DeviceManager,
) -> Result<Self, CreateDeviceError> {
trace!(id = identifier, "Setting up ContactSensor");
let presence = config
.presence
.map(|p| p.generate_topic("contact", identifier, presence_topic))
.transpose()?;
let lights = if let Some(lights_config) = config.lights {
let mut lights = Vec::new();
for name in lights_config.lights {
let light = device_manager
.get(&name)
.await
.ok_or(CreateDeviceError::DeviceDoesNotExist(name.clone()))?;
{
let light = light.read().await;
if As::<dyn OnOff>::cast(light.as_ref()).is_none() {
return Err(CreateDeviceError::OnOffExpected(name));
}
}
lights.push((light, false));
}
Some(Lights {
lights,
timeout: Duration::from_secs(lights_config.timeout),
})
} else {
None
};
Ok(Self {
identifier: identifier.to_owned(),
mqtt: config.mqtt,
presence,
client: client.clone(),
overall_presence: DEFAULT_PRESENCE,
is_closed: true,
handle: None,
lights,
})
}
}
impl Device for ContactSensor {
fn get_id(&self) -> &str {
&self.identifier
@@ -249,7 +247,7 @@ impl OnMqtt for ContactSensor {
// Once the door is closed again we start a timeout for removing the presence
let client = self.client.clone();
let id = self.identifier.clone();
let timeout = Duration::from_secs(presence.timeout);
let timeout = presence.timeout;
self.handle = Some(tokio::spawn(async move {
debug!(id, "Starting timeout ({timeout:?}) for contact sensor...");
tokio::time::sleep(timeout).await;

View File

@@ -24,14 +24,11 @@ pub struct DebugBridge {
}
impl DebugBridge {
pub fn new(
config: DebugBridgeConfig,
client: &AsyncClient,
) -> Result<Self, crate::error::CreateDeviceError> {
Ok(Self {
pub fn new(config: DebugBridgeConfig, client: &AsyncClient) -> Self {
Self {
mqtt: config.mqtt,
client: client.clone(),
})
}
}
}

View File

@@ -5,14 +5,17 @@ use std::{
use async_trait::async_trait;
use google_home::{errors::ErrorCode, traits::OnOff};
use rumqttc::AsyncClient;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tracing::{debug, error, trace, warn};
use crate::{
config::CreateDevice, device_manager::DeviceManager, devices::Device, error::CreateDeviceError,
event::EventChannel, event::OnDarkness, event::OnPresence, traits::Timeout,
config::{ConfigExternal, DeviceConfig},
devices::Device,
error::DeviceConfigError,
event::OnDarkness,
event::OnPresence,
traits::Timeout,
};
#[derive(Debug)]
@@ -117,8 +120,27 @@ pub struct HueLightConfig {
pub timer_id: isize,
}
#[async_trait]
impl DeviceConfig for HueLightConfig {
async fn create(
self,
identifier: &str,
_ext: &ConfigExternal,
) -> Result<Box<dyn Device>, DeviceConfigError> {
let device = HueLight {
identifier: identifier.to_owned(),
addr: (self.ip, 80).into(),
login: self.login,
light_id: self.light_id,
timer_id: self.timer_id,
};
Ok(Box::new(device))
}
}
#[derive(Debug)]
pub struct HueLight {
struct HueLight {
pub identifier: String,
pub addr: SocketAddr,
pub login: String,
@@ -126,28 +148,6 @@ pub struct HueLight {
pub timer_id: isize,
}
#[async_trait]
impl CreateDevice for HueLight {
type Config = HueLightConfig;
async fn create(
identifier: &str,
config: Self::Config,
_event_channel: &EventChannel,
_client: &AsyncClient,
_presence_topic: &str,
_devices: &DeviceManager,
) -> Result<Self, CreateDeviceError> {
Ok(Self {
identifier: identifier.to_owned(),
addr: (config.ip, 80).into(),
login: config.login,
light_id: config.light_id,
timer_id: config.timer_id,
})
}
}
impl Device for HueLight {
fn get_id(&self) -> &str {
&self.identifier

View File

@@ -8,15 +8,15 @@ use google_home::{
};
use rumqttc::{AsyncClient, Publish};
use serde::Deserialize;
use serde_with::serde_as;
use serde_with::DurationSeconds;
use std::time::Duration;
use tokio::task::JoinHandle;
use tracing::{debug, error, trace, warn};
use crate::config::{CreateDevice, InfoConfig, MqttDeviceConfig};
use crate::device_manager::DeviceManager;
use crate::config::{ConfigExternal, DeviceConfig, InfoConfig, MqttDeviceConfig};
use crate::devices::Device;
use crate::error::CreateDeviceError;
use crate::event::EventChannel;
use crate::error::DeviceConfigError;
use crate::event::OnMqtt;
use crate::event::OnPresence;
use crate::messages::OnOffMessage;
@@ -30,6 +30,7 @@ pub enum OutletType {
Light,
}
#[serde_as]
#[derive(Debug, Clone, Deserialize)]
pub struct IkeaOutletConfig {
#[serde(flatten)]
@@ -38,15 +39,45 @@ pub struct IkeaOutletConfig {
mqtt: MqttDeviceConfig,
#[serde(default = "default_outlet_type")]
outlet_type: OutletType,
timeout: Option<u64>, // Timeout in seconds
#[serde_as(as = "Option<DurationSeconds>")]
timeout: Option<Duration>, // Timeout in seconds
}
fn default_outlet_type() -> OutletType {
OutletType::Outlet
}
#[async_trait]
impl DeviceConfig for IkeaOutletConfig {
async fn create(
self,
identifier: &str,
ext: &ConfigExternal,
) -> Result<Box<dyn Device>, DeviceConfigError> {
trace!(
id = identifier,
name = self.info.name,
room = self.info.room,
"Setting up IkeaOutlet"
);
let device = IkeaOutlet {
identifier: identifier.to_owned(),
info: self.info,
mqtt: self.mqtt,
outlet_type: self.outlet_type,
timeout: self.timeout,
client: ext.client.clone(),
last_known_state: false,
handle: None,
};
Ok(Box::new(device))
}
}
#[derive(Debug)]
pub struct IkeaOutlet {
struct IkeaOutlet {
identifier: String,
info: InfoConfig,
mqtt: MqttDeviceConfig,
@@ -58,38 +89,6 @@ pub struct IkeaOutlet {
handle: Option<JoinHandle<()>>,
}
#[async_trait]
impl CreateDevice for IkeaOutlet {
type Config = IkeaOutletConfig;
async fn create(
identifier: &str,
config: Self::Config,
_event_channel: &EventChannel,
client: &AsyncClient,
_presence_topic: &str,
_device_manager: &DeviceManager,
) -> Result<Self, CreateDeviceError> {
trace!(
id = identifier,
name = config.info.name,
room = config.info.room,
"Setting up IkeaOutlet"
);
Ok(Self {
identifier: identifier.to_owned(),
info: config.info,
mqtt: config.mqtt,
outlet_type: config.outlet_type,
timeout: config.timeout.map(Duration::from_secs),
client: client.clone(),
last_known_state: false,
handle: None,
})
}
}
async fn set_on(client: AsyncClient, topic: &str, on: bool) {
let message = OnOffMessage::new(on);

View File

@@ -9,7 +9,6 @@ use google_home::{
errors::{self, DeviceError},
traits,
};
use rumqttc::AsyncClient;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tokio::{
@@ -19,8 +18,8 @@ use tokio::{
use tracing::trace;
use crate::{
config::CreateDevice, device_manager::DeviceManager, error::CreateDeviceError,
event::EventChannel,
config::{ConfigExternal, DeviceConfig},
error::DeviceConfigError,
};
use super::Device;
@@ -30,33 +29,30 @@ pub struct KasaOutletConfig {
ip: Ipv4Addr,
}
#[derive(Debug)]
pub struct KasaOutlet {
identifier: String,
addr: SocketAddr,
}
#[async_trait]
impl CreateDevice for KasaOutlet {
type Config = KasaOutletConfig;
impl DeviceConfig for KasaOutletConfig {
async fn create(
self,
identifier: &str,
config: Self::Config,
_event_channel: &EventChannel,
_client: &AsyncClient,
_presence_topic: &str,
_device_manager: &DeviceManager,
) -> Result<Self, CreateDeviceError> {
_ext: &ConfigExternal,
) -> Result<Box<dyn Device>, DeviceConfigError> {
trace!(id = identifier, "Setting up KasaOutlet");
Ok(Self {
let device = KasaOutlet {
identifier: identifier.to_owned(),
addr: (config.ip, 9999).into(),
})
addr: (self.ip, 9999).into(),
};
Ok(Box::new(device))
}
}
#[derive(Debug)]
struct KasaOutlet {
identifier: String,
addr: SocketAddr,
}
impl Device for KasaOutlet {
fn get_id(&self) -> &str {
&self.identifier

View File

@@ -10,17 +10,17 @@ mod presence;
mod wake_on_lan;
mod washer;
pub use self::audio_setup::AudioSetup;
pub use self::contact_sensor::ContactSensor;
pub use self::audio_setup::AudioSetupConfig;
pub use self::contact_sensor::ContactSensorConfig;
pub use self::debug_bridge::{DebugBridge, DebugBridgeConfig};
pub use self::hue_bridge::{HueBridge, HueBridgeConfig, HueLight};
pub use self::ikea_outlet::IkeaOutlet;
pub use self::kasa_outlet::KasaOutlet;
pub use self::hue_bridge::{HueBridge, HueBridgeConfig, HueLightConfig};
pub use self::ikea_outlet::IkeaOutletConfig;
pub use self::kasa_outlet::KasaOutletConfig;
pub use self::light_sensor::{LightSensor, LightSensorConfig};
pub use self::ntfy::{Notification, Ntfy};
pub use self::presence::{Presence, PresenceConfig, DEFAULT_PRESENCE};
pub use self::wake_on_lan::WakeOnLAN;
pub use self::washer::Washer;
pub use self::wake_on_lan::WakeOnLANConfig;
pub use self::washer::WasherConfig;
use google_home::{device::AsGoogleHomeDevice, traits::OnOff};

View File

@@ -9,15 +9,13 @@ use google_home::{
types::Type,
GoogleHomeDevice,
};
use rumqttc::{AsyncClient, Publish};
use rumqttc::Publish;
use serde::Deserialize;
use tracing::{debug, error, trace};
use crate::{
config::{CreateDevice, InfoConfig, MqttDeviceConfig},
device_manager::DeviceManager,
error::CreateDeviceError,
event::EventChannel,
config::{ConfigExternal, DeviceConfig, InfoConfig, MqttDeviceConfig},
error::DeviceConfigError,
event::OnMqtt,
messages::ActivateMessage,
};
@@ -39,8 +37,34 @@ fn default_broadcast_ip() -> Ipv4Addr {
Ipv4Addr::new(255, 255, 255, 255)
}
#[async_trait]
impl DeviceConfig for WakeOnLANConfig {
async fn create(
self,
identifier: &str,
_ext: &ConfigExternal,
) -> Result<Box<dyn Device>, DeviceConfigError> {
trace!(
id = identifier,
name = self.info.name,
room = self.info.room,
"Setting up WakeOnLAN"
);
let device = WakeOnLAN {
identifier: identifier.to_owned(),
info: self.info,
mqtt: self.mqtt,
mac_address: self.mac_address,
broadcast_ip: self.broadcast_ip,
};
Ok(Box::new(device))
}
}
#[derive(Debug)]
pub struct WakeOnLAN {
struct WakeOnLAN {
identifier: String,
info: InfoConfig,
mqtt: MqttDeviceConfig,
@@ -48,35 +72,6 @@ pub struct WakeOnLAN {
broadcast_ip: Ipv4Addr,
}
#[async_trait]
impl CreateDevice for WakeOnLAN {
type Config = WakeOnLANConfig;
async fn create(
identifier: &str,
config: Self::Config,
_event_channel: &EventChannel,
_client: &AsyncClient,
_presence_topic: &str,
_device_manager: &DeviceManager,
) -> Result<Self, CreateDeviceError> {
trace!(
id = identifier,
name = config.info.name,
room = config.info.room,
"Setting up WakeOnLAN"
);
Ok(Self {
identifier: identifier.to_owned(),
info: config.info,
mqtt: config.mqtt,
mac_address: config.mac_address,
broadcast_ip: config.broadcast_ip,
})
}
}
impl Device for WakeOnLAN {
fn get_id(&self) -> &str {
&self.identifier

View File

@@ -1,12 +1,11 @@
use async_trait::async_trait;
use rumqttc::{AsyncClient, Publish};
use rumqttc::Publish;
use serde::Deserialize;
use tracing::{debug, error, warn};
use crate::{
config::{CreateDevice, MqttDeviceConfig},
device_manager::DeviceManager,
error::CreateDeviceError,
config::{ConfigExternal, DeviceConfig, MqttDeviceConfig},
error::DeviceConfigError,
event::{Event, EventChannel, OnMqtt},
messages::PowerMessage,
};
@@ -20,10 +19,29 @@ pub struct WasherConfig {
threshold: f32, // Power in Watt
}
#[async_trait]
impl DeviceConfig for WasherConfig {
async fn create(
self,
identifier: &str,
ext: &ConfigExternal,
) -> Result<Box<dyn Device>, DeviceConfigError> {
let device = Washer {
identifier: identifier.to_owned(),
mqtt: self.mqtt,
event_channel: ext.event_channel.clone(),
threshold: self.threshold,
running: 0,
};
Ok(Box::new(device))
}
}
// TODO: Add google home integration
#[derive(Debug)]
pub struct Washer {
struct Washer {
identifier: String,
mqtt: MqttDeviceConfig,
@@ -32,28 +50,6 @@ pub struct Washer {
running: isize,
}
#[async_trait]
impl CreateDevice for Washer {
type Config = WasherConfig;
async fn create(
identifier: &str,
config: Self::Config,
event_channel: &EventChannel,
_client: &AsyncClient,
_presence_topic: &str,
_device_manager: &DeviceManager,
) -> Result<Self, CreateDeviceError> {
Ok(Self {
identifier: identifier.to_owned(),
mqtt: config.mqtt,
event_channel: event_channel.clone(),
threshold: config.threshold,
running: 0,
})
}
}
impl Device for Washer {
fn get_id(&self) -> &str {
&self.identifier

View File

@@ -90,11 +90,11 @@ impl MissingWildcard {
}
#[derive(Debug, Error)]
pub enum CreateDeviceError {
#[error("Child device '{0}' does not exist (yet?)")]
DeviceDoesNotExist(String),
#[error("Expected device '{0}' to implement OnOff trait")]
OnOffExpected(String),
pub enum DeviceConfigError {
#[error("Child '{1}' of device '{0}' does not exist")]
MissingChild(String, String),
#[error("Device '{0}' does not implement expected trait '{1}'")]
MissingTrait(String, String),
#[error(transparent)]
MissingWildcard(#[from] MissingWildcard),
}

View File

@@ -10,7 +10,7 @@ use tracing::{debug, error, info};
use automation::{
auth::{OpenIDConfig, User},
config::Config,
config::{Config, ConfigExternal, DeviceConfig},
device_manager::DeviceManager,
devices::{DebugBridge, HueBridge, LightSensor, Ntfy, Presence},
error::ApiError,
@@ -61,16 +61,15 @@ async fn app() -> anyhow::Result<()> {
let event_channel = device_manager.start();
// Create all the devices specified in the config
let ext = ConfigExternal {
client: &client,
device_manager: &device_manager,
presence_topic: &config.presence.mqtt.topic,
event_channel: &event_channel,
};
for (id, device_config) in config.devices {
let device = device_config
.create(
&id,
&event_channel,
&client,
&config.presence.mqtt.topic,
&device_manager,
)
.await?;
let device = device_config.create(&id, &ext).await?;
device_manager.add(device).await;
}
@@ -95,7 +94,7 @@ async fn app() -> anyhow::Result<()> {
// Start the debug bridge if it is configured
if let Some(config) = config.debug_bridge {
let debug_bridge = DebugBridge::new(config, &client)?;
let debug_bridge = DebugBridge::new(config, &client);
device_manager.add(Box::new(debug_bridge)).await;
}