6 Commits

7 changed files with 77 additions and 54 deletions

View File

@@ -1901,6 +1901,7 @@ dependencies = [
"chrono", "chrono",
"chrono-tz", "chrono-tz",
"crc", "crc",
"critical-section",
"deranged", "deranged",
"ds323x", "ds323x",
"edge-dhcp", "edge-dhcp",

View File

@@ -101,6 +101,7 @@ chrono-tz = { version = "0.10.4", default-features = false, features = ["filter-
heapless = { version = "0.7.17", features = ["serde"] } # stay in sync with mcutie version heapless = { version = "0.7.17", features = ["serde"] } # stay in sync with mcutie version
static_cell = "2.1.1" static_cell = "2.1.1"
portable-atomic = "1.11.1" portable-atomic = "1.11.1"
critical-section = "1"
crc = "3.3.0" crc = "3.3.0"
bytemuck = { version = "1.24.0", features = ["derive", "min_const_generics", "pod_saturating", "extern_crate_alloc"] } bytemuck = { version = "1.24.0", features = ["derive", "min_const_generics", "pod_saturating", "extern_crate_alloc"] }
deranged = "0.5.5" deranged = "0.5.5"

View File

@@ -0,0 +1,3 @@
One Wire does not seem to work.
Flow Sensor does not seem to work.
PlantProfiles with a dry out phase needs to be implemented + Memory for this

View File

@@ -161,9 +161,11 @@ pub(crate) async fn create_v4(
info!("Start v4"); info!("Start v4");
let mut awake = Output::new(peripherals.gpio21, Level::High, OutputConfig::default()); let mut awake = Output::new(peripherals.gpio21, Level::High, OutputConfig::default());
awake.set_high(); awake.set_high();
info!("v4: gpio21 awake ok");
let mut general_fault = Output::new(peripherals.gpio23, Level::Low, OutputConfig::default()); let mut general_fault = Output::new(peripherals.gpio23, Level::Low, OutputConfig::default());
general_fault.set_low(); general_fault.set_low();
info!("v4: gpio23 general_fault ok");
let twai_config = Some(TwaiConfiguration::new( let twai_config = Some(TwaiConfiguration::new(
peripherals.twai, peripherals.twai,
@@ -172,17 +174,24 @@ pub(crate) async fn create_v4(
TWAI_BAUDRATE, TWAI_BAUDRATE,
TwaiMode::Normal, TwaiMode::Normal,
)); ));
info!("v4: twai config ok");
let extra1 = Output::new(peripherals.gpio6, Level::Low, OutputConfig::default()); let extra1 = Output::new(peripherals.gpio6, Level::Low, OutputConfig::default());
info!("v4: gpio6 extra1 ok");
let extra2 = Output::new(peripherals.gpio15, Level::Low, OutputConfig::default()); let extra2 = Output::new(peripherals.gpio15, Level::Low, OutputConfig::default());
info!("v4: gpio15 extra2 ok");
let one_wire_pin = Flex::new(peripherals.gpio18); let one_wire_pin = Flex::new(peripherals.gpio18);
info!("v4: gpio18 one_wire ok");
let tank_power_pin = Output::new(peripherals.gpio11, Level::Low, OutputConfig::default()); let tank_power_pin = Output::new(peripherals.gpio11, Level::Low, OutputConfig::default());
info!("v4: gpio11 tank_power ok");
let flow_sensor_pin = Input::new( let flow_sensor_pin = Input::new(
peripherals.gpio4, peripherals.gpio4,
InputConfig::default().with_pull(Pull::Up), InputConfig::default().with_pull(Pull::Up),
); );
info!("v4: gpio4 flow_sensor ok");
info!("v4: creating tank sensor");
let tank_sensor = TankSensor::create( let tank_sensor = TankSensor::create(
one_wire_pin, one_wire_pin,
peripherals.adc1, peripherals.adc1,
@@ -191,12 +200,17 @@ pub(crate) async fn create_v4(
flow_sensor_pin, flow_sensor_pin,
peripherals.pcnt1, peripherals.pcnt1,
)?; )?;
info!("v4: tank sensor ok");
let can_power = Output::new(peripherals.gpio22, Level::Low, OutputConfig::default()); let can_power = Output::new(peripherals.gpio22, Level::Low, OutputConfig::default());
info!("v4: gpio22 can_power ok");
let solar_is_day = Input::new(peripherals.gpio7, InputConfig::default()); let solar_is_day = Input::new(peripherals.gpio7, InputConfig::default());
info!("v4: gpio7 solar_is_day ok");
let light = Output::new(peripherals.gpio10, Level::Low, Default::default()); let light = Output::new(peripherals.gpio10, Level::Low, Default::default());
info!("v4: gpio10 light ok");
let charge_indicator = Output::new(peripherals.gpio3, Level::Low, Default::default()); let charge_indicator = Output::new(peripherals.gpio3, Level::Low, Default::default());
info!("v4: gpio3 charge_indicator ok");
info!("Start pump expander"); info!("Start pump expander");
let pump_device = I2cDevice::new(I2C_DRIVER.get().await); let pump_device = I2cDevice::new(I2C_DRIVER.get().await);

View File

@@ -1,8 +1,6 @@
use crate::bail; use crate::bail;
use crate::fat_error::FatError; use crate::fat_error::FatError;
use crate::hal::{ADC1, TANK_MULTI_SAMPLE}; use crate::hal::{ADC1, TANK_MULTI_SAMPLE};
use core::cell::RefCell;
use embassy_sync::blocking_mutex::CriticalSectionMutex;
use embassy_time::Timer; use embassy_time::Timer;
use esp_hal::analog::adc::{Adc, AdcCalLine, AdcConfig, AdcPin, Attenuation}; use esp_hal::analog::adc::{Adc, AdcCalLine, AdcConfig, AdcPin, Attenuation};
use esp_hal::delay::Delay; use esp_hal::delay::Delay;
@@ -19,14 +17,13 @@ use portable_atomic::{AtomicUsize, Ordering};
unsafe impl Send for TankSensor<'_> {} unsafe impl Send for TankSensor<'_> {}
static FLOW_OVERFLOW_COUNTER: AtomicUsize = AtomicUsize::new(0); static FLOW_OVERFLOW_COUNTER: AtomicUsize = AtomicUsize::new(0);
static FLOW_UNIT: CriticalSectionMutex<RefCell<Option<Unit<'static, 1>>>> =
CriticalSectionMutex::new(RefCell::new(None));
pub struct TankSensor<'a> { pub struct TankSensor<'a> {
one_wire_bus: OneWire<Flex<'a>>, one_wire_bus: OneWire<Flex<'a>>,
tank_channel: Adc<'a, ADC1<'a>, Async>, tank_channel: Adc<'a, ADC1<'a>, Async>,
tank_power: Output<'a>, tank_power: Output<'a>,
tank_pin: AdcPin<GPIO5<'a>, ADC1<'a>, AdcCalLine<ADC1<'a>>>, tank_pin: AdcPin<GPIO5<'a>, ADC1<'a>, AdcCalLine<ADC1<'a>>>,
flow_unit: Unit<'static, 1>,
} }
impl<'a> TankSensor<'a> { impl<'a> TankSensor<'a> {
@@ -47,78 +44,76 @@ impl<'a> TankSensor<'a> {
one_wire_pin.set_high(); one_wire_pin.set_high();
one_wire_pin.set_input_enable(true); one_wire_pin.set_input_enable(true);
one_wire_pin.set_output_enable(true); one_wire_pin.set_output_enable(true);
info!("tank: one_wire pin config ok");
let mut adc1_config = AdcConfig::new(); let mut adc1_config = AdcConfig::new();
info!("tank: adc config created");
let tank_pin = let tank_pin =
adc1_config.enable_pin_with_cal::<_, AdcCalLine<_>>(gpio5, Attenuation::_11dB); adc1_config.enable_pin_with_cal::<_, AdcCalLine<_>>(gpio5, Attenuation::_11dB);
info!("tank: adc pin cal ok");
let tank_channel = Adc::new(adc1, adc1_config).into_async(); let tank_channel = Adc::new(adc1, adc1_config).into_async();
info!("tank: adc channel ok");
let one_wire_bus = OneWire::new(one_wire_pin, false); let one_wire_bus = OneWire::new(one_wire_pin, false);
info!("tank: one_wire bus ok");
pcnt1.set_high_limit(Some(i16::MAX))?; pcnt1.set_high_limit(Some(i16::MAX))?;
info!("tank: pcnt high limit ok");
// Reject pulses shorter than ~12.8 µs (1023 APB cycles @ 80 MHz) to suppress EMI noise
// on the sensor cable. Real flow pulses are in the millisecond range.
pcnt1.set_filter(Some(1023)).unwrap();
let ch0 = &pcnt1.channel0; let ch0 = &pcnt1.channel0;
ch0.set_edge_signal(flow_sensor.peripheral_input()); ch0.set_edge_signal(flow_sensor.peripheral_input());
info!("tank: pcnt edge signal ok");
ch0.set_input_mode(Hold, Increment); ch0.set_input_mode(Hold, Increment);
ch0.set_ctrl_mode(Keep, Keep); ch0.set_ctrl_mode(Keep, Keep);
info!("tank: pcnt input/ctrl mode ok");
pcnt1.listen(); pcnt1.listen();
info!("tank: pcnt listen ok");
FLOW_UNIT.lock(|refcell| {
refcell.borrow_mut().replace(pcnt1);
});
Ok(TankSensor { Ok(TankSensor {
one_wire_bus, one_wire_bus,
tank_channel, tank_channel,
tank_power, tank_power,
tank_pin, tank_pin,
flow_unit: pcnt1,
}) })
} }
pub fn reset_flow_meter(&mut self) { pub fn reset_flow_meter(&mut self) {
FLOW_OVERFLOW_COUNTER.store(0, Ordering::SeqCst); // Pause, clear counter, clear any pending interrupt, then reset the overflow counter —
FLOW_UNIT.lock(|refcell| { // all inside a single critical section to prevent a race where the interrupt fires
if let Some(unit) = refcell.borrow_mut().as_mut() { // between the overflow reset and the pause.
unit.pause(); critical_section::with(|_| {
unit.clear(); self.flow_unit.pause();
} self.flow_unit.clear();
self.flow_unit.reset_interrupt();
FLOW_OVERFLOW_COUNTER.store(0, Ordering::SeqCst);
}); });
} }
pub fn start_flow_meter(&mut self) { pub fn start_flow_meter(&mut self) {
FLOW_UNIT.lock(|refcell| { self.flow_unit.resume();
if let Some(unit) = refcell.borrow_mut().as_mut() {
unit.resume();
}
});
}
pub fn get_flow_meter_value(&mut self) -> i16 {
FLOW_UNIT.lock(|refcell| {
refcell.borrow_mut().as_mut().map_or(0, |unit| unit.value())
})
} }
pub fn stop_flow_meter(&mut self) -> i16 { pub fn stop_flow_meter(&mut self) -> i16 {
FLOW_UNIT.lock(|refcell| { critical_section::with(|_| {
let mut borrowed = refcell.borrow_mut(); let val = self.flow_unit.value();
if let Some(unit) = borrowed.as_mut() { self.flow_unit.pause();
let val = unit.value(); val
unit.pause();
val
} else {
0
}
}) })
} }
pub fn get_full_flow_count(&self) -> u32 { pub fn get_full_flow_count(&self) -> u32 {
let current = FLOW_UNIT.lock(|refcell| { // Read both values inside a single critical section so an overflow interrupt cannot
refcell.borrow().as_ref().map_or(0, |unit| unit.value() as u32) // fire between the two reads and produce an inconsistent result.
}); critical_section::with(|_| {
let overflowed = FLOW_OVERFLOW_COUNTER.load(Ordering::SeqCst) as u32; let overflowed = FLOW_OVERFLOW_COUNTER.load(Ordering::SeqCst) as u32;
overflowed * (i16::MAX.wrapping_add(1) as u32) + current let current = self.flow_unit.value() as u32;
overflowed * (i16::MAX as u32 + 1) + current
})
} }
pub async fn water_temperature_c(&mut self) -> Result<f32, FatError> { pub async fn water_temperature_c(&mut self) -> Result<f32, FatError> {
@@ -218,15 +213,12 @@ impl<'a> TankSensor<'a> {
#[esp_hal::handler] #[esp_hal::handler]
pub fn flow_interrupt_handler() { pub fn flow_interrupt_handler() {
FLOW_UNIT.lock(|refcell| { use esp_hal::peripherals::PCNT;
if let Some(unit) = refcell.borrow_mut().as_mut() { let pcnt = PCNT::regs();
if unit.interrupt_is_set() { if pcnt.int_raw().read().cnt_thr_event_u(1).bit() {
let events = unit.events(); if pcnt.u_status(1).read().h_lim().bit() {
if events.high_limit { FLOW_OVERFLOW_COUNTER.fetch_add(1, Ordering::SeqCst);
FLOW_OVERFLOW_COUNTER.fetch_add(1, Ordering::SeqCst);
}
unit.reset_interrupt();
}
} }
}); pcnt.int_clr().write(|w| w.cnt_thr_event_u(1).set_bit());
}
} }

View File

@@ -4,7 +4,7 @@ use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::blocking_mutex::Mutex as BlockingMutex; use embassy_sync::blocking_mutex::Mutex as BlockingMutex;
use log::{LevelFilter, Log, Metadata, Record}; use log::{LevelFilter, Log, Metadata, Record};
const MAX_LIVE_LOG_ENTRIES: usize = 64; const MAX_LIVE_LOG_ENTRIES: usize = 128;
struct LiveLogBuffer { struct LiveLogBuffer {
entries: Vec<(u64, String)>, entries: Vec<(u64, String)>,

View File

@@ -123,6 +123,8 @@ struct PumpInfo {
max_current_ma: u16, max_current_ma: u16,
min_current_ma: u16, min_current_ma: u16,
error: String, error: String,
flow_raw: u32,
flow_ml: f32,
} }
#[derive(Serialize)] #[derive(Serialize)]
@@ -132,7 +134,7 @@ pub struct PumpResult {
min_current_ma: u16, min_current_ma: u16,
error: String, error: String,
flow_value_ml: f32, flow_value_ml: f32,
flow_value_count: i16, flow_value_count: u32,
pump_time_s: u16, pump_time_s: u16,
overcurrent_ma: Option<u16>, overcurrent_ma: Option<u16>,
} }
@@ -456,6 +458,8 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
0, 0,
0, 0,
String::new(), String::new(),
0,
0.0,
) )
.await; .await;
@@ -472,6 +476,8 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
state.max_current_ma, state.max_current_ma,
state.min_current_ma, state.min_current_ma,
state.error, state.error,
state.flow_value_count,
state.flow_value_ml,
) )
.await; .await;
} }
@@ -485,6 +491,8 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
0, 0,
0, 0,
format!("{err:?}"), format!("{err:?}"),
0,
0.0,
) )
.await; .await;
} }
@@ -722,7 +730,7 @@ pub async fn do_secure_pump(
let steps_in_50ms = plant_config.pump_time_s as usize * 20; let steps_in_50ms = plant_config.pump_time_s as usize * 20;
let mut current_collector = vec![0_u16; steps_in_50ms]; let mut current_collector = vec![0_u16; steps_in_50ms];
let mut flow_collector = vec![0_i16; steps_in_50ms]; let mut flow_collector = vec![0_u32; steps_in_50ms];
let mut error = String::new(); let mut error = String::new();
let mut first_error = true; let mut first_error = true;
let mut pump_time_ms: u32 = 0; let mut pump_time_ms: u32 = 0;
@@ -773,7 +781,7 @@ pub async fn do_secure_pump(
for step in 0..steps_in_50ms { for step in 0..steps_in_50ms {
let step_start = Instant::now(); let step_start = Instant::now();
let flow_value = board.board_hal.get_tank_sensor()?.get_flow_meter_value(); let flow_value = board.board_hal.get_tank_sensor()?.get_full_flow_count();
flow_collector[step] = flow_value; flow_collector[step] = flow_value;
let flow_value_ml = flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse; let flow_value_ml = flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse;
@@ -885,7 +893,7 @@ pub async fn do_secure_pump(
pump_time_ms = 1337; pump_time_ms = 1337;
} }
board.board_hal.get_tank_sensor()?.stop_flow_meter(); board.board_hal.get_tank_sensor()?.stop_flow_meter();
let final_flow_value = board.board_hal.get_tank_sensor()?.get_flow_meter_value(); let final_flow_value = board.board_hal.get_tank_sensor()?.get_full_flow_count();
let flow_value_ml = final_flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse; let flow_value_ml = final_flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse;
info!("Final flow value is {final_flow_value} with {flow_value_ml} ml"); info!("Final flow value is {final_flow_value} with {flow_value_ml} ml");
current_collector.sort(); current_collector.sort();
@@ -1052,6 +1060,8 @@ async fn pump_info(
max_current_ma: u16, max_current_ma: u16,
min_current_ma: u16, min_current_ma: u16,
error: String, error: String,
flow_raw: u32,
flow_ml: f32,
) { ) {
let pump_info = PumpInfo { let pump_info = PumpInfo {
enabled: pump_active, enabled: pump_active,
@@ -1060,6 +1070,8 @@ async fn pump_info(
max_current_ma, max_current_ma,
min_current_ma, min_current_ma,
error, error,
flow_raw,
flow_ml,
}; };
let pump_topic = format!("/pump{}", plant_id + 1); let pump_topic = format!("/pump{}", plant_id + 1);