allow to selectivly enable redundant sensors

This commit is contained in:
Empire 2024-02-17 17:25:50 +01:00
parent 680d1c3aaf
commit 9d1a807805
5 changed files with 260 additions and 101 deletions

View File

@ -65,7 +65,7 @@ embedded-svc = { version = "0.27.0", features = ["experimental"] }
esp-idf-hal = "0.43.0"
esp-idf-sys = { version = "0.34.0", features = ["binstart", "native"] }
esp_idf_build = "0.1.3"
chrono = { version = "0.4.23", default-features = false , features = ["iana-time-zone"] }
chrono = { version = "0.4.23", default-features = false , features = ["iana-time-zone" , "alloc"] }
chrono-tz = {version="0.8.0", default-features = false , features = [ "filter-by-regex" ]}
embedded-hal = "1.0.0"
one-wire-bus = "0.1.1"

View File

@ -59,6 +59,8 @@ pub struct Plant {
pub pump_cooldown_min: u16,
pub pump_hour_start: u8,
pub pump_hour_end: u8,
pub sensor_b: bool,
pub sensor_p: bool
}
impl Default for Plant {
fn default() -> Self {
@ -69,6 +71,8 @@ impl Default for Plant {
pump_hour_start: 8,
pump_hour_end: 20,
mode: Mode::OFF,
sensor_b : false,
sensor_p : false
}
}
}

View File

