From 6d5340e9ca706424354abea5a803c11589861f6a Mon Sep 17 00:00:00 2001 From: LailaTheElf Date: Tue, 6 May 2025 23:37:44 +0200 Subject: [PATCH] first consept for configurable automation --- Cargo.lock | 208 ++++++++++++++++++ Cargo.toml | 1 + mqttAutomation.yml | 14 ++ src/automation/auto.rs | 48 ++++ src/automation/auto/config.rs | 30 +++ src/automation/auto/config/triggers.rs | 33 +++ .../auto/config/triggers/datetime.rs | 153 +++++++++++++ src/automation/auto/triggers.rs | 15 ++ src/automation/mod.rs | 13 +- 9 files changed, 513 insertions(+), 2 deletions(-) create mode 100644 src/automation/auto.rs create mode 100644 src/automation/auto/config.rs create mode 100644 src/automation/auto/config/triggers.rs create mode 100644 src/automation/auto/config/triggers/datetime.rs create mode 100644 src/automation/auto/triggers.rs diff --git a/Cargo.lock b/Cargo.lock index fd4d06f..9f1fb61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,21 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -44,6 +59,12 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + [[package]] name = "bytes" version = "1.9.0" @@ -65,6 +86,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -208,6 +243,30 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "indexmap" version = "2.7.1" @@ -224,6 +283,16 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "json" version = "0.12.4" @@ -291,6 +360,7 @@ dependencies = [ name = "mqttAutomation" version = "1.3.1" dependencies = [ + "chrono", "crossbeam", "json", "mqtt-client", @@ -299,6 +369,15 @@ dependencies = [ "serde_yaml", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.36.7" @@ -308,6 +387,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + [[package]] name = "openssl-probe" version = "0.1.5" @@ -436,6 +521,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + [[package]] name = "ryu" version = "1.0.18" @@ -646,6 +737,123 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 1c98056..50f44af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ rumqttc = "0.24.0" serde = { version = "1.0.217", features = ["derive"] } serde_yaml = "0.9.34" mqtt-client = { tag = "v4.0.0", git = "https://gitea.finnvanreenen.nl/LailaTheElf/mqttClient.git" } +chrono = "0.4.41" diff --git a/mqttAutomation.yml b/mqttAutomation.yml index 8e45f92..3124c0d 100644 --- a/mqttAutomation.yml +++ b/mqttAutomation.yml @@ -8,3 +8,17 @@ automation: base_topic: "/kees/automation/" alarm_hour: 7 alarm_minute: 0 + +# automation: +# - trigger: +# - type: time +# date: +# weekday: [0,1,2,3,4] +# time: +# hour: 7 +# minute: 0 +# seconds: 0 +# action: +# - type: publish +# topic: "/cool/devices/lamp-01/set" +# pauload: "ON" diff --git a/src/automation/auto.rs b/src/automation/auto.rs new file mode 100644 index 0000000..458211a --- /dev/null +++ b/src/automation/auto.rs @@ -0,0 +1,48 @@ +mod config; + + +pub mod auto_generator { + use std::collections::HashMap; + + pub use crate::automation::auto::config::automation_config as config; + + struct TimeTrigger { + next: u64, + time: config::triggers::Datetime + } + + pub struct Generator { + mqtt_triggers: HashMap, + time_triggers: HashMap, + } + impl Generator { + + pub fn new() -> Generator { + Generator { + mqtt_triggers: HashMap::new(), + time_triggers: HashMap::new() + } + } + + pub fn read_automations(&mut self, automations: Vec) { + for auto in automations { + self.read_automation_single(auto); + } + } + + fn read_automation_single(&mut self, auto: config::Automation) { + let auto_cloned = auto.clone(); + match auto.trigger { + config::triggers::Trigger::Mqtt(mqtt) => { + self.mqtt_triggers.insert(mqtt.topic, auto_cloned); + }, + config::triggers::Trigger::Time(datetime) => { + self.time_triggers.insert(mqtt.topic, auto_cloned); + },, + } + } + + } + + +} diff --git a/src/automation/auto/config.rs b/src/automation/auto/config.rs new file mode 100644 index 0000000..aaeee48 --- /dev/null +++ b/src/automation/auto/config.rs @@ -0,0 +1,30 @@ +mod triggers; + +pub mod automation_config { + use serde::Deserialize; + + pub use crate::automation::auto::config::triggers::triggers; + + mod actions { + use serde::Deserialize; + + #[derive(Deserialize, Clone)] + struct Publish { + topic: String, + payload: Option, + retain: Option + } + + #[derive(Deserialize, Clone)] + pub enum Action { + Publish(Publish), + } + } + + #[derive(Deserialize, Clone)] + pub struct Automation { + pub trigger: triggers::Trigger, + pub actions: Vec + } + +} diff --git a/src/automation/auto/config/triggers.rs b/src/automation/auto/config/triggers.rs new file mode 100644 index 0000000..43f9c86 --- /dev/null +++ b/src/automation/auto/config/triggers.rs @@ -0,0 +1,33 @@ +mod datetime; + +pub mod triggers { + use serde::Deserialize; + + pub trait TriggerBase { + fn get_next_trigger(&self) -> &String; + } + + #[derive(Deserialize, Clone)] + enum StringOrU16 { + String(String), + U16(u16) + } + + #[derive(Deserialize, Clone)] + pub struct Mqtt { + pub topic: String, + pub payload: Option + } + impl TriggerBase for Mqtt { + fn get_next_trigger(&self) -> &String { + &self.topic + } + } + + + #[derive(Deserialize, Clone)] + pub enum Trigger { + Mqtt(Mqtt), + Time(Datetime), + } +} diff --git a/src/automation/auto/config/triggers/datetime.rs b/src/automation/auto/config/triggers/datetime.rs new file mode 100644 index 0000000..381d41a --- /dev/null +++ b/src/automation/auto/config/triggers/datetime.rs @@ -0,0 +1,153 @@ + + +pub mod trigger_datetime { + use std::collections::btree_map::Range; + + use serde::Deserialize; + use crate::automation::auto::config::triggers::triggers::TriggerBase; + use chrono::{DateTime, Datelike, Local, Timelike}; + + fn get_u16_from_u32(value: u32) -> Option { + match u16::try_from(value) { + Ok(n) => Some(n), + Err(_) => None + } + } + fn get_u16_from_i32(value: i32) -> Option { + match u16::try_from(value) { + Ok(n) => Some(n), + Err(_) => None + } + } + + pub enum CompareError { + InvalidValue + DivNaN + } + + #[derive(Deserialize, Clone)] + enum StringOrU16 { + String(String), + U16(u16) + } + + #[derive(Deserialize, Clone)] + struct Date { + year: Option, + month: Option, + date: Option, + weekday: Option + } + #[derive(Deserialize, Clone)] + struct Time { + hour: Option, + minute: Option, + second: Option + } + + #[derive(Deserialize, Clone)] + pub struct Datetime { + date: Date, + time: Time + } + impl Datetime { + pub fn first_match(condition: String, start: u16, max: u16) -> Result, CompareError> { + if condition == "*" { + return Ok(Some(start)); + } + if condition.starts_with("*/") { + let divs = condition.split('/'); + let mut options: Vec = (start..max).collect(); + for div in divs { + match div.parse::() { + Ok(n) => { + options.retain(|x| x % n == 0); + }, + Err(_) => { + return Err(CompareError::DivNaN); + }, + } + } + let min = options.iter().min(); + match min { + Some(min) => { + return Ok(Some(min.clone())); + }, + None => { + return Ok(None); + }, + } + } + if condition.split(',').count() > 0 { + let parts = condition.split(','); + let mut options: Vec = [].to_vec(); + for part in parts { + match part.parse::() { + Ok(n) => { + options.push(n); + }, + Err(_) => { + return Err(CompareError::DivNaN); + }, + } + } + options.retain(|x| *x >= start && *x <= max); + let min = options.iter().min(); + match min { + Some(min) => { + return Ok(Some(min.clone())); + }, + None => { + return Ok(None); + }, + } + } + Err(CompareError::InvalidValue) + } + + fn find_next_trigger(&self, from: DateTime) -> DateTime { + let year: u16 = u16::MAX; + let month: u16 = u16::MAX; + let date: u16 = u16::MAX; + let weekday: u16 = u16::MAX; + let hour: u16 = u16::MAX; + let minute: u16 = u16::MAX; + let second: u16 = u16::MAX; + + let a_next_time: bool = true; + + // year + match self.date.year { + Some(y) => { + match y { + StringOrU16::String(y) => { + match self::Datetime::first_match(y, get_u16_from_i32(from.year()).unwrap(), u16::MAX) { + Ok(r) => { + match r { + Some(y) => year = y, + None => todo!(), + } + }, + Err(_) => todo!(), + } + }, + StringOrU16::U16(y) => { + if y == get_u16_from_u32(from.year()) { + year = y; + } + }, + } + + }, + None => todo!(), + } + + todo!() + } + } + impl TriggerBase for Datetime { + fn get_next_trigger(&self) -> &String { + let next_trigger = self.find_next_trigger(Local::now()) + } + } +} diff --git a/src/automation/auto/triggers.rs b/src/automation/auto/triggers.rs new file mode 100644 index 0000000..bf4df50 --- /dev/null +++ b/src/automation/auto/triggers.rs @@ -0,0 +1,15 @@ + +use + +pub trait TriggerBase { + fn key() +} +pub mod triggers { +} + + +pub impl clock { + + + +} diff --git a/src/automation/mod.rs b/src/automation/mod.rs index d0134b8..492674f 100644 --- a/src/automation/mod.rs +++ b/src/automation/mod.rs @@ -1,5 +1,7 @@ mod json; +//AUTO mod auto; +//AUTO use std::collections::HashMap; use std::{thread, time::Duration}; use serde::Deserialize; @@ -7,13 +9,16 @@ use mqtt_client::{MqttMessage, MqttEvent, Sender, Receiver, QoS}; use mqtt_client::mqtt_client; use crate::automation::json::json_parser; +//AUTO use crate::automation::auto::auto_generator::{config::Automation as AutoConfig, Generator}; #[derive(Deserialize)] pub struct SettingsConf { base_topic: String, alarm_hour: u8, - alarm_minute: u8 + alarm_minute: u8, + + // auto: AutoConfig } struct PingStats { @@ -53,7 +58,9 @@ pub struct Automation { ping_elfdesktop: PingStats, - config: SettingsConf + config: SettingsConf, + + //AUTO generator: Generator } @@ -340,6 +347,8 @@ impl mqtt_client::MqttTool for Automation { ping_elfdesktop: { PingStats { total: 0, fails: 0, avg: 0.0 } }, config: config, + + //AUTO generator: Generator::new() } }