From 0ad7a58219c7368259e0ed9f01e2d684739b4f23 Mon Sep 17 00:00:00 2001 From: Empire Phoenix Date: Mon, 6 Apr 2026 15:26:52 +0200 Subject: [PATCH] Improve error handling, ensure robust defaults, and eliminate unsafe unwraps/expectations across modules. --- Software/MainBoard/rust/Cargo.toml | 1 + Software/MainBoard/rust/clippy.toml | 2 + Software/MainBoard/rust/src/fat_error.rs | 45 +++++++++++- Software/MainBoard/rust/src/hal/esp.rs | 67 +++++++++++------- Software/MainBoard/rust/src/hal/mod.rs | 27 +++++--- Software/MainBoard/rust/src/hal/v4_hal.rs | 21 +++--- Software/MainBoard/rust/src/hal/water.rs | 12 +++- Software/MainBoard/rust/src/log/mod.rs | 8 ++- Software/MainBoard/rust/src/main.rs | 68 ++++++++++++------- Software/MainBoard/rust/src/tank.rs | 11 ++- .../rust/src/webserver/backup_manager.rs | 8 +-- Software/MainBoard/rust/src/webserver/mod.rs | 1 + .../MainBoard/rust/src/webserver/post_json.rs | 2 +- 13 files changed, 186 insertions(+), 87 deletions(-) create mode 100644 Software/MainBoard/rust/clippy.toml diff --git a/Software/MainBoard/rust/Cargo.toml b/Software/MainBoard/rust/Cargo.toml index 00c57ed..18dde2f 100644 --- a/Software/MainBoard/rust/Cargo.toml +++ b/Software/MainBoard/rust/Cargo.toml @@ -136,6 +136,7 @@ measurements = "0.11.1" # Project-specific mcutie = { version = "0.3.0", default-features = false, features = ["log", "homeassistant"] } +no-panic = "0.1.36" [patch.crates-io] mcutie = { git = 'https://github.com/empirephoenix/mcutie.git' } diff --git a/Software/MainBoard/rust/clippy.toml b/Software/MainBoard/rust/clippy.toml new file mode 100644 index 0000000..de1e4a1 --- /dev/null +++ b/Software/MainBoard/rust/clippy.toml @@ -0,0 +1,2 @@ +# This file is used for clippy configuration. +# It shouldn't contain the deny attributes, which belong to the crate root. diff --git a/Software/MainBoard/rust/src/fat_error.rs b/Software/MainBoard/rust/src/fat_error.rs index 604b3f0..6fbccc0 100644 --- a/Software/MainBoard/rust/src/fat_error.rs +++ b/Software/MainBoard/rust/src/fat_error.rs @@ -1,5 +1,7 @@ use alloc::format; use alloc::string::{String, ToString}; +use chrono::format::ParseErrorKind; +use chrono_tz::ParseError; use core::convert::Infallible; use core::fmt; use core::fmt::Debug; @@ -149,6 +151,23 @@ impl ContextExt for Option { } } +impl ContextExt for Result +where + E: fmt::Debug, +{ + fn context(self, context: C) -> Result + where + C: AsRef, + { + match self { + Ok(value) => Ok(value), + Err(err) => Err(FatError::String { + error: format!("{}: {:?}", context.as_ref(), err), + }), + } + } +} + impl From> for FatError { fn from(error: Error) -> Self { FatError::OneWireError { error } @@ -283,7 +302,7 @@ impl From>> for FatError impl From for FatError { fn from(value: Infallible) -> Self { - panic!("Infallible error: {:?}", value) + match value {} } } @@ -336,3 +355,27 @@ impl From for FatError { } } } + +impl From for FatError { + fn from(value: ParseError) -> Self { + FatError::String { + error: format!("Parsing error: {value:?}"), + } + } +} + +impl From for FatError { + fn from(value: ParseErrorKind) -> Self { + FatError::String { + error: format!("Parsing error: {value:?}"), + } + } +} + +impl From for FatError { + fn from(value: chrono::format::ParseError) -> Self { + FatError::String { + error: format!("Parsing error: {value:?}"), + } + } +} diff --git a/Software/MainBoard/rust/src/hal/esp.rs b/Software/MainBoard/rust/src/hal/esp.rs index e92f10e..a139635 100644 --- a/Software/MainBoard/rust/src/hal/esp.rs +++ b/Software/MainBoard/rust/src/hal/esp.rs @@ -314,17 +314,19 @@ impl Esp<'_> { &mut tx_meta, &mut tx_buffer, ); - socket.bind(123).unwrap(); + socket.bind(123).context("Could not bind UDP socket")?; let context = NtpContext::new(Timestamp::default()); let ntp_addrs = stack .dns_query(NTP_SERVER, DnsQueryType::A) - .await; - if ntp_addrs.is_err() { - bail!("Failed to resolve DNS"); + .await + .context("Failed to resolve DNS")?; + + if ntp_addrs.is_empty() { + bail!("No IP addresses found for NTP server"); } - let ntp = ntp_addrs.unwrap()[0]; + let ntp = ntp_addrs[0]; info!("NTP server: {ntp:?}"); let mut counter = 0; @@ -416,9 +418,14 @@ impl Esp<'_> { Err(_) => "PlantCtrl Emergency Mode".to_string(), }; - let device = self.interface_ap.take().unwrap(); + let device = self + .interface_ap + .take() + .context("AP interface already taken")?; let gw_ip_addr_str = "192.168.71.1"; - let gw_ip_addr = Ipv4Addr::from_str(gw_ip_addr_str).expect("failed to parse gateway ip"); + let gw_ip_addr = Ipv4Addr::from_str(gw_ip_addr_str).map_err(|_| FatError::String { + error: "failed to parse gateway ip".to_string(), + })?; let config = embassy_net::Config::ipv4_static(StaticConfigV4 { address: Ipv4Cidr::new(gw_ip_addr, 24), @@ -472,18 +479,17 @@ impl Esp<'_> { spawner: Spawner, ) -> FatResult> { esp_radio::wifi_set_log_verbose(); - let ssid = network_config.ssid.clone(); - match &ssid { + let ssid = match &network_config.ssid { Some(ssid) => { if ssid.is_empty() { bail!("Wifi ssid was empty") } + ssid.to_string() } None => { bail!("Wifi ssid was empty") } - } - let ssid = ssid.unwrap().to_string(); + }; info!("attempting to connect wifi {ssid}"); let password = match network_config.password { Some(ref password) => password.to_string(), @@ -491,7 +497,10 @@ impl Esp<'_> { }; let max_wait = network_config.max_wait; - let device = self.interface_sta.take().unwrap(); + let device = self + .interface_sta + .take() + .context("STA interface already taken")?; let config = embassy_net::Config::dhcpv4(DhcpConfig::default()); let seed = (self.rng.random() as u64) << 32 | self.rng.random() as u64; @@ -596,9 +605,9 @@ impl Esp<'_> { if let Ok(cur) = self.ota.current_ota_state() { if cur == OtaImageState::PendingVerify { info!("Marking OTA image as valid"); - self.ota - .set_current_ota_state(Valid) - .expect("Could not set image to valid"); + if let Err(err) = self.ota.set_current_ota_state(Valid) { + error!("Could not set image to valid: {:?}", err); + } } } else { info!("No OTA image to mark as valid"); @@ -753,10 +762,7 @@ impl Esp<'_> { network_config.mqtt_user.as_ref(), network_config.mqtt_password.as_ref(), ) { - builder = builder.with_authentication( - mqtt_user, - mqtt_password, - ); + builder = builder.with_authentication(mqtt_user, mqtt_password); info!("With authentification"); } @@ -808,11 +814,10 @@ impl Esp<'_> { Timer::after(Duration::from_millis(100)).await; } - Topic::General(round_trip_topic.clone()) + let _ = Topic::General(round_trip_topic.clone()) .with_display("online_text") .publish() - .await - .unwrap(); + .await; let timeout = { let guard = TIME_ACCESS.get().await.lock().await; @@ -974,7 +979,13 @@ async fn run_dhcp(stack: Stack<'static>, gw_ip_addr: &'static str) { use edge_nal::UdpBind; use edge_nal_embassy::{Udp, UdpBuffers}; - let ip = Ipv4Addr::from_str(gw_ip_addr).expect("dhcp task failed to parse gw ip"); + let ip = match Ipv4Addr::from_str(gw_ip_addr) { + Ok(ip) => ip, + Err(_) => { + error!("dhcp task failed to parse gw ip"); + return; + } + }; let mut buf = [0u8; 1500]; @@ -982,13 +993,19 @@ async fn run_dhcp(stack: Stack<'static>, gw_ip_addr: &'static str) { let buffers = UdpBuffers::<3, 1024, 1024, 10>::new(); let unbound_socket = Udp::new(stack, &buffers); - let mut bound_socket = unbound_socket + let mut bound_socket = match unbound_socket .bind(SocketAddr::V4(SocketAddrV4::new( Ipv4Addr::UNSPECIFIED, DEFAULT_SERVER_PORT, ))) .await - .unwrap(); + { + Ok(s) => s, + Err(e) => { + error!("dhcp task failed to bind socket: {:?}", e); + return; + } + }; loop { _ = io::server::run( diff --git a/Software/MainBoard/rust/src/hal/mod.rs b/Software/MainBoard/rust/src/hal/mod.rs index 4c2045a..2aba0d7 100644 --- a/Software/MainBoard/rust/src/hal/mod.rs +++ b/Software/MainBoard/rust/src/hal/mod.rs @@ -150,7 +150,6 @@ pub trait BoardInteraction<'a> { async fn set_charge_indicator(&mut self, charging: bool) -> Result<(), FatError>; async fn deep_sleep(&mut self, duration_in_ms: u64) -> !; - fn is_day(&self) -> bool; //should be multsampled async fn light(&mut self, enable: bool) -> FatResult<()>; @@ -165,7 +164,7 @@ pub trait BoardInteraction<'a> { async fn get_mptt_current(&mut self) -> FatResult; async fn can_power(&mut self, state: bool) -> FatResult<()>; - async fn backup_config(&mut self, config: &PlantControllerConfig) -> FatResult<()>; + async fn backup_config(&mut self, config: &PlantControllerConfig) -> FatResult<()>; async fn read_backup(&mut self) -> FatResult; async fn backup_info(&mut self) -> FatResult; @@ -271,12 +270,17 @@ impl PlantHal { let rng = Rng::new(); let esp_wifi_ctrl = &*mk_static!( Controller<'static>, - init().expect("Could not init wifi controller") + init().map_err(|e| FatError::String { + error: format!("Could not init wifi controller: {:?}", e) + })? ); let (controller, interfaces) = - esp_radio::wifi::new(esp_wifi_ctrl, peripherals.WIFI, Default::default()) - .expect("Could not init wifi"); + esp_radio::wifi::new(esp_wifi_ctrl, peripherals.WIFI, Default::default()).map_err( + |e| FatError::String { + error: format!("Could not init wifi: {:?}", e), + }, + )?; let pcnt_module = Pcnt::new(peripherals.PCNT); @@ -330,7 +334,7 @@ impl PlantHal { pt.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data( DataPartitionSubType::Ota, ))? - .expect("No OTA data partition found") + .context("No OTA data partition found")? ); let ota_data = mk_static!( @@ -375,7 +379,7 @@ impl PlantHal { .find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data( DataPartitionSubType::LittleFs, ))? - .expect("Data partition with littlefs not found"); + .context("Data partition with littlefs not found")?; let data_partition = mk_static!(PartitionEntry, data_partition); let data = mk_static!( @@ -399,7 +403,8 @@ impl PlantHal { #[allow(clippy::arc_with_non_send_sync)] let fs = Arc::new(Mutex::new( - lfs2Filesystem::mount(alloc, lfs2filesystem).expect("Could not mount lfs2 filesystem"), + lfs2Filesystem::mount(alloc, lfs2filesystem) + .context("Could not mount lfs2 filesystem")?, )); let uart0 = @@ -493,7 +498,9 @@ impl PlantHal { RefCell>, > = CriticalSectionMutex::new(RefCell::new(i2c)); - I2C_DRIVER.init(i2c_bus).expect("Could not init i2c driver"); + I2C_DRIVER.init(i2c_bus).map_err(|_| FatError::String { + error: "Could not init i2c driver".to_string(), + })?; let i2c_bus = I2C_DRIVER.get().await; let rtc_device = I2cDevice::new(i2c_bus); @@ -655,7 +662,7 @@ pub fn next_partition(current: AppPartitionSubType) -> FatResult DateTime { let guard = TIME_ACCESS.get().await.lock().await; - DateTime::from_timestamp_micros(guard.current_time_us() as i64).unwrap() + DateTime::from_timestamp_micros(guard.current_time_us() as i64).unwrap_or(DateTime::UNIX_EPOCH) } pub async fn esp_set_time(time: DateTime) -> FatResult<()> { diff --git a/Software/MainBoard/rust/src/hal/v4_hal.rs b/Software/MainBoard/rust/src/hal/v4_hal.rs index 19e653c..9ed69d4 100644 --- a/Software/MainBoard/rust/src/hal/v4_hal.rs +++ b/Software/MainBoard/rust/src/hal/v4_hal.rs @@ -31,6 +31,7 @@ use ina219::SyncIna219; use log::{error, info, warn}; use measurements::Resistance; use measurements::{Current, Voltage}; +// use no_panic::no_panic; use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; pub const BACKUP_HEADER_MAX_SIZE: usize = 64; @@ -368,7 +369,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> { async fn measure_moisture_hz(&mut self) -> FatResult { self.can_power.set_high(); Timer::after_millis(500).await; - let config = self.twai_config.take().expect("twai config not set"); + let config = self.twai_config.take().context("twai config not set")?; let mut twai = config.into_async().start(); if twai.is_bus_off() { @@ -398,7 +399,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> { async fn detect_sensors(&mut self, request: Detection) -> FatResult { self.can_power.set_high(); Timer::after_millis(500).await; - let config = self.twai_config.take().expect("twai config not set"); + let config = self.twai_config.take().context("twai config not set")?; let mut twai = config.into_async().start(); if twai.is_bus_off() { @@ -542,8 +543,8 @@ impl<'a> BoardInteraction<'a> for V4<'a> { } Ok(()) } - async fn backup_config(&mut self, controller_config: &PlantControllerConfig) -> FatResult<()>{ - let mut buffer: [u8; 4096-BACKUP_HEADER_MAX_SIZE] = [0; 4096-BACKUP_HEADER_MAX_SIZE]; + async fn backup_config(&mut self, controller_config: &PlantControllerConfig) -> FatResult<()> { + let mut buffer: [u8; 4096 - BACKUP_HEADER_MAX_SIZE] = [0; 4096 - BACKUP_HEADER_MAX_SIZE]; let length = bincode::encode_into_slice(controller_config, &mut buffer, CONFIG)?; let mut checksum = X25.digest(); checksum.update(&buffer[..length]); @@ -562,7 +563,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> { while to_write > 0 { self.progress(chunk as u32).await; - let start = BACKUP_HEADER_MAX_SIZE + chunk* EEPROM_PAGE; + let start = BACKUP_HEADER_MAX_SIZE + chunk * EEPROM_PAGE; let end = start + crate::hal::rtc::EEPROM_PAGE; let part = &buffer[start..end]; to_write -= part.len(); @@ -576,7 +577,8 @@ impl<'a> BoardInteraction<'a> for V4<'a> { async fn read_backup(&mut self) -> FatResult { let info = self.backup_info().await?; let mut store = alloc::vec![0_u8; info.size as usize]; - self.rtc_module.read(BACKUP_HEADER_MAX_SIZE as u32, store.as_mut_slice())?; + self.rtc_module + .read(BACKUP_HEADER_MAX_SIZE as u32, store.as_mut_slice())?; let mut checksum = X25.digest(); checksum.update(&store[..]); let crc = checksum.finalize(); @@ -593,7 +595,10 @@ impl<'a> BoardInteraction<'a> for V4<'a> { let info: Result<(BackupHeader, usize), bincode::error::DecodeError> = bincode::decode_from_slice(&header_page_buffer[..], CONFIG); - info.map(|(header, _)| header).map_err(|e| FatError::String {error:"Could not read backup header: ".to_string() + &e.to_string()}) + info.map(|(header, _)| header) + .map_err(|e| FatError::String { + error: "Could not read backup header: ".to_string() + &e.to_string(), + }) } } @@ -654,8 +659,6 @@ async fn wait_for_can_measurements( } } } - - } impl From for Detection { diff --git a/Software/MainBoard/rust/src/hal/water.rs b/Software/MainBoard/rust/src/hal/water.rs index bdbb3b7..d63e9bd 100644 --- a/Software/MainBoard/rust/src/hal/water.rs +++ b/Software/MainBoard/rust/src/hal/water.rs @@ -33,7 +33,8 @@ impl<'a> TankSensor<'a> { one_wire_pin.apply_output_config(&OutputConfig::default().with_pull(Pull::None)); let mut adc1_config = AdcConfig::new(); - let tank_pin = adc1_config.enable_pin_with_cal::<_, AdcCalLine<_>>(gpio5, Attenuation::_11dB); + let tank_pin = + adc1_config.enable_pin_with_cal::<_, AdcCalLine<_>>(gpio5, Attenuation::_11dB); let tank_channel = Adc::new(adc1, adc1_config); let one_wire_bus = OneWire::new(one_wire_pin, false); @@ -141,12 +142,17 @@ impl<'a> TankSensor<'a> { let value = self.tank_channel.read_oneshot(&mut self.tank_pin); //force yield Timer::after_millis(10).await; - *sample = value.unwrap(); + match value { + Ok(v) => *sample = v, + Err(e) => { + bail!("ADC Hardware error: {:?}", e); + } + }; } self.tank_power.set_low(); store.sort(); let median_mv = store[TANK_MULTI_SAMPLE / 2] as f32; - Ok(median_mv/1000.0) + Ok(median_mv / 1000.0) } } diff --git a/Software/MainBoard/rust/src/log/mod.rs b/Software/MainBoard/rust/src/log/mod.rs index 5bf6828..577e3ce 100644 --- a/Software/MainBoard/rust/src/log/mod.rs +++ b/Software/MainBoard/rust/src/log/mod.rs @@ -97,7 +97,7 @@ pub async fn log( impl LogArray { pub fn get(&mut self) -> Vec { let head: RangedU8<0, MAX_LOG_ARRAY_INDEX> = - RangedU8::new(self.head).unwrap_or(RangedU8::new(0).unwrap()); + RangedU8::new(self.head).unwrap_or(RangedU8::new_saturating(0)); let mut rv: Vec = Vec::new(); let mut index = head.wrapping_sub(1); @@ -120,7 +120,7 @@ impl LogArray { txt_long: &str, ) { let mut head: RangedU8<0, MAX_LOG_ARRAY_INDEX> = - RangedU8::new(self.head).unwrap_or(RangedU8::new(0).unwrap()); + RangedU8::new(self.head).unwrap_or(RangedU8::new_saturating(0)); let mut txt_short_stack: heapless::String = heapless::String::new(); let mut txt_long_stack: heapless::String = heapless::String::new(); @@ -281,6 +281,8 @@ pub enum LogMessage { PumpMissingSensorCurrent, #[strum(serialize = "MPPT Current sensor could not be reached")] MPPTError, + #[strum(serialize = "Parsing error reading message")] + UnknownMessage, } #[derive(Serialize)] @@ -301,7 +303,7 @@ impl From<&LogMessage> for MessageTranslation { impl LogMessage { pub fn log_localisation_config() -> Vec { Vec::from_iter((0..LogMessage::len()).map(|i| { - let msg_type = LogMessage::from_ordinal(i).unwrap(); + let msg_type = LogMessage::from_ordinal(i).unwrap_or(LogMessage::UnknownMessage); (&msg_type).into() })) } diff --git a/Software/MainBoard/rust/src/main.rs b/Software/MainBoard/rust/src/main.rs index 337011f..274b2ac 100644 --- a/Software/MainBoard/rust/src/main.rs +++ b/Software/MainBoard/rust/src/main.rs @@ -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, -) { +) -> 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, 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 diff --git a/Software/MainBoard/rust/src/tank.rs b/Software/MainBoard/rust/src/tank.rs index 5449826..49a30de 100644 --- a/Software/MainBoard/rust/src/tank.rs +++ b/Software/MainBoard/rust/src/tank.rs @@ -158,12 +158,11 @@ pub async fn determine_tank_state( board: &mut MutexGuard<'static, CriticalSectionRawMutex, HAL<'static>>, ) -> TankState { if board.board_hal.get_config().tank.tank_sensor_enabled { - match board - .board_hal - .get_tank_sensor() - .map(|f| f.tank_sensor_voltage()) - { - Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv.await.unwrap()), + match board.board_hal.get_tank_sensor() { + Ok(sensor) => match sensor.tank_sensor_voltage().await { + Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv), + Err(err) => TankState::Error(TankError::BoardError(err.to_string())), + }, Err(err) => TankState::Error(TankError::BoardError(err.to_string())), } } else { diff --git a/Software/MainBoard/rust/src/webserver/backup_manager.rs b/Software/MainBoard/rust/src/webserver/backup_manager.rs index f567fee..50ffb0c 100644 --- a/Software/MainBoard/rust/src/webserver/backup_manager.rs +++ b/Software/MainBoard/rust/src/webserver/backup_manager.rs @@ -34,7 +34,8 @@ where ) .await?; - conn.write_all(serde_json::to_string(&backup)?.as_bytes()).await?; + conn.write_all(serde_json::to_string(&backup)?.as_bytes()) + .await?; Ok(Some(200)) } @@ -44,7 +45,7 @@ pub(crate) async fn backup_config( where T: Read + Write, { - let input = read_up_to_bytes_from_request(conn, Option::None).await?; + let input = read_up_to_bytes_from_request(conn, Some(4096)).await?; let mut board = BOARD_ACCESS.get().await.lock().await; let config_to_backup = serde_json::from_slice(&input)?; board.board_hal.backup_config(&config_to_backup).await?; @@ -70,10 +71,9 @@ where let mut board = BOARD_ACCESS.get().await.lock().await; let info = board.board_hal.backup_info().await; - let json = match info { Ok(h) => { - let timestamp = DateTime::from_timestamp_millis(h.timestamp).unwrap(); + let timestamp = DateTime::from_timestamp_millis(h.timestamp).unwrap_or_default(); let wbh = WebBackupHeader { timestamp: timestamp.to_rfc3339(), size: h.size, diff --git a/Software/MainBoard/rust/src/webserver/mod.rs b/Software/MainBoard/rust/src/webserver/mod.rs index 052b39b..d6e716c 100644 --- a/Software/MainBoard/rust/src/webserver/mod.rs +++ b/Software/MainBoard/rust/src/webserver/mod.rs @@ -181,6 +181,7 @@ where } #[embassy_executor::task] +#[allow(clippy::panic, clippy::unwrap_used, clippy::expect_used)] pub async fn http_server(reboot_now: Arc, stack: Stack<'static>) { let buffer: TcpBuffers<2, 1024, 1024> = TcpBuffers::new(); let tcp = Tcp::new(stack, &buffer); diff --git a/Software/MainBoard/rust/src/webserver/post_json.rs b/Software/MainBoard/rust/src/webserver/post_json.rs index c92fa75..2c476a5 100644 --- a/Software/MainBoard/rust/src/webserver/post_json.rs +++ b/Software/MainBoard/rust/src/webserver/post_json.rs @@ -108,7 +108,7 @@ where { let actual_data = read_up_to_bytes_from_request(request, None).await?; let time: SetTime = serde_json::from_slice(&actual_data)?; - let parsed = DateTime::parse_from_rfc3339(time.time).unwrap(); + let parsed = DateTime::parse_from_rfc3339(time.time)?; esp_set_time(parsed).await?; Ok(None) }