@ -6,6 +6,7 @@ use std::{
use chrono::{DateTime, Datelike, Duration, NaiveDateTime, Timelike};
use chrono_tz::{Europe::Berlin, Tz};
use config::Plant;
use embedded_svc::mqtt;
use esp_idf_hal::delay::Delay;
use esp_idf_sys::{
esp_deep_sleep, esp_restart, gpio_deep_sleep_hold_dis, gpio_deep_sleep_hold_en, vTaskDelay,
@ -65,7 +66,7 @@ struct LightState {
is_day: bool,
}
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Default)]
#[derive(Copy, Clone, Debug, PartialEq, Default)]
struct PlantState {
a: Option<u8>,
b: Option<u8>,
@ -83,6 +84,7 @@ struct PlantState {
sensor_error_b: Option<SensorError>,
sensor_error_p: Option<SensorError>,
out_of_work_hour: bool,
next_pump: Option<DateTime<Tz>>
}
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
@ -230,7 +232,7 @@ fn safe_main() -> anyhow::Result<()> {
};
println!("attempting to connect wifi");
match board.wifi(wifi.ssid, wifi.password, 5000) {
match board.wifi(wifi.ssid, wifi.password, 10000) {
Ok(_) => {
online_mode = OnlineMode::Wifi;
}
@ -288,7 +290,10 @@ fn safe_main() -> anyhow::Result<()> {
if online_mode == OnlineMode::Online {
let _ = board.mqtt_publish(&config, "/firmware/githash", git_hash.as_bytes());
let _ = board.mqtt_publish(&config, "/state", "online".as_bytes());
let _ = board.mqtt_publish(&config, "/last_online", europe_time.to_rfc3339().as_bytes());
publish_battery_state(&mut board, &config);
}
@ -348,6 +353,20 @@ fn safe_main() -> anyhow::Result<()> {
let consecutive_pump_count = board.consecutive_pump_count(plant) + 1;
board.store_consecutive_pump_count(plant, consecutive_pump_count);
let plant_config = config.plants[plant];
if plant_config.sensor_p {
match map_range_moisture(
board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32
) {
Ok(p) => state.p = Some(p),
Err(err) => {
board.fault(plant, true);
state.sensor_error_p = Some(err);
}
}
}
println!(
"Trying to pump for {}s with pump {} now",
plant_config.pump_time_s, plant
@ -358,32 +377,61 @@ fn safe_main() -> anyhow::Result<()> {
board.pump(plant, true)?;
board.last_pump_time(plant);
state.active = true;
//FIXME do periodic pump test here and state update
unsafe { vTaskDelay(plant_config.pump_time_s as u32 * CONFIG_FREERTOS_HZ) };
for t in 0..plant_config.pump_time_s {
//FIXME do periodic pump test here and state update
unsafe { vTaskDelay(CONFIG_FREERTOS_HZ) };
if plant_config.sensor_p {
let moist = map_range_moisture(
board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32);
if online_mode == OnlineMode::Online {
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Sensor P after", plant+1).as_str(),
option_to_string(moist.ok()).as_bytes(),
);
}
} else {
if online_mode == OnlineMode::Online {
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Sensor P after", plant+1).as_str(),
"disabled".as_bytes(),
);
}
}
}
board.pump(plant, false)?;
match map_range_moisture(
board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32
) {
Ok(p) => state.after_p = Some(p),
Err(err) => {
if plant_config.sensor_p {
match map_range_moisture(
board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32
) {
Ok(p) => state.after_p = Some(p),
Err(err) => {
board.fault(plant, true);
state.sensor_error_p = Some(err);
}
}
if state.after_p.is_none()
|| state.p.is_none()
|| state.after_p.unwrap() < state.p.unwrap() + 5
{
state.pump_error = true;
board.fault(plant, true);
state.sensor_error_p = Some(err);
//mqtt sync pump error value
}
}
if state.after_p.is_none()
|| state.p.is_none()
|| state.after_p.unwrap() < state.p.unwrap() + 5
{
state.pump_error = true;
board.fault(plant, true);
//mqtt sync pump error value
}
}
None => {
println!("Nothing to do");
}
}
if online_mode == OnlineMode::Online {
update_plant_state(&mut plantstate, &mut board, &config);
}
let mut light_state = LightState {
..Default::default()
};
@ -700,20 +748,23 @@ fn determine_state_target_moisture_for_plant(
state.sensor_error_a = Some(SensorError::Unknown);
}
}
match board.measure_moisture_hz(plant, plant_hal::Sensor::B) {
Ok(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);
if plant_config.sensor_b {
match board.measure_moisture_hz(plant, plant_hal::Sensor::B) {
Ok(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);
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;
@ -729,6 +780,8 @@ fn determine_state_target_moisture_for_plant(
let duration = Duration::minutes((plant_config.pump_cooldown_min).into());
let next_pump = board.last_pump_time(plant) + duration;
if next_pump > cur {
let europe_time = next_pump.with_timezone(&Berlin);
state.next_pump = Some(europe_time);
state.cooldown = true;
}
if !in_time_range(
@ -779,6 +832,8 @@ fn determine_next_plant(
let duration = Duration::minutes((plant_config.pump_cooldown_min).into());
let next_pump = board.last_pump_time(plant) + duration;
if next_pump > cur {
let europe_time = next_pump.with_timezone(&Berlin);
state.next_pump = Some(europe_time);
state.cooldown = true;
} else {
if water_frozen {
@ -792,6 +847,8 @@ fn determine_next_plant(
let duration = Duration::minutes((60 * plant_config.pump_cooldown_min).into());
let next_pump = board.last_pump_time(plant) + duration;
if next_pump > cur {
let europe_time = next_pump.with_timezone(&Berlin);
state.next_pump = Some(europe_time);
state.cooldown = true;
}
if !in_time_range(
@ -828,69 +885,7 @@ fn determine_next_plant(
println!("Plant {} state is {:?}", plant, state);
}
if online_mode == OnlineMode::Online {
for plant in 0..PLANT_COUNT {
let state = &plantstate[plant];
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Sensor A", plant).as_str(),
option_to_string(state.a).as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Sensor B", plant).as_str(),
option_to_string(state.b).as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Sensor P before", plant).as_str(),
option_to_string(state.p).as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Sensor P after", plant).as_str(),
option_to_string(state.after_p).as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Should water", plant).as_str(),
state.do_water.to_string().as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Is frozen", plant).as_str(),
state.frozen.to_string().as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Is dry", plant).as_str(),
state.dry.to_string().as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Pump Error", plant).as_str(),
state.pump_error.to_string().as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Pump Ineffective", plant).as_str(),
state.not_effective.to_string().as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Is in Cooldown", plant).as_str(),
state.cooldown.to_string().as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/No Water", plant).as_str(),
state.no_water.to_string().as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Out of Work Hour", plant).as_str(),
state.out_of_work_hour.to_string().as_bytes(),
);
}
update_plant_state(plantstate, board, config);
}
for plant in 0..PLANT_COUNT {
let state = &plantstate[plant];
@ -906,6 +901,133 @@ fn determine_next_plant(
return None;
}
fn update_plant_state(plantstate: &mut [PlantState; PLANT_COUNT], board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, config: &Config){
for plant in 0..PLANT_COUNT {
let state = &plantstate[plant];
let plant_config = config.plants[plant];
let last_time = board.last_pump_time(plant);
let europe_time = last_time.with_timezone(&Berlin);
if europe_time.year() > 2023 {
let time = europe_time.to_rfc3339();
let _ = board.mqtt_publish(
&config,
format!("/plant{}/last pump", plant+1).as_str(),
time.as_bytes(),
);
} else {
let _ = board.mqtt_publish(
&config,
format!("/plant{}/last pump", plant+1).as_str(),
"N/A".as_bytes(),
);
}
match state.next_pump {
Some(next) => {
let time = next.to_rfc3339();
let _ = board.mqtt_publish(
&config,
format!("/plant{}/next pump", plant+1).as_str(),
time.as_bytes(),
);
},
None => {
let _ = board.mqtt_publish(
&config,
format!("/plant{}/next pump", plant+1).as_str(),
"N/A".as_bytes(),
);
},
}
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Sensor A", plant+1).as_str(),
option_to_string(state.a).as_bytes(),
);
if plant_config.sensor_b {
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Sensor B", plant+1).as_str(),
option_to_string(state.b).as_bytes(),
);
} else {
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Sensor B", plant+1).as_str(),
"disabled".as_bytes(),
);
}
if plant_config.sensor_p {
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Sensor P before", plant+1).as_str(),
option_to_string(state.p).as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Sensor P after", plant+1).as_str(),
option_to_string(state.after_p).as_bytes(),
);
} else {
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Sensor P before", plant+1).as_str(),
"disabled".as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Sensor P after", plant+1).as_str(),
"disabled".as_bytes(),
);
}
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Should water", plant+1).as_str(),
state.do_water.to_string().as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Is frozen", plant+1).as_str(),
state.frozen.to_string().as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Is dry", plant+1).as_str(),
state.dry.to_string().as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Pump Error", plant+1).as_str(),
state.pump_error.to_string().as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Pump Ineffective", plant+1).as_str(),
state.not_effective.to_string().as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Is in Cooldown", plant+1).as_str(),
state.cooldown.to_string().as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/No Water", plant+1).as_str(),
state.no_water.to_string().as_bytes(),
);
let _ = board.mqtt_publish(
&config,
format!("/plant{}/Out of Work Hour", plant+1).as_str(),
state.out_of_work_hour.to_string().as_bytes(),
);
}
}
fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
let delay = match wait_type {
WaitType::InitialConfig => 250_u32,

View File

@ -589,21 +589,18 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
}
for i in 0..8 {
self.pump(i, true)?;
unsafe { vTaskDelay(500) };
unsafe { vTaskDelay(100) };
self.pump(i, false)?;
unsafe { vTaskDelay(500) };
unsafe { vTaskDelay(100) };
}
for i in 0..8 {
self.measure_moisture_hz(i, Sensor::A)?;
unsafe { vTaskDelay(500) };
}
for i in 0..8 {
self.measure_moisture_hz(i, Sensor::B)?;
unsafe { vTaskDelay(500) };
}
for i in 0..8 {
self.measure_moisture_hz(i, Sensor::PUMP)?;
unsafe { vTaskDelay(500) };
}
Ok(())

View File

@ -18,7 +18,9 @@ interface PlantConfig {
pump_time_s: number,
pump_cooldown_min: number,
pump_hour_start: number,
pump_hour_end: number
pump_hour_end: number,
sensor_b: boolean,
sensor_p: boolean
}[]
}
@ -173,6 +175,32 @@ let fromWrapper = (() => {
holder.appendChild(text)
text.innerHTML += "Pump Hour End"
}
{
let holder = document.createElement("div");
plant.appendChild(holder);
let input = document.createElement("input");
input.id = "plant_" + i + "_sensor_b";
input.type = "checkbox";
input.onchange = updateJson;
holder.appendChild(input)
let text = document.createElement("span");
holder.appendChild(text)
text.innerHTML += "Sensor B installed"
}
{
let holder = document.createElement("div");
plant.appendChild(holder);
let input = document.createElement("input");
input.id = "plant_" + i + "_sensor_p";
input.type = "checkbox";
input.onchange = updateJson;
holder.appendChild(input)
let text = document.createElement("span");
holder.appendChild(text)
text.innerHTML += "Sensor P installed"
}
}
sync(current);
}
@ -208,6 +236,11 @@ let fromWrapper = (() => {
plant_pump_hour_start.value = current.plants[i].pump_hour_start.toString();
let plant_pump_hour_end = document.getElementById("plant_" + i + "_pump_hour_end") as HTMLInputElement;
plant_pump_hour_end.value = current.plants[i].pump_hour_end.toString();
let plant_sensor_b = document.getElementById("plant_" + i + "_sensor_b") as HTMLInputElement;
plant_sensor_b.checked = current.plants[i].sensor_b;
let plant_sensor_p = document.getElementById("plant_" + i + "_sensor_p") as HTMLInputElement;
plant_sensor_p.checked = current.plants[i].sensor_p;
}
}
@ -236,6 +269,8 @@ let fromWrapper = (() => {
let plant_pump_cooldown_min = document.getElementById("plant_" + i + "_pump_cooldown_min") as HTMLInputElement;
let plant_pump_hour_start = document.getElementById("plant_" + i + "_pump_hour_start") as HTMLInputElement;
let plant_pump_hour_end = document.getElementById("plant_" + i + "_pump_hour_end") as HTMLInputElement;
let plant_sensor_b = document.getElementById("plant_" + i + "_sensor_b") as HTMLInputElement;
let plant_sensor_p = document.getElementById("plant_" + i + "_sensor_p") as HTMLInputElement;
current.plants[i] = {
mode: plant_mode.value,
@ -243,8 +278,9 @@ let fromWrapper = (() => {
pump_time_s: +plant_pump_time_s.value,
pump_cooldown_min: +plant_pump_cooldown_min.value,
pump_hour_start: +plant_pump_hour_start.value,
pump_hour_end: +plant_pump_hour_end.value
pump_hour_end: +plant_pump_hour_end.value,
sensor_b: plant_sensor_b.checked,
sensor_p: plant_sensor_p.checked
}
}
sync(current);