Added initial basic washer integration
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
690090016e
commit
ee00959e8a
|
@ -83,3 +83,7 @@ speakers = "living_speakers"
|
||||||
type = "ContactSensor"
|
type = "ContactSensor"
|
||||||
topic = "zigbee2mqtt/hallway/frontdoor"
|
topic = "zigbee2mqtt/hallway/frontdoor"
|
||||||
presence = { timeout = 900 }
|
presence = { timeout = 900 }
|
||||||
|
|
||||||
|
[devices.bathroom_washer]
|
||||||
|
type = "Washer"
|
||||||
|
topic = "zigbee2mqtt/bathroom/washer"
|
||||||
|
|
|
@ -84,3 +84,7 @@ type = "ContactSensor"
|
||||||
topic = "zigbee2mqtt/hallway/frontdoor"
|
topic = "zigbee2mqtt/hallway/frontdoor"
|
||||||
presence = { timeout = 10 }
|
presence = { timeout = 10 }
|
||||||
lights = { lights = ["bathroom_light"], timeout = 10 }
|
lights = { lights = ["bathroom_light"], timeout = 10 }
|
||||||
|
|
||||||
|
[devices.bathroom_washer]
|
||||||
|
type = "Washer"
|
||||||
|
topic = "zigbee2mqtt/bathroom/washer"
|
||||||
|
|
|
@ -15,7 +15,7 @@ use crate::{
|
||||||
device_manager::DeviceManager,
|
device_manager::DeviceManager,
|
||||||
devices::{
|
devices::{
|
||||||
AudioSetup, ContactSensor, DebugBridgeConfig, Device, HueBridgeConfig, IkeaOutlet,
|
AudioSetup, ContactSensor, DebugBridgeConfig, Device, HueBridgeConfig, IkeaOutlet,
|
||||||
KasaOutlet, LightSensorConfig, PresenceConfig, WakeOnLAN,
|
KasaOutlet, LightSensorConfig, PresenceConfig, WakeOnLAN, Washer,
|
||||||
},
|
},
|
||||||
error::{ConfigParseError, CreateDeviceError, MissingEnv},
|
error::{ConfigParseError, CreateDeviceError, MissingEnv},
|
||||||
event::EventChannel,
|
event::EventChannel,
|
||||||
|
@ -130,6 +130,7 @@ pub enum DeviceConfig {
|
||||||
IkeaOutlet(<IkeaOutlet as CreateDevice>::Config),
|
IkeaOutlet(<IkeaOutlet as CreateDevice>::Config),
|
||||||
KasaOutlet(<KasaOutlet as CreateDevice>::Config),
|
KasaOutlet(<KasaOutlet as CreateDevice>::Config),
|
||||||
WakeOnLAN(<WakeOnLAN as CreateDevice>::Config),
|
WakeOnLAN(<WakeOnLAN as CreateDevice>::Config),
|
||||||
|
Washer(<Washer as CreateDevice>::Config),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
@ -200,7 +201,8 @@ impl DeviceConfig {
|
||||||
ContactSensor,
|
ContactSensor,
|
||||||
IkeaOutlet,
|
IkeaOutlet,
|
||||||
KasaOutlet,
|
KasaOutlet,
|
||||||
WakeOnLAN
|
WakeOnLAN,
|
||||||
|
Washer
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ mod light_sensor;
|
||||||
mod ntfy;
|
mod ntfy;
|
||||||
mod presence;
|
mod presence;
|
||||||
mod wake_on_lan;
|
mod wake_on_lan;
|
||||||
|
mod washer;
|
||||||
|
|
||||||
pub use self::audio_setup::AudioSetup;
|
pub use self::audio_setup::AudioSetup;
|
||||||
pub use self::contact_sensor::ContactSensor;
|
pub use self::contact_sensor::ContactSensor;
|
||||||
|
@ -19,6 +20,7 @@ pub use self::light_sensor::{LightSensor, LightSensorConfig};
|
||||||
pub use self::ntfy::{Notification, Ntfy};
|
pub use self::ntfy::{Notification, Ntfy};
|
||||||
pub use self::presence::{Presence, PresenceConfig, DEFAULT_PRESENCE};
|
pub use self::presence::{Presence, PresenceConfig, DEFAULT_PRESENCE};
|
||||||
pub use self::wake_on_lan::WakeOnLAN;
|
pub use self::wake_on_lan::WakeOnLAN;
|
||||||
|
pub use self::washer::Washer;
|
||||||
|
|
||||||
use google_home::{device::AsGoogleHomeDevice, traits::OnOff};
|
use google_home::{device::AsGoogleHomeDevice, traits::OnOff};
|
||||||
|
|
||||||
|
|
98
src/devices/washer.rs
Normal file
98
src/devices/washer.rs
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use rumqttc::{AsyncClient, Publish};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use tracing::{error, warn};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::{CreateDevice, MqttDeviceConfig},
|
||||||
|
device_manager::DeviceManager,
|
||||||
|
error::CreateDeviceError,
|
||||||
|
event::{Event, EventChannel, OnMqtt},
|
||||||
|
messages::PowerMessage,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{ntfy::Priority, Device, Notification};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct WasherConfig {
|
||||||
|
#[serde(flatten)]
|
||||||
|
mqtt: MqttDeviceConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add google home integration
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Washer {
|
||||||
|
identifier: String,
|
||||||
|
mqtt: MqttDeviceConfig,
|
||||||
|
|
||||||
|
event_channel: EventChannel,
|
||||||
|
running: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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(),
|
||||||
|
running: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Device for Washer {
|
||||||
|
fn get_id(&self) -> &str {
|
||||||
|
&self.identifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl OnMqtt for Washer {
|
||||||
|
fn topics(&self) -> Vec<&str> {
|
||||||
|
vec![&self.mqtt.topic]
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_mqtt(&mut self, message: Publish) {
|
||||||
|
let power = match PowerMessage::try_from(message) {
|
||||||
|
Ok(state) => state.power(),
|
||||||
|
Err(err) => {
|
||||||
|
error!(id = self.identifier, "Failed to parse message: {err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.running && power < 1.0 {
|
||||||
|
// The washer is done running
|
||||||
|
self.running = false;
|
||||||
|
let notification = Notification::new()
|
||||||
|
.set_title("Laundy is done")
|
||||||
|
.set_message("Don't forget to hang it!")
|
||||||
|
.add_tag("womans_clothes")
|
||||||
|
.set_priority(Priority::High);
|
||||||
|
|
||||||
|
if self
|
||||||
|
.event_channel
|
||||||
|
.get_tx()
|
||||||
|
.send(Event::Ntfy(notification))
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
warn!("There are no receivers on the event channel");
|
||||||
|
}
|
||||||
|
} else if !self.running && power >= 1.0 {
|
||||||
|
// We just started washing
|
||||||
|
self.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ pub enum Event {
|
||||||
pub type Sender = mpsc::Sender<Event>;
|
pub type Sender = mpsc::Sender<Event>;
|
||||||
pub type Receiver = mpsc::Receiver<Event>;
|
pub type Receiver = mpsc::Receiver<Event>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct EventChannel(Sender);
|
pub struct EventChannel(Sender);
|
||||||
|
|
||||||
impl EventChannel {
|
impl EventChannel {
|
||||||
|
|
|
@ -119,7 +119,7 @@ impl TryFrom<Publish> for PresenceMessage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message use to report the state of a light sensor
|
// Message used to report the state of a light sensor
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct BrightnessMessage {
|
pub struct BrightnessMessage {
|
||||||
illuminance: isize,
|
illuminance: isize,
|
||||||
|
@ -194,3 +194,24 @@ impl TryFrom<Publish> for DarknessMessage {
|
||||||
.or(Err(ParseError::InvalidPayload(message.payload.clone())))
|
.or(Err(ParseError::InvalidPayload(message.payload.clone())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Message used to report the power draw a smart plug
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct PowerMessage {
|
||||||
|
power: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PowerMessage {
|
||||||
|
pub fn power(&self) -> f32 {
|
||||||
|
self.power
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Publish> for PowerMessage {
|
||||||
|
type Error = ParseError;
|
||||||
|
|
||||||
|
fn try_from(message: Publish) -> Result<Self, Self::Error> {
|
||||||
|
serde_json::from_slice(&message.payload)
|
||||||
|
.or(Err(ParseError::InvalidPayload(message.payload.clone())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user