diff --git a/Software/MainBoard/rust/src/hal/battery.rs b/Software/MainBoard/rust/src/hal/battery.rs index d723fb9..fd05964 100644 --- a/Software/MainBoard/rust/src/hal/battery.rs +++ b/Software/MainBoard/rust/src/hal/battery.rs @@ -1,5 +1,6 @@ use crate::fat_error::{FatError, FatResult}; use crate::hal::Box; +use alloc::string::String; use async_trait::async_trait; use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; @@ -18,15 +19,23 @@ pub trait BatteryInteraction { async fn reset(&mut self) -> FatResult<()>; } -#[derive(Debug, Serialize, Copy, Clone)] +#[derive(Debug, Serialize, Clone)] pub struct BatteryInfo { - pub voltage_milli_volt: u32, - pub average_current_milli_ampere: i32, - pub design_milli_ampere_hour: u32, - pub remaining_milli_ampere_hour: u32, - pub state_of_charge: u8, - pub state_of_health: u32, - pub temperature: i32, + pub voltage_mv: Option, + pub avg_current_ma: Option, + pub design_mah: Option, + pub remaining_mah: Option, + pub soc_pct: Option, + pub soh_pct: Option, + pub temperature_c: Option, + pub error: Option, +} + +#[derive(Debug, Serialize, Clone)] +#[serde(tag = "kind")] +pub enum BatteryError { + NoBatteryMonitor, + CommunicationError { message: String }, } #[derive(Debug, Serialize)] @@ -71,17 +80,18 @@ impl BatteryInteraction for WCHI2CSlave<'_> { let config = Config::read_from_i2c(&mut self.i2c)?; let state_of_charge = - (state.remaining_capacity_mah * 100 / state.lifetime_capacity_mah) as u8; - let state_of_health = state.lifetime_capacity_mah / config.capacity_mah * 100; + state.remaining_capacity_mah as f32 * 100. / state.lifetime_capacity_mah as f32; + let state_of_health = state.lifetime_capacity_mah as f32 / config.capacity_mah as f32 * 100.; Ok(BatteryState::Info(BatteryInfo { - voltage_milli_volt: state.current_mv, - average_current_milli_ampere: 1337, - design_milli_ampere_hour: config.capacity_mah, - remaining_milli_ampere_hour: state.remaining_capacity_mah, - state_of_charge, - state_of_health, - temperature: state.temperature_celcius, + voltage_mv: Some(state.current_mv), + avg_current_ma: Some(1337), + design_mah: Some(config.capacity_mah), + remaining_mah: Some(state.remaining_capacity_mah), + soc_pct: Some(state_of_charge), + soh_pct: Some(state_of_health), + temperature_c: Some(state.temperature_celcius), + error: None })) } diff --git a/Software/MainBoard/rust/src/main.rs b/Software/MainBoard/rust/src/main.rs index 0764326..727ca3d 100644 --- a/Software/MainBoard/rust/src/main.rs +++ b/Software/MainBoard/rust/src/main.rs @@ -42,8 +42,8 @@ use embassy_sync::once_lock::OnceLock; use embassy_time::{Duration, Instant, Timer}; use esp_hal::rom::ets_delay_us; use esp_hal::system::software_reset; -use esp_println::println; -use hal::battery::BatteryState; +use esp_println::{logger, println}; +use hal::battery::{BatteryError, BatteryInfo, BatteryState}; use log::LogMessage; use option_lock::OptionLock; use plant_state::PlantState; @@ -275,11 +275,11 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { ); if let network::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.unwrap_or_else(|e| { + mqtt::publish_firmware_info(&mut board, version, ip_address, &timezone_time.to_rfc3339()).await; + mqtt::publish_battery_state(&mut board).await.unwrap_or_else(|e| { error!("Error publishing battery state {e}"); }); - let _ = publish_mppt_state(&mut board).await; + let _ = mqtt::publish_mppt_state(&mut board).await; } log( @@ -324,7 +324,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { if let Some(err) = tank_state.got_error(&board.board_hal.get_config().tank) { match err { TankError::SensorDisabled => { /* unreachable */ } - TankError::SensorMissing(raw_value_mv) => log( + TankError::SensorMissing { raw_mv: raw_value_mv } => log( LogMessage::TankSensorMissing, raw_value_mv as u32, 0, @@ -338,8 +338,8 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { &format!("{value}"), "", ), - TankError::BoardError(err) => { - log(LogMessage::TankSensorBoardError, 0, 0, "", &err.to_string()) + TankError::BoardError { message: err } => { + log(LogMessage::TankSensorBoardError, 0, 0, "", &err) } } // disabled cannot trigger this because of wrapping if is_enabled @@ -366,7 +366,7 @@ 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) + mqtt::publish_tank_state(&mut board, &tank_state, water_temp) .await .unwrap_or_else(|e| { error!("Error publishing tank state {e}"); @@ -385,7 +385,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { PlantState::interpret_raw_values(moisture, 7, &mut board).await, ]; - publish_plant_states(&mut board, &timezone_time.clone(), &plantstate) + mqtt::publish_plant_states(&mut board, &timezone_time.clone(), &plantstate) .await .unwrap_or_else(|e| { error!("Error publishing plant states {e}"); @@ -436,7 +436,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { board.board_hal.get_esp().last_pump_time(plant_id); //state.active = true; - pump_info( + mqtt::pump_info( &mut board, plant_id, true, @@ -454,7 +454,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { match result { Ok(state) => { overcurrent_results[plant_id] = state.overcurrent_ma; - pump_info( + mqtt::pump_info( &mut board, plant_id, false, @@ -469,7 +469,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { .await; } Err(err) => { - pump_info( + mqtt::pump_info( &mut board, plant_id, false, @@ -500,7 +500,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { plantstate[plant_id].pump.overcurrent_error = Some(current_ma); } } - publish_plant_states(&mut board, &timezone_time.clone(), &plantstate) + mqtt::publish_plant_states(&mut board, &timezone_time.clone(), &plantstate) .await .unwrap_or_else(|e| { error!("Error publishing plant states after pumping {e}"); @@ -581,16 +581,16 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { board.board_hal.get_config().night_lamp.night_lamp_hour_end, ); - match battery_state { + match &battery_state { BatteryState::Unknown => { light_state.battery_low = false; } BatteryState::Info(data) => { - if data.state_of_charge < board.board_hal.get_config().night_lamp.low_soc_cutoff { + if data.soc_pct.is_some_and(|soc| soc < board.board_hal.get_config().night_lamp.low_soc_cutoff as f32) { board.board_hal.get_esp().set_low_voltage_in_cycle(); info!("Set low voltage in cycle"); } - if data.state_of_charge > board.board_hal.get_config().night_lamp.low_soc_restore { + if data.soc_pct.is_some_and(|soc| soc > board.board_hal.get_config().night_lamp.low_soc_restore as f32) { board.board_hal.get_esp().clear_low_voltage_in_cycle(); info!("Clear low voltage in cycle"); } @@ -639,7 +639,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { let deep_sleep_duration_minutes: u32 = // if battery soc is unknown assume battery has enough change - if matches!(battery_state, BatteryState::Info(data) if data.state_of_charge < 10) { + if matches!(battery_state, BatteryState::Info(data) if data.soc_pct.is_some_and(|soc| soc < 10.)) { let _ = mqtt::publish("/deepsleep", "low Volt 12h").await; 12 * 60 } else if is_day { @@ -890,114 +890,6 @@ async fn update_charge_indicator( Ok(()) } -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), - )?; - let _ = 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))?; - let plant_topic = format!("/plant{}", plant_id + 1); - let _ = mqtt::publish(&plant_topic, &state).await; - } - Ok(()) -} - -async fn publish_firmware_info( - board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>, - version: VersionInfo, - ip_address: &str, - timezone_time: &str, -) { - mqtt::publish("/firmware/address", ip_address).await; - mqtt::publish("/firmware/state", format!("{:?}", &version).as_str()) - .await; - mqtt::publish("/firmware/last_online", timezone_time) - .await; - mqtt::publish("/state", "online").await; -} -async fn pump_info( - board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>, - plant_id: usize, - pump_active: bool, - pump_ineffective: bool, - median_current_ma: u16, - max_current_ma: u16, - min_current_ma: u16, - error: String, - flow_raw: u32, - flow_ml: f32, -) { - let pump_info = mqtt::PumpInfo { - enabled: pump_active, - pump_ineffective, - median_current_ma, - max_current_ma, - min_current_ma, - error, - flow_raw, - flow_ml, - }; - let pump_topic = format!("/pump{}", plant_id + 1); - - match serde_json::to_string(&pump_info) { - Ok(state) => { - let _ = mqtt::publish(&pump_topic, &state).await; - } - Err(err) => { - warn!("Error publishing pump state {err}"); - } - }; -} - -async fn publish_mppt_state( - board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>, -) -> FatResult<()> { - let current = board.board_hal.get_mptt_current().await?; - let voltage = board.board_hal.get_mptt_voltage().await?; - let solar_state = mqtt::Solar { - current_ma: current.as_milliamperes() as u32, - voltage_ma: voltage.as_millivolts() as u32, - }; - if let Ok(serialized_solar_state_bytes) = serde_json::to_string(&solar_state) { - let _ = mqtt::publish("/mppt", &serialized_solar_state_bytes).await; - } - Ok(()) -} - -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)?.to_owned(); - json.to_owned() - } - Err(_) => "error".to_owned(), - }; - { - let _ = mqtt::publish("/battery", &*value).await; - } - Ok(()) -} - async fn wait_infinity( board: MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>, wait_type: WaitType, diff --git a/Software/MainBoard/rust/src/mqtt.rs b/Software/MainBoard/rust/src/mqtt.rs index fcbedd4..0174a8d 100644 --- a/Software/MainBoard/rust/src/mqtt.rs +++ b/Software/MainBoard/rust/src/mqtt.rs @@ -1,16 +1,23 @@ -use crate::bail; use crate::config::NetworkConfig; use crate::fat_error::{ContextExt, FatError, FatResult}; -use crate::hal::PlantHal; +use crate::hal::battery::{BatteryError, BatteryInfo, BatteryState}; +use crate::hal::{PlantHal, HAL}; use crate::log::{log, LogMessage}; +use crate::plant_state::PlantState; +use crate::tank::TankState; +use crate::{bail, VersionInfo}; use alloc::string::String; use alloc::{format, string::ToString}; +use chrono::DateTime; +use chrono_tz::Tz; use core::sync::atomic::Ordering; use embassy_executor::Spawner; use embassy_net::Stack; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::mutex::MutexGuard; use embassy_sync::once_lock::OnceLock; use embassy_time::{Duration, Timer, WithTimeout}; -use log::info; +use log::{info, warn}; use mcutie::{ Error, McutieBuilder, McutieReceiver, McutieTask, MqttMessage, PublishDisplay, Publishable, QoS, Topic, @@ -18,25 +25,6 @@ use mcutie::{ use portable_atomic::AtomicBool; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug, PartialEq, Default)] -///mqtt struct to track pump activities -pub struct PumpInfo { - pub enabled: bool, - pub pump_ineffective: bool, - pub median_current_ma: u16, - pub max_current_ma: u16, - pub min_current_ma: u16, - pub error: String, - pub flow_raw: u32, - pub flow_ml: f32, -} - -#[derive(Serialize, Debug, PartialEq)] -pub struct Solar { - pub current_ma: u32, - pub voltage_ma: u32, -} - static MQTT_CONNECTED_EVENT_RECEIVED: AtomicBool = AtomicBool::new(false); static MQTT_ROUND_TRIP_RECEIVED: AtomicBool = AtomicBool::new(false); pub static MQTT_STAY_ALIVE: AtomicBool = AtomicBool::new(false); @@ -269,3 +257,150 @@ async fn mqtt_incoming_task( } } } + +pub 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), + )?; + let _ = publish("/water", &*state).await; + Ok(()) +} + +pub 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))?; + let plant_topic = format!("/plant{}", plant_id + 1); + let _ = publish(&plant_topic, &state).await; + } + Ok(()) +} + +pub async fn publish_firmware_info( + board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>, + version: VersionInfo, + ip_address: &str, + timezone_time: &str, +) { + publish("/firmware/address", ip_address).await; + publish("/firmware/state", &serde_json::to_string(&version).unwrap()).await; + publish("/firmware/last_online", timezone_time).await; + publish("/state", "online").await; +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Default)] +///mqtt struct to track pump activities +pub struct PumpInfo { + pub enabled: bool, + pub pump_ineffective: bool, + pub median_current_ma: u16, + pub max_current_ma: u16, + pub min_current_ma: u16, + pub error: String, + pub flow_raw: u32, + pub flow_ml: f32, +} + +pub async fn pump_info( + board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>, + plant_id: usize, + pump_active: bool, + pump_ineffective: bool, + median_current_ma: u16, + max_current_ma: u16, + min_current_ma: u16, + error: String, + flow_raw: u32, + flow_ml: f32, +) { + let pump_info = PumpInfo { + enabled: pump_active, + pump_ineffective, + median_current_ma, + max_current_ma, + min_current_ma, + error, + flow_raw, + flow_ml, + }; + let pump_topic = format!("/pump{}", plant_id + 1); + + match serde_json::to_string(&pump_info) { + Ok(state) => { + let _ = publish(&pump_topic, &state).await; + } + Err(err) => { + warn!("Error publishing pump state {err}"); + } + }; +} + +#[derive(Serialize, Debug, PartialEq)] +pub struct Solar { + pub current_ma: u32, + pub voltage_ma: u32, +} + +pub async fn publish_mppt_state( + board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>, +) -> FatResult<()> { + let current = board.board_hal.get_mptt_current().await?; + let voltage = board.board_hal.get_mptt_voltage().await?; + let solar_state = Solar { + current_ma: current.as_milliamperes() as u32, + voltage_ma: voltage.as_millivolts() as u32, + }; + if let Ok(serialized_solar_state_bytes) = serde_json::to_string(&solar_state) { + let _ = publish("/mppt", &serialized_solar_state_bytes).await; + } + Ok(()) +} + +pub async fn publish_battery_state( + board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>, +) -> FatResult<()> { + let telemetry = match board + .board_hal + .get_battery_monitor() + .get_state() + .await + { + Ok(BatteryState::Info(info)) => info, + Ok(BatteryState::Unknown) => BatteryInfo { + voltage_mv: None, + avg_current_ma: None, + soc_pct: None, + soh_pct: None, + temperature_c: None, + remaining_mah: None, + design_mah: None, + error: Some(BatteryError::NoBatteryMonitor), + }, + Err(e) => BatteryInfo { + voltage_mv: None, + avg_current_ma: None, + soc_pct: None, + soh_pct: None, + temperature_c: None, + remaining_mah: None, + design_mah: None, + error: Some(BatteryError::CommunicationError { + message: alloc::format!("{:?}", e), + }), + }, + }; + let json = serde_json::to_string(&telemetry)?; + publish("/battery", &json).await; + Ok(()) +} diff --git a/Software/MainBoard/rust/src/plant_state.rs b/Software/MainBoard/rust/src/plant_state.rs index 445e5fa..8c2c35f 100644 --- a/Software/MainBoard/rust/src/plant_state.rs +++ b/Software/MainBoard/rust/src/plant_state.rs @@ -1,5 +1,6 @@ use crate::hal::Moistures; use crate::{config::PlantConfig, hal::HAL, in_time_range}; +use alloc::string::String; use chrono::{DateTime, TimeDelta, Utc}; use chrono_tz::Tz; use serde::{Deserialize, Serialize}; @@ -7,12 +8,14 @@ use serde::{Deserialize, Serialize}; const MOIST_SENSOR_MAX_FREQUENCY: f32 = 70000.; // 70kHz const MOIST_SENSOR_MIN_FREQUENCY: f32 = 400.; // this is really, really dry, think like cactus levels -#[derive(Debug, PartialEq, Serialize)] +#[derive(Debug, PartialEq, Clone, Serialize)] +#[serde(tag = "kind")] pub enum MoistureSensorError { MissingMessage, NotExpectedMessage { hz: f32 }, ShortCircuit { hz: f32, max: f32 }, OpenLoop { hz: f32, min: f32 }, + BoardError { message: String }, } #[derive(Debug, PartialEq, Serialize)] @@ -46,6 +49,14 @@ impl MoistureSensorState { impl MoistureSensorState {} #[derive(Debug, PartialEq, Serialize)] +pub struct SensorTelemetry { + pub moisture_pct: Option, + pub raw_hz: Option, + pub error: Option, +} + +#[derive(Debug, PartialEq, Serialize)] +#[serde(tag = "kind")] pub enum PumpError { PumpNotWorking { failed_attempts: usize, @@ -215,7 +226,7 @@ impl PlantState { pub fn plant_moisture( &self, ) -> ( - Option, + Option, (Option<&MoistureSensorError>, Option<&MoistureSensorError>), ) { match ( @@ -223,13 +234,13 @@ impl PlantState { self.sensor_b.moisture_percent(), ) { (Some(moisture_a), Some(moisture_b)) => { - (Some(((moisture_a + moisture_b) / 2.) as u8), (None, None)) + (Some((moisture_a + moisture_b) / 2.), (None, None)) } (Some(moisture_percent), _) => { - (Some(moisture_percent as u8), (None, self.sensor_b.is_err())) + (Some(moisture_percent), (None, self.sensor_b.is_err())) } (_, Some(moisture_percent)) => { - (Some(moisture_percent as u8), (self.sensor_a.is_err(), None)) + (Some(moisture_percent), (self.sensor_a.is_err(), None)) } _ => (None, (self.sensor_a.is_err(), self.sensor_b.is_err())), } @@ -247,7 +258,7 @@ impl PlantState { if let Some(moisture_percent) = moisture_percent { if self.pump_in_timeout(plant_conf, current_time) { false - } else if moisture_percent < plant_conf.target_moisture { + } else if moisture_percent < plant_conf.target_moisture.into() { in_time_range( current_time, plant_conf.pump_hour_start, @@ -273,19 +284,21 @@ impl PlantState { &self, plant_conf: &PlantConfig, current_time: &DateTime, - ) -> PlantInfo<'_> { + ) -> PlantInfo { + let (moisture_pct, _) = self.plant_moisture(); PlantInfo { - sensor_a: &self.sensor_a, - sensor_b: &self.sensor_b, + moisture_pct, + sensor_a: Self::sensor_to_telemetry(&self.sensor_a), + sensor_b: Self::sensor_to_telemetry(&self.sensor_b), mode: plant_conf.mode, do_water: self.needs_to_be_watered(plant_conf, current_time), - dry: if let Some(moisture_percent) = self.plant_moisture().0 { - moisture_percent < plant_conf.target_moisture + dry: if let Some(moisture_percent) = moisture_pct { + moisture_percent < plant_conf.target_moisture.into() } else { false }, cooldown: self.pump_in_timeout(plant_conf, current_time), - out_of_work_hour: in_time_range( + out_of_work_hour: !in_time_range( current_time, plant_conf.pump_hour_start, plant_conf.pump_hour_end, @@ -315,15 +328,42 @@ impl PlantState { last_fertilizer_time: self.last_fertilizer_time, } } + + fn sensor_to_telemetry(sensor: &MoistureSensorState) -> SensorTelemetry { + match sensor { + MoistureSensorState::NoMessage => { + SensorTelemetry { + moisture_pct: None, + raw_hz: None, + error: None + } + } + MoistureSensorState::MoistureValue { + hz, + moisture_percent, + } => SensorTelemetry { + moisture_pct: Some(*moisture_percent), + raw_hz: Some(*hz), + error: None, + }, + MoistureSensorState::SensorError(err) => SensorTelemetry { + moisture_pct: None, + raw_hz: None, + error: Some(err.clone()), + }, + } + } } #[derive(Debug, PartialEq, Serialize)] /// State of a single plant to be tracked -pub struct PlantInfo<'a> { +pub struct PlantInfo { + /// combined plant moisture from available sensors + moisture_pct: Option, /// state of humidity sensor on bank a - sensor_a: &'a MoistureSensorState, + sensor_a: SensorTelemetry, /// state of humidity sensor on bank b - sensor_b: &'a MoistureSensorState, + sensor_b: SensorTelemetry, /// configured plant watering mode mode: PlantWateringMode, /// the plant needs to be watered diff --git a/Software/MainBoard/rust/src/tank.rs b/Software/MainBoard/rust/src/tank.rs index 49a30de..e6d7c2e 100644 --- a/Software/MainBoard/rust/src/tank.rs +++ b/Software/MainBoard/rust/src/tank.rs @@ -10,11 +10,12 @@ const OPEN_TANK_VOLTAGE: f32 = 3.0; pub const WATER_FROZEN_THRESH: f32 = 4.0; #[derive(Debug, Clone, Serialize)] +#[serde(tag = "kind")] pub enum TankError { SensorDisabled, - SensorMissing(f32), + SensorMissing { raw_mv: f32 }, SensorValueError { value: f32, min: f32, max: f32 }, - BoardError(String), + BoardError { message: String }, } pub enum TankState { @@ -25,7 +26,7 @@ pub enum TankState { fn raw_voltage_to_divider_percent(raw_value_mv: f32) -> Result { if raw_value_mv > OPEN_TANK_VOLTAGE { - return Err(TankError::SensorMissing(raw_value_mv)); + return Err(TankError::SensorMissing { raw_mv: raw_value_mv }); } let r2 = raw_value_mv * 50.0 / (3.3 - raw_value_mv); @@ -141,15 +142,15 @@ impl TankState { TankInfo { enough_water, warn_level, - left_ml, + volume_ml: left_ml, sensor_error: tank_err, - raw, + fill_raw_v: raw, water_frozen: water_temp .as_ref() .is_ok_and(|temp| *temp < WATER_FROZEN_THRESH), - water_temp: water_temp.as_ref().copied().ok(), + water_temp_c: water_temp.as_ref().copied().ok(), temp_sensor_error: water_temp.as_ref().err().map(|err| err.to_string()), - percent, + fill_pct: percent, } } } @@ -158,12 +159,13 @@ 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() { - 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())), + match board + .board_hal + .get_tank_sensor() + .and_then(|f| core::prelude::v1::Ok(f.tank_sensor_voltage())) + { + Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv.await.unwrap()), + Err(err) => TankState::Error(TankError::BoardError { message: err.to_string() }), } } else { TankState::Disabled @@ -178,16 +180,16 @@ pub struct TankInfo { /// warning that water needs to be refilled soon pub(crate) warn_level: bool, /// estimation how many ml are still in the tank - pub(crate) left_ml: Option, + pub(crate) volume_ml: Option, /// if there is an issue with the water level sensor pub(crate) sensor_error: Option, /// raw water sensor value - pub(crate) raw: Option, + pub(crate) fill_raw_v: Option, /// percent value - pub(crate) percent: Option, + pub(crate) fill_pct: Option, /// water in the tank might be frozen pub(crate) water_frozen: bool, /// water temperature - pub(crate) water_temp: Option, + pub(crate) water_temp_c: Option, pub(crate) temp_sensor_error: Option, }