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 {
|
||||||
pub impl clock {
|
fn get_next_trigger(&self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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