WIP refactor plant_state
This commit is contained in:
292
rust/src/main.rs
292
rust/src/main.rs
@@ -7,7 +7,7 @@ use anyhow::{bail, Result};
|
||||
use chrono::{DateTime, Datelike, TimeDelta, Timelike, Utc};
|
||||
use chrono_tz::{Europe::Berlin, Tz};
|
||||
|
||||
use config::Mode;
|
||||
use config::PlantWateringMode;
|
||||
use esp_idf_hal::delay::Delay;
|
||||
use esp_idf_sys::{
|
||||
esp_ota_get_app_partition_count, esp_ota_get_running_partition, esp_ota_get_state_partition,
|
||||
@@ -28,8 +28,9 @@ mod log;
|
||||
pub mod plant_hal;
|
||||
mod plant_state;
|
||||
mod tank;
|
||||
pub mod util;
|
||||
|
||||
use plant_state::{PlantInfo, PlantStateMQTT};
|
||||
use plant_state::{PlantInfo, PlantState};
|
||||
use tank::*;
|
||||
|
||||
const TIME_ZONE: Tz = Berlin;
|
||||
@@ -406,18 +407,10 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
}
|
||||
};
|
||||
|
||||
let mut plantstate: [PlantInfo; PLANT_COUNT] = core::array::from_fn(|_| PlantInfo {
|
||||
..Default::default()
|
||||
});
|
||||
determine_plant_state(
|
||||
&mut plantstate,
|
||||
timezone_time,
|
||||
&tank_state,
|
||||
&config,
|
||||
&mut board,
|
||||
);
|
||||
let mut plantstate: [PlantState; PLANT_COUNT] =
|
||||
core::array::from_fn(|i| PlantState::read_hardware_state(i, &mut board, &config.plants[i]));
|
||||
|
||||
let pump_required = plantstate.iter().any(|it| it.do_water) && !water_frozen;
|
||||
let pump_required = plantstate.iter().any(|it| it.needs_to_be_watered(&config.plants[i], &timezone_time)) && !water_frozen;
|
||||
if pump_required {
|
||||
log(log::LogMessage::EnableMain, dry_run as u32, 0, "", "");
|
||||
if !dry_run {
|
||||
@@ -568,279 +561,6 @@ fn publish_battery_state(
|
||||
};
|
||||
}
|
||||
|
||||
fn determine_state_target_moisture_for_plant(
|
||||
board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
|
||||
plant: usize,
|
||||
state: &mut PlantInfo,
|
||||
config: &PlantControllerConfig,
|
||||
tank_state: &TankState,
|
||||
cur: DateTime<Tz>,
|
||||
) {
|
||||
let plant_config = &config.plants[plant];
|
||||
if plant_config.mode == Mode::OFF {
|
||||
return;
|
||||
}
|
||||
match board.measure_moisture_hz(plant, plant_hal::Sensor::A) {
|
||||
Ok(a) => {
|
||||
state.a_raw = Some(a);
|
||||
let mapped = map_range_moisture(a as f32);
|
||||
match mapped {
|
||||
Ok(result) => state.a = Some(result),
|
||||
Err(err) => {
|
||||
state.sensor_error_a = Some(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
state.sensor_error_a = Some(SensorError::Unknown);
|
||||
}
|
||||
}
|
||||
if plant_config.sensor_b {
|
||||
match board.measure_moisture_hz(plant, plant_hal::Sensor::B) {
|
||||
Ok(b) => {
|
||||
state.b_raw = Some(b);
|
||||
let mapped = map_range_moisture(b as f32);
|
||||
match mapped {
|
||||
Ok(result) => state.b = Some(result),
|
||||
Err(err) => {
|
||||
state.sensor_error_b = Some(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
state.sensor_error_b = Some(SensorError::Unknown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//FIXME how to average analyze whatever?
|
||||
let a_low = state.a.is_some() && state.a.unwrap() < plant_config.target_moisture;
|
||||
let b_low = state.b.is_some() && state.b.unwrap() < plant_config.target_moisture;
|
||||
|
||||
if a_low || b_low {
|
||||
state.dry = true;
|
||||
match tank_state.enough_water(&config.tank) {
|
||||
Err(_tank_err) => {
|
||||
if !config.tank.tank_allow_pumping_if_sensor_error {
|
||||
state.no_water = true;
|
||||
}
|
||||
}
|
||||
// when no tank error, if plant should be watered depends on if enough water is in tank
|
||||
// no_water behaves inversly to enough_water
|
||||
Ok(enough_water) => state.no_water = !enough_water,
|
||||
}
|
||||
}
|
||||
let duration = TimeDelta::try_minutes(plant_config.pump_cooldown_min as i64).unwrap();
|
||||
let last_pump = board.last_pump_time(plant);
|
||||
match last_pump {
|
||||
Some(last_pump) => {
|
||||
let next_pump = last_pump + duration;
|
||||
if next_pump > cur {
|
||||
let local_time = next_pump.with_timezone(&TIME_ZONE);
|
||||
state.next_pump = Some(local_time);
|
||||
state.cooldown = true;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
println!(
|
||||
"Could not restore last pump for plant {}, restoring",
|
||||
plant + 1
|
||||
);
|
||||
board.store_last_pump_time(plant, DateTime::from_timestamp_millis(0).unwrap());
|
||||
state.pump_error = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !in_time_range(
|
||||
&cur,
|
||||
plant_config.pump_hour_start,
|
||||
plant_config.pump_hour_end,
|
||||
) {
|
||||
state.out_of_work_hour = true;
|
||||
}
|
||||
if state.dry && !state.no_water && !state.cooldown && !state.out_of_work_hour {
|
||||
state.do_water = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn determine_state_timer_only_for_plant(
|
||||
board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
|
||||
plant: usize,
|
||||
state: &mut PlantInfo,
|
||||
config: &PlantControllerConfig,
|
||||
tank_state: &TankState,
|
||||
cur: DateTime<Tz>,
|
||||
) {
|
||||
let plant_config = &config.plants[plant];
|
||||
let duration = TimeDelta::try_minutes(plant_config.pump_cooldown_min as i64).unwrap();
|
||||
|
||||
let last_pump = board.last_pump_time(plant);
|
||||
match last_pump {
|
||||
Some(last_pump) => {
|
||||
let next_pump = last_pump + duration;
|
||||
if next_pump > cur {
|
||||
let europe_time = next_pump.with_timezone(&TIME_ZONE);
|
||||
state.next_pump = Some(europe_time);
|
||||
state.cooldown = true;
|
||||
} else {
|
||||
match tank_state.enough_water(&config.tank) {
|
||||
Err(_tank_err) => {
|
||||
if !config.tank.tank_allow_pumping_if_sensor_error {
|
||||
state.do_water = true;
|
||||
}
|
||||
}
|
||||
Ok(enough_water) => {
|
||||
state.no_water = !enough_water;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
println!(
|
||||
"Could not restore last pump for plant {}, restoring",
|
||||
plant + 1
|
||||
);
|
||||
board.store_last_pump_time(plant, DateTime::from_timestamp_millis(0).unwrap());
|
||||
state.pump_error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn determine_state_timer_and_deadzone_for_plant(
|
||||
board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
|
||||
plant: usize,
|
||||
state: &mut PlantInfo,
|
||||
config: &PlantControllerConfig,
|
||||
tank_state: &TankState,
|
||||
cur: DateTime<Tz>,
|
||||
) {
|
||||
let plant_config = &config.plants[plant];
|
||||
let duration = TimeDelta::try_minutes(plant_config.pump_cooldown_min as i64).unwrap();
|
||||
|
||||
let last_pump = board.last_pump_time(plant);
|
||||
match last_pump {
|
||||
Some(last_pump) => {
|
||||
let next_pump = last_pump + duration;
|
||||
if next_pump > cur {
|
||||
let europe_time = next_pump.with_timezone(&TIME_ZONE);
|
||||
state.next_pump = Some(europe_time);
|
||||
state.cooldown = true;
|
||||
}
|
||||
if !in_time_range(
|
||||
&cur,
|
||||
plant_config.pump_hour_start,
|
||||
plant_config.pump_hour_end,
|
||||
) {
|
||||
state.out_of_work_hour = true;
|
||||
}
|
||||
if !state.cooldown && !state.out_of_work_hour {
|
||||
match tank_state.enough_water(&config.tank) {
|
||||
Err(_tank_err) => {
|
||||
if !config.tank.tank_allow_pumping_if_sensor_error {
|
||||
state.do_water = true;
|
||||
}
|
||||
}
|
||||
Ok(enough_water) => {
|
||||
state.no_water = !enough_water;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
println!(
|
||||
"Could not restore last pump for plant {}, restoring",
|
||||
plant + 1
|
||||
);
|
||||
board.store_last_pump_time(plant, DateTime::from_timestamp_millis(0).unwrap());
|
||||
state.pump_error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn determine_plant_state(
|
||||
plantstate: &mut [PlantInfo; PLANT_COUNT],
|
||||
cur: DateTime<Tz>,
|
||||
tank_state: &TankState,
|
||||
config: &PlantControllerConfig,
|
||||
board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
|
||||
) {
|
||||
for plant in 0..PLANT_COUNT {
|
||||
let state = &mut plantstate[plant];
|
||||
let plant_config = &config.plants[plant];
|
||||
match plant_config.mode {
|
||||
config::Mode::OFF => {}
|
||||
config::Mode::TargetMoisture => {
|
||||
determine_state_target_moisture_for_plant(
|
||||
board, plant, state, config, tank_state, cur,
|
||||
);
|
||||
}
|
||||
config::Mode::TimerOnly => {
|
||||
determine_state_timer_only_for_plant(board, plant, state, config, tank_state, cur);
|
||||
}
|
||||
config::Mode::TimerAndDeadzone => {
|
||||
determine_state_timer_and_deadzone_for_plant(
|
||||
board, plant, state, config, tank_state, cur,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if state.sensor_error_a.is_some() || state.sensor_error_b.is_some() {
|
||||
board.fault(plant, true);
|
||||
}
|
||||
if !state.dry {
|
||||
state.consecutive_pump_count = 0;
|
||||
board.store_consecutive_pump_count(plant, 0);
|
||||
}
|
||||
println!("Plant {} state is {:?}", plant, state);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_plant_state(
|
||||
plantstate: &mut [PlantInfo; PLANT_COUNT],
|
||||
board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
|
||||
config: &PlantControllerConfig,
|
||||
) {
|
||||
for plant in 0..PLANT_COUNT {
|
||||
let state = &plantstate[plant];
|
||||
let plant_config = &config.plants[plant];
|
||||
|
||||
let mode = format!("{:?}", plant_config.mode);
|
||||
|
||||
let plant_dto = PlantStateMQTT {
|
||||
a: &sensor_to_string(
|
||||
&state.a,
|
||||
&state.sensor_error_a,
|
||||
plant_config.mode != Mode::OFF,
|
||||
),
|
||||
a_raw: &state.a_raw.unwrap_or(0).to_string(),
|
||||
b: &sensor_to_string(&state.b, &state.sensor_error_b, plant_config.sensor_b),
|
||||
b_raw: &state.b_raw.unwrap_or(0).to_string(),
|
||||
active: state.active,
|
||||
mode: &mode,
|
||||
last_pump: &time_to_string_utc(board.last_pump_time(plant)),
|
||||
next_pump: &time_to_string(state.next_pump),
|
||||
consecutive_pump_count: state.consecutive_pump_count,
|
||||
cooldown: state.cooldown,
|
||||
dry: state.dry,
|
||||
not_effective: state.not_effective,
|
||||
out_of_work_hour: state.out_of_work_hour,
|
||||
pump_error: state.pump_error,
|
||||
};
|
||||
|
||||
match serde_json::to_string(&plant_dto) {
|
||||
Ok(state) => {
|
||||
let plant_topic = format!("/plant{}", plant + 1);
|
||||
let _ = board.mqtt_publish(&config, &plant_topic, state.as_bytes());
|
||||
//reduce speed as else messages will be dropped
|
||||
Delay::new_default().delay_ms(200);
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error publishing lightstate {}", err);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
|
||||
let delay = wait_type.blink_pattern();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user