From 7106e9fbe2c5eeac8812bf3a648efbe5d428bbdc Mon Sep 17 00:00:00 2001 From: LailaTheElf Date: Sat, 12 Apr 2025 11:34:46 +0200 Subject: [PATCH] change config over mqtt, restructure code --- Cargo.lock | 6 +- Cargo.toml | 4 +- mqttAutomation.yml | 4 + src/automation/json.rs | 80 +++++++++++++++++ src/automation/mod.rs | 199 +++++++++++++++++++++++++++++++++++++++++ src/main.rs | 198 ++-------------------------------------- 6 files changed, 296 insertions(+), 195 deletions(-) create mode 100644 src/automation/json.rs create mode 100644 src/automation/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 31e6f6b..13a9ae3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,8 +280,8 @@ dependencies = [ [[package]] name = "mqtt-client" -version = "2.0.2" -source = "git+https://gitea.finnvanreenen.nl/LailaTheElf/mqttClient.git?tag=v2.0.2#d2d31b2bc16b6d347f49a60a20290aa6880702ae" +version = "3.0.0" +source = "git+https://gitea.finnvanreenen.nl/LailaTheElf/mqttClient.git?tag=v3.0.0#f186e437331b0c8680ac9917d7b88f17bb5f176b" dependencies = [ "crossbeam", "rumqttc", @@ -289,7 +289,7 @@ dependencies = [ [[package]] name = "mqttAutomation" -version = "1.1.4" +version = "1.2.0" dependencies = [ "crossbeam", "json", diff --git a/Cargo.toml b/Cargo.toml index 7e4e105..081df73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mqttAutomation" -version = "1.1.4" +version = "1.2.0" edition = "2021" [dependencies] @@ -9,4 +9,4 @@ json = "0.12.4" rumqttc = "0.24.0" serde = { version = "1.0.217", features = ["derive"] } serde_yaml = "0.9.34" -mqtt-client = { tag = "v2.0.2", git = "https://gitea.finnvanreenen.nl/LailaTheElf/mqttClient.git" } +mqtt-client = { tag = "v3.0.0", git = "https://gitea.finnvanreenen.nl/LailaTheElf/mqttClient.git" } diff --git a/mqttAutomation.yml b/mqttAutomation.yml index c530e6b..8e45f92 100644 --- a/mqttAutomation.yml +++ b/mqttAutomation.yml @@ -4,3 +4,7 @@ mqtt: client: "mqttAutomation01" user: "mqttAutomation" pass: "password" +automation: + base_topic: "/kees/automation/" + alarm_hour: 7 + alarm_minute: 0 diff --git a/src/automation/json.rs b/src/automation/json.rs new file mode 100644 index 0000000..9e3cfd4 --- /dev/null +++ b/src/automation/json.rs @@ -0,0 +1,80 @@ +pub mod json_parser { + pub enum Error { + Null, + InvalidType, + ConvertionFaild, + JsonParseError(String) + } + impl Error { + pub fn to_string(&self) -> String { + match self { + Error::Null => String::from("path not found"), + Error::InvalidType => String::from("invalid type"), + Error::ConvertionFaild => String::from("type convertion faild"), + Error::JsonParseError(s) => s.to_string(), + } + } + } + + pub enum Json { + Value(json::JsonValue), + Text(String) + } + + pub fn get_value(value: json::JsonValue, mut path: Vec) -> json::JsonValue { + if path.len() == 0 { + return value + } + match value { + json::JsonValue::Object(obj) => { + let key = path[0].clone(); + path.remove(0); + get_value(obj[key].clone(), path) + }, + json::JsonValue::Array(a) => { + let key = path[0].clone(); + match key.parse::() { + Ok(i) => { + if i < a.len() { + get_value(a[i].clone(), path) + } else { + json::JsonValue::Null + } + }, + Err(_) => json::JsonValue::Null + } + }, + json::JsonValue::String(_) => json::JsonValue::Null, + json::JsonValue::Short(_) => json::JsonValue::Null, + json::JsonValue::Number(_) => json::JsonValue::Null, + json::JsonValue::Boolean(_) => json::JsonValue::Null, + json::JsonValue::Null => json::JsonValue::Null, + } + } + + pub fn get_u32(data: Json, path: Vec) -> Result { + match data { + Json::Value(value) => match get_value(value, path) { + json::JsonValue::Object(_) => Err(Error::InvalidType), + json::JsonValue::Array(_) => Err(Error::InvalidType), + json::JsonValue::String(_) => Err(Error::InvalidType), + json::JsonValue::Short(_) => Err(Error::InvalidType), + json::JsonValue::Number(num) => { + match u32::try_from(num) { + Err(_) => Err(Error::ConvertionFaild), + Ok(n) => Ok(n) + } + }, + json::JsonValue::Boolean(_) => Err(Error::InvalidType), + json::JsonValue::Null => Err(Error::Null), + } + Json::Text(data) => match json::parse(&data) { + Err(e) => { + Err(Error::JsonParseError(e.to_string())) + }, + Ok(value) => get_u32(Json::Value(value), path) + } + } + + } +} diff --git a/src/automation/mod.rs b/src/automation/mod.rs new file mode 100644 index 0000000..578ac18 --- /dev/null +++ b/src/automation/mod.rs @@ -0,0 +1,199 @@ +mod json; + +use std::{thread, time::Duration}; +use serde::Deserialize; + +use mqtt_client::{MqttMessage, Sender, Receiver, QoS}; +use mqtt_client::mqtt_client; + +use crate::automation::json::json_parser; + +#[derive(Deserialize)] +pub struct SettingsConf { + base_topic: String, + alarm_hour: u8, + alarm_minute: u8 +} + +pub struct Automation { + tx: Sender, + clock_dow: u8, + clock_hour: u8, + clock_min: u8, + + config: SettingsConf +} + + +impl Automation { + fn tx(&self, message: mqtt_client::MqttMessage) { + match self.tx.send(message) { + Err(n) => println!("ERROR: faild to send publish ({:?})", n), + Ok(_n) => {} + } + } + + fn lamp01_set(&self, state: bool) { + let payload: String; + if state { + payload = String::from("ON"); + } else { + payload = String::from("OFF"); + } + self.tx({ mqtt_client::MqttMessage { + topic: String::from("/cool/devices/lamp-01/set"), + payload: payload, + retain: false, + qos: mqtt_client::QoS::AtMostOnce, + }}); + } + + fn alarm(&self) { + self.lamp01_set(true); + } + + fn config_message_in(&mut self, message: mqtt_client::MqttMessage, topic: String) { + if topic.starts_with("alarm/hour") { + + match message.payload.parse::() { + Err(e) => + println!("ERROR: config_message_in: alarm/hour has invalid payload ({:?})", e), + Ok(n) => self.config.alarm_hour = n + } + + } + else if topic.eq("alarm/minute") { + + match message.payload.parse::() { + Err(e) => + println!("ERROR: config_message_in: alarm/minute has invalid payload ({:?})", e), + Ok(n) => self.config.alarm_minute = n + } + + } + } + + fn clock_message_in(&mut self, message: mqtt_client::MqttMessage) { + if message.topic.eq("clock/time/hour") { + + match message.payload.parse::() { + Err(e) => + println!("ERROR: clock_message_in: clock/time/hour has invalid payload ({:?})", e), + Ok(n) => { + self.clock_hour = n; + + if self.config.alarm_minute == 0 && n == self.config.alarm_hour && self.clock_dow < 5 { + self.alarm(); + } + } + } + + } + else if message.topic.eq("clock/time/minute") { + + match message.payload.parse::() { + Err(e) => + println!("ERROR: clock_message_in: clock/time/minute has invalid payload ({:?})", e), + Ok(n) => { + self.clock_min = n; + + if n == self.config.alarm_minute && self.clock_hour == self.config.alarm_hour && self.clock_dow < 5 { + self.alarm(); + } + } + } + + } + else if message.topic.eq("clock/date/dow") { + + match message.payload.parse::() { + Err(e) => + println!("ERROR: clock_message_in: clock/date/dow has invalid payload ({:?})", e), + Ok(n) => self.clock_dow = n + } + + } + // println!("DEBUG: clock_message_in: current time: {}:{}", self.clock_hour, self.clock_min); + } + + fn message_in(&mut self, message: mqtt_client::MqttMessage) { + // println!("DEBUG : mqtt_automation: {}: {}", message.topic, message.payload); + if message.topic.starts_with(&self.config.base_topic) { + let topic = message.topic[self.config.base_topic.len()..].to_string(); + self.config_message_in(message, topic); + } + else if message.topic.starts_with("clock/") { + self.clock_message_in(message); + } + else if message.topic.eq("/cool/devices/KNMITemp/values") { + + let payload_json = json_parser::Json::Text(message.payload); + let path = Vec::from([String::from("gr")]); + match json_parser::get_u32(payload_json, path) { + Ok(gr) => { + if gr > 30 { + self.lamp01_set(false); + } + }, + Err(e) => + print!("ERROR: mqtt_automation: KNMITemp: {}", e.to_string()) + } + + } + } +} + + +impl mqtt_client::MqttTool for Automation { + fn new(client: rumqttc::Client, tx: Sender, config: SettingsConf) -> Automation { + + match client.subscribe("clock/time/hour", QoS::AtMostOnce) { + Err(e) => + println!("ERROR: main: faild to subscribe to clock/time/hour ({})", e), + Ok(_) => {} + } + match client.subscribe("clock/time/minute", QoS::AtMostOnce) { + Err(e) => + println!("ERROR: main: faild to subscribe to clock/time/minute ({})", e), + Ok(_) => {} + } + match client.subscribe("clock/date/dow", QoS::AtMostOnce) { + Err(e) => + println!("ERROR: main: faild to subscribe to clock/date/dow ({})", e), + Ok(_) => {} + } + match client.subscribe("/kees/automation/#", QoS::AtMostOnce) { + Err(e) => + println!("ERROR: main: faild to subscribe to automation/alarm/# ({})", e), + Ok(_) => {} + } + match client.subscribe("/cool/devices/KNMITemp/values", QoS::AtMostOnce) { + Err(e) => + println!("ERROR: main: faild to subscribe to KNMITemp/values ({})", e), + Ok(_) => {} + } + + Automation { + tx, + clock_dow: u8::MAX, + clock_hour: u8::MAX, + clock_min: u8::MAX, + config: config + } + } + + fn run(&mut self, rx: Receiver) { + loop { + let message = rx.recv(); + match message { + Err(e) => { + println!("ERROR: mqttAutomation: failed to receve an message ({})", e); + thread::sleep(Duration::from_millis(500)); + }, + Ok(message) => { + self.message_in(message); + } + } + } + } +} diff --git a/src/main.rs b/src/main.rs index f8c2e11..872f755 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,192 +1,10 @@ -use std::{fs, thread, time::Duration}; - -use mqtt_client::{MqttMessage, Sender, Receiver, QoS}; +use std::fs; use serde::Deserialize; use mqtt_client::mqtt_client; -mod json_parser { - pub enum Error { - Null, - InvalidType, - ConvertionFaild, - JsonParseError(String) - } - impl Error { - pub fn to_string(&self) -> String { - match self { - Error::Null => String::from("path not found"), - Error::InvalidType => String::from("invalid type"), - Error::ConvertionFaild => String::from("type convertion faild"), - Error::JsonParseError(s) => s.to_string(), - } - } - } - - pub enum Json { - Value(json::JsonValue), - Text(String) - } - - pub fn get_value(value: json::JsonValue, mut path: Vec) -> json::JsonValue { - if path.len() == 0 { - return value - } - match value { - json::JsonValue::Object(obj) => { - let key = path[0].clone(); - path.remove(0); - get_value(obj[key].clone(), path) - }, - json::JsonValue::Array(a) => { - let key = path[0].clone(); - match key.parse::() { - Ok(i) => { - if i < a.len() { - get_value(a[i].clone(), path) - } else { - json::JsonValue::Null - } - }, - Err(_) => json::JsonValue::Null - } - }, - json::JsonValue::String(_) => json::JsonValue::Null, - json::JsonValue::Short(_) => json::JsonValue::Null, - json::JsonValue::Number(_) => json::JsonValue::Null, - json::JsonValue::Boolean(_) => json::JsonValue::Null, - json::JsonValue::Null => json::JsonValue::Null, - } - } - - pub fn get_u32(data: Json, path: Vec) -> Result { - match data { - Json::Value(value) => match get_value(value, path) { - json::JsonValue::Object(_) => Err(Error::InvalidType), - json::JsonValue::Array(_) => Err(Error::InvalidType), - json::JsonValue::String(_) => Err(Error::InvalidType), - json::JsonValue::Short(_) => Err(Error::InvalidType), - json::JsonValue::Number(num) => { - match u32::try_from(num) { - Err(_) => Err(Error::ConvertionFaild), - Ok(n) => Ok(n) - } - }, - json::JsonValue::Boolean(_) => Err(Error::InvalidType), - json::JsonValue::Null => Err(Error::Null), - } - Json::Text(data) => match json::parse(&data) { - Err(e) => { - Err(Error::JsonParseError(e.to_string())) - }, - Ok(value) => get_u32(Json::Value(value), path) - } - } - - } -} - -struct Automation { - tx: Sender, - clock_dow: u8 -} -impl Automation { - fn tx(&self, message: mqtt_client::MqttMessage) { - match self.tx.send(message) { - Err(n) => println!("ERROR: faild to send publish ({:?})", n), - Ok(_n) => {} - } - } - - fn lamp01_set(&self, state: bool) { - let payload: String; - if state { - payload = String::from("ON"); - } else { - payload = String::from("OFF"); - } - self.tx({ mqtt_client::MqttMessage { - topic: String::from("/cool/devices/lamp-01/set"), - payload: payload, - retain: false, - qos: mqtt_client::QoS::AtMostOnce, - }}); - } - - fn message_in(&mut self, message: mqtt_client::MqttMessage) { - // println!("INFO : mqtt_automation: {}: {}", message.topic, message.payload); - if message.topic.eq("clock/time/hour") { - - if message.payload.eq("7") && (self.clock_dow < 5) { - self.lamp01_set(true); - } - - } else if message.topic.eq("/cool/devices/KNMITemp/values") { - - let payload = json_parser::Json::Text(message.payload); - match json_parser::get_u32(payload, Vec::from([String::from("gr")])) { - Ok(gr) => { - if gr > 30 { - self.lamp01_set(false); - } - }, - Err(e) => - print!("ERROR: mqtt_automation: KNMITemp: {}", e.to_string()) - } - - } - else if message.topic.eq("clock/date/dow") { - - match message.payload.parse::() { - Err(e) => - println!("ERROR: mqtt_automation: clock/date/dow has invalid payload ({:?})", e), - Ok(n) => self.clock_dow = n - } - - } - } -} -impl mqtt_client::MqttTool for Automation { - fn new(client: rumqttc::Client, tx: Sender) -> Automation { - - match client.subscribe("clock/time/hour", QoS::AtMostOnce) { - Err(e) => - println!("ERROR: main: faild to subscribe to clock/time/hour ({})", e), - Ok(_) => {} - } - match client.subscribe("clock/date/dow", QoS::AtMostOnce) { - Err(e) => - println!("ERROR: main: faild to subscribe to clock/date/dow ({})", e), - Ok(_) => {} - } - match client.subscribe("/cool/devices/KNMITemp/values", QoS::AtMostOnce) { - Err(e) => - println!("ERROR: main: faild to subscribe to KNMITemp/values ({})", e), - Ok(_) => {} - } - - Automation { - tx, - clock_dow: u8::MAX - } - } - - fn run(&mut self, rx: Receiver) { - loop { - let message = rx.recv(); - match message { - Err(e) => { - println!("ERROR: mqttAutomation: failed to receve an message ({})", e); - thread::sleep(Duration::from_millis(500)); - }, - Ok(message) => { - self.message_in(message); - } - } - } - } -} - +mod automation; +use automation::{Automation, SettingsConf}; #[derive(Deserialize)] struct SettingsMQTT { @@ -196,12 +14,11 @@ struct SettingsMQTT { user: String, pass: String } - #[derive(Deserialize)] struct Settings { - mqtt: SettingsMQTT + mqtt: SettingsMQTT, + automation: SettingsConf } - enum SettingsError { ReadError, SyntaxError @@ -245,12 +62,13 @@ fn main() { // get setting match read_config() { Ok(conf) => - mqtt_client::run::( + mqtt_client::run::( conf.mqtt.host, conf.mqtt.port, conf.mqtt.client, conf.mqtt.user, - conf.mqtt.pass + conf.mqtt.pass, + conf.automation ), Err(_) => {} }