more work on configurable automation
This commit is contained in:
		
							parent
							
								
									31e0664aa6
								
							
						
					
					
						commit
						4419442101
					
				| @ -9,16 +9,17 @@ automation: | |||||||
|   alarm_hour: 7 |   alarm_hour: 7 | ||||||
|   alarm_minute: 0 |   alarm_minute: 0 | ||||||
| 
 | 
 | ||||||
| # automation: |   # auto: | ||||||
| #   - trigger: |   #   - trigger: | ||||||
| #       - type: time |   #       !Time | ||||||
| #         date: |   #         date: | ||||||
| #           weekday: [0,1,2,3,4] |   #           weekday: "0,1,2,3,4" | ||||||
| #         time: |   #         time: | ||||||
| #           hour: 7 |   #           hour: "7" | ||||||
| #           minute: 0 |   #           minute: "0" | ||||||
| #           seconds: 0 |   #           seconds: "0" | ||||||
| #     action: |   #     actions: | ||||||
| #       - type: publish |   #       - !Publish | ||||||
| #         topic: "/cool/devices/lamp-01/set" |   #           type: publish | ||||||
| #         pauload: "ON" |   #           topic: "/cool/devices/lamp-01/set" | ||||||
|  |   #           pauload: "ON" | ||||||
|  | |||||||
| @ -1,48 +0,0 @@ | |||||||
| 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<String, config::Automation>, |  | ||||||
|         time_triggers: HashMap<String, TimeTrigger>, |  | ||||||
|     } |  | ||||||
|     impl Generator { |  | ||||||
| 
 |  | ||||||
|         pub fn new() -> Generator { |  | ||||||
|             Generator { |  | ||||||
|                 mqtt_triggers: HashMap::new(), |  | ||||||
|                 time_triggers: HashMap::new() |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         pub fn read_automations(&mut self, automations: Vec<config::Automation>) { |  | ||||||
|             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); |  | ||||||
|                 },, |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| @ -1,30 +0,0 @@ | |||||||
| 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<String>, |  | ||||||
|             retain: Option<bool> |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         #[derive(Deserialize, Clone)] |  | ||||||
|         pub enum Action { |  | ||||||
|             Publish(Publish), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     #[derive(Deserialize, Clone)] |  | ||||||
|     pub struct Automation { |  | ||||||
|         pub trigger: triggers::Trigger, |  | ||||||
|         pub actions: Vec<actions::Action> |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| @ -1,33 +0,0 @@ | |||||||
| 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<String> |  | ||||||
|     } |  | ||||||
|     impl TriggerBase for Mqtt { |  | ||||||
|         fn get_next_trigger(&self) -> &String { |  | ||||||
|             &self.topic |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     
 |  | ||||||
| 
 |  | ||||||
|     #[derive(Deserialize, Clone)] |  | ||||||
|     pub enum Trigger { |  | ||||||
|         Mqtt(Mqtt), |  | ||||||
|         Time(Datetime), |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,153 +0,0 @@ | |||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 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<u16> { |  | ||||||
|         match u16::try_from(value) { |  | ||||||
|             Ok(n) => Some(n), |  | ||||||
|             Err(_) => None |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     fn get_u16_from_i32(value: i32) -> Option<u16> { |  | ||||||
|         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<StringOrU16>, |  | ||||||
|         month: Option<StringOrU16>, |  | ||||||
|         date: Option<StringOrU16>, |  | ||||||
|         weekday: Option<StringOrU16> |  | ||||||
|     } |  | ||||||
|     #[derive(Deserialize, Clone)] |  | ||||||
|     struct Time { |  | ||||||
|         hour: Option<StringOrU16>, |  | ||||||
|         minute: Option<StringOrU16>, |  | ||||||
|         second: Option<StringOrU16> |  | ||||||
|     } |  | ||||||
|     
 |  | ||||||
|     #[derive(Deserialize, Clone)] |  | ||||||
|     pub struct Datetime { |  | ||||||
|         date: Date, |  | ||||||
|         time: Time |  | ||||||
|     } |  | ||||||
|     impl Datetime { |  | ||||||
|         pub fn first_match(condition: String, start: u16, max: u16) -> Result<Option<u16>, CompareError> { |  | ||||||
|             if condition == "*" { |  | ||||||
|                 return Ok(Some(start)); |  | ||||||
|             } |  | ||||||
|             if condition.starts_with("*/") { |  | ||||||
|                 let divs = condition.split('/'); |  | ||||||
|                 let mut options: Vec<u16> = (start..max).collect(); |  | ||||||
|                 for div in divs { |  | ||||||
|                     match div.parse::<u16>() { |  | ||||||
|                         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<u16> = [].to_vec(); |  | ||||||
|                 for part in parts { |  | ||||||
|                     match part.parse::<u16>() { |  | ||||||
|                         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<Local>) -> DateTime<Local> { |  | ||||||
|             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()) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										66
									
								
								src/automation/auto/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/automation/auto/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | |||||||
|  | mod triggers; | ||||||
|  | 
 | ||||||
|  | pub mod automation { | ||||||
|  |     use std::collections::HashMap; | ||||||
|  | 
 | ||||||
|  |     use serde::Deserialize; | ||||||
|  | 
 | ||||||
|  |     pub use crate::automation::auto::triggers::triggers; | ||||||
|  | 
 | ||||||
|  |     mod actions { | ||||||
|  |         use serde::Deserialize; | ||||||
|  | 
 | ||||||
|  |         #[derive(Deserialize, Clone)] | ||||||
|  |         struct Publish { | ||||||
|  |             topic: String, | ||||||
|  |             payload: Option<String>, | ||||||
|  |             retain: Option<bool> | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         #[derive(Deserialize, Clone)] | ||||||
|  |         pub enum Action { | ||||||
|  |             Publish(Publish), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[derive(Deserialize, Clone)] | ||||||
|  |     pub struct Automation { | ||||||
|  |         pub trigger: triggers::Trigger, | ||||||
|  |         pub actions: Vec<actions::Action> | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub struct Generator { | ||||||
|  |         mqtt_triggers: HashMap<String, triggers::Mqtt>, | ||||||
|  |         time_triggers: HashMap<String, triggers::Datetime>, | ||||||
|  |     } | ||||||
|  |     impl Generator { | ||||||
|  | 
 | ||||||
|  |         pub fn new() -> Generator { | ||||||
|  |             Generator { | ||||||
|  |                 mqtt_triggers: HashMap::new(), | ||||||
|  |                 time_triggers: HashMap::new() | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         pub fn read_automations(&mut self, automations: Vec<Automation>) { | ||||||
|  |             for auto in automations { | ||||||
|  |                 self.read_automation_single(auto); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         fn read_automation_single(&mut self, auto: Automation) { | ||||||
|  |             // let auto_cloned = auto.clone();
 | ||||||
|  |             match auto.trigger { | ||||||
|  |                 triggers::Trigger::Mqtt(mqtt) => { | ||||||
|  |                     self.mqtt_triggers.insert(mqtt.topic.clone(), mqtt.clone()); | ||||||
|  |                 }, | ||||||
|  |                 triggers::Trigger::Time(_datetime) => { | ||||||
|  |                     // self.time_triggers.insert(mqtt.topic, auto_cloned);
 | ||||||
|  |                     todo!() | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -1,15 +1,39 @@ | |||||||
|  | mod datetime; | ||||||
| 
 | 
 | ||||||
| use |  | ||||||
| 
 |  | ||||||
| pub trait TriggerBase { |  | ||||||
|     fn key() |  | ||||||
| } |  | ||||||
| pub mod triggers { | pub mod triggers { | ||||||
| } |     use serde::Deserialize; | ||||||
| 
 | 
 | ||||||
|  |     pub trait TriggerBase { | ||||||
|  |         fn get_next_trigger(&self) -> String; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| pub impl clock { |     #[derive(Deserialize, Clone)] | ||||||
|  |     enum StringOrU16 { | ||||||
|  |         String(String), | ||||||
|  |         U16(u16) | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|  |     // ===== mqtt
 | ||||||
|  | 
 | ||||||
|  |     #[derive(Deserialize, Clone)] | ||||||
|  |     pub struct Mqtt { | ||||||
|  |         pub topic: String, | ||||||
|  |         pub payload: Option<String> | ||||||
|  |     } | ||||||
|  |     impl TriggerBase for Mqtt { | ||||||
|  |         fn get_next_trigger(&self) -> String { | ||||||
|  |             self.topic.clone() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // ===== datetime
 | ||||||
|  |     
 | ||||||
|  |     pub use crate::automation::auto::triggers::datetime::trigger_datetime::Datetime; | ||||||
|     
 |     
 | ||||||
| 
 | 
 | ||||||
|  |     #[derive(Deserialize, Clone)] | ||||||
|  |     pub enum Trigger { | ||||||
|  |         Mqtt(Mqtt), | ||||||
|  |         Time(Datetime), | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										316
									
								
								src/automation/auto/triggers/datetime.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										316
									
								
								src/automation/auto/triggers/datetime.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,316 @@ | |||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | pub mod trigger_datetime { | ||||||
|  |     use serde::Deserialize; | ||||||
|  |     use crate::automation::auto::triggers::triggers::TriggerBase; | ||||||
|  |     use chrono::{DateTime, Datelike, Local, NaiveDate, TimeZone, Timelike}; | ||||||
|  | 
 | ||||||
|  |     fn get_u16_from_u32(value: u32) -> Option<u16> { | ||||||
|  |         match u16::try_from(value) { | ||||||
|  |             Ok(n) => Some(n), | ||||||
|  |             Err(_) => None | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     fn get_u16_from_i32(value: i32) -> Option<u16> { | ||||||
|  |         match u16::try_from(value) { | ||||||
|  |             Ok(n) => Some(n), | ||||||
|  |             Err(_) => None | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[derive(Debug)] | ||||||
|  |     pub enum CompareError { | ||||||
|  |         InvalidValue, | ||||||
|  |         DivNaN | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[derive(Deserialize, Clone)] | ||||||
|  |     struct Date { | ||||||
|  |         year: Option<String>, | ||||||
|  |         month: Option<String>, | ||||||
|  |         date: Option<String>, | ||||||
|  |         weekday: Option<String> | ||||||
|  |     } | ||||||
|  |     #[derive(Deserialize, Clone)] | ||||||
|  |     struct Time { | ||||||
|  |         hour: Option<String>, | ||||||
|  |         minute: Option<String>, | ||||||
|  |         second: Option<String> | ||||||
|  |     } | ||||||
|  |     
 | ||||||
|  |     #[derive(Deserialize, Clone)] | ||||||
|  |     pub struct Datetime { | ||||||
|  |         date: Date, | ||||||
|  |         time: Time | ||||||
|  |     } | ||||||
|  |     impl Datetime { | ||||||
|  |         pub fn first_match(condition: String, start: u16, end: u16) -> Result<Option<u16>, CompareError> { | ||||||
|  |             if condition == "*" { | ||||||
|  |                 return Ok(Some(start)); | ||||||
|  |             } | ||||||
|  |             if condition.starts_with("*/") { | ||||||
|  |                 let div = condition[2..].to_string(); | ||||||
|  |                 let mut options: Vec<u16> = (start..end).collect(); | ||||||
|  |                 match div.parse::<u16>() { | ||||||
|  |                     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<u16> = [].to_vec(); | ||||||
|  |                 for part in parts { | ||||||
|  |                     match part.parse::<u16>() { | ||||||
|  |                         Ok(n) => { | ||||||
|  |                             options.push(n); | ||||||
|  |                         }, | ||||||
|  |                         Err(_) => { | ||||||
|  |                             return Err(CompareError::DivNaN); | ||||||
|  |                         }, | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 options.retain(|x| *x >= start && *x <= end); | ||||||
|  |                 let min = options.iter().min(); | ||||||
|  |                 match min { | ||||||
|  |                     Some(min) => { | ||||||
|  |                         return Ok(Some(min.clone())); | ||||||
|  |                     }, | ||||||
|  |                     None => { | ||||||
|  |                         return Ok(None); | ||||||
|  |                     }, | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             match condition.parse::<u16>() { | ||||||
|  |                 Ok(n) => { | ||||||
|  |                     if n >= start && n <= end { | ||||||
|  |                         return Ok(Some(n)) | ||||||
|  |                     } | ||||||
|  |                     else { | ||||||
|  |                         return Ok(None); | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 Err(_) => { | ||||||
|  |                     return Err(CompareError::InvalidValue); | ||||||
|  |                 }, | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         fn check_thing(&self, option: String, start: u16, end: u16) -> Option<u16> { | ||||||
|  |             match self::Datetime::first_match(option, start, end) { | ||||||
|  |                 Ok(r) => { | ||||||
|  |                     return r | ||||||
|  |                 }, | ||||||
|  |                 Err(e) => { | ||||||
|  |                     println!("ERROR: trigger.datetime.find_next: failed to parse month: {:?}", e); | ||||||
|  |                     return None | ||||||
|  |                 }, | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         fn get_days_in_month(date: &DateTime<Local>) -> u16 { | ||||||
|  |             let year = date.year(); | ||||||
|  |             let month = date.month(); | ||||||
|  |             let date = match NaiveDate::from_ymd_opt(year, month + 1, 1) { | ||||||
|  |                 Some(d) => d, | ||||||
|  |                 None => NaiveDate::from_ymd_opt(year + 1, 1, 1).unwrap() | ||||||
|  |             }; | ||||||
|  |             let d = date.signed_duration_since(NaiveDate::from_ymd_opt(year, month, 1).unwrap()); | ||||||
|  |             u16::try_from(d.num_days()).unwrap() | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         fn find_next_trigger(&self, from: DateTime<Local>) -> Option<DateTime<Local>> { | ||||||
|  |             let mut next = from.clone(); | ||||||
|  | 
 | ||||||
|  |             // year
 | ||||||
|  |             match &self.date.year { | ||||||
|  |                 Some(y) => { | ||||||
|  |                     match self::Datetime::first_match(y.to_string(), get_u16_from_i32(next.year()).unwrap(), u16::MAX) { | ||||||
|  |                         Ok(r) => { | ||||||
|  |                             match r { | ||||||
|  |                                 Some(y) => { | ||||||
|  |                                     if get_u16_from_i32(next.year()).unwrap() != y { | ||||||
|  |                                         next = match Local::with_ymd_and_hms(&Local, i32::from(y), 1, 1, 0, 0, 0) { | ||||||
|  |                                             chrono::offset::LocalResult::Single(d) => d, | ||||||
|  |                                             chrono::offset::LocalResult::Ambiguous(d, _) => d, | ||||||
|  |                                             chrono::offset::LocalResult::None => return None, | ||||||
|  |                                         } | ||||||
|  |                                     } | ||||||
|  |                                 }, | ||||||
|  |                                 None => return None, | ||||||
|  |                             } | ||||||
|  |                         }, | ||||||
|  |                         Err(e) => { | ||||||
|  |                             println!("ERROR: trigger.datetime.find_next: failed to parse year: {:?}", e); | ||||||
|  |                             return None | ||||||
|  |                         }, | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 None => {}, | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // month
 | ||||||
|  |             match &self.date.month { | ||||||
|  |                 Some(opt) => { | ||||||
|  |                     let now = get_u16_from_u32(next.month()).unwrap(); | ||||||
|  |                     match self.check_thing(opt.to_string(), now, 12) { | ||||||
|  |                         Some(r) => { | ||||||
|  |                             if r > now { | ||||||
|  |                                 next = match Local::with_ymd_and_hms(&Local, next.year(), u32::from(r), 1, 0, 0, 0) { | ||||||
|  |                                     chrono::offset::LocalResult::Single(d) => d, | ||||||
|  |                                     chrono::offset::LocalResult::Ambiguous(d, _) => d, | ||||||
|  |                                     chrono::offset::LocalResult::None => return None, | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         }, | ||||||
|  |                         None => { | ||||||
|  |                             next = match Local::with_ymd_and_hms(&Local, next.year()+1, 1, 1, 0, 0, 0) { | ||||||
|  |                                 chrono::offset::LocalResult::Single(d) => d, | ||||||
|  |                                 chrono::offset::LocalResult::Ambiguous(d, _) => d, | ||||||
|  |                                 chrono::offset::LocalResult::None => return None | ||||||
|  |                             }; | ||||||
|  |                             if (next - Local::now()).num_days() < 2*365 { | ||||||
|  |                                 return self.find_next_trigger(next); | ||||||
|  |                             } | ||||||
|  |                             else { | ||||||
|  |                                 return None; | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 None => {}, | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // date
 | ||||||
|  |             match &self.date.date { | ||||||
|  |                 Some(opt) => { | ||||||
|  |                     let now = get_u16_from_u32(next.day()).unwrap(); | ||||||
|  |                     match self.check_thing(opt.to_string(), now, Datetime::get_days_in_month(&next)) { | ||||||
|  |                         Some(r) => { | ||||||
|  |                             if r > now { | ||||||
|  |                                 next = match Local::with_ymd_and_hms(&Local, next.year(), next.month(), u32::from(r), 0, 0, 0) { | ||||||
|  |                                     chrono::offset::LocalResult::Single(d) => d, | ||||||
|  |                                     chrono::offset::LocalResult::Ambiguous(d, _) => d, | ||||||
|  |                                     chrono::offset::LocalResult::None => return None, | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         }, | ||||||
|  |                         None => { | ||||||
|  |                             let m = match next.month() { | ||||||
|  |                                 12 => 1, | ||||||
|  |                                 munth => munth | ||||||
|  |                             }; | ||||||
|  |                             let y = match m { | ||||||
|  |                                 1 => next.year()+1, | ||||||
|  |                                 _ => next.year() | ||||||
|  |                             }; | ||||||
|  |                             next = match Local::with_ymd_and_hms(&Local, y, m, 1, 0, 0, 0) { | ||||||
|  |                                 chrono::offset::LocalResult::Single(d) => d, | ||||||
|  |                                 chrono::offset::LocalResult::Ambiguous(d, _) => d, | ||||||
|  |                                 chrono::offset::LocalResult::None => return None | ||||||
|  |                             }; | ||||||
|  |                             if (next - Local::now()).num_days() < 2*365 { | ||||||
|  |                                 return self.find_next_trigger(next); | ||||||
|  |                             } | ||||||
|  |                             else { | ||||||
|  |                                 return None; | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 None => {}, | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // hour
 | ||||||
|  |             match &self.time.hour { | ||||||
|  |                 Some(opt) => { | ||||||
|  |                     let now = get_u16_from_u32(next.hour()).unwrap(); | ||||||
|  |                     match self.check_thing(opt.to_string(), now, 23) { | ||||||
|  |                         Some(r) => { | ||||||
|  |                             if r > now { | ||||||
|  |                                 next = match Local::with_ymd_and_hms(&Local, next.year(), next.month(), next.hour(), u32::from(r), 0, 0) { | ||||||
|  |                                     chrono::offset::LocalResult::Single(d) => d, | ||||||
|  |                                     chrono::offset::LocalResult::Ambiguous(d, _) => d, | ||||||
|  |                                     chrono::offset::LocalResult::None => return None, | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         }, | ||||||
|  |                         None => { | ||||||
|  |                             let mut d = next.day() + 1; | ||||||
|  |                             if d == u32::from(Datetime::get_days_in_month(&next)) { | ||||||
|  |                                 d = 1; | ||||||
|  |                             } | ||||||
|  |                             let m = match d { | ||||||
|  |                                 1 => next.month()+1, | ||||||
|  |                                 _ => next.month() | ||||||
|  |                             }; | ||||||
|  |                             let y = match m { | ||||||
|  |                                 1 => next.year()+1, | ||||||
|  |                                 _ => next.year() | ||||||
|  |                             }; | ||||||
|  |                             next = match Local::with_ymd_and_hms(&Local, y, m, d, 0, 0, 0) { | ||||||
|  |                                 chrono::offset::LocalResult::Single(d) => d, | ||||||
|  |                                 chrono::offset::LocalResult::Ambiguous(d, _) => d, | ||||||
|  |                                 chrono::offset::LocalResult::None => return None | ||||||
|  |                             }; | ||||||
|  |                             if (next - Local::now()).num_days() < 2*365 { | ||||||
|  |                                 return self.find_next_trigger(next); | ||||||
|  |                             } | ||||||
|  |                             else { | ||||||
|  |                                 return None; | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 None => {}, | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             Some(next) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     impl TriggerBase for Datetime { | ||||||
|  |         fn get_next_trigger(&self) -> String { | ||||||
|  |             let next_trigger: Option<DateTime<Local>> = self.find_next_trigger(Local::now()); | ||||||
|  |             match next_trigger { | ||||||
|  |                 Some(next) => { | ||||||
|  |                     let str = next.format("%S").to_string(); | ||||||
|  |                     print!("datetime string: {str}"); | ||||||
|  |                     return str | ||||||
|  |                 }, | ||||||
|  |                 None => { | ||||||
|  |                     print!("ERROR: trigger.datetime.get_next: No next trigger found"); | ||||||
|  |                     return "None".to_string(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     #[cfg(test)] | ||||||
|  |     mod tests { | ||||||
|  |         use super::*; | ||||||
|  | 
 | ||||||
|  |         #[test] | ||||||
|  |         fn datetime_first_match() { | ||||||
|  |             assert_eq!(Datetime::first_match("*".to_string(), 123, 128).unwrap(), Some(123)); | ||||||
|  |             assert_eq!(Datetime::first_match("*/4".to_string(), 123, 128).unwrap(), Some(124)); | ||||||
|  |             assert_eq!(Datetime::first_match("*/10".to_string(), 123, 128).unwrap(), None); | ||||||
|  |             assert_eq!(Datetime::first_match("125".to_string(), 123, 128).unwrap(), Some(125)); | ||||||
|  |             assert_eq!(Datetime::first_match("122,126".to_string(), 123, 128).unwrap(), Some(126)); | ||||||
|  |             assert_eq!(Datetime::first_match("122,129".to_string(), 123, 128).unwrap(), None); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,7 +1,6 @@ | |||||||
| mod json; | mod json; | ||||||
| //AUTO mod auto;
 | //AUTO mod auto;
 | ||||||
| 
 | 
 | ||||||
| //AUTO use std::collections::HashMap;
 |  | ||||||
| use std::{thread, time::Duration}; | use std::{thread, time::Duration}; | ||||||
| use serde::Deserialize; | use serde::Deserialize; | ||||||
| 
 | 
 | ||||||
| @ -9,7 +8,7 @@ use mqtt_client::{MqttMessage, MqttEvent, Sender, Receiver, QoS}; | |||||||
| use mqtt_client::mqtt_client; | use mqtt_client::mqtt_client; | ||||||
| 
 | 
 | ||||||
| use crate::automation::json::json_parser; | use crate::automation::json::json_parser; | ||||||
| //AUTO use crate::automation::auto::auto_generator::{config::Automation as AutoConfig, Generator};
 | //AUTO use crate::automation::auto::automation::{Automation as AutoConfig, Generator};
 | ||||||
| 
 | 
 | ||||||
| #[derive(Deserialize)] | #[derive(Deserialize)] | ||||||
| pub struct SettingsConf { | pub struct SettingsConf { | ||||||
| @ -18,7 +17,7 @@ pub struct SettingsConf { | |||||||
|     alarm_hour: u8, |     alarm_hour: u8, | ||||||
|     alarm_minute: u8, |     alarm_minute: u8, | ||||||
| 
 | 
 | ||||||
|     // auto: AutoConfig
 |     //AUTO auto: Vec<AutoConfig>
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| struct PingStats { | struct PingStats { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user