From db17b68e904d311b7c44f7055431049b95823bed Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Fri, 17 Nov 2023 00:01:13 +0100 Subject: [PATCH] Feature: Schedule devices turning on/off --- Cargo.lock | 47 ++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + config/config.yml | 12 +++++++++- config/zeus.dev.yml | 13 +++++++++-- src/config.rs | 2 ++ src/device_manager.rs | 52 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/main.rs | 2 ++ src/schedule.rs | 17 ++++++++++++++ 9 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 src/schedule.rs diff --git a/Cargo.lock b/Cargo.lock index 0a5d579..4c319e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,6 +98,7 @@ dependencies = [ "serde_yaml", "thiserror", "tokio", + "tokio-cron-scheduler", "tracing", "tracing-subscriber", "wakey", @@ -292,6 +293,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "cron" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff76b51e4c068c52bfd2866e1567bee7c567ae8f24ada09fd4307019e25eab7" +dependencies = [ + "chrono", + "nom", + "once_cell", +] + [[package]] name = "crossbeam-channel" version = "0.5.8" @@ -919,6 +931,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "num-traits" version = "0.2.16" @@ -1637,6 +1660,21 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "tokio-cron-scheduler" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de2c1fd54a857b29c6cd1846f31903d0ae8e28175615c14a277aed45c58d8e27" +dependencies = [ + "chrono", + "cron", + "num-derive", + "num-traits", + "tokio", + "tracing", + "uuid", +] + [[package]] name = "tokio-io-timeout" version = "1.2.0" @@ -1877,6 +1915,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "uuid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" +dependencies = [ + "getrandom", +] + [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 8189dd8..9a78143 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ serde_with = "3.2.0" enum_dispatch = "0.3.12" indexmap = { version = "2.0.0", features = ["serde"] } serde_yaml = "0.9.27" +tokio-cron-scheduler = "0.9.4" [patch.crates-io] wakey = { git = "https://git.huizinga.dev/Dreaded_X/wakey" } diff --git a/config/config.yml b/config/config.yml index d725b79..6986e49 100644 --- a/config/config.yml +++ b/config/config.yml @@ -117,8 +117,18 @@ devices: timeout: 60 - bedroom_air_filter: + &air_filter bedroom_air_filter: !AirFilter name: "Air Filter" room: "Bedroom" topic: "pico/filter/test" + +# Run the air filter everyday for 19:00 to 20:00 +schedule: + 0 0 19 * * *: + on: + - *air_filter + + 0 0 20 * * *: + off: + - *air_filter diff --git a/config/zeus.dev.yml b/config/zeus.dev.yml index ffb6f92..d9ceb7f 100644 --- a/config/zeus.dev.yml +++ b/config/zeus.dev.yml @@ -89,7 +89,7 @@ devices: topic: "zigbee2mqtt/workbench/charger" timeout: 5 - workbench_outlet: + &outlet workbench_outlet: !IkeaOutlet name: "Outlet" room: "Workbench" @@ -121,4 +121,13 @@ devices: !AirFilter name: "Air Filter" room: "Bedroom" - topic: "pio/filter/test" + topic: "pico/filter/test" + +# schedule: +# 0/30 * * * * *: +# on: +# - *outlet +# +# 15/30 * * * * *: +# off: +# - *outlet diff --git a/src/config.rs b/src/config.rs index dd09abe..89cfee5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,6 +15,7 @@ use crate::{ device_manager::DeviceConfigs, devices::PresenceConfig, error::{ConfigParseError, MissingEnv}, + schedule::Schedule, }; #[derive(Debug, Deserialize)] @@ -27,6 +28,7 @@ pub struct Config { pub ntfy: Option, pub presence: PresenceConfig, pub devices: IndexMap, + pub schedule: Schedule, } #[derive(Debug, Clone, Deserialize)] diff --git a/src/device_manager.rs b/src/device_manager.rs index ba2d412..02c9e24 100644 --- a/src/device_manager.rs +++ b/src/device_manager.rs @@ -4,9 +4,11 @@ use std::sync::Arc; use async_trait::async_trait; use enum_dispatch::enum_dispatch; use futures::future::join_all; +use google_home::traits::OnOff; use rumqttc::{matches, AsyncClient, QoS}; use serde::Deserialize; use tokio::sync::{RwLock, RwLockReadGuard}; +use tokio_cron_scheduler::{Job, JobScheduler}; use tracing::{debug, error, instrument, trace}; use crate::{ @@ -20,6 +22,7 @@ use crate::{ event::OnNotification, event::OnPresence, event::{Event, EventChannel, OnMqtt}, + schedule::{Action, Schedule}, }; pub struct ConfigExternal<'a> { @@ -90,6 +93,55 @@ impl DeviceManager { device_manager } + // TODO: This function is currently extremely cursed... + pub async fn add_schedule(&self, schedule: Schedule) { + let sched = JobScheduler::new().await.unwrap(); + + for (when, actions) in schedule { + let manager = self.clone(); + sched + .add( + Job::new_async(when.as_str(), move |_uuid, _l| { + let actions = actions.clone(); + let manager = manager.clone(); + + Box::pin(async move { + for (action, targets) in actions { + for target in targets { + let device = manager.get(&target).await.unwrap(); + match action { + Action::On => { + As::::cast_mut( + device.write().await.as_mut(), + ) + .unwrap() + .set_on(true) + .await + .unwrap(); + } + Action::Off => { + As::::cast_mut( + device.write().await.as_mut(), + ) + .unwrap() + .set_on(false) + .await + .unwrap(); + } + } + } + } + }) + }) + .unwrap(), + ) + .await + .unwrap(); + } + + sched.start().await.unwrap(); + } + pub async fn add(&self, device: Box) { let id = device.get_id().into(); diff --git a/src/lib.rs b/src/lib.rs index 3c12fc4..931fcb5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,4 +9,5 @@ pub mod error; pub mod event; pub mod messages; pub mod mqtt; +pub mod schedule; pub mod traits; diff --git a/src/main.rs b/src/main.rs index f4c254a..8412f51 100644 --- a/src/main.rs +++ b/src/main.rs @@ -64,6 +64,8 @@ async fn app() -> anyhow::Result<()> { device_manager.create(&id, device_config).await?; } + device_manager.add_schedule(config.schedule).await; + let event_channel = device_manager.event_channel(); // Create and add the presence system diff --git a/src/schedule.rs b/src/schedule.rs new file mode 100644 index 0000000..3300629 --- /dev/null +++ b/src/schedule.rs @@ -0,0 +1,17 @@ +use indexmap::IndexMap; +use serde::Deserialize; + +#[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone, Copy)] +#[serde(rename_all = "snake_case")] +pub enum Action { + On, + Off, +} + +pub type Schedule = IndexMap>>; + +// #[derive(Debug, Deserialize)] +// pub struct Schedule { +// pub when: String, +// pub actions: IndexMap>, +// }