Compare commits

...

3 Commits

Author SHA1 Message Date
bfee21796a fix rollback and windows build 2024-02-21 15:36:26 +01:00
57a0971c4b also print mode to mqtt 2024-02-17 18:43:36 +01:00
556e64740a allow build to run on windows as well 2024-02-17 18:40:11 +01:00
7 changed files with 190 additions and 109 deletions

View File

@ -4,8 +4,8 @@ target = "xtensa-esp32-espidf"
[target.xtensa-esp32-espidf] [target.xtensa-esp32-espidf]
linker = "ldproxy" linker = "ldproxy"
#runner = "espflash flash --monitor --partition-table partitions.csv" # Select this runner for espflash v2.x.x #runner = "espflash flash --monitor --partition-table partitions.csv" # Select this runner for espflash v2.x.x
runner = "espflash flash --monitor --baud 921600 --partition-table partitions.csv" # Select this runner for espflash v2.x.x #runner = "espflash flash --monitor --baud 921600 --partition-table partitions.csv" # Select this runner for espflash v2.x.x
#runner = "cargo runner" runner = "cargo runner"
rustflags = [ "--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110 rustflags = [ "--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
[unstable] [unstable]

View File

@ -6,20 +6,37 @@ fn main() {
Command::new("rm") Command::new("rm")
.arg("./src/webserver/bundle.js") .arg("./src/webserver/bundle.js")
.output() .output()
.expect("failed to execute process"); .unwrap();
let output = Command::new("npx") match Command::new("cmd").spawn() {
.arg("webpack") Ok(_) => {
.current_dir("./src_webpack") println!("Assuming build on windows");
.output() let output = Command::new("cmd")
.expect("failed to execute process"); .arg("/K")
.arg("npx")
println!("status: {}", output.status); .arg("webpack")
println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); .current_dir("./src_webpack")
println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); .output()
.unwrap();
assert!(output.status.success()); println!("status: {}", output.status);
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
assert!(output.status.success());
}
Err(_) => {
println!("Assuming build on linux");
let output = Command::new("bash")
.arg("webpack")
.current_dir("./src_webpack")
.output()
.unwrap();
println!("status: {}", output.status);
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
assert!(output.status.success());
}
}
embuild::espidf::sysenv::output(); embuild::espidf::sysenv::output();
let _ = EmitBuilder::builder().all_git().emit(); let _ = EmitBuilder::builder().all_git().all_build().emit();
} }

View File

@ -1,3 +1,3 @@
[toolchain] [toolchain]
channel = "nightly" channel = "nightly"
toolchain = "esp"

View File

@ -5,4 +5,4 @@ CONFIG_ESP_MAIN_TASK_STACK_SIZE=20000
# This allows to use 1 ms granuality for thread sleeps (10 ms by default). # This allows to use 1 ms granuality for thread sleeps (10 ms by default).
CONFIG_FREERTOS_HZ=1000 CONFIG_FREERTOS_HZ=1000
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=true CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y

View File

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

View File

