remove: delete initial_hal implementation, update moisture sensor logic to handle optional raw values, optimize TWAI management, and improve CAN data handling

This commit is contained in:
2026-02-01 03:57:36 +01:00
parent ce10d084f8
commit e6f8e34f7d
7 changed files with 86 additions and 246 deletions

View File

@@ -162,7 +162,7 @@ impl Esp<'_> {
loop {
match self.uart0.read_buffered(&mut buf) {
Ok(read) => {
if (read == 0) {
if read == 0 {
return Ok(None);
}
let c = buf[0] as char;

View File

@@ -1,147 +0,0 @@
use crate::alloc::boxed::Box;
use crate::fat_error::{FatError, FatResult};
use crate::hal::esp::Esp;
use crate::hal::rtc::{BackupHeader, RTCModuleInteraction};
use crate::hal::water::TankSensor;
use crate::hal::{BoardInteraction, FreePeripherals, Moistures, TIME_ACCESS};
use crate::{
bail,
config::PlantControllerConfig,
hal::battery::{BatteryInteraction, NoBatteryMonitor},
};
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use esp_hal::gpio::{Level, Output, OutputConfig};
use measurements::{Current, Voltage};
pub struct Initial<'a> {
pub(crate) general_fault: Output<'a>,
pub(crate) esp: Esp<'a>,
pub(crate) config: PlantControllerConfig,
pub(crate) battery: Box<dyn BatteryInteraction + Send>,
pub rtc: Box<dyn RTCModuleInteraction + Send>,
}
pub(crate) struct NoRTC {}
#[async_trait(?Send)]
impl RTCModuleInteraction for NoRTC {
async fn get_backup_info(&mut self) -> Result<BackupHeader, FatError> {
bail!("Please configure board revision")
}
async fn get_backup_config(&mut self, _chunk: usize) -> FatResult<([u8; 32], usize, u16)> {
bail!("Please configure board revision")
}
async fn backup_config(&mut self, _offset: usize, _bytes: &[u8]) -> FatResult<()> {
bail!("Please configure board revision")
}
async fn backup_config_finalize(&mut self, _crc: u16, _length: usize) -> FatResult<()> {
bail!("Please configure board revision")
}
async fn get_rtc_time(&mut self) -> Result<DateTime<Utc>, FatError> {
bail!("Please configure board revision")
}
async fn set_rtc_time(&mut self, _time: &DateTime<Utc>) -> Result<(), FatError> {
bail!("Please configure board revision")
}
}
pub(crate) fn create_initial_board(
free_pins: FreePeripherals<'static>,
config: PlantControllerConfig,
esp: Esp<'static>,
) -> Result<Box<dyn BoardInteraction<'static> + Send>, FatError> {
log::info!("Start initial");
let general_fault = Output::new(free_pins.gpio23, Level::Low, OutputConfig::default());
let v = Initial {
general_fault,
config,
esp,
battery: Box::new(NoBatteryMonitor {}),
rtc: Box::new(NoRTC {}),
};
Ok(Box::new(v))
}
#[async_trait(?Send)]
impl<'a> BoardInteraction<'a> for Initial<'a> {
fn get_tank_sensor(&mut self) -> Result<&mut TankSensor<'a>, FatError> {
bail!("Please configure board revision")
}
fn get_esp(&mut self) -> &mut Esp<'a> {
&mut self.esp
}
fn get_config(&mut self) -> &PlantControllerConfig {
&self.config
}
fn get_battery_monitor(&mut self) -> &mut Box<dyn BatteryInteraction + Send> {
&mut self.battery
}
fn get_rtc_module(&mut self) -> &mut Box<dyn RTCModuleInteraction + Send> {
&mut self.rtc
}
async fn set_charge_indicator(&mut self, _charging: bool) -> Result<(), FatError> {
bail!("Please configure board revision")
}
async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
let rtc = TIME_ACCESS.get().await.lock().await;
self.esp.deep_sleep(duration_in_ms, rtc);
}
fn is_day(&self) -> bool {
false
}
async fn light(&mut self, _enable: bool) -> Result<(), FatError> {
bail!("Please configure board revision")
}
async fn pump(&mut self, _plant: usize, _enable: bool) -> Result<(), FatError> {
bail!("Please configure board revision")
}
async fn pump_current(&mut self, _plant: usize) -> Result<Current, FatError> {
bail!("Please configure board revision")
}
async fn fault(&mut self, _plant: usize, _enable: bool) -> Result<(), FatError> {
bail!("Please configure board revision")
}
async fn measure_moisture_hz(&mut self) -> Result<Moistures, FatError> {
bail!("Please configure board revision")
}
async fn general_fault(&mut self, enable: bool) {
self.general_fault.set_level(enable.into());
}
async fn test(&mut self) -> Result<(), FatError> {
bail!("Please configure board revision")
}
fn set_config(&mut self, config: PlantControllerConfig) {
self.config = config;
}
async fn get_mptt_voltage(&mut self) -> Result<Voltage, FatError> {
bail!("Please configure board revision")
}
async fn get_mptt_current(&mut self) -> Result<Current, FatError> {
bail!("Please configure board revision")
}
async fn can_power(&mut self, state: bool) -> FatResult<()> {
bail!("Please configure board revision")
}
}

