Refactor formatting and remove unused imports in mqtt and plant_state modules

This commit is contained in:
2026-05-30 20:57:39 +02:00
parent 4b3c003996
commit a2abc99275
12 changed files with 143 additions and 120 deletions

View File

@@ -81,7 +81,8 @@ impl BatteryInteraction for WCHI2CSlave<'_> {
let state_of_charge = let state_of_charge =
state.remaining_capacity_mah as f32 * 100. / state.lifetime_capacity_mah as f32; 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.; let state_of_health =
state.lifetime_capacity_mah as f32 / config.capacity_mah as f32 * 100.;
Ok(BatteryState::Info(BatteryInfo { Ok(BatteryState::Info(BatteryInfo {
voltage_mv: Some(state.current_mv), voltage_mv: Some(state.current_mv),
@@ -91,7 +92,7 @@ impl BatteryInteraction for WCHI2CSlave<'_> {
soc_pct: Some(state_of_charge), soc_pct: Some(state_of_charge),
soh_pct: Some(state_of_health), soh_pct: Some(state_of_health),
temperature_c: Some(state.temperature_celcius), temperature_c: Some(state.temperature_celcius),
error: None error: None,
})) }))
} }

View File

@@ -1,5 +1,5 @@
use crate::bail; use crate::bail;
use crate::config::{PlantControllerConfig}; use crate::config::PlantControllerConfig;
use crate::hal::savegame_manager::SavegameManager; use crate::hal::savegame_manager::SavegameManager;
use crate::hal::PLANT_COUNT; use crate::hal::PLANT_COUNT;
use crate::log::{log, LogMessage}; use crate::log::{log, LogMessage};
@@ -16,7 +16,6 @@ use embedded_storage::nor_flash::{check_erase, NorFlash, ReadNorFlash, RmwNorFla
use esp_bootloader_esp_idf::ota::OtaImageState::Valid; use esp_bootloader_esp_idf::ota::OtaImageState::Valid;
use esp_bootloader_esp_idf::ota::{Ota, OtaImageState}; use esp_bootloader_esp_idf::ota::{Ota, OtaImageState};
use esp_bootloader_esp_idf::partitions::{AppPartitionSubType, FlashRegion}; use esp_bootloader_esp_idf::partitions::{AppPartitionSubType, FlashRegion};
use serde::{Deserialize, Serialize};
use esp_hal::gpio::{Input, RtcPinWithResistors}; use esp_hal::gpio::{Input, RtcPinWithResistors};
use esp_hal::rng::Rng; use esp_hal::rng::Rng;
use esp_hal::rtc_cntl::{ use esp_hal::rtc_cntl::{
@@ -26,10 +25,11 @@ use esp_hal::rtc_cntl::{
use esp_hal::system::software_reset; use esp_hal::system::software_reset;
use esp_hal::uart::Uart; use esp_hal::uart::Uart;
use esp_hal::Blocking; use esp_hal::Blocking;
use esp_radio::wifi::ap::{AccessPointInfo}; use esp_radio::wifi::ap::AccessPointInfo;
use esp_radio::wifi::scan::{ScanConfig, ScanTypeConfig}; use esp_radio::wifi::scan::{ScanConfig, ScanTypeConfig};
use esp_radio::wifi::{Interface, WifiController}; use esp_radio::wifi::{Interface, WifiController};
use log::{error, info}; use log::{error, info};
use serde::{Deserialize, Serialize};
/// Detailed Wi-Fi scan information including signal strength /// Detailed Wi-Fi scan information including signal strength
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
@@ -45,11 +45,15 @@ pub struct WifiScanDetails {
fn format_bssid(bssid: &[u8; 6]) -> String { fn format_bssid(bssid: &[u8; 6]) -> String {
alloc::format!( alloc::format!(
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5] bssid[0],
bssid[1],
bssid[2],
bssid[3],
bssid[4],
bssid[5]
) )
} }
#[esp_hal::ram(unstable(rtc_fast), unstable(persistent))] #[esp_hal::ram(unstable(rtc_fast), unstable(persistent))]
static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT]; static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT];
#[esp_hal::ram(unstable(rtc_fast), unstable(persistent))] #[esp_hal::ram(unstable(rtc_fast), unstable(persistent))]
@@ -414,5 +418,4 @@ impl Esp<'_> {
} }
} }
} }
} }

