From d010c5d12afb8b1e661b0d46f5e4910045c4aa5e Mon Sep 17 00:00:00 2001 From: Empire Phoenix Date: Tue, 23 Sep 2025 20:51:59 +0200 Subject: [PATCH] get more functions online --- rust/src/FatError.rs | 25 ++ rust/src/hal/battery.rs | 283 +++++++++------- rust/src/hal/mod.rs | 85 +++-- rust/src/hal/v4_hal.rs | 438 +++++++++++++------------ rust/{scratch => src/hal}/v4_sensor.rs | 74 +++-- rust/src/main.rs | 117 ++++--- 6 files changed, 569 insertions(+), 453 deletions(-) rename rust/{scratch => src/hal}/v4_sensor.rs (64%) diff --git a/rust/src/FatError.rs b/rust/src/FatError.rs index 7a0a03e..9cf11d8 100644 --- a/rust/src/FatError.rs +++ b/rust/src/FatError.rs @@ -6,8 +6,10 @@ use core::str::Utf8Error; use embassy_embedded_hal::shared_bus::I2cDeviceError; use embassy_executor::SpawnError; use embassy_sync::mutex::TryLockError; +use esp_bootloader_esp_idf::partitions::DataPartitionSubType::Fat; use esp_hal::i2c::master::ConfigError; use esp_wifi::wifi::WifiError; +use ina219::errors::{BusVoltageReadError, ShuntVoltageReadError}; use littlefs2_core::PathError; use onewire::Error; use pca9535::ExpanderError; @@ -233,3 +235,26 @@ impl From for FatError { FatError::I2CConfigError { error: value } } } + +impl From> for FatError { + fn from(value: I2cDeviceError) -> Self { + FatError::String { + error: format!("{:?}", value), + } + } +} + +impl From>> for FatError { + fn from(value: BusVoltageReadError>) -> Self { + FatError::String { + error: format!("{:?}", value), + } + } +} +impl From>> for FatError { + fn from(value: ShuntVoltageReadError>) -> Self { + FatError::String { + error: format!("{:?}", value), + } + } +} diff --git a/rust/src/hal/battery.rs b/rust/src/hal/battery.rs index aed5fec..dbaf333 100644 --- a/rust/src/hal/battery.rs +++ b/rust/src/hal/battery.rs @@ -2,6 +2,13 @@ use crate::hal::Box; use crate::FatError::{FatError, FatResult}; use alloc::string::String; use async_trait::async_trait; +use bq34z100::{Bq34z100g1, Bq34z100g1Driver, Flags}; +use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use esp_hal::delay::Delay; +use esp_hal::i2c::master::I2c; +use esp_hal::Blocking; +use measurements::Temperature; use serde::Serialize; #[async_trait] @@ -99,115 +106,167 @@ impl BatteryInteraction for NoBatteryMonitor { #[allow(dead_code)] pub struct WchI2cSlave {} -// pub struct BQ34Z100G1<'a> { -// pub battery_driver: Bq34z100g1Driver>, Delay>, -// } -// -// impl BatteryInteraction for BQ34Z100G1<'_> { -// fn state_charge_percent(&mut self) -> Result { -// Ok(self.battery_driver.state_of_charge().map(f32::from)?) -// } -// -// fn remaining_milli_ampere_hour(&mut self) -> Result { -// Ok(self.battery_driver.remaining_capacity()?) -// } -// -// fn max_milli_ampere_hour(&mut self) -> Result { -// Ok(self.battery_driver.full_charge_capacity()?) -// } -// -// fn design_milli_ampere_hour(&mut self) -> Result { -// Ok(self.battery_driver.design_capacity()?) -// } -// -// fn voltage_milli_volt(&mut self) -> Result { -// Ok(self.battery_driver.voltage()?) -// } -// -// fn average_current_milli_ampere(&mut self) -> Result { -// Ok(self.battery_driver.average_current()?) -// } -// -// fn cycle_count(&mut self) -> Result { -// Ok(self.battery_driver.cycle_count()?) -// } -// -// fn state_health_percent(&mut self) -> Result { -// Ok(self.battery_driver.state_of_health()?) -// } -// -// fn bat_temperature(&mut self) -> Result { -// Ok(self.battery_driver.temperature()?) -// } -// -// fn get_battery_state(&mut self) -> Result { -// Ok(BatteryState::Info(BatteryInfo { -// voltage_milli_volt: self.voltage_milli_volt()?, -// average_current_milli_ampere: self.average_current_milli_ampere()?, -// cycle_count: self.cycle_count()?, -// design_milli_ampere_hour: self.design_milli_ampere_hour()?, -// remaining_milli_ampere_hour: self.remaining_milli_ampere_hour()?, -// state_of_charge: self.state_charge_percent()?, -// state_of_health: self.state_health_percent()?, -// temperature: self.bat_temperature()?, -// })) -// } -// } -// -// pub fn print_battery_bq34z100( -// battery_driver: &mut Bq34z100g1Driver>, Delay>, -// ) -> anyhow::Result<(), Bq34Z100Error> { -// log::info!("Try communicating with battery"); -// let fwversion = battery_driver.fw_version().unwrap_or_else(|e| { -// log::info!("Firmware {:?}", e); -// 0 -// }); -// log::info!("fw version is {}", fwversion); -// -// let design_capacity = battery_driver.design_capacity().unwrap_or_else(|e| { -// log::info!("Design capacity {:?}", e); -// 0 -// }); -// log::info!("Design Capacity {}", design_capacity); -// if design_capacity == 1000 { -// log::info!("Still stock configuring battery, readouts are likely to be wrong!"); -// } -// -// let flags = battery_driver.get_flags_decoded()?; -// log::info!("Flags {:?}", flags); -// -// let chem_id = battery_driver.chem_id().unwrap_or_else(|e| { -// log::info!("Chemid {:?}", e); -// 0 -// }); -// -// let bat_temp = battery_driver.internal_temperature().unwrap_or_else(|e| { -// log::info!("Bat Temp {:?}", e); -// 0 -// }); -// let temp_c = Temperature::from_kelvin(bat_temp as f64 / 10_f64).as_celsius(); -// let voltage = battery_driver.voltage().unwrap_or_else(|e| { -// log::info!("Bat volt {:?}", e); -// 0 -// }); -// let current = battery_driver.current().unwrap_or_else(|e| { -// log::info!("Bat current {:?}", e); -// 0 -// }); -// let state = battery_driver.state_of_charge().unwrap_or_else(|e| { -// log::info!("Bat Soc {:?}", e); -// 0 -// }); -// let charge_voltage = battery_driver.charge_voltage().unwrap_or_else(|e| { -// log::info!("Bat Charge Volt {:?}", e); -// 0 -// }); -// let charge_current = battery_driver.charge_current().unwrap_or_else(|e| { -// log::info!("Bat Charge Current {:?}", e); -// 0 -// }); -// log::info!("ChemId: {} Current voltage {} and current {} with charge {}% and temp {} CVolt: {} CCur {}", chem_id, voltage, current, state, temp_c, charge_voltage, charge_current); -// let _ = battery_driver.unsealed(); -// let _ = battery_driver.it_enable(); -// anyhow::Result::Ok(()) -// } +pub type I2cDev = I2cDevice<'static, CriticalSectionRawMutex, I2c<'static, Blocking>>; + +pub struct BQ34Z100G1 { + pub battery_driver: Bq34z100g1Driver, +} + +#[async_trait] +impl BatteryInteraction for BQ34Z100G1 { + async fn state_charge_percent(&mut self) -> FatResult { + self.battery_driver + .state_of_charge() + .map(|v| v as f32) + .map_err(|e| FatError::String { + error: alloc::format!("{:?}", e), + }) + } + + async fn remaining_milli_ampere_hour(&mut self) -> FatResult { + self.battery_driver + .remaining_capacity() + .map_err(|e| FatError::String { + error: alloc::format!("{:?}", e), + }) + } + + async fn max_milli_ampere_hour(&mut self) -> FatResult { + self.battery_driver + .full_charge_capacity() + .map_err(|e| FatError::String { + error: alloc::format!("{:?}", e), + }) + } + + async fn design_milli_ampere_hour(&mut self) -> FatResult { + self.battery_driver + .design_capacity() + .map_err(|e| FatError::String { + error: alloc::format!("{:?}", e), + }) + } + + async fn voltage_milli_volt(&mut self) -> FatResult { + self.battery_driver.voltage().map_err(|e| FatError::String { + error: alloc::format!("{:?}", e), + }) + } + + async fn average_current_milli_ampere(&mut self) -> FatResult { + self.battery_driver + .average_current() + .map_err(|e| FatError::String { + error: alloc::format!("{:?}", e), + }) + } + + async fn cycle_count(&mut self) -> FatResult { + self.battery_driver + .cycle_count() + .map_err(|e| FatError::String { + error: alloc::format!("{:?}", e), + }) + } + + async fn state_health_percent(&mut self) -> FatResult { + self.battery_driver + .state_of_health() + .map_err(|e| FatError::String { + error: alloc::format!("{:?}", e), + }) + } + + async fn bat_temperature(&mut self) -> FatResult { + self.battery_driver + .temperature() + .map_err(|e| FatError::String { + error: alloc::format!("{:?}", e), + }) + } + + async fn get_battery_state(&mut self) -> FatResult { + Ok(BatteryState::Info(BatteryInfo { + voltage_milli_volt: self.voltage_milli_volt().await?, + average_current_milli_ampere: self.average_current_milli_ampere().await?, + cycle_count: self.cycle_count().await?, + design_milli_ampere_hour: self.design_milli_ampere_hour().await?, + remaining_milli_ampere_hour: self.remaining_milli_ampere_hour().await?, + state_of_charge: self.state_charge_percent().await?, + state_of_health: self.state_health_percent().await?, + temperature: self.bat_temperature().await?, + })) + } +} + +pub fn print_battery_bq34z100( + battery_driver: &mut Bq34z100g1Driver>, Delay>, +) -> FatResult<()> { + log::info!("Try communicating with battery"); + let fwversion = battery_driver.fw_version().unwrap_or_else(|e| { + log::info!("Firmware {:?}", e); + 0 + }); + log::info!("fw version is {}", fwversion); + + let design_capacity = battery_driver.design_capacity().unwrap_or_else(|e| { + log::info!("Design capacity {:?}", e); + 0 + }); + log::info!("Design Capacity {}", design_capacity); + if design_capacity == 1000 { + log::info!("Still stock configuring battery, readouts are likely to be wrong!"); + } + + let flags = battery_driver.get_flags_decoded().unwrap_or(Flags { + fast_charge_allowed: false, + full_chage: false, + charging_not_allowed: false, + charge_inhibit: false, + bat_low: false, + bat_high: false, + over_temp_discharge: false, + over_temp_charge: false, + discharge: false, + state_of_charge_f: false, + state_of_charge_1: false, + cf: false, + ocv_taken: false, + }); + log::info!("Flags {:?}", flags); + + let chem_id = battery_driver.chem_id().unwrap_or_else(|e| { + log::info!("Chemid {:?}", e); + 0 + }); + + let bat_temp = battery_driver.internal_temperature().unwrap_or_else(|e| { + log::info!("Bat Temp {:?}", e); + 0 + }); + let temp_c = Temperature::from_kelvin(bat_temp as f64 / 10_f64).as_celsius(); + let voltage = battery_driver.voltage().unwrap_or_else(|e| { + log::info!("Bat volt {:?}", e); + 0 + }); + let current = battery_driver.current().unwrap_or_else(|e| { + log::info!("Bat current {:?}", e); + 0 + }); + let state = battery_driver.state_of_charge().unwrap_or_else(|e| { + log::info!("Bat Soc {:?}", e); + 0 + }); + let charge_voltage = battery_driver.charge_voltage().unwrap_or_else(|e| { + log::info!("Bat Charge Volt {:?}", e); + 0 + }); + let charge_current = battery_driver.charge_current().unwrap_or_else(|e| { + log::info!("Bat Charge Current {:?}", e); + 0 + }); + log::info!("ChemId: {} Current voltage {} and current {} with charge {}% and temp {} CVolt: {} CCur {}", chem_id, voltage, current, state, temp_c, charge_voltage, charge_current); + let _ = battery_driver.unsealed(); + let _ = battery_driver.it_enable(); + Ok(()) +} diff --git a/rust/src/hal/mod.rs b/rust/src/hal/mod.rs index c1adc5c..cd4807b 100644 --- a/rust/src/hal/mod.rs +++ b/rust/src/hal/mod.rs @@ -4,8 +4,8 @@ mod initial_hal; mod little_fs2storage_adapter; pub(crate) mod rtc; mod v4_hal; +mod v4_sensor; mod water; -//mod water; use crate::alloc::string::ToString; use crate::hal::rtc::{DS3231Module, RTCModuleInteraction}; @@ -38,8 +38,8 @@ use esp_hal::peripherals::GPIO5; use esp_hal::peripherals::GPIO6; use esp_hal::peripherals::GPIO7; use esp_hal::peripherals::GPIO8; +use esp_hal::peripherals::TWAI0; -//use crate::hal::water::TankSensor; use crate::{ bail, config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig}, @@ -53,6 +53,7 @@ use alloc::boxed::Box; use alloc::format; use alloc::sync::Arc; use async_trait::async_trait; +use bq34z100::Bq34z100g1Driver; use chrono::{DateTime, FixedOffset, Utc}; use core::cell::RefCell; use ds323x::ic::DS3231; @@ -75,6 +76,7 @@ use esp_hal::clock::CpuClock; use esp_hal::gpio::{Input, InputConfig, Pull}; use measurements::{Current, Voltage}; +use crate::hal::battery::{print_battery_bq34z100, BQ34Z100G1}; use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem; use crate::hal::water::TankSensor; use crate::log::LOG_ACCESS; @@ -84,6 +86,7 @@ use embassy_sync::once_lock::OnceLock; use esp_alloc as _; use esp_backtrace as _; use esp_bootloader_esp_idf::ota::Slot; +use esp_bootloader_esp_idf::partitions::DataPartitionSubType::Fat; use esp_hal::delay::Delay; use esp_hal::i2c::master::{BusTimeout, Config, I2c}; use esp_hal::rng::Rng; @@ -91,6 +94,7 @@ use esp_hal::rtc_cntl::{Rtc, SocResetReason}; use esp_hal::system::reset_reason; use esp_hal::time::Rate; use esp_hal::timer::timg::TimerGroup; +use esp_hal::twai::Twai; use esp_hal::Blocking; use esp_storage::FlashStorage; use esp_wifi::{init, EspWifiController}; @@ -196,6 +200,7 @@ pub struct FreePeripherals<'a> { pub gpio28: GPIO28<'a>, pub gpio29: GPIO29<'a>, pub gpio30: GPIO30<'a>, + pub twai: TWAI0<'a>, // pub pcnt0: PCNT0, // pub pcnt1: PCNT1, // pub adc1: ADC1, @@ -214,22 +219,6 @@ macro_rules! mk_static { const GW_IP_ADDR_ENV: Option<&'static str> = option_env!("GATEWAY_IP"); impl PlantHal { - //fn create_i2c() -> Mutex { - // let peripherals = unsafe { Peripherals::new() }; - // - // let config = I2cConfig::new() - // .scl_enable_pullup(true) - // .sda_enable_pullup(true) - // .baudrate(100_u32.kHz().into()) - // .timeout(APBTickType::from(Duration::from_millis(100))); - // - // let i2c = peripherals.i2c0; - // let scl = peripherals.pins.gpio19.downgrade(); - // let sda = peripherals.pins.gpio20.downgrade(); - // - // Mutex::new(I2cDriver::new(i2c, sda, scl, &config).unwrap()) - //} - pub async fn create( spawner: Spawner, ) -> Result>, FatError> { @@ -240,10 +229,9 @@ impl PlantHal { esp_alloc::heap_allocator!(#[link_section = ".dram2_uninit"] size: 64000); let rtc: Rtc = Rtc::new(peripherals.LPWR); - match (TIME_ACCESS.init(rtc)) { - Result::Ok(_) => {} - Err(_) => {} - } + TIME_ACCESS.init(rtc).map_err(|_| FatError::String { + error: "Init error rct".to_string(), + })?; let systimer = SystemTimer::new(peripherals.SYSTIMER); @@ -300,6 +288,7 @@ impl PlantHal { gpio28: peripherals.GPIO28, gpio29: peripherals.GPIO29, gpio30: peripherals.GPIO30, + twai: peripherals.TWAI0, }; let tablebuffer = mk_static!( @@ -500,34 +489,36 @@ impl PlantHal { let battery_interaction: Box = match config.hardware.battery { BatteryBoardVersion::Disabled => Box::new(NoBatteryMonitor {}), - // BatteryBoardVersion::BQ34Z100G1 => { - // let mut battery_driver = Bq34z100g1Driver { - // i2c: MutexDevice::new(&I2C_DRIVER), - // delay: Delay::new(0), - // flash_block_data: [0; 32], - // }; - // let status = print_battery_bq34z100(&mut battery_driver); - // match status { - // Ok(_) => {} - // Err(err) => { - // log( - // LogMessage::BatteryCommunicationError, - // 0u32, - // 0, - // "", - // &format!("{err:?})"), - // ); - // } - // } - // Box::new(BQ34Z100G1 { battery_driver }) - // } + BatteryBoardVersion::BQ34Z100G1 => { + let battery_device = I2cDevice::new(I2C_DRIVER.get().await); + let mut battery_driver = Bq34z100g1Driver { + i2c: battery_device, + delay: Delay::new(), + flash_block_data: [0; 32], + }; + let status = print_battery_bq34z100(&mut battery_driver); + match status { + Ok(_) => {} + Err(err) => { + LOG_ACCESS + .lock() + .await + .log( + LogMessage::BatteryCommunicationError, + 0u32, + 0, + "", + &format!("{err:?})"), + ) + .await; + } + } + Box::new(BQ34Z100G1 { battery_driver }) + } BatteryBoardVersion::WchI2cSlave => { // TODO use correct implementation once availible Box::new(NoBatteryMonitor {}) } - _ => { - todo!() - } }; let board_hal: Box = match config.hardware.board { @@ -542,7 +533,7 @@ impl PlantHal { .await? } _ => { - todo!() + bail!("Unknown board version"); } }; diff --git a/rust/src/hal/v4_hal.rs b/rust/src/hal/v4_hal.rs index 14c21a8..f749830 100644 --- a/rust/src/hal/v4_hal.rs +++ b/rust/src/hal/v4_hal.rs @@ -8,99 +8,114 @@ use alloc::boxed::Box; use async_trait::async_trait; use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use esp_hal::Blocking; +use esp_hal::{twai, Blocking}; //use embedded_hal_bus::i2c::MutexDevice; use crate::bail; +use crate::hal::v4_sensor::SensorImpl; use crate::FatError::{FatError, FatResult}; -use esp_hal::gpio::{Flex, Level, Output, OutputConfig}; +use esp_hal::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig}; use esp_hal::i2c::master::I2c; +use esp_hal::twai::{EspTwaiFrame, StandardId, TwaiMode}; +use esp_println::println; use ina219::address::{Address, Pin}; +use ina219::calibration::UnCalibrated; +use ina219::configuration::{Configuration, OperatingMode, Resolution}; use ina219::SyncIna219; +use measurements::Resistance; use measurements::{Current, Voltage}; use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; -// pub enum Charger<'a> { -// SolarMpptV1 { -// mppt_ina: SyncIna219>, UnCalibrated>, -// solar_is_day: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, -// charge_indicator: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, -// }, -// ErrorInit {}, -// } -// -// impl Charger<'_> { -// pub(crate) fn power_save(&mut self) { -// match self { -// Charger::SolarMpptV1 { mppt_ina, .. } => { -// let _ = mppt_ina -// .set_configuration(Configuration { -// reset: Default::default(), -// bus_voltage_range: Default::default(), -// shunt_voltage_range: Default::default(), -// bus_resolution: Default::default(), -// shunt_resolution: Default::default(), -// operating_mode: OperatingMode::PowerDown, -// }) -// .map_err(|e| { -// log::info!( -// "Error setting ina mppt configuration during deep sleep preparation{:?}", -// e -// ); -// }); -// } -// _ => {} -// } -// } -// fn set_charge_indicator(&mut self, charging: bool) -> anyhow::Result<()> { -// match self { -// Self::SolarMpptV1 { -// charge_indicator, .. -// } => { -// charge_indicator.set_state(charging.into())?; -// } -// _ => {} -// } -// Ok(()) -// } -// -// fn is_day(&self) -> bool { -// match self { -// Charger::SolarMpptV1 { solar_is_day, .. } => solar_is_day.get_level().into(), -// _ => true, -// } -// } -// -// fn get_mptt_voltage(&mut self) -> anyhow::Result { -// let voltage = match self { -// Charger::SolarMpptV1 { mppt_ina, .. } => mppt_ina -// .bus_voltage() -// .map(|v| Voltage::from_millivolts(v.voltage_mv() as f64))?, -// _ => { -// bail!("hardware error during init") -// } -// }; -// Ok(voltage) -// } -// -// fn get_mptt_current(&mut self) -> anyhow::Result { -// let current = match self { -// Charger::SolarMpptV1 { mppt_ina, .. } => mppt_ina.shunt_voltage().map(|v| { -// let shunt_voltage = Voltage::from_microvolts(v.shunt_voltage_uv().abs() as f64); -// let shut_value = Resistance::from_ohms(0.05_f64); -// let current = shunt_voltage.as_volts() / shut_value.as_ohms(); -// Current::from_amperes(current) -// })?, -// _ => { -// bail!("hardware error during init") -// } -// }; -// Ok(current) -// } -// } + +const MPPT_CURRENT_SHUNT_OHMS: f64 = 0.05_f64; +const TWAI_BAUDRATE: twai::BaudRate = twai::BaudRate::B125K; + +pub enum Charger<'a> { + SolarMpptV1 { + mppt_ina: SyncIna219< + I2cDevice<'a, CriticalSectionRawMutex, I2c<'static, Blocking>>, + UnCalibrated, + >, + solar_is_day: Input<'a>, + charge_indicator: Output<'a>, + }, + ErrorInit {}, +} + +impl<'a> Charger<'a> { + pub(crate) fn get_mppt_current(&mut self) -> FatResult { + match self { + Charger::SolarMpptV1 { mppt_ina, .. } => { + let v = mppt_ina.shunt_voltage()?; + let shunt_voltage = Voltage::from_microvolts(v.shunt_voltage_uv().abs() as f64); + let shut_value = Resistance::from_ohms(MPPT_CURRENT_SHUNT_OHMS); + let current = shunt_voltage.as_volts() / shut_value.as_ohms(); + Ok(Current::from_amperes(current)) + } + Charger::ErrorInit { .. } => { + bail!("hardware error during init"); + } + } + } + + pub(crate) fn get_mptt_voltage(&mut self) -> FatResult { + match self { + Charger::SolarMpptV1 { mppt_ina, .. } => { + let v = mppt_ina.bus_voltage()?; + Ok(Voltage::from_millivolts(v.voltage_mv() as f64)) + } + Charger::ErrorInit { .. } => { + bail!("hardware error during init"); + } + } + } +} + +impl Charger<'_> { + pub(crate) fn power_save(&mut self) { + match self { + Charger::SolarMpptV1 { mppt_ina, .. } => { + let _ = mppt_ina + .set_configuration(Configuration { + reset: Default::default(), + bus_voltage_range: Default::default(), + shunt_voltage_range: Default::default(), + bus_resolution: Default::default(), + shunt_resolution: Default::default(), + operating_mode: OperatingMode::PowerDown, + }) + .map_err(|e| { + log::info!( + "Error setting ina mppt configuration during deep sleep preparation{:?}", + e + ); + }); + } + _ => {} + } + } + fn set_charge_indicator(&mut self, charging: bool) -> FatResult<()> { + match self { + Self::SolarMpptV1 { + charge_indicator, .. + } => { + charge_indicator.set_level(charging.into()); + } + _ => {} + } + Ok(()) + } + + fn is_day(&self) -> bool { + match self { + Charger::SolarMpptV1 { solar_is_day, .. } => solar_is_day.is_high(), + _ => true, + } + } +} pub struct V4<'a> { esp: Esp<'a>, tank_sensor: TankSensor<'a>, - //charger: Charger<'a>, + charger: Charger<'a>, rtc_module: Box, battery_monitor: Box, config: PlantControllerConfig, @@ -109,8 +124,10 @@ pub struct V4<'a> { light: Output<'a>, general_fault: Output<'a>, pump_expander: Pca9535Immediate>>, - //pump_ina: Option>, UnCalibrated>>, - //sensor: SensorImpl<'a>, + pump_ina: Option< + SyncIna219>, UnCalibrated>, + >, + sensor: SensorImpl, extra1: Output<'a>, extra2: Output<'a>, } @@ -148,73 +165,71 @@ pub(crate) async fn create_v4( //flow_sensor_pin, //peripherals.pcnt1, )?; - // - // let mut sensor_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 34); - // let sensor = match sensor_expander.pin_into_output(GPIOBank::Bank0, 0) { - // Ok(_) => { - // log::info!("SensorExpander answered"); - // //pulse counter version - // let mut signal_counter = PcntDriver::new( - // peripherals.pcnt0, - // Some(peripherals.gpio22), - // Option::::None, - // Option::::None, - // Option::::None, - // )?; - // - // signal_counter.channel_config( - // PcntChannel::Channel0, - // PinIndex::Pin0, - // PinIndex::Pin1, - // &PcntChannelConfig { - // lctrl_mode: PcntControlMode::Keep, - // hctrl_mode: PcntControlMode::Keep, - // pos_mode: PcntCountMode::Increment, - // neg_mode: PcntCountMode::Hold, - // counter_h_lim: i16::MAX, - // counter_l_lim: 0, - // }, - // )?; - // - // 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 timing = can::config::Timing::B25K; - // let config = can::config::Config::new().timing(timing); - // let can = can::CanDriver::new( - // peripherals.can, - // peripherals.gpio0, - // peripherals.gpio2, - // &config, - // ) - // .unwrap(); - // - // let frame = StandardId::new(0x042).unwrap(); - // let tx_frame = Frame::new(frame, &[0, 1, 2, 3, 4, 5, 6, 7]).unwrap(); - // can.transmit(&tx_frame, 1000).unwrap(); - // - // if let Ok(rx_frame) = can.receive(1000) { - // log::info!("rx {:}:", rx_frame); - // } - // //can bus version - // SensorImpl::CanBus { can } - // } - // }; - let mut solar_is_day = Output::new(peripherals.gpio7, Level::Low, Default::default()); - let mut light = Output::new(peripherals.gpio10, Level::Low, Default::default()); - let mut charge_indicator = Output::new(peripherals.gpio3, Level::Low, Default::default()); + 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"); + //pulse counter version + // let mut signal_counter = PcntDriver::new( + // peripherals.pcnt0, + // Some(peripherals.gpio22), + // Option::::None, + // Option::::None, + // Option::::None, + // )?; + // + // signal_counter.channel_config( + // PcntChannel::Channel0, + // PinIndex::Pin0, + // PinIndex::Pin1, + // &PcntChannelConfig { + // lctrl_mode: PcntControlMode::Keep, + // hctrl_mode: PcntControlMode::Keep, + // pos_mode: PcntCountMode::Increment, + // neg_mode: PcntCountMode::Hold, + // counter_h_lim: i16::MAX, + // counter_l_lim: 0, + // }, + // )?; + + 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 mut twai_config = twai::TwaiConfiguration::new( + peripherals.twai, + peripherals.gpio0, + peripherals.gpio2, + TWAI_BAUDRATE, + TwaiMode::Normal, + ); + + let mut twai = twai_config.start(); + let frame = EspTwaiFrame::new(StandardId::ZERO, &[1, 2, 3]).unwrap(); + twai.transmit(&frame).unwrap(); + + let frame = twai.receive().unwrap(); + println!("Received a frame: {frame:?}"); + //can bus version + SensorImpl::CanBus { twai } + } + }; + + let solar_is_day = Input::new(peripherals.gpio7, InputConfig::default()); + let light = Output::new(peripherals.gpio10, Level::Low, Default::default()); + let charge_indicator = Output::new(peripherals.gpio3, Level::Low, Default::default()); let pump_device = I2cDevice::new(I2C_DRIVER.get().await); let mut pump_expander = Pca9535Immediate::new(pump_device, 32); @@ -226,38 +241,53 @@ pub(crate) async fn create_v4( } let mppt_current = I2cDevice::new(I2C_DRIVER.get().await); - let mppt_ina = SyncIna219::new(mppt_current, Address::from_pins(Pin::Vcc, Pin::Gnd)); + let mppt_ina = match SyncIna219::new(mppt_current, Address::from_pins(Pin::Vcc, Pin::Gnd)) { + Ok(mut ina) => { + // Prefer higher averaging for more stable readings + let _ = ina.set_configuration(Configuration { + reset: Default::default(), + bus_voltage_range: Default::default(), + shunt_voltage_range: Default::default(), + bus_resolution: Default::default(), + shunt_resolution: Resolution::Avg128, + operating_mode: Default::default(), + }); + Some(ina) + } + Err(err) => { + log::info!("Error creating mppt ina: {:?}", err); + None + } + }; - // let charger = match mppt_ina { - // Ok(mut mppt_ina) => { - // mppt_ina.set_configuration(Configuration { - // reset: Default::default(), - // bus_voltage_range: Default::default(), - // shunt_voltage_range: Default::default(), - // bus_resolution: Default::default(), - // shunt_resolution: ina219::configuration::Resolution::Avg128, - // operating_mode: Default::default(), - // })?; - // - // Charger::SolarMpptV1 { - // mppt_ina, - // solar_is_day, - // charge_indicator, - // } - // } - // Err(_) => Charger::ErrorInit {}, - // }; - // - // let pump_ina = match SyncIna219::new( - // MutexDevice::new(&I2C_DRIVER), - // Address::from_pins(Pin::Gnd, Pin::Sda), - // ) { - // Ok(pump_ina) => Some(pump_ina), - // Err(err) => { - // log::info!("Error creating pump ina: {:?}", err); - // None - // } - // }; + let pump_current_dev = I2cDevice::new(I2C_DRIVER.get().await); + 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); + None + } + }; + + let charger = match mppt_ina { + Some(mut mppt_ina) => { + mppt_ina.set_configuration(Configuration { + reset: Default::default(), + bus_voltage_range: Default::default(), + shunt_voltage_range: Default::default(), + bus_resolution: Default::default(), + shunt_resolution: ina219::configuration::Resolution::Avg128, + operating_mode: Default::default(), + })?; + + Charger::SolarMpptV1 { + mppt_ina, + solar_is_day, + charge_indicator, + } + } + None => Charger::ErrorInit {}, + }; let v = V4 { rtc_module, @@ -270,10 +300,11 @@ pub(crate) async fn create_v4( pump_expander, config, battery_monitor, - //charger, + pump_ina, + charger, extra1, extra2, - //sensor, + sensor, }; Ok(Box::new(v)) } @@ -336,22 +367,27 @@ impl<'a> BoardInteraction<'a> for V4<'a> { } async fn pump_current(&mut self, _plant: usize) -> Result { - bail!("not implemented"); - // //sensore is shared for all pumps, ignore plant id - // match self.pump_ina.as_mut() { - // None => { - // bail!("pump current sensor not available"); - // } - // Some(pump_ina) => { - // let v = pump_ina.shunt_voltage().map(|v| { - // let shunt_voltage = Voltage::from_microvolts(v.shunt_voltage_uv().abs() as f64); - // let shut_value = Resistance::from_ohms(0.05_f64); - // let current = shunt_voltage.as_volts() / shut_value.as_ohms(); - // Current::from_amperes(current) - // })?; - // Ok(v) - // } - // } + // sensor is shared for all pumps, ignore plant id + match self.pump_ina.as_mut() { + None => { + bail!("pump current sensor not available"); + } + Some(pump_ina) => { + let v = pump_ina + .shunt_voltage() + .map_err(|e| FatError::String { + error: alloc::format!("{:?}", e), + }) + .map(|v| { + let shunt_voltage = + Voltage::from_microvolts(v.shunt_voltage_uv().abs() as f64); + let shut_value = Resistance::from_ohms(0.05_f64); + let current = shunt_voltage.as_volts() / shut_value.as_ohms(); + Current::from_amperes(current) + })?; + Ok(v) + } + } } async fn fault(&mut self, plant: usize, enable: bool) -> FatResult<()> { @@ -419,12 +455,10 @@ impl<'a> BoardInteraction<'a> for V4<'a> { } async fn get_mptt_voltage(&mut self) -> Result { - bail!("not implemented"); - //self.charger.get_mptt_voltage() + self.charger.get_mptt_voltage() } async fn get_mptt_current(&mut self) -> Result { - bail!("not implemented"); - //self.charger.get_mptt_current() + self.charger.get_mppt_current() } } diff --git a/rust/scratch/v4_sensor.rs b/rust/src/hal/v4_sensor.rs similarity index 64% rename from rust/scratch/v4_sensor.rs rename to rust/src/hal/v4_sensor.rs index 6c5ea0f..4c776a1 100644 --- a/rust/scratch/v4_sensor.rs +++ b/rust/src/hal/v4_sensor.rs @@ -1,17 +1,24 @@ +use crate::hal::Box; use crate::hal::Sensor; -use crate::log::{log, LogMessage}; +use crate::log::{LogMessage, LOG_ACCESS}; +use crate::FatError::FatResult; +use alloc::format; use alloc::string::ToString; -use embedded_hal_bus::i2c::MutexDevice; -use esp_idf_hal::can::CanDriver; -use esp_idf_hal::delay::Delay; -use esp_idf_hal::i2c::I2cDriver; -use esp_idf_hal::pcnt::PcntDriver; +use async_trait::async_trait; +use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_time::{Delay, Timer}; +use esp_hal::i2c::master::I2c; +use esp_hal::twai::Twai; +use esp_hal::{Blocking, DriverMode}; +use log::log; use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; const REPEAT_MOIST_MEASURE: usize = 10; +#[async_trait] pub trait SensorInteraction { - async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> anyhow::Result; + async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> FatResult; } const MS0: u8 = 1_u8; @@ -21,28 +28,30 @@ const MS3: u8 = 4_u8; const MS4: u8 = 2_u8; const SENSOR_ON: u8 = 5_u8; -pub enum SensorImpl<'a> { +pub enum SensorImpl { PulseCounter { - signal_counter: PcntDriver<'a>, - sensor_expander: Pca9535Immediate>>, + //signal_counter: PcntDriver<'a>, + sensor_expander: + Pca9535Immediate>>, }, CanBus { - can: CanDriver<'a>, + twai: Twai<'static, Blocking>, }, } -impl SensorInteraction for SensorImpl<'_> { - fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> anyhow::Result { +#[async_trait] +impl SensorInteraction for SensorImpl { + async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> FatResult { match self { SensorImpl::PulseCounter { - signal_counter, + //signal_counter, sensor_expander, .. } => { let mut results = [0_f32; REPEAT_MOIST_MEASURE]; for repeat in 0..REPEAT_MOIST_MEASURE { - signal_counter.counter_pause()?; - signal_counter.counter_clear()?; + //signal_counter.counter_pause()?; + //signal_counter.counter_clear()?; //Disable all sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?; @@ -77,38 +86,41 @@ impl SensorInteraction for SensorImpl<'_> { sensor_expander.pin_set_low(GPIOBank::Bank0, MS4)?; sensor_expander.pin_set_high(GPIOBank::Bank0, SENSOR_ON)?; - let delay = Delay::new_default(); let measurement = 100; // TODO what is this scaling factor? what is its purpose? let factor = 1000f32 / measurement as f32; //give some time to stabilize - delay.delay_ms(10); - signal_counter.counter_resume()?; - delay.delay_ms(measurement); - signal_counter.counter_pause()?; + Timer::after_millis(10).await; + //signal_counter.counter_resume()?; + Timer::after_millis(measurement).await; + //signal_counter.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)?; - delay.delay_ms(10); - let unscaled = signal_counter.get_counter_value()? as i32; + Timer::after_millis(10).await; + let unscaled = 1337; //signal_counter.get_counter_value()? as i32; let hz = unscaled as f32 * factor; - log( - LogMessage::RawMeasure, - unscaled as u32, - hz as u32, - &plant.to_string(), - &format!("{sensor:?}"), - ); + LOG_ACCESS + .lock() + .await + .log( + LogMessage::RawMeasure, + unscaled as u32, + hz as u32, + &plant.to_string(), + &format!("{sensor:?}"), + ) + .await; results[repeat] = 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]; - anyhow::Ok(median) + Ok(median) } SensorImpl::CanBus { .. } => { todo!() diff --git a/rust/src/main.rs b/rust/src/main.rs index 7e6bde7..54981d3 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -15,13 +15,12 @@ use esp_backtrace as _; use crate::config::PlantConfig; use crate::hal::esp_time; use crate::log::LOG_ACCESS; -use crate::tank::WATER_FROZEN_THRESH; +use crate::tank::{determine_tank_state, TankError, WATER_FROZEN_THRESH}; use crate::webserver::httpd; use crate::FatError::FatResult; use crate::{ config::BoardVersion::INITIAL, hal::{PlantHal, HAL, PLANT_COUNT}, - //webserver::httpd, }; use ::log::{error, info, warn}; use alloc::borrow::ToOwned; @@ -162,30 +161,6 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { version.git_hash, version.build_time ); - //TODO - - // TODO - //let ota_state_string = unsafe { - //esp_ota_get_state_partition(running_partition, &mut ota_state); - //if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_NEW { - //"ESP_OTA_IMG_NEW" - //} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_PENDING_VERIFY { - //"ESP_OTA_IMG_PENDING_VERIFY" - //} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_VALID { - //"ESP_OTA_IMG_VALID" - //} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_INVALID { - //"ESP_OTA_IMG_INVALID" - //} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_ABORTED { - //"ESP_OTA_IMG_ABORTED" - //} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_UNDEFINED { - //"ESP_OTA_IMG_UNDEFINED" - //} else { - //&format!("unknown {ota_state}") - //} - //}; - //log(LogMessage::PartitionState, 0, 0, "", ota_state_string); - let _ota_state_string = "unknown"; - board.board_hal.general_fault(false).await; let cur = match board.board_hal.get_rtc_module().get_rtc_time().await { Ok(value) => value, @@ -348,41 +323,61 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { } let _dry_run = false; - // - // let tank_state = determine_tank_state(&mut board); - // - // if tank_state.is_enabled() { - // if let Some(err) = tank_state.got_error(&board.board_hal.get_config().tank) { - // match err { - // TankError::SensorDisabled => { /* unreachable */ } - // TankError::SensorMissing(raw_value_mv) => log( - // LogMessage::TankSensorMissing, - // raw_value_mv as u32, - // 0, - // "", - // "", - // ).await, - // TankError::SensorValueError { value, min, max } => log( - // LogMessage::TankSensorValueRangeError, - // min as u32, - // max as u32, - // &format!("{}", value), - // "", - // ).await, - // TankError::BoardError(err) => { - // log(LogMessage::TankSensorBoardError, 0, 0, "", &err.to_string()).await - // } - // } - // // disabled cannot trigger this because of wrapping if is_enabled - // board.board_hal.general_fault(true).await; - // } else if tank_state - // .warn_level(&board.board_hal.get_config().tank) - // .is_ok_and(|warn| warn) - // { - // log(LogMessage::TankWaterLevelLow, 0, 0, "", "").await; - // board.board_hal.general_fault(true).await; - // } - // } + + let tank_state = determine_tank_state(&mut board).await; + + if tank_state.is_enabled() { + if let Some(err) = tank_state.got_error(&board.board_hal.get_config().tank) { + match err { + TankError::SensorDisabled => { /* unreachable */ } + TankError::SensorMissing(raw_value_mv) => { + LOG_ACCESS + .lock() + .await + .log( + LogMessage::TankSensorMissing, + raw_value_mv as u32, + 0, + "", + "", + ) + .await + } + TankError::SensorValueError { value, min, max } => { + LOG_ACCESS + .lock() + .await + .log( + LogMessage::TankSensorValueRangeError, + min as u32, + max as u32, + &format!("{}", value), + "", + ) + .await + } + TankError::BoardError(err) => { + LOG_ACCESS + .lock() + .await + .log(LogMessage::TankSensorBoardError, 0, 0, "", &err.to_string()) + .await + } + } + // disabled cannot trigger this because of wrapping if is_enabled + board.board_hal.general_fault(true).await; + } else if tank_state + .warn_level(&board.board_hal.get_config().tank) + .is_ok_and(|warn| warn) + { + LOG_ACCESS + .lock() + .await + .log(LogMessage::TankWaterLevelLow, 0, 0, "", "") + .await; + board.board_hal.general_fault(true).await; + } + } let mut _water_frozen = false; let water_temp: FatResult = match board.board_hal.get_tank_sensor() {