Improve error handling, ensure robust defaults, and eliminate unsafe unwraps/expectations across modules.

This commit is contained in:
2026-04-06 15:26:52 +02:00
parent 4d4fcbe33b
commit 0ad7a58219
13 changed files with 186 additions and 87 deletions

View File

@@ -8,6 +8,7 @@
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
holding buffers for the duration of a data transfer."
)]
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
//TODO insert version here and read it in other parts, also read this for the ota webview
esp_bootloader_esp_idf::esp_app_desc!();
@@ -39,7 +40,7 @@ use embassy_net::Stack;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::{Mutex, MutexGuard};
use embassy_sync::once_lock::OnceLock;
use embassy_time::{Duration, Instant, Timer, WithTimeout};
use embassy_time::{Duration, Instant, Timer};
use esp_hal::rom::ets_delay_us;
use esp_hal::system::software_reset;
use esp_println::{logger, println};
@@ -297,7 +298,9 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
if let NetworkMode::Wifi { ref ip_address, .. } = network_mode {
publish_firmware_info(&mut board, version, ip_address, &timezone_time.to_rfc3339()).await;
publish_battery_state(&mut board).await;
publish_battery_state(&mut board).await.unwrap_or_else(|e| {
error!("Error publishing battery state {e}");
});
let _ = publish_mppt_state(&mut board).await;
}
@@ -326,7 +329,12 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
info!("executing config mode override");
//config upload will trigger reboot!
let reboot_now = Arc::new(AtomicBool::new(false));
spawner.spawn(http_server(reboot_now.clone(), stack.take().unwrap()))?;
let stack_val = stack.take();
if let Some(s) = stack_val {
spawner.spawn(http_server(reboot_now.clone(), s))?;
} else {
bail!("Network stack missing, hard abort")
}
wait_infinity(board, WaitType::ConfigButton, reboot_now.clone()).await;
} else {
LOG_ACCESS
@@ -406,7 +414,11 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
}
info!("Water temp is {}", water_temp.as_ref().unwrap_or(&0.));
publish_tank_state(&mut board, &tank_state, water_temp).await;
publish_tank_state(&mut board, &tank_state, water_temp)
.await
.unwrap_or_else(|e| {
error!("Error publishing tank state {e}");
});
let moisture = board.board_hal.measure_moisture_hz().await?;
@@ -421,7 +433,11 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
PlantState::read_hardware_state(moisture, 7, &mut board).await,
];
publish_plant_states(&mut board, &timezone_time.clone(), &plantstate).await;
publish_plant_states(&mut board, &timezone_time.clone(), &plantstate)
.await
.unwrap_or_else(|e| {
error!("Error publishing plant states {e}");
});
let pump_required = plantstate
.iter()
@@ -625,14 +641,18 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
if stay_alive {
let reboot_now = Arc::new(AtomicBool::new(false));
let _webserver = http_server(reboot_now.clone(), stack.take().unwrap());
wait_infinity(board, WaitType::MqttConfig, reboot_now.clone()).await;
if let Some(s) = stack.take() {
let _webserver = http_server(reboot_now.clone(), s);
wait_infinity(board, WaitType::MqttConfig, reboot_now.clone()).await;
} else {
bail!("Network Stack missing, hard abort");
}
} else {
//TODO wait for all mqtt publishes?
Timer::after_millis(5000).await;
board.board_hal.get_esp().set_restart_to_conf(false);
board
let _ = board
.board_hal
.deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64)
.await;
@@ -801,30 +821,29 @@ async fn publish_tank_state(
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
tank_state: &TankState,
water_temp: FatResult<f32>,
) {
) -> FatResult<()> {
let state = serde_json::to_string(
&tank_state.as_mqtt_info(&board.board_hal.get_config().tank, &water_temp),
)
.unwrap();
)?;
board
.board_hal
.get_esp()
.mqtt_publish("/water", &state)
.await;
Ok(())
}
async fn publish_plant_states(
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
timezone_time: &DateTime<Tz>,
plantstate: &[PlantState; 8],
) {
) -> FatResult<()> {
for (plant_id, (plant_state, plant_conf)) in plantstate
.iter()
.zip(&board.board_hal.get_config().plants.clone())
.enumerate()
{
let state =
serde_json::to_string(&plant_state.to_mqtt_info(plant_conf, timezone_time)).unwrap();
let state = serde_json::to_string(&plant_state.to_mqtt_info(plant_conf, timezone_time))?;
let plant_topic = format!("/plant{}", plant_id + 1);
let _ = board
.board_hal
@@ -832,6 +851,7 @@ async fn publish_plant_states(
.mqtt_publish(&plant_topic, &state)
.await;
}
Ok(())
}
async fn publish_firmware_info(
@@ -907,13 +927,9 @@ async fn try_connect_wifi_sntp_mqtt(
let ip = match stack.config_v4() {
Some(config) => config.address.address().to_string(),
None => {
match stack.config_v6() {
Some(config) => config.address.address().to_string(),
None => {
String::from("No IP")
}
}
None => match stack.config_v6() {
Some(config) => config.address.address().to_string(),
None => String::from("No IP"),
},
};
NetworkMode::Wifi {
@@ -987,11 +1003,11 @@ async fn publish_mppt_state(
async fn publish_battery_state(
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
) -> () {
) -> FatResult<()> {
let state = board.board_hal.get_battery_monitor().get_state().await;
let value = match state {
Ok(state) => {
let json = serde_json::to_string(&state).unwrap().to_owned();
let json = serde_json::to_string(&state)?.to_owned();
json.to_owned()
}
Err(_) => "error".to_owned(),
@@ -1003,6 +1019,7 @@ async fn publish_battery_state(
.mqtt_publish("/battery", &value)
.await;
}
Ok(())
}
async fn wait_infinity(
@@ -1049,8 +1066,7 @@ async fn wait_infinity(
exit_hold_blink = !exit_hold_blink;
let progress = core::cmp::min(elapsed, exit_hold_duration);
let lit = ((progress.as_millis() * 8)
/ exit_hold_duration.as_millis())
let lit = ((progress.as_millis() * 8) / exit_hold_duration.as_millis())
.saturating_add(1)
.min(8) as usize;
@@ -1200,6 +1216,8 @@ async fn handle_serial_config(
}
}
use embassy_time::WithTimeout;
#[allow(clippy::panic, clippy::unwrap_used, clippy::expect_used)]
#[esp_rtos::main]
async fn main(spawner: Spawner) -> ! {
// intialize embassy