View File

@@ -7,7 +7,7 @@ use esp_hal::delay::Delay;
use esp_hal::gpio::{DriveMode, Flex, Input, InputConfig, Output, OutputConfig, Pull}; use esp_hal::gpio::{DriveMode, Flex, Input, InputConfig, Output, OutputConfig, Pull};
use esp_hal::pcnt::channel::CtrlMode::Keep; use esp_hal::pcnt::channel::CtrlMode::Keep;
use esp_hal::pcnt::channel::EdgeMode::{Hold, Increment}; use esp_hal::pcnt::channel::EdgeMode::{Hold, Increment};
use esp_hal::pcnt::unit::{Unit}; use esp_hal::pcnt::unit::Unit;
use esp_hal::peripherals::GPIO5; use esp_hal::peripherals::GPIO5;
use esp_hal::Async; use esp_hal::Async;
use log::{error, info}; use log::{error, info};
@@ -145,7 +145,11 @@ impl<'a> TankSensor<'a> {
water_temp_sensor = Some(device); water_temp_sensor = Some(device);
break; break;
} else { } else {
info!("OneWire: skipping device — not a DS18B20 (family 0x{:02X} != 0x{:02X})", device.address[0], ds18b20::FAMILY_CODE); info!(
"OneWire: skipping device — not a DS18B20 (family 0x{:02X} != 0x{:02X})",
device.address[0],
ds18b20::FAMILY_CODE
);
} }
} }
if devices_found == 0 { if devices_found == 0 {

View File

@@ -32,7 +32,8 @@ impl LiveLogBuffer {
match after { match after {
None => (self.entries.clone(), false, next_seq), None => (self.entries.clone(), false, next_seq),
Some(after_seq) => { Some(after_seq) => {
let result: Vec<_> = self.entries let result: Vec<_> = self
.entries
.iter() .iter()
.filter(|(seq, _)| *seq > after_seq) .filter(|(seq, _)| *seq > after_seq)
.cloned() .cloned()

View File

@@ -14,9 +14,9 @@
esp_bootloader_esp_idf::esp_app_desc!(); esp_bootloader_esp_idf::esp_app_desc!();
use esp_backtrace as _; use esp_backtrace as _;
use crate::hal::PROGRESS_ACTIVE;
use crate::config::{PlantConfig, PlantControllerConfig}; use crate::config::{PlantConfig, PlantControllerConfig};
use crate::fat_error::{ContextExt, FatResult}; use crate::fat_error::{ContextExt, FatResult};
use crate::hal::PROGRESS_ACTIVE;
use crate::log::log; use crate::log::log;
use crate::tank::{determine_tank_state, TankError, WATER_FROZEN_THRESH}; use crate::tank::{determine_tank_state, TankError, WATER_FROZEN_THRESH};
@@ -42,8 +42,8 @@ use embassy_sync::once_lock::OnceLock;
use embassy_time::{Duration, Instant, Timer}; use embassy_time::{Duration, Instant, Timer};
use esp_hal::rom::ets_delay_us; use esp_hal::rom::ets_delay_us;
use esp_hal::system::software_reset; use esp_hal::system::software_reset;
use esp_println::{println}; use esp_println::println;
use hal::battery::{BatteryState}; use hal::battery::BatteryState;
use log::LogMessage; use log::LogMessage;
use option_lock::OptionLock; use option_lock::OptionLock;
use plant_state::PlantState; use plant_state::PlantState;
@@ -123,8 +123,6 @@ pub struct PumpResult {
overcurrent_ma: Option<u16>, overcurrent_ma: Option<u16>,
} }
async fn safe_main(spawner: Spawner) -> FatResult<()> { async fn safe_main(spawner: Spawner) -> FatResult<()> {
info!("Startup Rust"); info!("Startup Rust");
@@ -208,10 +206,15 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
info!("No wifi configured, starting initial config mode"); info!("No wifi configured, starting initial config mode");
let esp = board.board_hal.get_esp(); let esp = board.board_hal.get_esp();
let ssid = esp.load_config().await let ssid = esp
.load_config()
.await
.map(|config| config.network.ap_ssid.to_string()) .map(|config| config.network.ap_ssid.to_string())
.unwrap_or_else(|_| String::from("PlantCtrl Emergency Mode")); .unwrap_or_else(|_| String::from("PlantCtrl Emergency Mode"));
let device = esp.interface_ap.take().context("AP interface already taken")?; let device = esp
.interface_ap
.take()
.context("AP interface already taken")?;
let stack = network::wifi_ap(ssid, device, &esp.controller, &mut esp.rng, spawner).await?; let stack = network::wifi_ap(ssid, device, &esp.controller, &mut esp.rng, spawner).await?;
let reboot_now = Arc::new(AtomicBool::new(false)); let reboot_now = Arc::new(AtomicBool::new(false));
@@ -236,14 +239,18 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
let res = { let res = {
let esp = board.board_hal.get_esp(); let esp = board.board_hal.get_esp();
let ssid = esp.load_config().await let ssid = esp
.load_config()
.await
.map(|config| config.network.ap_ssid.to_string()) .map(|config| config.network.ap_ssid.to_string())
.unwrap_or_else(|_| String::from("PlantCtrl Emergency Mode")); .unwrap_or_else(|_| String::from("PlantCtrl Emergency Mode"));
let device = match esp.interface_ap.take() { let device = match esp.interface_ap.take() {
Some(d) => d, Some(d) => d,
None => { None => {
use crate::fat_error::FatError; use crate::fat_error::FatError;
return Err(FatError::String { error: "AP interface already taken".to_string() }); return Err(FatError::String {
error: "AP interface already taken".to_string(),
});
} }
}; };
network::wifi_ap(ssid, device, &esp.controller, &mut esp.rng, spawner).await network::wifi_ap(ssid, device, &esp.controller, &mut esp.rng, spawner).await
@@ -276,7 +283,9 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
if let network::NetworkMode::WIFI { ref ip_address, .. } = network_mode { if let network::NetworkMode::WIFI { ref ip_address, .. } = network_mode {
mqtt::publish_firmware_info(version, ip_address, &timezone_time.to_rfc3339()).await; mqtt::publish_firmware_info(version, ip_address, &timezone_time.to_rfc3339()).await;
mqtt::publish_battery_state(&mut board).await.unwrap_or_else(|e| { mqtt::publish_battery_state(&mut board)
.await
.unwrap_or_else(|e| {
error!("Error publishing battery state {e}"); error!("Error publishing battery state {e}");
}); });
let _ = mqtt::publish_mppt_state(&mut board).await; let _ = mqtt::publish_mppt_state(&mut board).await;
@@ -325,7 +334,9 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
if let Some(err) = tank_state.got_error(&board.board_hal.get_config().tank) { if let Some(err) = tank_state.got_error(&board.board_hal.get_config().tank) {
match err { match err {
TankError::SensorDisabled => { /* unreachable */ } TankError::SensorDisabled => { /* unreachable */ }
TankError::SensorMissing { raw_mv: raw_value_mv } => log( TankError::SensorMissing {
raw_mv: raw_value_mv,
} => log(
LogMessage::TankSensorMissing, LogMessage::TankSensorMissing,
raw_value_mv as u32, raw_value_mv as u32,
0, 0,
@@ -355,7 +366,11 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
} }
let mut water_frozen = false; let mut water_frozen = false;
let water_temp: FatResult<f32> = board.board_hal.get_tank_sensor().water_temperature_c().await; let water_temp: FatResult<f32> = board
.board_hal
.get_tank_sensor()
.water_temperature_c()
.await;
if let Ok(res) = water_temp { if let Ok(res) = water_temp {
if res < WATER_FROZEN_THRESH { if res < WATER_FROZEN_THRESH {
@@ -581,11 +596,15 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
light_state.battery_low = false; light_state.battery_low = false;
} }
BatteryState::Info(data) => { BatteryState::Info(data) => {
if data.soc_pct.is_some_and(|soc| soc < board.board_hal.get_config().night_lamp.low_soc_cutoff as f32) { 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(); board.board_hal.get_esp().set_low_voltage_in_cycle();
info!("Set low voltage in cycle"); info!("Set low voltage in cycle");
} }
if data.soc_pct.is_some_and(|soc| soc > board.board_hal.get_config().night_lamp.low_soc_restore as f32) { 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(); board.board_hal.get_esp().clear_low_voltage_in_cycle();
info!("Clear low voltage in cycle"); info!("Clear low voltage in cycle");
} }
@@ -988,8 +1007,7 @@ async fn wait_infinity(
let timezone_time = cur.with_timezone(&timezone); let timezone_time = cur.with_timezone(&timezone);
mqtt::publish("/state", "config").await; mqtt::publish("/state", "config").await;
mqtt::publish("/firmware/last_online", &timezone_time.to_rfc3339()) mqtt::publish("/firmware/last_online", &timezone_time.to_rfc3339()).await;
.await;
last_mqtt_update = Some(now); last_mqtt_update = Some(now);
} }

View File

@@ -288,11 +288,7 @@ pub async fn publish_plant_states(
Ok(()) Ok(())
} }
pub async fn publish_firmware_info( pub async fn publish_firmware_info(version: VersionInfo, ip_address: &str, timezone_time: &str) {
version: VersionInfo,
ip_address: &str,
timezone_time: &str,
) {
publish("/firmware/address", ip_address).await; publish("/firmware/address", ip_address).await;
let version = &serde_json::to_string(&version); let version = &serde_json::to_string(&version);
match version { match version {
@@ -384,12 +380,7 @@ pub async fn publish_mppt_state(
pub async fn publish_battery_state( pub async fn publish_battery_state(
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>, board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
) -> FatResult<()> { ) -> FatResult<()> {
let telemetry = match board let telemetry = match board.board_hal.get_battery_monitor().get_state().await {
.board_hal
.get_battery_monitor()
.get_state()
.await
{
Ok(BatteryState::Info(info)) => info, Ok(BatteryState::Info(info)) => info,
Ok(BatteryState::Unknown) => BatteryInfo { Ok(BatteryState::Unknown) => BatteryInfo {
voltage_mv: None, voltage_mv: None,

View File

@@ -1,13 +1,19 @@
use crate::bail; use crate::bail;
use crate::config::NetworkConfig; use crate::config::NetworkConfig;
use crate::fat_error::{ContextExt, FatError, FatResult}; use crate::fat_error::{ContextExt, FatError, FatResult};
use crate::hal::{HAL}; use crate::hal::HAL;
use crate::mqtt; use crate::mqtt;
use crate::util::mk_static; use crate::util::mk_static;
use alloc::string::{String, ToString}; use alloc::string::{String, ToString};
use alloc::sync::Arc; use alloc::sync::Arc;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use core::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; use core::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
use edge_dhcp::{
io::{self, DEFAULT_SERVER_PORT},
server::{Server, ServerOptions},
};
use edge_nal::UdpBind;
use edge_nal_embassy::{Udp, UdpBuffers};
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_net::dns::DnsQueryType; use embassy_net::dns::DnsQueryType;
use embassy_net::udp::{PacketMetadata, UdpSocket}; use embassy_net::udp::{PacketMetadata, UdpSocket};
@@ -15,21 +21,15 @@ use embassy_net::{DhcpConfig, Runner, Stack, StackResources, StaticConfigV4};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::{Mutex, MutexGuard}; use embassy_sync::mutex::{Mutex, MutexGuard};
use embassy_time::{Duration, Timer, WithTimeout}; use embassy_time::{Duration, Timer, WithTimeout};
use option_lock::OptionLock;
use edge_dhcp::{
io::{self, DEFAULT_SERVER_PORT},
server::{Server, ServerOptions},
};
use edge_nal::UdpBind;
use edge_nal_embassy::{Udp, UdpBuffers};
use esp_hal::rng::Rng; use esp_hal::rng::Rng;
use esp_println::println; use esp_println::println;
use esp_radio::wifi::ap::AccessPointConfig; use esp_radio::wifi::ap::AccessPointConfig;
use esp_radio::wifi::sta::StationConfig; use esp_radio::wifi::sta::StationConfig;
use esp_radio::wifi::{AuthenticationMethod, Config, Interface}; use esp_radio::wifi::{AuthenticationMethod, Config, Interface};
use log::{info, warn, error}; use log::{error, info, warn};
use option_lock::OptionLock;
use serde::Serialize; use serde::Serialize;
use sntpc::{NtpContext, NtpTimestampGenerator, NtpUdpSocket, get_time}; use sntpc::{get_time, NtpContext, NtpTimestampGenerator, NtpUdpSocket};
const NTP_SERVER: &str = "pool.ntp.org"; const NTP_SERVER: &str = "pool.ntp.org";
@@ -225,8 +225,7 @@ pub async fn wifi_ap(
); );
let stack = mk_static!(Stack, stack); let stack = mk_static!(Stack, stack);
let client_config = let client_config = Config::AccessPoint(AccessPointConfig::default().with_ssid(ssid.clone()));
Config::AccessPoint(AccessPointConfig::default().with_ssid(ssid.clone()));
controller.lock().await.set_config(&client_config)?; controller.lock().await.set_config(&client_config)?;
println!("start net task"); println!("start net task");
@@ -352,7 +351,10 @@ pub async fn wifi(
.await; .await;
if res.is_err() { if res.is_err() {
warn!("WiFi connection attempt {} failed: link up timeout", attempts + 1); warn!(
"WiFi connection attempt {} failed: link up timeout",
attempts + 1
);
attempts += 1; attempts += 1;
Timer::after(Duration::from_millis(500)).await; Timer::after(Duration::from_millis(500)).await;
continue; continue;
@@ -368,7 +370,10 @@ pub async fn wifi(
.await; .await;
if res.is_err() { if res.is_err() {
warn!("WiFi connection attempt {} failed: config up timeout", attempts + 1); warn!(
"WiFi connection attempt {} failed: config up timeout",
attempts + 1
);
attempts += 1; attempts += 1;
Timer::after(Duration::from_millis(500)).await; Timer::after(Duration::from_millis(500)).await;
continue; continue;

View File

@@ -5,7 +5,6 @@ use crate::{config::PlantConfig, hal::HAL, in_time_range};
use chrono::{DateTime, TimeDelta, Utc}; use chrono::{DateTime, TimeDelta, Utc};
use chrono_tz::Tz; use chrono_tz::Tz;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::config::SensorCombineMode;
const MOIST_SENSOR_MAX_FREQUENCY: f32 = 160000.; // 160kHz -> very wet const MOIST_SENSOR_MAX_FREQUENCY: f32 = 160000.; // 160kHz -> very wet
const MOIST_SENSOR_MIN_FREQUENCY: f32 = 400.; // this is really, really dry, think like cactus levels const MOIST_SENSOR_MIN_FREQUENCY: f32 = 400.; // this is really, really dry, think like cactus levels
@@ -16,7 +15,7 @@ pub enum MoistureSensorError {
MissingMessage, MissingMessage,
NotExpectedMessage { hz: f32 }, NotExpectedMessage { hz: f32 },
ShortCircuit { hz: f32, max: f32 }, ShortCircuit { hz: f32, max: f32 },
OpenLoop { hz: f32, min: f32 } OpenLoop { hz: f32, min: f32 },
} }
#[derive(Debug, PartialEq, Serialize)] #[derive(Debug, PartialEq, Serialize)]
@@ -247,11 +246,14 @@ impl PlantState {
}; };
// Check for sensor warning condition (expected 2 sensors, only 1 responding) // Check for sensor warning condition (expected 2 sensors, only 1 responding)
let has_a = state.sensor_a.moisture_percent().is_some() && state.sensor_a.is_err().is_none(); let has_a =
let has_b = state.sensor_b.moisture_percent().is_some() && state.sensor_b.is_err().is_none(); state.sensor_a.moisture_percent().is_some() && state.sensor_a.is_err().is_none();
let has_b =
state.sensor_b.moisture_percent().is_some() && state.sensor_b.is_err().is_none();
// Check if we expected two sensors but only got one // Check if we expected two sensors but only got one
let has_sensor_warning = expected_a && expected_b && ((has_a && !has_b) || (!has_a && has_b)); let has_sensor_warning =
expected_a && expected_b && ((has_a && !has_b) || (!has_a && has_b));
// Set fault LED for both errors AND sensor warnings // Set fault LED for both errors AND sensor warnings
let has_issue = state.is_err() || has_sensor_warning; let has_issue = state.is_err() || has_sensor_warning;
@@ -284,30 +286,22 @@ impl PlantState {
/// - Combined moisture percentage (or None if no valid readings) /// - Combined moisture percentage (or None if no valid readings)
/// - Tuple of errors from sensor A and B /// - Tuple of errors from sensor A and B
/// - Sensor warning indicating if warning LED should be lit (MissingSecondSensor) /// - Sensor warning indicating if warning LED should be lit (MissingSecondSensor)
pub fn plant_moisture_with_warning( pub fn plant_moisture_with_warning(&self, plant_conf: &PlantConfig) -> Option<f32> {
&self,
plant_conf: &PlantConfig,
) -> Option<f32>
{
let moisture = match ( let moisture = match (
self.sensor_a.moisture_percent(), self.sensor_a.moisture_percent(),
self.sensor_b.moisture_percent(), self.sensor_b.moisture_percent(),
) { ) {
(Some(moisture_a), Some(moisture_b)) => { (Some(moisture_a), Some(moisture_b)) => match plant_conf.sensor_combine_mode {
match plant_conf.sensor_combine_mode {
SensorCombineMode::Min => Some(moisture_a.min(moisture_b)), SensorCombineMode::Min => Some(moisture_a.min(moisture_b)),
SensorCombineMode::Max => Some(moisture_a.max(moisture_b)), SensorCombineMode::Max => Some(moisture_a.max(moisture_b)),
SensorCombineMode::Avg => Some((moisture_a + moisture_b) / 2.0), SensorCombineMode::Avg => Some((moisture_a + moisture_b) / 2.0),
} },
}
(Some(moisture), _) => Some(moisture), (Some(moisture), _) => Some(moisture),
(_, Some(moisture)) => Some(moisture), (_, Some(moisture)) => Some(moisture),
_ => None, _ => None,
}; };
moisture moisture
} }
pub fn needs_to_be_watered( pub fn needs_to_be_watered(
@@ -344,11 +338,7 @@ impl PlantState {
} }
} }
pub fn to_mqtt_info( pub fn to_mqtt_info(&self, plant_conf: &PlantConfig, current_time: &DateTime<Tz>) -> PlantInfo {
&self,
plant_conf: &PlantConfig,
current_time: &DateTime<Tz>,
) -> PlantInfo {
let moisture_pct = self.plant_moisture_with_warning(plant_conf); let moisture_pct = self.plant_moisture_with_warning(plant_conf);
PlantInfo { PlantInfo {
moisture_pct, moisture_pct,
@@ -392,7 +382,9 @@ impl PlantState {
} else { } else {
None None
}, },
last_fertilizer: self.last_fertilizer_time.map(|t| t.with_timezone(&current_time.timezone())), last_fertilizer: self
.last_fertilizer_time
.map(|t| t.with_timezone(&current_time.timezone())),
next_fertilizer: if matches!( next_fertilizer: if matches!(
plant_conf.mode, plant_conf.mode,
PlantWateringMode::TimerOnly PlantWateringMode::TimerOnly
@@ -403,7 +395,9 @@ impl PlantState {
// Convert to Tz for calculation, then back // Convert to Tz for calculation, then back
let tz_last_fert = last_fert.with_timezone(&current_time.timezone()); let tz_last_fert = last_fert.with_timezone(&current_time.timezone());
tz_last_fert tz_last_fert
.checked_add_signed(TimeDelta::minutes(plant_conf.fertilizer_cooldown_min.into())) .checked_add_signed(TimeDelta::minutes(
plant_conf.fertilizer_cooldown_min.into(),
))
.map(|t| t.with_timezone(&current_time.timezone())) .map(|t| t.with_timezone(&current_time.timezone()))
}) })
} else { } else {
@@ -416,13 +410,11 @@ impl PlantState {
fn sensor_to_telemetry(sensor: &MoistureSensorState) -> SensorTelemetry { fn sensor_to_telemetry(sensor: &MoistureSensorState) -> SensorTelemetry {
match sensor { match sensor {
MoistureSensorState::NoMessage => { MoistureSensorState::NoMessage => SensorTelemetry {
SensorTelemetry {
moisture_pct: None, moisture_pct: None,
raw_hz: None, raw_hz: None,
error: None error: None,
} },
}
MoistureSensorState::MoistureValue { MoistureSensorState::MoistureValue {
hz, hz,
moisture_percent, moisture_percent,

View File

@@ -26,7 +26,9 @@ pub enum TankState {
fn raw_voltage_to_divider_percent(raw_value_mv: f32) -> Result<f32, TankError> { fn raw_voltage_to_divider_percent(raw_value_mv: f32) -> Result<f32, TankError> {
if raw_value_mv > OPEN_TANK_VOLTAGE { if raw_value_mv > OPEN_TANK_VOLTAGE {
return Err(TankError::SensorMissing { raw_mv: raw_value_mv }); return Err(TankError::SensorMissing {
raw_mv: raw_value_mv,
});
} }
let r2 = raw_value_mv * 50.0 / (3.3 - raw_value_mv); let r2 = raw_value_mv * 50.0 / (3.3 - raw_value_mv);
@@ -161,12 +163,14 @@ pub async fn determine_tank_state(
if board.board_hal.get_config().tank.tank_sensor_enabled { if board.board_hal.get_config().tank.tank_sensor_enabled {
match board match board
.board_hal .board_hal
.get_tank_sensor().tank_sensor_voltage().await .get_tank_sensor()
.tank_sensor_voltage()
.await
{ {
Ok(raw_sensor_value_mv) => { Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv),
TankState::Present(raw_sensor_value_mv) Err(err) => TankState::Error(TankError::BoardError {
}, message: err.to_string(),
Err(err) => TankState::Error(TankError::BoardError { message: err.to_string() }), }),
} }
} else { } else {
TankState::Disabled TankState::Disabled

View File

@@ -93,7 +93,11 @@ where
{ {
let mut board = BOARD_ACCESS.get().await.lock().await; let mut board = BOARD_ACCESS.get().await.lock().await;
let tank_state = determine_tank_state(&mut board).await; let tank_state = determine_tank_state(&mut board).await;
let water_temp: FatResult<f32> = board.board_hal.get_tank_sensor().water_temperature_c().await; let water_temp: FatResult<f32> = board
.board_hal
.get_tank_sensor()
.water_temperature_c()
.await;
Ok(Some(serde_json::to_string(&tank_state.as_mqtt_info( Ok(Some(serde_json::to_string(&tank_state.as_mqtt_info(
&board.board_hal.get_config().tank, &board.board_hal.get_config().tank,
&water_temp, &water_temp,

View File

@@ -10,9 +10,9 @@ mod post_json;
use crate::fat_error::{FatError, FatResult}; use crate::fat_error::{FatError, FatResult};
use crate::webserver::backup_manager::{backup_config, backup_info, get_backup_config}; use crate::webserver::backup_manager::{backup_config, backup_info, get_backup_config};
use crate::webserver::get_json::{ use crate::webserver::get_json::{
delete_save, get_battery_state, get_config, get_live_moisture, get_log_localization_config, delete_save, get_battery_state, get_config, get_firmware_info_web, get_live_moisture,
get_firmware_info_web, get_solar_state, get_time, get_timezones, list_saves, tank_info, get_log_localization_config, get_solar_state, get_time, get_timezones, get_wifi_details,
get_wifi_details, list_saves, tank_info,
}; };
use crate::webserver::get_log::{get_live_log, get_log}; use crate::webserver::get_log::{get_live_log, get_log};
use crate::webserver::get_static::{serve_bundle, serve_favicon, serve_index}; use crate::webserver::get_static::{serve_bundle, serve_favicon, serve_index};