View File

@@ -3,7 +3,6 @@ use esp_hal::uart::{Config as UartConfig};
pub(crate) mod battery;
// mod can_api; // replaced by external canapi crate
pub mod esp;
mod initial_hal;
mod little_fs2storage_adapter;
pub(crate) mod rtc;
mod shared_flash;
@@ -40,7 +39,7 @@ use esp_hal::peripherals::TWAI0;
use crate::{
bail,
config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig},
config::{BatteryBoardVersion, PlantControllerConfig},
hal::{
battery::{BatteryInteraction, NoBatteryMonitor},
esp::Esp,
@@ -50,7 +49,6 @@ use crate::{
};
use alloc::boxed::Box;
use alloc::format;
use alloc::string::String;
use alloc::sync::Arc;
use async_trait::async_trait;
use bincode::{Decode, Encode};
@@ -682,8 +680,8 @@ pub async fn esp_set_time(time: DateTime<FixedOffset>) -> FatResult<()> {
#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize)]
pub struct Moistures {
pub sensor_a_hz: [f32; PLANT_COUNT],
pub sensor_b_hz: [f32; PLANT_COUNT],
pub sensor_a_hz: [Option<f32>; PLANT_COUNT],
pub sensor_b_hz: [Option<f32>; PLANT_COUNT],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)]

View File

@@ -11,8 +11,7 @@ use crate::hal::{
};
use crate::log::{LogMessage, LOG_ACCESS};
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::string::{ToString};
use async_trait::async_trait;
use canapi::id::{classify, plant_id, MessageKind, IDENTIFY_CMD_OFFSET};
use canapi::SensorSlot;
@@ -22,7 +21,6 @@ use embassy_time::{Duration, Timer, WithTimeout};
use embedded_can::{Frame, Id};
use esp_hal::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig, Pull};
use esp_hal::i2c::master::I2c;
use esp_hal::peripherals;
use esp_hal::twai::{EspTwaiError, EspTwaiFrame, StandardId, Twai, TwaiConfiguration, TwaiMode};
use esp_hal::{twai, Async, Blocking};
use ina219::address::{Address, Pin};
@@ -134,7 +132,7 @@ pub struct V4<'a> {
extra1: Output<'a>,
extra2: Output<'a>,
can_mutex: embassy_sync::mutex::Mutex<CriticalSectionRawMutex, ()>,
twai_config: Option<TwaiConfiguration<'static, Blocking>>
}
@@ -152,11 +150,13 @@ pub(crate) async fn create_v4(
let mut general_fault = Output::new(peripherals.gpio23, Level::Low, OutputConfig::default());
general_fault.set_low();
let twai_peripheral = Some(peripherals.twai);
let twai_rx_pin = Some(peripherals.gpio2);
let twai_tx_pin = Some(peripherals.gpio0);
let twai_config = Some(TwaiConfiguration::new(
peripherals.twai,
peripherals.gpio0,
peripherals.gpio2,
TWAI_BAUDRATE,
TwaiMode::Normal,
));
let extra1 = Output::new(peripherals.gpio6, Level::Low, OutputConfig::default());
let extra2 = Output::new(peripherals.gpio15, Level::Low, OutputConfig::default());
@@ -260,38 +260,11 @@ pub(crate) async fn create_v4(
extra1,
extra2,
can_power,
can_mutex: embassy_sync::mutex::Mutex::new(()),
twai_config
};
Ok(Box::new(v))
}
fn teardown_twai(old: Twai<Async>) {
let config = old.stop();
drop(config);
// Re-acquire the peripheral and pins
let rx_pin = unsafe { peripherals::GPIO2::steal() };
let tx_pin = unsafe { peripherals::GPIO0::steal() };
// Set pins to low to avoid parasitic powering
let _ = Input::new(rx_pin, InputConfig::default().with_pull(Pull::None));
let _ = Input::new(tx_pin, InputConfig::default().with_pull(Pull::None));
}
fn create_twai<'a>() -> Twai<'a, Async> {
// Release the pins from Output back to raw pins and store everything
let twai = unsafe { peripherals::TWAI0::steal() };
let twai_rx_pin = unsafe { peripherals::GPIO2::steal() };
let twai_tx_pin = unsafe { peripherals::GPIO0::steal() };
let twai_config = TwaiConfiguration::new(
twai,
twai_rx_pin,
twai_tx_pin,
TWAI_BAUDRATE,
TwaiMode::Normal,
);
twai_config.into_async().start()
}
#[async_trait(?Send)]
impl<'a> BoardInteraction<'a> for V4<'a> {
@@ -384,10 +357,8 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
}
async fn measure_moisture_hz(&mut self) -> FatResult<Moistures> {
self.can_power.set_high();
let mut twai = create_twai();
let config = self.twai_config.take().expect("twai config not set");
let mut twai = config.into_async().start();
Timer::after_millis(10).await;
@@ -395,17 +366,20 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
let _ = wait_for_can_measurements(&mut twai, &mut moistures)
.with_timeout(Duration::from_millis(5000))
.await;
teardown_twai(twai);
let config = twai.stop().into_blocking();
self.twai_config.replace(config);
self.can_power.set_low();
Ok(moistures)
}
async fn detect_sensors(&mut self) -> FatResult<DetectionResult> {
self.can_power.set_high();
let mut twai = create_twai();
// Give CAN some time to stabilize
Timer::after_millis(3000).await;
let config = self.twai_config.take().expect("twai config not set");
let mut twai = config.into_async().start();
Timer::after_millis(1000).await;
info!("Sending info messages now");
// Send a few test messages per potential sensor node
for plant in 0..PLANT_COUNT {
@@ -418,7 +392,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
// Try a few times; we intentionally ignore rx here and rely on stub logic
let resu = twai
.transmit_async(&frame)
.with_timeout(Duration::from_millis(1000))
.with_timeout(Duration::from_millis(3000))
.await;
match resu {
Ok(_) => {
@@ -438,11 +412,13 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
let mut moistures = Moistures::default();
let _ = wait_for_can_measurements(&mut twai, &mut moistures)
.with_timeout(Duration::from_millis(1000))
.with_timeout(Duration::from_millis(3000))
.await;
teardown_twai(twai);
let config = twai.stop().into_blocking();
self.twai_config.replace(config);
self.can_power.set_low();
@@ -490,8 +466,8 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
}
let moisture = self.measure_moisture_hz().await?;
for plant in 0..PLANT_COUNT {
let a = moisture.sensor_a_hz[plant] as u32;
let b = moisture.sensor_b_hz[plant] as u32;
let a = moisture.sensor_a_hz[plant].unwrap_or(0.0) as u32;
let b = moisture.sensor_b_hz[plant].unwrap_or(0.0) as u32;
LOG_ACCESS
.lock()
.await
@@ -515,11 +491,9 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
}
async fn can_power(&mut self, state: bool) -> FatResult<()> {
if state && self.can_power.is_set_low(){
self.can_power.set_high();
create_twai();
if state && self.can_power.is_set_low() {
self.can_power.set_high();
} else {
teardown_twai(create_twai());
self.can_power.set_low();
}
Ok(())
@@ -552,10 +526,10 @@ async fn wait_for_can_measurements(
let frequency = u16::from_be_bytes([data[0], data[1]]);
match sensor {
SensorSlot::A => {
moistures.sensor_a_hz[plant] = frequency as f32;
moistures.sensor_a_hz[plant] = Some(frequency as f32);
}
SensorSlot::B => {
moistures.sensor_b_hz[plant] = frequency as f32;
moistures.sensor_b_hz[plant] = Some(frequency as f32);
}
}
}
@@ -585,10 +559,10 @@ impl From<Moistures> for DetectionResult {
fn from(value: Moistures) -> Self {
let mut result = DetectionResult::default();
for (plant, sensor) in value.sensor_a_hz.iter().enumerate() {
result.plant[plant].sensor_a = *sensor > 1.0_f32;
result.plant[plant].sensor_a = sensor.is_some();
}
for (plant, sensor) in value.sensor_b_hz.iter().enumerate() {
result.plant[plant].sensor_b = *sensor > 1.0_f32;
result.plant[plant].sensor_b = sensor.is_some();
}
result
}

View File

@@ -9,6 +9,7 @@ const MOIST_SENSOR_MIN_FREQUENCY: f32 = 150.; // this is really, really dry, thi
#[derive(Debug, PartialEq, Serialize)]
pub enum MoistureSensorError {
NoMessage,
ShortCircuit { hz: f32, max: f32 },
OpenLoop { hz: f32, min: f32 },
}
@@ -118,41 +119,56 @@ impl PlantState {
) -> Self {
let sensor_a = if board.board_hal.get_config().plants[plant_id].sensor_a {
let raw = moistures.sensor_a_hz[plant_id];
match map_range_moisture(
raw,
board.board_hal.get_config().plants[plant_id]
.moisture_sensor_min_frequency
.map(|a| a as f32),
board.board_hal.get_config().plants[plant_id]
.moisture_sensor_max_frequency
.map(|b| b as f32),
) {
Ok(moisture_percent) => MoistureSensorState::MoistureValue {
raw_hz: raw,
moisture_percent,
},
Err(err) => MoistureSensorState::SensorError(err),
match raw {
None => {
MoistureSensorState::SensorError(MoistureSensorError::NoMessage)
}
Some(raw) => {
match map_range_moisture(
raw,
board.board_hal.get_config().plants[plant_id]
.moisture_sensor_min_frequency
.map(|a| a as f32),
board.board_hal.get_config().plants[plant_id]
.moisture_sensor_max_frequency
.map(|b| b as f32),
) {
Ok(moisture_percent) => MoistureSensorState::MoistureValue {
raw_hz: raw,
moisture_percent,
},
Err(err) => MoistureSensorState::SensorError(err),
}
}
}
} else {
MoistureSensorState::Disabled
};
let sensor_b = if board.board_hal.get_config().plants[plant_id].sensor_b {
let raw = moistures.sensor_b_hz[plant_id];
match map_range_moisture(
raw,
board.board_hal.get_config().plants[plant_id]
.moisture_sensor_min_frequency
.map(|a| a as f32),
board.board_hal.get_config().plants[plant_id]
.moisture_sensor_max_frequency
.map(|b| b as f32),
) {
Ok(moisture_percent) => MoistureSensorState::MoistureValue {
raw_hz: raw,
moisture_percent,
},
Err(err) => MoistureSensorState::SensorError(err),
match raw {
None => {
MoistureSensorState::SensorError(MoistureSensorError::NoMessage)
}
Some(raw) => {
match map_range_moisture(
raw,
board.board_hal.get_config().plants[plant_id]
.moisture_sensor_min_frequency
.map(|a| a as f32),
board.board_hal.get_config().plants[plant_id]
.moisture_sensor_max_frequency
.map(|b| b as f32),
) {
Ok(moisture_percent) => MoistureSensorState::MoistureValue {
raw_hz: raw,
moisture_percent,
},
Err(err) => MoistureSensorState::SensorError(err),
}
}
}
} else {
MoistureSensorState::Disabled

View File

@@ -130,11 +130,11 @@ where
T: Read + Write,
{
let actual_data = read_up_to_bytes_from_request(request, None).await?;
let pump_test: CanPower = serde_json::from_slice(&actual_data)?;
let can_power_request: CanPower = serde_json::from_slice(&actual_data)?;
let mut board = BOARD_ACCESS.get().await.lock().await;
let config = &board.board_hal.can_power(pump_test.state).await?;
let enable = pump_test.state;
board.board_hal.can_power(can_power_request.state).await?;
let enable = can_power_request.state;
info!(
"set can power to {enable}"
);