@ -1,16 +1,11 @@
use std::{ use std::sync::{atomic::AtomicBool, Arc, Mutex};
env,
sync::{atomic::AtomicBool, Arc, Mutex},
};
use chrono::{DateTime, Datelike, Duration, NaiveDateTime, Timelike}; use chrono::{DateTime, Datelike, Duration, NaiveDateTime, Timelike};
use chrono_tz::{Europe::Berlin, Tz}; use chrono_tz::{Europe::Berlin, Tz};
use config::Plant;
use embedded_svc::mqtt;
use esp_idf_hal::delay::Delay; use esp_idf_hal::delay::Delay;
use esp_idf_sys::{ use esp_idf_sys::{
esp_deep_sleep, esp_restart, gpio_deep_sleep_hold_dis, gpio_deep_sleep_hold_en, vTaskDelay, esp_deep_sleep, esp_ota_get_app_partition_count, esp_ota_get_running_partition, esp_ota_get_state_partition, esp_ota_img_states_t, esp_ota_img_states_t_ESP_OTA_IMG_ABORTED, esp_ota_img_states_t_ESP_OTA_IMG_INVALID, esp_ota_img_states_t_ESP_OTA_IMG_NEW, esp_ota_img_states_t_ESP_OTA_IMG_PENDING_VERIFY, esp_ota_img_states_t_ESP_OTA_IMG_UNDEFINED, esp_ota_img_states_t_ESP_OTA_IMG_VALID, esp_restart, gpio_deep_sleep_hold_dis, gpio_deep_sleep_hold_en, vTaskDelay, CONFIG_FREERTOS_HZ
CONFIG_FREERTOS_HZ,
}; };
use log::error; use log::error;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
@ -19,7 +14,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
config::{Config, WifiConfig}, config::{Config, WifiConfig},
espota::rollback_and_reboot, espota::{mark_app_valid, rollback_and_reboot},
webserver::webserver::{httpd, httpd_initial}, webserver::webserver::{httpd, httpd_initial},
}; };
mod config; mod config;
@ -84,7 +79,7 @@ struct PlantState {
sensor_error_b: Option<SensorError>, sensor_error_b: Option<SensorError>,
sensor_error_p: Option<SensorError>, sensor_error_p: Option<SensorError>,
out_of_work_hour: bool, out_of_work_hour: bool,
next_pump: Option<DateTime<Tz>> next_pump: Option<DateTime<Tz>>,
} }
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)] #[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
@ -121,28 +116,41 @@ fn safe_main() -> anyhow::Result<()> {
log::info!("Startup Rust"); log::info!("Startup Rust");
let git_hash = env!("VERGEN_GIT_DESCRIBE"); let git_hash = env!("VERGEN_GIT_DESCRIBE");
println!("Version useing git has {}", git_hash); let build_timestamp = env!("VERGEN_BUILD_TIMESTAMP");
println!(
"Version useing git has {} build on {}",
git_hash, build_timestamp
);
let partition_state: embedded_svc::ota::SlotState = embedded_svc::ota::SlotState::Unknown; let count = unsafe { esp_ota_get_app_partition_count() };
match esp_idf_svc::ota::EspOta::new() { println!("Partition count is {}", count);
Ok(ota) => { let mut ota_state: esp_ota_img_states_t = 0;
//match ota.get_running_slot(){ let running_partition = unsafe { esp_ota_get_running_partition() };
// Ok(slot) => { let address = unsafe {
// partition_state = slot.state; (*running_partition).address
// println!(
// "Booting from {} with state {:?}", };
// slot.label, partition_state println!("Partition address is {}", address);
// );
//}, let ota_state_string = unsafe {
// Err(err) => { esp_ota_get_state_partition(running_partition, &mut ota_state);
// println!("Error getting running slot {}", err); if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_NEW {
// }, format!("Partition state is {}", "ESP_OTA_IMG_NEW")
//} } else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_PENDING_VERIFY {
format!("Partition state is {}", "ESP_OTA_IMG_PENDING_VERIFY")
} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_VALID {
format!("Partition state is {}", "ESP_OTA_IMG_VALID")
} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_INVALID {
format!("Partition state is {}", "ESP_OTA_IMG_INVALID")
} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_ABORTED {
format!("Partition state is {}", "ESP_OTA_IMG_ABORTED")
} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_UNDEFINED {
format!("Partition state is {}", "ESP_OTA_IMG_UNDEFINED")
} else {
format!("Partition state is {}", ota_state)
} }
Err(err) => { };
println!("Error obtaining ota info {}", err); println!("{}", ota_state_string);
}
}
println!("Board hal init"); println!("Board hal init");
let mut board: std::sync::MutexGuard<'_, PlantCtrlBoard<'_>> = BOARD_ACCESS.lock().unwrap(); let mut board: std::sync::MutexGuard<'_, PlantCtrlBoard<'_>> = BOARD_ACCESS.lock().unwrap();
@ -212,13 +220,9 @@ fn safe_main() -> anyhow::Result<()> {
} }
Err(err) => { Err(err) => {
if board.is_wifi_config_file_existant() { if board.is_wifi_config_file_existant() {
match partition_state { if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_PENDING_VERIFY {
embedded_svc::ota::SlotState::Invalid println!("Config seem to be unparsable after upgrade, reverting");
| embedded_svc::ota::SlotState::Unverified => { rollback_and_reboot()?;
println!("Config seem to be unparsable after upgrade, reverting");
rollback_and_reboot()?;
}
_ => {}
} }
} }
println!("Missing wifi config, entering initial config mode {}", err); println!("Missing wifi config, entering initial config mode {}", err);
@ -289,15 +293,26 @@ fn safe_main() -> anyhow::Result<()> {
if online_mode == OnlineMode::Online { if online_mode == OnlineMode::Online {
let _ = board.mqtt_publish(&config, "/firmware/githash", git_hash.as_bytes()); let _ = board.mqtt_publish(&config, "/firmware/githash", git_hash.as_bytes());
let _ = board.mqtt_publish(&config, "/firmware/buildtime", build_timestamp.as_bytes());
let _ = board.mqtt_publish(&config, "/firmware/last_online", europe_time.to_rfc3339().as_bytes());
let _ = board.mqtt_publish(&config, "/firmware/ota_state", ota_state_string.as_bytes());
let _ = board.mqtt_publish(&config, "/firmware/partition_address", format!("{:#06x}",address).as_bytes());
let _ = board.mqtt_publish(&config, "/state", "online".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); publish_battery_state(&mut board, &config);
} }
let tank_state = determine_tank_state(&mut board, &config); let tank_state = determine_tank_state(&mut board, &config);
if online_mode == OnlineMode::Online {
if tank_state.sensor_error {
let _ = board.mqtt_publish(&config, "/water/ml", "error".to_string().as_bytes());
} else {
let _ = board.mqtt_publish(&config, "/water/ml", tank_state.left_ml.to_string().as_bytes());
let _ = board.mqtt_publish(&config, "/water/enough_water", tank_state.enough_water.to_string().as_bytes());
let _ = board.mqtt_publish(&config, "/water/raw", tank_state.raw.to_string().as_bytes());
}
}
let mut water_frozen = false; let mut water_frozen = false;
for _attempt in 0..5 { for _attempt in 0..5 {
@ -354,10 +369,9 @@ fn safe_main() -> anyhow::Result<()> {
board.store_consecutive_pump_count(plant, consecutive_pump_count); board.store_consecutive_pump_count(plant, consecutive_pump_count);
let plant_config = config.plants[plant]; let plant_config = config.plants[plant];
if plant_config.sensor_p { if plant_config.sensor_p {
match map_range_moisture( match map_range_moisture(
board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32 board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32,
) { ) {
Ok(p) => state.p = Some(p), Ok(p) => state.p = Some(p),
Err(err) => { Err(err) => {
@ -377,36 +391,34 @@ fn safe_main() -> anyhow::Result<()> {
board.pump(plant, true)?; board.pump(plant, true)?;
board.last_pump_time(plant); board.last_pump_time(plant);
state.active = true; state.active = true;
for t in 0..plant_config.pump_time_s { for _ in 0..plant_config.pump_time_s {
//FIXME do periodic pump test here and state update
unsafe { vTaskDelay(CONFIG_FREERTOS_HZ) }; unsafe { vTaskDelay(CONFIG_FREERTOS_HZ) };
if plant_config.sensor_p { if plant_config.sensor_p {
let moist = map_range_moisture( let moist = map_range_moisture(
board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32); board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32,
);
if online_mode == OnlineMode::Online { if online_mode == OnlineMode::Online {
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/Sensor P after", plant+1).as_str(), format!("/plant{}/Sensor P after", plant + 1).as_str(),
option_to_string(moist.ok()).as_bytes(), option_to_string(moist.ok()).as_bytes(),
); );
} }
} else { } else {
if online_mode == OnlineMode::Online { if online_mode == OnlineMode::Online {
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/Sensor P after", plant+1).as_str(), format!("/plant{}/Sensor P after", plant + 1).as_str(),
"disabled".as_bytes(), "disabled".as_bytes(),
); );
} }
} }
} }
board.pump(plant, false)?; board.pump(plant, false)?;
if plant_config.sensor_p { if plant_config.sensor_p {
match map_range_moisture( match map_range_moisture(
board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32 board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32,
) { ) {
Ok(p) => state.after_p = Some(p), Ok(p) => state.after_p = Some(p),
Err(err) => { Err(err) => {
@ -423,7 +435,6 @@ fn safe_main() -> anyhow::Result<()> {
//mqtt sync pump error value //mqtt sync pump error value
} }
} }
} }
None => { None => {
println!("Nothing to do"); println!("Nothing to do");
@ -435,7 +446,8 @@ fn safe_main() -> anyhow::Result<()> {
let mut light_state = LightState { let mut light_state = LightState {
..Default::default() ..Default::default()
}; };
light_state.is_day = board.is_day(); let is_day = board.is_day();
light_state.is_day = is_day;
light_state.out_of_work_hour = !in_time_range( light_state.out_of_work_hour = !in_time_range(
europe_time, europe_time,
config.night_lamp_hour_start, config.night_lamp_hour_start,
@ -490,12 +502,42 @@ fn safe_main() -> anyhow::Result<()> {
unsafe { gpio_deep_sleep_hold_dis() }; unsafe { gpio_deep_sleep_hold_dis() };
unsafe { gpio_deep_sleep_hold_en() }; unsafe { gpio_deep_sleep_hold_en() };
let deep_sleep_duration_minutes: u32 = if state_of_charge < 10 {
if online_mode == OnlineMode::Online {
let _ = board.mqtt_publish(
&config,
"/deepsleep",
"Entering low voltage long deep sleep".as_bytes(),
);
}
12 * 60
} else if is_day {
if online_mode == OnlineMode::Online {
let _ = board.mqtt_publish(
&config,
"/deepsleep",
"Entering normal mode 20m deep sleep".as_bytes(),
);
}
20
} else {
if online_mode == OnlineMode::Online {
let _ = board.mqtt_publish(
&config,
"/deepsleep",
"Entering night mode 1h deep sleep".as_bytes(),
);
}
60
};
//determine next event //determine next event
//is light out of work trigger soon? //is light out of work trigger soon?
//is battery low ?? //is battery low ??
//is deep sleep //is deep sleep
unsafe { esp_deep_sleep(1000 * 1000 * 20) }; mark_app_valid();
unsafe { esp_deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64) };
} }
fn publish_battery_state( fn publish_battery_state(
@ -764,16 +806,17 @@ fn determine_state_target_moisture_for_plant(
} }
} }
} }
//FIXME how to average analyze whatever? //FIXME how to average analyze whatever?
let a_low = state.a.is_some() && state.a.unwrap() < plant_config.target_moisture; 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; let b_low = state.b.is_some() && state.b.unwrap() < plant_config.target_moisture;
if a_low || b_low { if a_low || b_low {
state.dry = true; state.dry = true;
if tank_state.sensor_error && !config.tank_allow_pumping_if_sensor_error if tank_state.sensor_error && !config.tank_allow_pumping_if_sensor_error {
|| !tank_state.enough_water //ignore is ok
{ }
else if !tank_state.enough_water {
state.no_water = true; state.no_water = true;
} }
} }
@ -901,24 +944,39 @@ fn determine_next_plant(
return None; return None;
} }
fn update_plant_state(plantstate: &mut [PlantState; PLANT_COUNT], board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, config: &Config){ fn update_plant_state(
plantstate: &mut [PlantState; PLANT_COUNT],
board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
config: &Config,
) {
for plant in 0..PLANT_COUNT { for plant in 0..PLANT_COUNT {
let state = &plantstate[plant]; let state = &plantstate[plant];
let plant_config = config.plants[plant]; let plant_config = config.plants[plant];
let _ = board.mqtt_publish(
&config,
format!("/plant{}/mode", plant + 1).as_str(),
match plant_config.mode {
config::Mode::OFF => "OFF".as_bytes(),
config::Mode::TargetMoisture => "TargetMoisture".as_bytes(),
config::Mode::TimerOnly => "TimerOnly".as_bytes(),
config::Mode::TimerAndDeadzone => "TimerAndDeadzone".as_bytes(),
},
);
let last_time = board.last_pump_time(plant); let last_time = board.last_pump_time(plant);
let europe_time = last_time.with_timezone(&Berlin); let europe_time = last_time.with_timezone(&Berlin);
if europe_time.year() > 2023 { if europe_time.year() > 2023 {
let time = europe_time.to_rfc3339(); let time = europe_time.to_rfc3339();
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/last pump", plant+1).as_str(), format!("/plant{}/last pump", plant + 1).as_str(),
time.as_bytes(), time.as_bytes(),
); );
} else { } else {
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/last pump", plant+1).as_str(), format!("/plant{}/last pump", plant + 1).as_str(),
"N/A".as_bytes(), "N/A".as_bytes(),
); );
} }
@ -928,101 +986,100 @@ fn update_plant_state(plantstate: &mut [PlantState; PLANT_COUNT], board: &mut st
let time = next.to_rfc3339(); let time = next.to_rfc3339();
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/next pump", plant+1).as_str(), format!("/plant{}/next pump", plant + 1).as_str(),
time.as_bytes(), time.as_bytes(),
); );
}, }
None => { None => {
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/next pump", plant+1).as_str(), format!("/plant{}/next pump", plant + 1).as_str(),
"N/A".as_bytes(), "N/A".as_bytes(),
); );
}, }
} }
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/Sensor A", plant+1).as_str(), format!("/plant{}/Sensor A", plant + 1).as_str(),
option_to_string(state.a).as_bytes(), option_to_string(state.a).as_bytes(),
); );
if plant_config.sensor_b { if plant_config.sensor_b {
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/Sensor B", plant+1).as_str(), format!("/plant{}/Sensor B", plant + 1).as_str(),
option_to_string(state.b).as_bytes(), option_to_string(state.b).as_bytes(),
); );
} else { } else {
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/Sensor B", plant+1).as_str(), format!("/plant{}/Sensor B", plant + 1).as_str(),
"disabled".as_bytes(), "disabled".as_bytes(),
); );
} }
if plant_config.sensor_p { if plant_config.sensor_p {
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/Sensor P before", plant+1).as_str(), format!("/plant{}/Sensor P before", plant + 1).as_str(),
option_to_string(state.p).as_bytes(), option_to_string(state.p).as_bytes(),
); );
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/Sensor P after", plant+1).as_str(), format!("/plant{}/Sensor P after", plant + 1).as_str(),
option_to_string(state.after_p).as_bytes(), option_to_string(state.after_p).as_bytes(),
); );
} else { } else {
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/Sensor P before", plant+1).as_str(), format!("/plant{}/Sensor P before", plant + 1).as_str(),
"disabled".as_bytes(), "disabled".as_bytes(),
); );
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/Sensor P after", plant+1).as_str(), format!("/plant{}/Sensor P after", plant + 1).as_str(),
"disabled".as_bytes(), "disabled".as_bytes(),
); );
} }
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/Should water", plant+1).as_str(), format!("/plant{}/Should water", plant + 1).as_str(),
state.do_water.to_string().as_bytes(), state.do_water.to_string().as_bytes(),
); );
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/Is frozen", plant+1).as_str(), format!("/plant{}/Is frozen", plant + 1).as_str(),
state.frozen.to_string().as_bytes(), state.frozen.to_string().as_bytes(),
); );
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/Is dry", plant+1).as_str(), format!("/plant{}/Is dry", plant + 1).as_str(),
state.dry.to_string().as_bytes(), state.dry.to_string().as_bytes(),
); );
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/Pump Error", plant+1).as_str(), format!("/plant{}/Pump Error", plant + 1).as_str(),
state.pump_error.to_string().as_bytes(), state.pump_error.to_string().as_bytes(),
); );
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/Pump Ineffective", plant+1).as_str(), format!("/plant{}/Pump Ineffective", plant + 1).as_str(),
state.not_effective.to_string().as_bytes(), state.not_effective.to_string().as_bytes(),
); );
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/Is in Cooldown", plant+1).as_str(), format!("/plant{}/Is in Cooldown", plant + 1).as_str(),
state.cooldown.to_string().as_bytes(), state.cooldown.to_string().as_bytes(),
); );
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/No Water", plant+1).as_str(), format!("/plant{}/No Water", plant + 1).as_str(),
state.no_water.to_string().as_bytes(), state.no_water.to_string().as_bytes(),
); );
let _ = board.mqtt_publish( let _ = board.mqtt_publish(
&config, &config,
format!("/plant{}/Out of Work Hour", plant+1).as_str(), format!("/plant{}/Out of Work Hour", plant + 1).as_str(),
state.out_of_work_hour.to_string().as_bytes(), state.out_of_work_hour.to_string().as_bytes(),
); );
} }
@ -1069,7 +1126,18 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
fn main() { fn main() {
let result = safe_main(); let result = safe_main();
result.unwrap(); match result {
Ok(_) => {
println!("Main app finished, restarting");
unsafe { esp_restart() };
},
Err(err) => {
println!("Failed main {}", err);
let rollback_successful = rollback_and_reboot();
println!("Failed to rollback :(");
rollback_successful.unwrap();
},
}
} }
//error codes //error codes
//error_reading_config_after_upgrade //error_reading_config_after_upgrade

