Added initial basic washer integration
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Dreaded_X 2023-08-14 03:52:00 +02:00
parent 690090016e
commit ee00959e8a
Signed by: Dreaded_X
GPG Key ID: FA5F485356B0D2D4
7 changed files with 135 additions and 4 deletions

View File

@ -83,3 +83,7 @@ speakers = "living_speakers"
type = "ContactSensor"
topic = "zigbee2mqtt/hallway/frontdoor"
presence = { timeout = 900 }
[devices.bathroom_washer]
type = "Washer"
topic = "zigbee2mqtt/bathroom/washer"

View File

@ -84,3 +84,7 @@ type = "ContactSensor"
topic = "zigbee2mqtt/hallway/frontdoor"
presence = { timeout = 10 }
lights = { lights = ["bathroom_light"], timeout = 10 }
[devices.bathroom_washer]
type = "Washer"
topic = "zigbee2mqtt/bathroom/washer"

View File

@ -15,7 +15,7 @@ use crate::{
device_manager::DeviceManager,
devices::{
AudioSetup, ContactSensor, DebugBridgeConfig, Device, HueBridgeConfig, IkeaOutlet,
KasaOutlet, LightSensorConfig, PresenceConfig, WakeOnLAN,
KasaOutlet, LightSensorConfig, PresenceConfig, WakeOnLAN, Washer,
},
error::{ConfigParseError, CreateDeviceError, MissingEnv},
event::EventChannel,
@ -130,6 +130,7 @@ pub enum DeviceConfig {
IkeaOutlet(<IkeaOutlet as CreateDevice>::Config),
KasaOutlet(<KasaOutlet as CreateDevice>::Config),
WakeOnLAN(<WakeOnLAN as CreateDevice>::Config),
Washer(<Washer as CreateDevice>::Config),
}
impl Config {
@ -200,7 +201,8 @@ impl DeviceConfig {
ContactSensor,
IkeaOutlet,
KasaOutlet,
WakeOnLAN
WakeOnLAN,
Washer
]
})
}

View File

@ -8,6 +8,7 @@ mod light_sensor;
mod ntfy;
mod presence;
mod wake_on_lan;
mod washer;
pub use self::audio_setup::AudioSetup;
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::presence::{Presence, PresenceConfig, DEFAULT_PRESENCE};
pub use self::wake_on_lan::WakeOnLAN;
pub use self::washer::Washer;
use google_home::{device::AsGoogleHomeDevice, traits::OnOff};

98
src/devices/washer.rs Normal file
View 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
}
}
}

View File

@ -17,7 +17,7 @@ pub enum Event {
pub type Sender = mpsc::Sender<Event>;
pub type Receiver = mpsc::Receiver<Event>;
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct EventChannel(Sender);
impl EventChannel {

View File

@ -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)]
pub struct BrightnessMessage {
illuminance: isize,
@ -194,3 +194,24 @@ impl TryFrom<Publish> for DarknessMessage {
.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())))
}
}