Refactor formatting and remove unused imports in mqtt and plant_state modules
This commit is contained in:
@@ -5,7 +5,6 @@ use crate::{config::PlantConfig, hal::HAL, in_time_range};
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
use chrono_tz::Tz;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::config::SensorCombineMode;
|
||||
|
||||
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
|
||||
@@ -16,7 +15,7 @@ pub enum MoistureSensorError {
|
||||
MissingMessage,
|
||||
NotExpectedMessage { hz: f32 },
|
||||
ShortCircuit { hz: f32, max: f32 },
|
||||
OpenLoop { hz: f32, min: f32 }
|
||||
OpenLoop { hz: f32, min: f32 },
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize)]
|
||||
@@ -117,20 +116,20 @@ pub struct PlantState {
|
||||
}
|
||||
|
||||
/// Map sensor frequency to moisture percentage using inverse power-law scaling (quadratic).
|
||||
///
|
||||
///
|
||||
/// For resistive probes with 555 timer oscillator:
|
||||
/// - Dry soil has high resistance → low oscillation frequency
|
||||
/// - Wet soil has low resistance → high oscillation frequency
|
||||
///
|
||||
///
|
||||
/// The relationship is non-linear: most frequency change occurs in the wet range.
|
||||
/// Using inverse power-law to give better discrimination at high moisture levels.
|
||||
///
|
||||
///
|
||||
/// Formula: moisture = (1 - (f_max - f) / (f_max - f_min))^2 * 100
|
||||
/// = ((f - f_min) / (f_max - f_min))^2 * 100
|
||||
///
|
||||
///
|
||||
/// But with k=0.5 (square root) for better high-end discrimination:
|
||||
/// Formula: moisture = sqrt((f - f_min) / (f_max - f_min)) * 100
|
||||
///
|
||||
///
|
||||
/// Examples with default range (400-160000 Hz) using k=0.5:
|
||||
/// 400 Hz → 0% (bone dry)
|
||||
/// 10,240 Hz → 25% (dry soil)
|
||||
@@ -158,10 +157,10 @@ fn map_range_moisture(
|
||||
max: max_freq,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Normalize to 0-1 range
|
||||
let t = (s - min_freq) / (max_freq - min_freq);
|
||||
|
||||
|
||||
// Apply power-law mapping with k=0.5 (square root) for better high-moisture discrimination
|
||||
// For resistive probes: frequency ↑ as moisture ↑, but non-linearly
|
||||
// Using sqrt gives more resolution in the wet range (60-160kHz)
|
||||
@@ -229,9 +228,9 @@ impl PlantState {
|
||||
let consecutive_pump_count = board.board_hal.get_esp().consecutive_pump_count(plant_id);
|
||||
let last_fertilizer_timestamp = board.board_hal.get_esp().last_fertilizer_time(plant_id);
|
||||
let (a_builds, b_builds) = board.board_hal.get_sensor_build_minutes();
|
||||
|
||||
|
||||
let last_fertilizer_time = DateTime::from_timestamp_millis(last_fertilizer_timestamp);
|
||||
|
||||
|
||||
// Create plant state first, then check for warnings
|
||||
let state = Self {
|
||||
sensor_a,
|
||||
@@ -245,14 +244,17 @@ impl PlantState {
|
||||
sensor_b_firmware_build_minutes: b_builds[plant_id],
|
||||
last_fertilizer_time,
|
||||
};
|
||||
|
||||
|
||||
// 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_b = state.sensor_b.moisture_percent().is_some() && state.sensor_b.is_err().is_none();
|
||||
|
||||
let has_a =
|
||||
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
|
||||
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
|
||||
let has_issue = state.is_err() || has_sensor_warning;
|
||||
if has_issue {
|
||||
@@ -279,35 +281,27 @@ impl PlantState {
|
||||
}
|
||||
|
||||
/// Get combined moisture value with configurable combination mode and sensor warning.
|
||||
///
|
||||
///
|
||||
/// Returns:
|
||||
/// - Combined moisture percentage (or None if no valid readings)
|
||||
/// - Tuple of errors from sensor A and B
|
||||
/// - Sensor warning indicating if warning LED should be lit (MissingSecondSensor)
|
||||
pub fn plant_moisture_with_warning(
|
||||
&self,
|
||||
plant_conf: &PlantConfig,
|
||||
) -> Option<f32>
|
||||
{
|
||||
pub fn plant_moisture_with_warning(&self, plant_conf: &PlantConfig) -> Option<f32> {
|
||||
let moisture = match (
|
||||
self.sensor_a.moisture_percent(),
|
||||
self.sensor_b.moisture_percent(),
|
||||
) {
|
||||
(Some(moisture_a), Some(moisture_b)) => {
|
||||
match plant_conf.sensor_combine_mode {
|
||||
SensorCombineMode::Min => Some(moisture_a.min(moisture_b)),
|
||||
SensorCombineMode::Max => Some(moisture_a.max(moisture_b)),
|
||||
SensorCombineMode::Avg => Some((moisture_a + moisture_b) / 2.0),
|
||||
}
|
||||
}
|
||||
(Some(moisture_a), Some(moisture_b)) => match plant_conf.sensor_combine_mode {
|
||||
SensorCombineMode::Min => Some(moisture_a.min(moisture_b)),
|
||||
SensorCombineMode::Max => Some(moisture_a.max(moisture_b)),
|
||||
SensorCombineMode::Avg => Some((moisture_a + moisture_b) / 2.0),
|
||||
},
|
||||
(Some(moisture), _) => Some(moisture),
|
||||
(_, Some(moisture)) => Some(moisture),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
|
||||
moisture
|
||||
|
||||
moisture
|
||||
}
|
||||
|
||||
pub fn needs_to_be_watered(
|
||||
@@ -344,11 +338,7 @@ impl PlantState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_mqtt_info(
|
||||
&self,
|
||||
plant_conf: &PlantConfig,
|
||||
current_time: &DateTime<Tz>,
|
||||
) -> PlantInfo {
|
||||
pub fn to_mqtt_info(&self, plant_conf: &PlantConfig, current_time: &DateTime<Tz>) -> PlantInfo {
|
||||
let moisture_pct = self.plant_moisture_with_warning(plant_conf);
|
||||
PlantInfo {
|
||||
moisture_pct,
|
||||
@@ -392,7 +382,9 @@ impl PlantState {
|
||||
} else {
|
||||
None
|
||||
},
|
||||
last_fertilizer: self.last_fertilizer_time.map(|t| t.with_timezone(¤t_time.timezone())),
|
||||
last_fertilizer: self
|
||||
.last_fertilizer_time
|
||||
.map(|t| t.with_timezone(¤t_time.timezone())),
|
||||
next_fertilizer: if matches!(
|
||||
plant_conf.mode,
|
||||
PlantWateringMode::TimerOnly
|
||||
@@ -403,7 +395,9 @@ impl PlantState {
|
||||
// Convert to Tz for calculation, then back
|
||||
let tz_last_fert = last_fert.with_timezone(¤t_time.timezone());
|
||||
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(¤t_time.timezone()))
|
||||
})
|
||||
} else {
|
||||
@@ -416,13 +410,11 @@ impl PlantState {
|
||||
|
||||
fn sensor_to_telemetry(sensor: &MoistureSensorState) -> SensorTelemetry {
|
||||
match sensor {
|
||||
MoistureSensorState::NoMessage => {
|
||||
SensorTelemetry {
|
||||
moisture_pct: None,
|
||||
raw_hz: None,
|
||||
error: None
|
||||
}
|
||||
}
|
||||
MoistureSensorState::NoMessage => SensorTelemetry {
|
||||
moisture_pct: None,
|
||||
raw_hz: None,
|
||||
error: None,
|
||||
},
|
||||
MoistureSensorState::MoistureValue {
|
||||
hz,
|
||||
moisture_percent,
|
||||
|
||||
Reference in New Issue
Block a user