allow to selectivly enable redundant sensors
This commit is contained in:
parent
680d1c3aaf
commit
9d1a807805
@ -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"
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
256
rust/src/main.rs
256
rust/src/main.rs
@ -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,6 +290,9 @@ 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,9 +377,34 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
board.pump(plant, true)?;
|
||||
board.last_pump_time(plant);
|
||||
state.active = true;
|
||||
for t in 0..plant_config.pump_time_s {
|
||||
//FIXME do periodic pump test here and state update
|
||||
unsafe { vTaskDelay(plant_config.pump_time_s as u32 * CONFIG_FREERTOS_HZ) };
|
||||
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)?;
|
||||
if plant_config.sensor_p {
|
||||
match map_range_moisture(
|
||||
board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32
|
||||
) {
|
||||
@ -379,11 +423,15 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
//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,6 +748,7 @@ fn determine_state_target_moisture_for_plant(
|
||||
state.sensor_error_a = Some(SensorError::Unknown);
|
||||
}
|
||||
}
|
||||
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);
|
||||
@ -714,6 +763,8 @@ fn determine_state_target_moisture_for_plant(
|
||||
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,
|
||||
|
@ -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(())
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user