Config is passed directly to IkeaOutlet and now supports turning off automatically after a specified amount of time

This commit is contained in:
Dreaded_X 2022-12-27 22:26:42 +01:00
parent f735216dc4
commit fb455b4e4c
Signed by: Dreaded_X
GPG Key ID: 76BDEC4E165D8AD9
5 changed files with 76 additions and 34 deletions

View File

@ -11,7 +11,7 @@ username="Dreaded_X"
type = "IkeaOutlet"
info = { name = "Kettle", room = "Kitchen" }
zigbee = { topic = "zigbee2mqtt/kitchen/kettle" }
kettle = {} # This is for future config
kettle = { timeout = 5 }
[devices.living_workbench]
type = "IkeaOutlet"

View File

@ -38,7 +38,7 @@ pub struct ZigbeeDeviceConfig {
#[derive(Debug, Deserialize)]
pub struct KettleConfig {
// @TODO Add options for the kettle
pub timeout: Option<u64>, // Timeout in seconds
}
#[derive(Debug, Deserialize)]

View File

@ -1,37 +1,53 @@
use std::time::Duration;
use google_home::errors::ErrorCode;
use google_home::{GoogleHomeDevice, device, types::Type, traits};
use rumqttc::{AsyncClient, Publish};
use serde::{Deserialize, Serialize};
use log::debug;
use log::{debug, trace};
use tokio::task::JoinHandle;
use crate::config::{KettleConfig, InfoConfig, ZigbeeDeviceConfig};
use crate::devices::Device;
use crate::mqtt::Listener;
pub struct IkeaOutlet {
identifier: String,
name: String,
room: Option<String>,
topic: String,
kettle: bool,
info: InfoConfig,
zigbee: ZigbeeDeviceConfig,
kettle: Option<KettleConfig>,
client: AsyncClient,
last_known_state: bool,
handle: Option<JoinHandle<()>>,
}
impl IkeaOutlet {
pub fn new(identifier: String, name: String, room: Option<String>, kettle: bool, topic: String, client: AsyncClient) -> Self {
pub fn new(identifier: String, info: InfoConfig, zigbee: ZigbeeDeviceConfig, kettle: Option<KettleConfig>, client: AsyncClient) -> Self {
let c = client.clone();
let t = topic.clone();
let t = zigbee.topic.clone();
// @TODO Handle potential errors here
tokio::spawn(async move {
c.subscribe(t, rumqttc::QoS::AtLeastOnce).await.unwrap();
});
Self{ identifier, name, room, kettle, topic, client, last_known_state: false }
Self{ identifier, info, zigbee, kettle, client, last_known_state: false, handle: None }
}
}
async fn set_on(client: AsyncClient, topic: String, on: bool) {
let message = StateMessage{
state: if on {
"ON".to_owned()
} else {
"OFF".to_owned()
}
};
// @TODO Handle potential errors here
client.publish(topic + "/set", rumqttc::QoS::AtLeastOnce, false, serde_json::to_string(&message).unwrap()).await.unwrap();
}
impl Device for IkeaOutlet {
fn get_id(&self) -> String {
self.identifier.clone()
@ -59,19 +75,55 @@ impl From<&Publish> for StateMessage {
impl Listener for IkeaOutlet {
fn notify(&mut self, message: &Publish) {
// Update the internal state based on what the device has reported
if message.topic == self.topic {
let state = StateMessage::from(message);
if message.topic == self.zigbee.topic {
let new_state = StateMessage::from(message).state == "ON";
let new_state = state.state == "ON";
debug!("Updating state: {} => {}", self.last_known_state, new_state);
// No need to do anything if the state has not changed
if new_state == self.last_known_state {
return;
}
// Abort any timer that is currently running
if let Some(handle) = self.handle.take() {
handle.abort();
}
trace!("Updating state: {} => {}", self.last_known_state, new_state);
self.last_known_state = new_state;
// If this is a kettle start a timeout for turning it of again
if new_state {
if let Some(kettle) = &self.kettle {
if let Some(timeout) = kettle.timeout.clone() {
let client = self.client.clone();
let topic = self.zigbee.topic.clone();
// Turn the kettle of after the specified timeout
// @TODO Impl Drop for IkeaOutlet that will abort the handle if the IkeaOutlet
// get dropped
self.handle = Some(
tokio::spawn(async move {
debug!("Starting timeout ({timeout}s) for kettle...");
tokio::time::sleep(Duration::from_secs(timeout)).await;
// @TODO We need to call set_on(false) in order to turn the device off
// again, how are we going to do this?
debug!("Turning kettle off!");
set_on(client, topic, false).await;
})
);
} else {
trace!("Outlet is a kettle without timeout");
}
}
}
}
}
}
impl GoogleHomeDevice for IkeaOutlet {
fn get_device_type(&self) -> Type {
if self.kettle {
if self.kettle.is_some() {
Type::Kettle
} else {
Type::Outlet
@ -79,7 +131,7 @@ impl GoogleHomeDevice for IkeaOutlet {
}
fn get_device_name(&self) -> device::Name {
device::Name::new(&self.name)
device::Name::new(&self.info.name)
}
fn get_id(&self) -> String {
@ -91,7 +143,7 @@ impl GoogleHomeDevice for IkeaOutlet {
}
fn get_room_hint(&self) -> Option<String> {
self.room.clone()
self.info.room.clone()
}
}
@ -101,19 +153,10 @@ impl traits::OnOff for IkeaOutlet {
}
fn set_on(&mut self, on: bool) -> Result<(), ErrorCode> {
let message = StateMessage{
state: if on {
"ON".to_owned()
} else {
"OFF".to_owned()
}
};
// @TODO Handle potential errors here
let client = self.client.clone();
let topic = self.topic.to_owned();
let topic = self.zigbee.topic.clone();
tokio::spawn(async move {
client.publish(topic + "/set", rumqttc::QoS::AtLeastOnce, false, serde_json::to_string(&message).unwrap()).await.unwrap();
set_on(client, topic, on).await;
});
Ok(())

View File

@ -1,3 +1,4 @@
#![feature(specialization)]
pub mod devices;
pub mod mqtt;
pub mod config;

View File

@ -1,8 +1,6 @@
mod config;
use std::{time::Duration, sync::{Arc, RwLock}, process, net::SocketAddr};
use config::Config;
use automation::config::{Config, Device};
use dotenv::dotenv;
use warp::Filter;
use rumqttc::{MqttOptions, Transport, AsyncClient};
@ -59,9 +57,9 @@ async fn main() {
debug!("Adding device {identifier}");
let device: automation::devices::DeviceBox = match device_config {
config::Device::IkeaOutlet { info, zigbee, kettle } => {
Device::IkeaOutlet { info, zigbee, kettle } => {
trace!("\tIkeaOutlet [{} in {:?}]", info.name, info.room);
Box::new(IkeaOutlet::new(identifier, info.name, info.room, kettle.is_some(), zigbee.topic, client.clone()))
Box::new(IkeaOutlet::new(identifier, info, zigbee, kettle, client.clone()))
},
};