remove HAL implementation files for v3 and v4, and the build script

This commit is contained in:
2026-01-04 18:41:38 +01:00
parent 412a26390a
commit d33b05e1d7
21 changed files with 504 additions and 1418 deletions

View File

@@ -88,9 +88,8 @@ pub enum BatteryBoardVersion {
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
pub enum BoardVersion {
#[default]
Initial,
V3,
#[default]
V4,
}

View File

@@ -194,7 +194,7 @@ impl From<Utf8Error> for FatError {
}
}
impl<E: core::fmt::Debug> From<edge_http::io::Error<E>> for FatError {
impl<E: fmt::Debug> From<edge_http::io::Error<E>> for FatError {
fn from(value: edge_http::io::Error<E>) -> Self {
FatError::String {
error: format!("{value:?}"),
@@ -202,7 +202,7 @@ impl<E: core::fmt::Debug> From<edge_http::io::Error<E>> for FatError {
}
}
impl<E: core::fmt::Debug> From<ds323x::Error<E>> for FatError {
impl<E: fmt::Debug> From<ds323x::Error<E>> for FatError {
fn from(value: ds323x::Error<E>) -> Self {
FatError::DS323 {
error: format!("{value:?}"),
@@ -210,7 +210,7 @@ impl<E: core::fmt::Debug> From<ds323x::Error<E>> for FatError {
}
}
impl<E: core::fmt::Debug> From<eeprom24x::Error<E>> for FatError {
impl<E: fmt::Debug> From<eeprom24x::Error<E>> for FatError {
fn from(value: eeprom24x::Error<E>) -> Self {
FatError::Eeprom24x {
error: format!("{value:?}"),
@@ -218,7 +218,7 @@ impl<E: core::fmt::Debug> From<eeprom24x::Error<E>> for FatError {
}
}
impl<E: core::fmt::Debug> From<ExpanderError<I2cDeviceError<E>>> for FatError {
impl<E: fmt::Debug> From<ExpanderError<I2cDeviceError<E>>> for FatError {
fn from(value: ExpanderError<I2cDeviceError<E>>) -> Self {
FatError::ExpanderError {
error: format!("{value:?}"),
@@ -248,7 +248,7 @@ impl From<ConfigError> for FatError {
}
}
impl<E: core::fmt::Debug> From<I2cDeviceError<E>> for FatError {
impl<E: fmt::Debug> From<I2cDeviceError<E>> for FatError {
fn from(value: I2cDeviceError<E>) -> Self {
FatError::String {
error: format!("{value:?}"),
@@ -256,14 +256,14 @@ impl<E: core::fmt::Debug> From<I2cDeviceError<E>> for FatError {
}
}
impl<E: core::fmt::Debug> From<BusVoltageReadError<I2cDeviceError<E>>> for FatError {
impl<E: fmt::Debug> From<BusVoltageReadError<I2cDeviceError<E>>> for FatError {
fn from(value: BusVoltageReadError<I2cDeviceError<E>>) -> Self {
FatError::String {
error: format!("{value:?}"),
}
}
}
impl<E: core::fmt::Debug> From<ShuntVoltageReadError<I2cDeviceError<E>>> for FatError {
impl<E: fmt::Debug> From<ShuntVoltageReadError<I2cDeviceError<E>>> for FatError {
fn from(value: ShuntVoltageReadError<I2cDeviceError<E>>) -> Self {
FatError::String {
error: format!("{value:?}"),

View File

@@ -560,14 +560,11 @@ impl Esp<'_> {
duration_in_ms: u64,
mut rtc: MutexGuard<CriticalSectionRawMutex, Rtc>,
) -> ! {
// Configure and enter deep sleep using esp-hal. Also keep prior behavior where
// duration_in_ms == 0 triggers an immediate reset.
// Mark the current OTA image as valid if we reached here while in pending verify.
if let Ok(cur) = self.ota.current_ota_state() {
if cur == OtaImageState::PendingVerify {
self.ota
.set_current_ota_state(OtaImageState::Valid)
.set_current_ota_state(Valid)
.expect("Could not set image to valid");
}
}
@@ -673,12 +670,12 @@ impl Esp<'_> {
// is executed before main, no other code will alter these values during printing
#[allow(static_mut_refs)]
for (i, time) in LAST_WATERING_TIMESTAMP.iter().enumerate() {
log::info!("LAST_WATERING_TIMESTAMP[{i}] = UTC {time}");
info!("LAST_WATERING_TIMESTAMP[{i}] = UTC {time}");
}
// is executed before main, no other code will alter these values during printing
#[allow(static_mut_refs)]
for (i, item) in CONSECUTIVE_WATERING_PLANT.iter().enumerate() {
log::info!("CONSECUTIVE_WATERING_PLANT[{i}] = {item}");
info!("CONSECUTIVE_WATERING_PLANT[{i}] = {item}");
}
}
}
@@ -963,7 +960,7 @@ async fn run_dhcp(stack: Stack<'static>, gw_ip_addr: &'static str) {
&mut buf,
)
.await
.inspect_err(|e| log::warn!("DHCP server error: {e:?}"));
.inspect_err(|e| warn!("DHCP server error: {e:?}"));
Timer::after(Duration::from_millis(500)).await;
}
}

View File

@@ -63,25 +63,25 @@ impl lfs2Storage for LittleFs2Filesystem {
let block_size: usize = Self::BLOCK_SIZE;
if off % block_size != 0 {
error!("Littlefs2Filesystem erase error: offset not aligned to block size offset: {off} block_size: {block_size}");
return lfs2Result::Err(lfs2Error::IO);
return Err(lfs2Error::IO);
}
if len % block_size != 0 {
error!("Littlefs2Filesystem erase error: length not aligned to block size length: {len} block_size: {block_size}");
return lfs2Result::Err(lfs2Error::IO);
return Err(lfs2Error::IO);
}
match check_erase(self.storage, off as u32, (off + len) as u32) {
Ok(_) => {}
Err(err) => {
error!("Littlefs2Filesystem check erase error: {err:?}");
return lfs2Result::Err(lfs2Error::IO);
return Err(lfs2Error::IO);
}
}
match self.storage.erase(off as u32, (off + len) as u32) {
Ok(..) => lfs2Result::Ok(len),
Ok(..) => Ok(len),
Err(err) => {
error!("Littlefs2Filesystem erase error: {err:?}");
lfs2Result::Err(lfs2Error::IO)
Err(lfs2Error::IO)
}
}
}

View File

@@ -5,10 +5,7 @@ mod initial_hal;
mod little_fs2storage_adapter;
pub(crate) mod rtc;
mod shared_flash;
mod v3_hal;
mod v3_shift_register;
mod v4_hal;
pub(crate) mod v4_sensor;
mod water;
use crate::alloc::string::ToString;
@@ -169,7 +166,7 @@ pub trait BoardInteraction<'a> {
async fn progress(&mut self, counter: u32) {
// Indicate progress is active to suppress default wait_infinity blinking
crate::hal::PROGRESS_ACTIVE.store(true, core::sync::atomic::Ordering::Relaxed);
PROGRESS_ACTIVE.store(true, core::sync::atomic::Ordering::Relaxed);
let current = counter % PLANT_COUNT as u32;
for led in 0..PLANT_COUNT {
@@ -190,7 +187,7 @@ pub trait BoardInteraction<'a> {
let _ = self.general_fault(false).await;
// Reset progress active flag so wait_infinity can resume blinking
crate::hal::PROGRESS_ACTIVE.store(false, core::sync::atomic::Ordering::Relaxed);
PROGRESS_ACTIVE.store(false, core::sync::atomic::Ordering::Relaxed);
}
}
@@ -345,7 +342,7 @@ impl PlantHal {
info!("Slot0 state: {state_0:?}");
info!("Slot1 state: {state_1:?}");
//obtain current_state and next_state here!
//get current_state and next_state here!
let ota_target = match target {
AppPartitionSubType::Ota0 => pt
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App(
@@ -382,11 +379,11 @@ impl PlantHal {
let lfs2filesystem = mk_static!(LittleFs2Filesystem, LittleFs2Filesystem { storage: data });
let alloc = mk_static!(Allocation<LittleFs2Filesystem>, lfs2Filesystem::allocate());
if lfs2filesystem.is_mountable() {
log::info!("Littlefs2 filesystem is mountable");
info!("Littlefs2 filesystem is mountable");
} else {
match lfs2filesystem.format() {
Result::Ok(..) => {
log::info!("Littlefs2 filesystem is formatted");
Ok(..) => {
info!("Littlefs2 filesystem is formatted");
}
Err(err) => {
error!("Littlefs2 filesystem could not be formatted: {err:?}");
@@ -466,7 +463,7 @@ impl PlantHal {
let config = esp.load_config().await;
log::info!("Init rtc driver");
info!("Init rtc driver");
let sda = peripherals.GPIO20;
let scl = peripherals.GPIO19;
@@ -500,10 +497,10 @@ impl PlantHal {
let rtc_time = rtc.datetime();
match rtc_time {
Ok(tt) => {
log::info!("Rtc Module reports time at UTC {tt}");
info!("Rtc Module reports time at UTC {tt}");
}
Err(err) => {
log::info!("Rtc Module could not be read {err:?}");
info!("Rtc Module could not be read {err:?}");
}
}
@@ -518,7 +515,7 @@ impl PlantHal {
Box::new(DS3231Module { rtc, storage }) as Box<dyn RTCModuleInteraction + Send>;
let hal = match config {
Result::Ok(config) => {
Ok(config) => {
let battery_interaction: Box<dyn BatteryInteraction + Send> =
match config.hardware.battery {
BatteryBoardVersion::Disabled => Box::new(NoBatteryMonitor {}),
@@ -558,9 +555,6 @@ impl PlantHal {
BoardVersion::Initial => {
initial_hal::create_initial_board(free_pins, config, esp)?
}
BoardVersion::V3 => {
v3_hal::create_v3(free_pins, esp, config, battery_interaction, rtc_module)?
}
BoardVersion::V4 => {
v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)
.await?
@@ -705,7 +699,7 @@ pub struct Moistures {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)]
pub struct DetectionResult {
plant: [DetectionSensorResult; crate::hal::PLANT_COUNT],
plant: [DetectionSensorResult; PLANT_COUNT],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)]
pub struct DetectionSensorResult {

View File

@@ -36,7 +36,7 @@ impl ErrorType for MutexFlashStorage {
}
impl ReadNorFlash for MutexFlashStorage {
const READ_SIZE: usize = 0;
const READ_SIZE: usize = 1;
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
ReadStorage::read(self, offset, bytes)
@@ -48,8 +48,8 @@ impl ReadNorFlash for MutexFlashStorage {
}
impl NorFlash for MutexFlashStorage {
const WRITE_SIZE: usize = 0;
const ERASE_SIZE: usize = 0;
const WRITE_SIZE: usize = 1;
const ERASE_SIZE: usize = 4096;
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
self.inner

View File

@@ -1,458 +0,0 @@
use crate::bail;
use crate::fat_error::FatError;
use crate::hal::esp::{hold_disable, hold_enable};
use crate::hal::rtc::RTCModuleInteraction;
use crate::hal::v3_shift_register::ShiftRegister40;
use crate::hal::water::TankSensor;
use crate::hal::{BoardInteraction, FreePeripherals, Moistures, Sensor, PLANT_COUNT, TIME_ACCESS};
use crate::log::{LogMessage, LOG_ACCESS};
use crate::{
config::PlantControllerConfig,
hal::{battery::BatteryInteraction, esp::Esp},
};
use alloc::boxed::Box;
use alloc::format;
use alloc::string::ToString;
use async_trait::async_trait;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::Mutex;
use embassy_time::Timer;
use embedded_hal::digital::OutputPin as _;
use esp_hal::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig, Pull};
use esp_hal::pcnt::channel::CtrlMode::Keep;
use esp_hal::pcnt::channel::EdgeMode::{Hold, Increment};
use esp_hal::pcnt::unit::Unit;
use measurements::{Current, Voltage};
const PUMP8_BIT: usize = 0;
const PUMP1_BIT: usize = 1;
const PUMP2_BIT: usize = 2;
const PUMP3_BIT: usize = 3;
const PUMP4_BIT: usize = 4;
const PUMP5_BIT: usize = 5;
const PUMP6_BIT: usize = 6;
const PUMP7_BIT: usize = 7;
const MS_0: usize = 8;
const MS_4: usize = 9;
const MS_2: usize = 10;
const MS_3: usize = 11;
const MS_1: usize = 13;
const SENSOR_ON: usize = 12;
const SENSOR_A_1: u8 = 7;
const SENSOR_A_2: u8 = 6;
const SENSOR_A_3: u8 = 5;
const SENSOR_A_4: u8 = 4;
const SENSOR_A_5: u8 = 3;
const SENSOR_A_6: u8 = 2;
const SENSOR_A_7: u8 = 1;
const SENSOR_A_8: u8 = 0;
const SENSOR_B_1: u8 = 8;
const SENSOR_B_2: u8 = 9;
const SENSOR_B_3: u8 = 10;
const SENSOR_B_4: u8 = 11;
const SENSOR_B_5: u8 = 12;
const SENSOR_B_6: u8 = 13;
const SENSOR_B_7: u8 = 14;
const SENSOR_B_8: u8 = 15;
const CHARGING: usize = 14;
const AWAKE: usize = 15;
const FAULT_3: usize = 16;
const FAULT_8: usize = 17;
const FAULT_7: usize = 18;
const FAULT_6: usize = 19;
const FAULT_5: usize = 20;
const FAULT_4: usize = 21;
const FAULT_1: usize = 22;
const FAULT_2: usize = 23;
const REPEAT_MOIST_MEASURE: usize = 1;
pub struct V3<'a> {
config: PlantControllerConfig,
battery_monitor: Box<dyn BatteryInteraction + Send>,
rtc_module: Box<dyn RTCModuleInteraction + Send>,
esp: Esp<'a>,
shift_register:
Mutex<CriticalSectionRawMutex, ShiftRegister40<Output<'a>, Output<'a>, Output<'a>>>,
_shift_register_enable_invert: Output<'a>,
tank_sensor: TankSensor<'a>,
solar_is_day: Input<'a>,
light: Output<'a>,
main_pump: Output<'a>,
general_fault: Output<'a>,
pub signal_counter: Unit<'static, 0>,
}
pub(crate) fn create_v3(
peripherals: FreePeripherals<'static>,
esp: Esp<'static>,
config: PlantControllerConfig,
battery_monitor: Box<dyn BatteryInteraction + Send>,
rtc_module: Box<dyn RTCModuleInteraction + Send>,
) -> Result<Box<dyn BoardInteraction<'static> + Send + 'static>, FatError> {
log::info!("Start v3");
let clock = Output::new(peripherals.gpio15, Level::Low, OutputConfig::default());
let latch = Output::new(peripherals.gpio3, Level::Low, OutputConfig::default());
let data = Output::new(peripherals.gpio23, Level::Low, OutputConfig::default());
let shift_register = ShiftRegister40::new(clock, latch, data);
//disable all
for mut pin in shift_register.decompose() {
let _ = pin.set_low();
}
// Set always-on status bits
let _ = shift_register.decompose()[AWAKE].set_high();
let _ = shift_register.decompose()[CHARGING].set_high();
// Multiplexer defaults: ms0..ms3 low, ms4 high (disabled)
let _ = shift_register.decompose()[MS_0].set_low();
let _ = shift_register.decompose()[MS_1].set_low();
let _ = shift_register.decompose()[MS_2].set_low();
let _ = shift_register.decompose()[MS_3].set_low();
let _ = shift_register.decompose()[MS_4].set_high();
let one_wire_pin = Flex::new(peripherals.gpio18);
let tank_power_pin = Output::new(peripherals.gpio11, Level::Low, OutputConfig::default());
let flow_sensor_pin = Input::new(
peripherals.gpio4,
InputConfig::default().with_pull(Pull::Up),
);
let tank_sensor = TankSensor::create(
one_wire_pin,
peripherals.adc1,
peripherals.gpio5,
tank_power_pin,
flow_sensor_pin,
peripherals.pcnt1,
)?;
let solar_is_day = Input::new(peripherals.gpio7, InputConfig::default());
let light = Output::new(peripherals.gpio10, Level::Low, OutputConfig::default());
let mut main_pump = Output::new(peripherals.gpio2, Level::Low, OutputConfig::default());
main_pump.set_low();
let mut general_fault = Output::new(peripherals.gpio6, Level::Low, OutputConfig::default());
general_fault.set_low();
let mut shift_register_enable_invert =
Output::new(peripherals.gpio21, Level::Low, OutputConfig::default());
shift_register_enable_invert.set_low();
let signal_counter = peripherals.pcnt0;
signal_counter.set_high_limit(Some(i16::MAX))?;
let ch0 = &signal_counter.channel0;
let edge_pin = Input::new(peripherals.gpio22, InputConfig::default());
ch0.set_edge_signal(edge_pin.peripheral_input());
ch0.set_input_mode(Hold, Increment);
ch0.set_ctrl_mode(Keep, Keep);
signal_counter.listen();
Ok(Box::new(V3 {
config,
battery_monitor,
rtc_module,
esp,
shift_register: Mutex::new(shift_register),
_shift_register_enable_invert: shift_register_enable_invert,
tank_sensor,
solar_is_day,
light,
main_pump,
general_fault,
signal_counter,
}))
}
impl V3<'_> {
async fn inner_measure_moisture_hz(
&mut self,
plant: usize,
sensor: Sensor,
) -> Result<f32, FatError> {
let mut results = [0_f32; REPEAT_MOIST_MEASURE];
for sample in results.iter_mut() {
self.signal_counter.pause();
self.signal_counter.clear();
//Disable all
{
let shift_register = self.shift_register.lock().await;
shift_register.decompose()[MS_4].set_high()?;
}
let sensor_channel = match sensor {
Sensor::A => match plant {
0 => SENSOR_A_1,
1 => SENSOR_A_2,
2 => SENSOR_A_3,
3 => SENSOR_A_4,
4 => SENSOR_A_5,
5 => SENSOR_A_6,
6 => SENSOR_A_7,
7 => SENSOR_A_8,
_ => bail!("Invalid plant id {}", plant),
},
Sensor::B => match plant {
0 => SENSOR_B_1,
1 => SENSOR_B_2,
2 => SENSOR_B_3,
3 => SENSOR_B_4,
4 => SENSOR_B_5,
5 => SENSOR_B_6,
6 => SENSOR_B_7,
7 => SENSOR_B_8,
_ => bail!("Invalid plant id {}", plant),
},
};
let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 };
{
let shift_register = self.shift_register.lock().await;
let pin_0 = &mut shift_register.decompose()[MS_0];
let pin_1 = &mut shift_register.decompose()[MS_1];
let pin_2 = &mut shift_register.decompose()[MS_2];
let pin_3 = &mut shift_register.decompose()[MS_3];
if is_bit_set(0) {
pin_0.set_high()?;
} else {
pin_0.set_low()?;
}
if is_bit_set(1) {
pin_1.set_high()?;
} else {
pin_1.set_low()?;
}
if is_bit_set(2) {
pin_2.set_high()?;
} else {
pin_2.set_low()?;
}
if is_bit_set(3) {
pin_3.set_high()?;
} else {
pin_3.set_low()?;
}
shift_register.decompose()[MS_4].set_low()?;
shift_register.decompose()[SENSOR_ON].set_high()?;
}
let measurement = 100; //how long to measure and then extrapolate to hz
let factor = 1000f32 / measurement as f32; //scale raw cound by this number to get hz
//give some time to stabilize
Timer::after_millis(10).await;
self.signal_counter.resume();
Timer::after_millis(measurement).await;
self.signal_counter.pause();
{
let shift_register = self.shift_register.lock().await;
shift_register.decompose()[MS_4].set_high()?;
shift_register.decompose()[SENSOR_ON].set_low()?;
}
Timer::after_millis(10).await;
let unscaled = self.signal_counter.value();
let hz = unscaled as f32 * factor;
LOG_ACCESS
.lock()
.await
.log(
LogMessage::RawMeasure,
unscaled as u32,
hz as u32,
&plant.to_string(),
&format!("{sensor:?}"),
)
.await;
*sample = hz;
}
results.sort_by(|a, b| a.partial_cmp(b).unwrap()); // floats don't seem to implement total_ord
let mid = results.len() / 2;
let median = results[mid];
Ok(median)
}
}
#[async_trait(?Send)]
impl<'a> BoardInteraction<'a> for V3<'a> {
fn get_tank_sensor(&mut self) -> Result<&mut TankSensor<'a>, FatError> {
Ok(&mut self.tank_sensor)
}
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_monitor
}
fn get_rtc_module(&mut self) -> &mut Box<dyn RTCModuleInteraction + Send> {
&mut self.rtc_module
}
async fn set_charge_indicator(&mut self, charging: bool) -> Result<(), FatError> {
let shift_register = self.shift_register.lock().await;
if charging {
let _ = shift_register.decompose()[CHARGING].set_high();
} else {
let _ = shift_register.decompose()[CHARGING].set_low();
}
Ok(())
}
async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
let _ = self.shift_register.lock().await.decompose()[AWAKE].set_low();
let guard = TIME_ACCESS.get().await.lock().await;
self.esp.deep_sleep(duration_in_ms, guard)
}
fn is_day(&self) -> bool {
self.solar_is_day.is_high()
}
async fn light(&mut self, enable: bool) -> Result<(), FatError> {
hold_disable(10);
if enable {
self.light.set_high();
} else {
self.light.set_low();
}
hold_enable(10);
Ok(())
}
async fn pump(&mut self, plant: usize, enable: bool) -> Result<(), FatError> {
if enable {
self.main_pump.set_high();
}
let index = match plant {
0 => PUMP1_BIT,
1 => PUMP2_BIT,
2 => PUMP3_BIT,
3 => PUMP4_BIT,
4 => PUMP5_BIT,
5 => PUMP6_BIT,
6 => PUMP7_BIT,
7 => PUMP8_BIT,
_ => bail!("Invalid pump {plant}"),
};
let shift_register = self.shift_register.lock().await;
if enable {
let _ = shift_register.decompose()[index].set_high();
} else {
let _ = shift_register.decompose()[index].set_low();
}
if !enable {
self.main_pump.set_low();
}
Ok(())
}
async fn pump_current(&mut self, _plant: usize) -> Result<Current, FatError> {
bail!("Not implemented in v3")
}
async fn fault(&mut self, plant: usize, enable: bool) -> Result<(), FatError> {
let index = match plant {
0 => FAULT_1,
1 => FAULT_2,
2 => FAULT_3,
3 => FAULT_4,
4 => FAULT_5,
5 => FAULT_6,
6 => FAULT_7,
7 => FAULT_8,
_ => panic!("Invalid plant id {}", plant),
};
let shift_register = self.shift_register.lock().await;
if enable {
let _ = shift_register.decompose()[index].set_high();
} else {
let _ = shift_register.decompose()[index].set_low();
}
Ok(())
}
async fn measure_moisture_hz(&mut self) -> Result<Moistures, FatError> {
let mut result = Moistures::default();
for plant in 0..PLANT_COUNT {
let a = self.inner_measure_moisture_hz(plant, Sensor::A).await;
let b = self.inner_measure_moisture_hz(plant, Sensor::B).await;
let aa = a.unwrap_or(u32::MAX as f32);
let bb = b.unwrap_or(u32::MAX as f32);
LOG_ACCESS
.lock()
.await
.log(
LogMessage::TestSensor,
aa as u32,
bb as u32,
&plant.to_string(),
"",
)
.await;
result.sensor_a_hz[plant] = aa;
result.sensor_b_hz[plant] = bb;
}
Ok(result)
}
async fn general_fault(&mut self, enable: bool) {
hold_disable(6);
if enable {
self.general_fault.set_high();
} else {
self.general_fault.set_low();
}
hold_enable(6);
}
async fn test(&mut self) -> Result<(), FatError> {
self.general_fault(true).await;
Timer::after_millis(100).await;
self.general_fault(false).await;
Timer::after_millis(100).await;
self.light(true).await?;
Timer::after_millis(500).await;
self.light(false).await?;
Timer::after_millis(500).await;
for i in 0..PLANT_COUNT {
self.fault(i, true).await?;
Timer::after_millis(500).await;
self.fault(i, false).await?;
Timer::after_millis(500).await;
}
for i in 0..PLANT_COUNT {
self.pump(i, true).await?;
Timer::after_millis(100).await;
self.pump(i, false).await?;
Timer::after_millis(100).await;
}
self.measure_moisture_hz().await?;
Timer::after_millis(10).await;
Ok(())
}
fn set_config(&mut self, config: PlantControllerConfig) {
self.config = config;
}
async fn get_mptt_voltage(&mut self) -> Result<Voltage, FatError> {
bail!("Not implemented in v3")
}
async fn get_mptt_current(&mut self) -> Result<Current, FatError> {
bail!("Not implemented in v3")
}
}

View File

@@ -1,154 +0,0 @@
//! Serial-in parallel-out shift register
#![allow(warnings)]
use core::cell::RefCell;
use core::convert::Infallible;
use core::iter::Iterator;
use core::mem::{self, MaybeUninit};
use core::result::{Result, Result::Ok};
use embedded_hal::digital::OutputPin;
trait ShiftRegisterInternal: Send {
fn update(&self, index: usize, command: bool) -> Result<(), ()>;
}
/// Output pin of the shift register
pub struct ShiftRegisterPin<'a> {
shift_register: &'a dyn ShiftRegisterInternal,
index: usize,
}
impl<'a> ShiftRegisterPin<'a> {
fn new(shift_register: &'a dyn ShiftRegisterInternal, index: usize) -> Self {
ShiftRegisterPin {
shift_register,
index,
}
}
}
impl embedded_hal::digital::ErrorType for ShiftRegisterPin<'_> {
type Error = Infallible;
}
impl OutputPin for ShiftRegisterPin<'_> {
fn set_low(&mut self) -> Result<(), Infallible> {
self.shift_register.update(self.index, false).unwrap();
Ok(())
}
fn set_high(&mut self) -> Result<(), Infallible> {
self.shift_register.update(self.index, true).unwrap();
Ok(())
}
}
macro_rules! ShiftRegisterBuilder {
($name: ident, $size: expr) => {
/// Serial-in parallel-out shift register
pub struct $name<Pin1, Pin2, Pin3>
where
Pin1: OutputPin + Send,
Pin2: OutputPin + Send,
Pin3: OutputPin + Send,
{
clock: RefCell<Pin1>,
latch: RefCell<Pin2>,
data: RefCell<Pin3>,
output_state: RefCell<[bool; $size]>,
}
impl<Pin1, Pin2, Pin3> ShiftRegisterInternal for $name<Pin1, Pin2, Pin3>
where
Pin1: OutputPin + Send,
Pin2: OutputPin + Send,
Pin3: OutputPin + Send,
{
/// Sets the value of the shift register output at `index` to value `command`
fn update(&self, index: usize, command: bool) -> Result<(), ()> {
self.output_state.borrow_mut()[index] = command;
let output_state = self.output_state.borrow();
self.latch.borrow_mut().set_low().map_err(|_e| ())?;
for i in 1..=output_state.len() {
if output_state[output_state.len() - i] {
self.data.borrow_mut().set_high().map_err(|_e| ())?;
} else {
self.data.borrow_mut().set_low().map_err(|_e| ())?;
}
self.clock.borrow_mut().set_high().map_err(|_e| ())?;
self.clock.borrow_mut().set_low().map_err(|_e| ())?;
}
self.latch.borrow_mut().set_high().map_err(|_e| ())?;
Ok(())
}
}
impl<Pin1, Pin2, Pin3> $name<Pin1, Pin2, Pin3>
where
Pin1: OutputPin + Send,
Pin2: OutputPin + Send,
Pin3: OutputPin + Send,
{
/// Creates a new SIPO shift register from clock, latch, and data output pins
pub fn new(clock: Pin1, latch: Pin2, data: Pin3) -> Self {
$name {
clock: RefCell::new(clock),
latch: RefCell::new(latch),
data: RefCell::new(data),
output_state: RefCell::new([false; $size]),
}
}
/// Get embedded-hal output pins to control the shift register outputs
pub fn decompose(&self) -> [ShiftRegisterPin<'_>; $size] {
// Create an uninitialized array of `MaybeUninit`. The `assume_init` is
// safe because the type we are claiming to have initialized here is a
// bunch of `MaybeUninit`s, which do not require initialization.
let mut pins: [MaybeUninit<ShiftRegisterPin>; $size] =
unsafe { MaybeUninit::uninit().assume_init() };
// Dropping a `MaybeUninit` does nothing, so if there is a panic during this loop,
// we have a memory leak, but there is no memory safety issue.
for (index, elem) in pins.iter_mut().enumerate() {
elem.write(ShiftRegisterPin::new(self, index));
}
// Everything is initialized. Transmute the array to the
// initialized type.
unsafe { mem::transmute::<_, [ShiftRegisterPin; $size]>(pins) }
}
/// Consume the shift register and return the original clock, latch, and data output pins
pub fn release(self) -> (Pin1, Pin2, Pin3) {
let Self {
clock,
latch,
data,
output_state: _,
} = self;
(clock.into_inner(), latch.into_inner(), data.into_inner())
}
}
};
}
ShiftRegisterBuilder!(ShiftRegister8, 8);
ShiftRegisterBuilder!(ShiftRegister16, 16);
ShiftRegisterBuilder!(ShiftRegister24, 24);
ShiftRegisterBuilder!(ShiftRegister32, 32);
ShiftRegisterBuilder!(ShiftRegister40, 40);
ShiftRegisterBuilder!(ShiftRegister48, 48);
ShiftRegisterBuilder!(ShiftRegister56, 56);
ShiftRegisterBuilder!(ShiftRegister64, 64);
ShiftRegisterBuilder!(ShiftRegister72, 72);
ShiftRegisterBuilder!(ShiftRegister80, 80);
ShiftRegisterBuilder!(ShiftRegister88, 88);
ShiftRegisterBuilder!(ShiftRegister96, 96);
ShiftRegisterBuilder!(ShiftRegister104, 104);
ShiftRegisterBuilder!(ShiftRegister112, 112);
ShiftRegisterBuilder!(ShiftRegister120, 120);
ShiftRegisterBuilder!(ShiftRegister128, 128);
/// 8 output serial-in parallel-out shift register
pub type ShiftRegister<Pin1, Pin2, Pin3> = ShiftRegister8<Pin1, Pin2, Pin3>;

View File

@@ -1,32 +1,33 @@
use crate::bail;
use crate::config::PlantControllerConfig;
use crate::fat_error::{FatError, FatResult};
use crate::fat_error::{ContextExt, FatError, FatResult};
use crate::hal::battery::BatteryInteraction;
use crate::hal::esp::{hold_disable, hold_enable, Esp};
use crate::hal::rtc::RTCModuleInteraction;
use crate::hal::v4_sensor::{SensorImpl, SensorInteraction};
use crate::hal::water::TankSensor;
use crate::hal::{
BoardInteraction, DetectionResult, FreePeripherals, Moistures, I2C_DRIVER, PLANT_COUNT,
BoardInteraction, DetectionResult, FreePeripherals, Moistures, Sensor, I2C_DRIVER, PLANT_COUNT,
TIME_ACCESS,
};
use crate::log::{LogMessage, LOG_ACCESS};
use alloc::boxed::Box;
use alloc::string::ToString;
use async_trait::async_trait;
use canapi::id::{classify, plant_id, MessageKind, IDENTIFY_CMD_OFFSET};
use canapi::SensorSlot;
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_time::Timer;
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::pcnt::channel::CtrlMode::Keep;
use esp_hal::pcnt::channel::EdgeMode::{Hold, Increment};
use esp_hal::twai::TwaiMode;
use esp_hal::{twai, Blocking};
use esp_hal::twai::{EspTwaiError, EspTwaiFrame, StandardId, Twai, TwaiConfiguration, TwaiMode};
use esp_hal::{twai, Async, Blocking};
use ina219::address::{Address, Pin};
use ina219::calibration::UnCalibrated;
use ina219::configuration::{Configuration, OperatingMode, Resolution};
use ina219::SyncIna219;
use log::{error, info, warn};
use measurements::Resistance;
use measurements::{Current, Voltage};
use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
@@ -88,7 +89,7 @@ impl Charger<'_> {
operating_mode: OperatingMode::PowerDown,
})
.map_err(|e| {
log::info!(
info!(
"Error setting ina mppt configuration during deep sleep preparation{e:?}"
);
});
@@ -127,7 +128,8 @@ pub struct V4<'a> {
pump_ina: Option<
SyncIna219<I2cDevice<'a, CriticalSectionRawMutex, I2c<'static, Blocking>>, UnCalibrated>,
>,
sensor: SensorImpl,
twai_config: Option<TwaiConfiguration<'static, Blocking>>,
can_power: Output<'static>,
extra1: Output<'a>,
extra2: Output<'a>,
}
@@ -139,7 +141,7 @@ pub(crate) async fn create_v4(
battery_monitor: Box<dyn BatteryInteraction + Send>,
rtc_module: Box<dyn RTCModuleInteraction + Send>,
) -> Result<Box<dyn BoardInteraction<'static> + Send + 'static>, FatError> {
log::info!("Start v4");
info!("Start v4");
let mut awake = Output::new(peripherals.gpio21, Level::High, OutputConfig::default());
awake.set_high();
@@ -165,53 +167,14 @@ pub(crate) async fn create_v4(
peripherals.pcnt1,
)?;
let sensor_expander_device = I2cDevice::new(I2C_DRIVER.get().await);
let mut sensor_expander = Pca9535Immediate::new(sensor_expander_device, 34);
let sensor = match sensor_expander.pin_into_output(GPIOBank::Bank0, 0) {
Ok(_) => {
log::info!("SensorExpander answered");
let signal_counter = peripherals.pcnt0;
signal_counter.set_high_limit(Some(i16::MAX))?;
let ch0 = &signal_counter.channel0;
let edge_pin = Input::new(peripherals.gpio22, InputConfig::default());
ch0.set_edge_signal(edge_pin.peripheral_input());
ch0.set_input_mode(Hold, Increment);
ch0.set_ctrl_mode(Keep, Keep);
signal_counter.listen();
for pin in 0..8 {
let _ = sensor_expander.pin_into_output(GPIOBank::Bank0, pin);
let _ = sensor_expander.pin_into_output(GPIOBank::Bank1, pin);
let _ = sensor_expander.pin_set_low(GPIOBank::Bank0, pin);
let _ = sensor_expander.pin_set_low(GPIOBank::Bank1, pin);
}
SensorImpl::PulseCounter {
signal_counter,
sensor_expander,
}
}
Err(_) => {
log::info!("Can bus mode ");
let twai_config = Some(twai::TwaiConfiguration::new(
peripherals.twai,
peripherals.gpio2,
peripherals.gpio0,
TWAI_BAUDRATE,
TwaiMode::Normal,
));
let can_power = Output::new(peripherals.gpio22, Level::Low, OutputConfig::default());
//can bus version
SensorImpl::CanBus {
twai_config,
can_power,
}
}
};
let twai_config = Some(TwaiConfiguration::new(
peripherals.twai,
peripherals.gpio2,
peripherals.gpio0,
TWAI_BAUDRATE,
TwaiMode::Normal,
));
let can_power = Output::new(peripherals.gpio22, Level::Low, OutputConfig::default());
let solar_is_day = Input::new(peripherals.gpio7, InputConfig::default());
let light = Output::new(peripherals.gpio10, Level::Low, Default::default());
@@ -241,7 +204,7 @@ pub(crate) async fn create_v4(
Some(ina)
}
Err(err) => {
log::info!("Error creating mppt ina: {err:?}");
info!("Error creating mppt ina: {err:?}");
None
}
};
@@ -250,7 +213,7 @@ pub(crate) async fn create_v4(
let pump_ina = match SyncIna219::new(pump_current_dev, Address::from_pins(Pin::Gnd, Pin::Sda)) {
Ok(ina) => Some(ina),
Err(err) => {
log::info!("Error creating pump ina: {err:?}");
info!("Error creating pump ina: {err:?}");
None
}
};
@@ -262,7 +225,7 @@ pub(crate) async fn create_v4(
bus_voltage_range: Default::default(),
shunt_voltage_range: Default::default(),
bus_resolution: Default::default(),
shunt_resolution: ina219::configuration::Resolution::Avg128,
shunt_resolution: Resolution::Avg128,
operating_mode: Default::default(),
})?;
@@ -275,6 +238,7 @@ pub(crate) async fn create_v4(
None => Charger::ErrorInit {},
};
info!("Assembling final v4 board interaction object");
let v = V4 {
rtc_module,
esp,
@@ -286,10 +250,11 @@ pub(crate) async fn create_v4(
config,
battery_monitor,
pump_ina,
twai_config,
charger,
extra1,
extra2,
sensor,
can_power,
};
Ok(Box::new(v))
}
@@ -383,9 +348,35 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
}
Ok(())
}
async fn measure_moisture_hz(&mut self) -> FatResult<Moistures> {
self.can_power.set_high();
let config = self.twai_config.take().expect("twai config not set");
let mut twai = config.into_async().start();
async fn measure_moisture_hz(&mut self) -> Result<Moistures, FatError> {
self.sensor.measure_moisture_hz().await
loop {
let rec = twai.receive();
match rec {
Ok(_) => {}
Err(err) => {
info!("Error receiving CAN message: {err:?}");
break;
}
}
}
Timer::after_millis(10).await;
let mut moistures = Moistures::default();
let _ = wait_for_can_measurements(&mut twai, &mut moistures)
.with_timeout(Duration::from_millis(2000))
.await;
self.can_power.set_low();
let config = twai.stop().into_blocking();
self.twai_config.replace(config);
Ok(moistures)
}
async fn general_fault(&mut self, enable: bool) {
@@ -450,6 +441,123 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
}
async fn detect_sensors(&mut self) -> FatResult<DetectionResult> {
self.sensor.autodetect().await
// Power on CAN transceiver and start controller
self.can_power.set_high();
let config = self.twai_config.take().expect("twai config not set");
info!("convert can");
let mut as_async = config.into_async().start();
// Give CAN some time to stabilize
Timer::after_millis(10).await;
info!("Sending info messages now");
// Send a few test messages per potential sensor node
for plant in 0..PLANT_COUNT {
for sensor in [Sensor::A, Sensor::B] {
let target =
StandardId::new(plant_id(IDENTIFY_CMD_OFFSET, sensor.into(), plant as u16))
.context(">> Could not create address for sensor! (plant: {}) <<")?;
let can_buffer = [0_u8; 0];
if let Some(frame) = EspTwaiFrame::new(target, &can_buffer) {
// Try a few times; we intentionally ignore rx here and rely on stub logic
let resu = as_async
.transmit_async(&frame)
.with_timeout(Duration::from_millis(1000))
.await;
match resu {
Ok(_) => {
info!("Sent test message to plant {plant} sensor {sensor:?}");
}
Err(err) => {
info!(
"Error sending test message to plant {plant} sensor {sensor:?}: {err:?}"
);
}
}
} else {
info!("Error building CAN frame");
}
}
}
let mut moistures = Moistures::default();
let _ = wait_for_can_measurements(&mut as_async, &mut moistures)
.with_timeout(Duration::from_millis(1000))
.await;
let config = as_async.stop().into_blocking();
self.can_power.set_low();
self.twai_config.replace(config);
let result = moistures.into();
info!("Autodetection result: {result:?}");
Ok(result)
}
}
async fn wait_for_can_measurements(
as_async: &mut Twai<'_, Async>,
moistures: &mut Moistures,
) -> FatResult<()> {
loop {
match as_async.receive_async().await {
Ok(can_frame) => match can_frame.id() {
Id::Standard(id) => {
info!("Received CAN message: {id:?}");
let rawid = id.as_raw();
match classify(rawid) {
None => {}
Some(msg) => {
info!(
"received message of kind {:?} (plant: {}, sensor: {:?})",
msg.0, msg.1, msg.2
);
if msg.0 == MessageKind::MoistureData {
let plant = msg.1 as usize;
let sensor = msg.2;
let data = can_frame.data();
if data.len() == 2 {
let frequency = u16::from_be_bytes([data[0], data[1]]);
match sensor {
SensorSlot::A => {
moistures.sensor_a_hz[plant] = frequency as f32;
}
SensorSlot::B => {
moistures.sensor_b_hz[plant] = frequency as f32;
}
}
}
}
}
}
}
Id::Extended(ext) => {
warn!("Received extended ID: {ext:?}");
}
},
Err(err) => {
match err {
EspTwaiError::BusOff => {
bail!("Bus offline")
}
EspTwaiError::NonCompliantDlc(_) => {}
EspTwaiError::EmbeddedHAL(_) => {}
}
error!("Error receiving CAN message: {err:?}");
}
}
}
}
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;
}
for (plant, sensor) in value.sensor_b_hz.iter().enumerate() {
result.plant[plant].sensor_b = *sensor > 1.0_f32;
}
result
}
}

View File

@@ -1,334 +0,0 @@
use crate::bail;
use crate::fat_error::{ContextExt, FatResult};
use crate::hal::Box;
use crate::hal::{DetectionResult, Moistures, Sensor};
use crate::log::{LogMessage, LOG_ACCESS};
use alloc::format;
use alloc::string::ToString;
use async_trait::async_trait;
use canapi::id::{classify, plant_id, MessageKind, IDENTIFY_CMD_OFFSET};
use canapi::SensorSlot;
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_time::{Duration, Instant, Timer, WithTimeout};
use embedded_can::{Frame, Id};
use esp_hal::gpio::Output;
use esp_hal::i2c::master::I2c;
use esp_hal::pcnt::unit::Unit;
use esp_hal::twai::{EspTwaiError, EspTwaiFrame, StandardId, Twai, TwaiConfiguration};
use esp_hal::{Async, Blocking};
use log::{error, info, warn};
use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
const REPEAT_MOIST_MEASURE: usize = 10;
#[async_trait(?Send)]
pub trait SensorInteraction {
async fn measure_moisture_hz(&mut self) -> FatResult<Moistures>;
}
const MS0: u8 = 1_u8;
const MS1: u8 = 0_u8;
const MS2: u8 = 3_u8;
const MS3: u8 = 4_u8;
const MS4: u8 = 2_u8;
const SENSOR_ON: u8 = 5_u8;
pub enum SensorImpl {
PulseCounter {
signal_counter: Unit<'static, 0>,
sensor_expander:
Pca9535Immediate<I2cDevice<'static, CriticalSectionRawMutex, I2c<'static, Blocking>>>,
},
CanBus {
twai_config: Option<TwaiConfiguration<'static, Blocking>>,
can_power: Output<'static>,
},
}
#[async_trait(?Send)]
impl SensorInteraction for SensorImpl {
async fn measure_moisture_hz(&mut self) -> FatResult<Moistures> {
match self {
SensorImpl::PulseCounter {
signal_counter,
sensor_expander,
..
} => {
let mut result = Moistures::default();
for plant in 0..crate::hal::PLANT_COUNT {
result.sensor_a_hz[plant] =
Self::inner_pulse(plant, Sensor::A, signal_counter, sensor_expander)
.await?;
info!(
"Sensor {} {:?}: {}",
plant,
Sensor::A,
result.sensor_a_hz[plant]
);
result.sensor_b_hz[plant] =
Self::inner_pulse(plant, Sensor::B, signal_counter, sensor_expander)
.await?;
info!(
"Sensor {} {:?}: {}",
plant,
Sensor::B,
result.sensor_b_hz[plant]
);
}
Ok(result)
}
SensorImpl::CanBus {
twai_config,
can_power,
} => {
can_power.set_high();
let config = twai_config.take().expect("twai config not set");
let mut twai = config.into_async().start();
loop {
let rec = twai.receive();
match rec {
Ok(_) => {}
Err(err) => {
info!("Error receiving CAN message: {err:?}");
break;
}
}
}
Timer::after_millis(10).await;
let mut moistures = Moistures::default();
let _ = Self::wait_for_can_measurements(&mut twai, &mut moistures)
.with_timeout(Duration::from_millis(5000))
.await;
can_power.set_low();
let config = twai.stop().into_blocking();
twai_config.replace(config);
Ok(moistures)
}
}
}
}
impl SensorImpl {
pub async fn autodetect(&mut self) -> FatResult<DetectionResult> {
match self {
SensorImpl::PulseCounter { .. } => {
bail!("Only CAN bus implementation supports autodetection")
}
SensorImpl::CanBus {
twai_config,
can_power,
} => {
// Power on CAN transceiver and start controller
can_power.set_high();
let config = twai_config.take().expect("twai config not set");
info!("convert can");
let mut as_async = config.into_async().start();
// Give CAN some time to stabilize
Timer::after_millis(10).await;
info!("Sending info messages now");
// Send a few test messages per potential sensor node
for plant in 0..crate::hal::PLANT_COUNT {
for sensor in [Sensor::A, Sensor::B] {
let target = StandardId::new(plant_id(
IDENTIFY_CMD_OFFSET,
sensor.into(),
plant as u16,
))
.context(">> Could not create address for sensor! (plant: {}) <<")?;
let can_buffer = [0_u8; 0];
if let Some(frame) = EspTwaiFrame::new(target, &can_buffer) {
// Try a few times; we intentionally ignore rx here and rely on stub logic
let resu = as_async.transmit_async(&frame).with_timeout(Duration::from_millis(1000)).await;
match resu {
Ok(_) => {
info!("Sent test message to plant {plant} sensor {sensor:?}");
}
Err(err) => {
info!(
"Error sending test message to plant {plant} sensor {sensor:?}: {err:?}"
);
}
}
} else {
info!("Error building CAN frame");
}
}
}
let mut moistures = Moistures::default();
let _ = Self::wait_for_can_measurements(&mut as_async, &mut moistures)
.with_timeout(Duration::from_millis(1000))
.await;
let config = as_async.stop().into_blocking();
can_power.set_low();
twai_config.replace(config);
let result = moistures.into();
info!("Autodetection result: {result:?}");
Ok(result)
}
}
}
async fn wait_for_can_measurements(
as_async: &mut Twai<'_, Async>,
moistures: &mut Moistures,
) -> FatResult<()> {
loop {
match as_async.receive_async().await {
Ok(can_frame) => match can_frame.id() {
Id::Standard(id) => {
info!("Received CAN message: {id:?}");
let rawid = id.as_raw();
match classify(rawid) {
None => {}
Some(msg) => {
info!(
"received message of kind {:?} (plant: {}, sensor: {:?})",
msg.0, msg.1, msg.2
);
if msg.0 == MessageKind::MoistureData {
let plant = msg.1 as usize;
let sensor = msg.2;
let data = can_frame.data();
if data.len() == 2 {
let frequency = u16::from_be_bytes([data[0], data[1]]);
match sensor {
SensorSlot::A => {
moistures.sensor_a_hz[plant] = frequency as f32;
}
SensorSlot::B => {
moistures.sensor_b_hz[plant] = frequency as f32;
}
}
}
}
}
}
}
Id::Extended(ext) => {
warn!("Received extended ID: {ext:?}");
}
},
Err(err) => {
match err {
EspTwaiError::BusOff => {
bail!("Bus offline")
}
EspTwaiError::NonCompliantDlc(_) => {}
EspTwaiError::EmbeddedHAL(_) => {}
}
error!("Error receiving CAN message: {err:?}");
}
}
}
}
pub async fn inner_pulse(
plant: usize,
sensor: Sensor,
signal_counter: &mut Unit<'_, 0>,
sensor_expander: &mut Pca9535Immediate<
I2cDevice<'static, CriticalSectionRawMutex, I2c<'static, Blocking>>,
>,
) -> FatResult<f32> {
let mut results = [0_f32; REPEAT_MOIST_MEASURE];
for sample in results.iter_mut() {
signal_counter.pause();
signal_counter.clear();
//Disable all
sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?;
let sensor_channel = match sensor {
Sensor::A => plant as u32,
Sensor::B => (15 - plant) as u32,
};
let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 };
if is_bit_set(0) {
sensor_expander.pin_set_high(GPIOBank::Bank0, MS0)?;
} else {
sensor_expander.pin_set_low(GPIOBank::Bank0, MS0)?;
}
if is_bit_set(1) {
sensor_expander.pin_set_high(GPIOBank::Bank0, MS1)?;
} else {
sensor_expander.pin_set_low(GPIOBank::Bank0, MS1)?;
}
if is_bit_set(2) {
sensor_expander.pin_set_high(GPIOBank::Bank0, MS2)?;
} else {
sensor_expander.pin_set_low(GPIOBank::Bank0, MS2)?;
}
if is_bit_set(3) {
sensor_expander.pin_set_high(GPIOBank::Bank0, MS3)?;
} else {
sensor_expander.pin_set_low(GPIOBank::Bank0, MS3)?;
}
sensor_expander.pin_set_low(GPIOBank::Bank0, MS4)?;
sensor_expander.pin_set_high(GPIOBank::Bank0, SENSOR_ON)?;
let measurement = 100; // TODO what is this scaling factor? what is its purpose?
let factor = 1000f32 / measurement as f32;
//give some time to stabilize
Timer::after_millis(10).await;
signal_counter.resume();
Timer::after_millis(measurement).await;
signal_counter.pause();
sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?;
sensor_expander.pin_set_low(GPIOBank::Bank0, SENSOR_ON)?;
sensor_expander.pin_set_low(GPIOBank::Bank0, MS0)?;
sensor_expander.pin_set_low(GPIOBank::Bank0, MS1)?;
sensor_expander.pin_set_low(GPIOBank::Bank0, MS2)?;
sensor_expander.pin_set_low(GPIOBank::Bank0, MS3)?;
Timer::after_millis(10).await;
let unscaled = 1337; //signal_counter.get_counter_value()? as i32;
let hz = unscaled as f32 * factor;
LOG_ACCESS
.lock()
.await
.log(
LogMessage::RawMeasure,
unscaled as u32,
hz as u32,
&plant.to_string(),
&format!("{sensor:?}"),
)
.await;
*sample = hz;
}
results.sort_by(|a, b| a.partial_cmp(b).unwrap()); // floats don't seem to implement total_ord
let mid = results.len() / 2;
let median = results[mid];
Ok(median)
}
}
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;
}
for (plant, sensor) in value.sensor_b_hz.iter().enumerate() {
result.plant[plant].sensor_b = *sensor > 1.0_f32;
}
result
}
}

View File

@@ -27,7 +27,7 @@ static mut LOG_ARRAY: LogArray = LogArray {
head: 0,
};
// this is the only reference that is created for LOG_ARRAY and the only way to access it
// this is the only reference created for LOG_ARRAY and the only way to access it
#[allow(static_mut_refs)]
pub static LOG_ACCESS: Mutex<CriticalSectionRawMutex, &'static mut LogArray> =
unsafe { Mutex::new(&mut LOG_ARRAY) };
@@ -298,7 +298,7 @@ impl From<&LogMessage> for MessageTranslation {
}
impl LogMessage {
pub fn to_log_localisation_config() -> Vec<MessageTranslation> {
pub fn log_localisation_config() -> Vec<MessageTranslation> {
Vec::from_iter((0..LogMessage::len()).map(|i| {
let msg_type = LogMessage::from_ordinal(i).unwrap();
(&msg_type).into()

View File

@@ -38,7 +38,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::Timer;
use embassy_time::{Duration, Timer, WithTimeout};
use esp_hal::rom::ets_delay_us;
use esp_hal::system::software_reset;
use esp_println::{logger, println};
@@ -1063,7 +1063,13 @@ async fn main(spawner: Spawner) -> ! {
// intialize embassy
logger::init_logger_from_env();
//force init here!
match BOARD_ACCESS.init(PlantHal::create().await.unwrap()) {
match BOARD_ACCESS.init(
PlantHal::create()
.with_timeout(Duration::from_secs(10))
.await
.unwrap()
.unwrap(),
) {
Ok(_) => {}
Err(_) => {
panic!("Could not set hal to static")

View File

@@ -175,6 +175,6 @@ pub(crate) async fn get_log_localization_config<T, const N: usize>(
_request: &mut Connection<'_, T, N>,
) -> FatResult<Option<String>> {
Ok(Some(serde_json::to_string(
&LogMessage::to_log_localisation_config(),
&LogMessage::log_localisation_config(),
)?))
}