View File

@ -159,7 +159,6 @@ pub struct PlantCtrlBoard<'a> {
PinDriver<'a, esp_idf_hal::gpio::Gpio22, InputOutput>, PinDriver<'a, esp_idf_hal::gpio::Gpio22, InputOutput>,
PinDriver<'a, esp_idf_hal::gpio::Gpio19, InputOutput>, PinDriver<'a, esp_idf_hal::gpio::Gpio19, InputOutput>,
>, >,
low_voltage_detected: Mutex<bool>,
tank_driver: AdcDriver<'a, esp_idf_hal::adc::ADC1>, tank_driver: AdcDriver<'a, esp_idf_hal::adc::ADC1>,
tank_channel: esp_idf_hal::adc::AdcChannelDriver<'a, { attenuation::DB_11 }, Gpio39>, tank_channel: esp_idf_hal::adc::AdcChannelDriver<'a, { attenuation::DB_11 }, Gpio39>,
solar_is_day: PinDriver<'a, esp_idf_hal::gpio::Gpio25, esp_idf_hal::gpio::Input>, solar_is_day: PinDriver<'a, esp_idf_hal::gpio::Gpio25, esp_idf_hal::gpio::Input>,
@ -963,8 +962,6 @@ impl CreatePlantHal<'_> for PlantHal {
let nvs = EspDefaultNvsPartition::take()?; let nvs = EspDefaultNvsPartition::take()?;
let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?; let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?;
let low_voltage_detected = Mutex::new(unsafe { LOW_VOLTAGE_DETECTED });
let adc_config = esp_idf_hal::adc::config::Config { let adc_config = esp_idf_hal::adc::config::Config {
resolution: esp_idf_hal::adc::config::Resolution::Resolution12Bit, resolution: esp_idf_hal::adc::config::Resolution::Resolution12Bit,
calibration: true, calibration: true,
@ -1001,7 +998,6 @@ impl CreatePlantHal<'_> for PlantHal {
} }
let rv = Mutex::new(PlantCtrlBoard { let rv = Mutex::new(PlantCtrlBoard {
shift_register, shift_register,
low_voltage_detected,
tank_driver, tank_driver,
tank_channel, tank_channel,
solar_is_day, solar_is_day,