Merge branch 'develop' into housekeeping

This commit is contained in:
2025-06-20 23:40:32 +02:00
16 changed files with 312 additions and 54 deletions

View File

@@ -9,6 +9,7 @@ use chrono::{DateTime, Utc};
use embedded_hal::digital::OutputPin;
use esp_idf_hal::gpio::{IOPin, Pull};
use esp_idf_hal::gpio::{InputOutput, PinDriver};
use measurements::{Current, Voltage};
pub struct Initial<'a> {
pub(crate) general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
@@ -125,4 +126,12 @@ impl<'a> BoardInteraction<'a> for Initial<'a> {
self.esp.save_config(&self.config)?;
anyhow::Ok(())
}
fn get_mptt_voltage(&mut self) -> Result<Voltage> {
bail!("Please configure board revision")
}
fn get_mptt_current(&mut self) -> Result<Current> {
bail!("Please configure board revision")
}
}

View File

@@ -42,6 +42,7 @@ use serde::{Deserialize, Serialize};
use std::result::Result::Ok as OkStd;
use std::sync::Mutex;
use std::time::Duration;
use measurements::{Current, Voltage};
//Only support for 8 right now!
pub const PLANT_COUNT: usize = 8;
@@ -125,6 +126,8 @@ pub trait BoardInteraction<'a> {
fn test_pump(&mut self, plant: usize) -> Result<()>;
fn test(&mut self) -> Result<()>;
fn set_config(&mut self, config: PlantControllerConfig) -> Result<()>;
fn get_mptt_voltage(&mut self) -> anyhow::Result<Voltage>;
fn get_mptt_current(&mut self) -> anyhow::Result<Current>;
}
#[allow(dead_code)]

View File

@@ -26,6 +26,7 @@ use esp_idf_hal::{
pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex},
};
use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError};
use measurements::{Current, Voltage};
use one_wire_bus::OneWire;
use plant_ctrl2::sipo::ShiftRegister40;
use std::result::Result::Ok as OkStd;
@@ -249,6 +250,26 @@ pub(crate) fn create_v3(
}
impl<'a> BoardInteraction<'a> for V3<'a> {
fn set_charge_indicator(&mut self, charging: bool) -> Result<()> {
Ok(self.shift_register.decompose()[CHARGING].set_state(charging.into())?)
}
fn is_day(&self) -> bool {
self.solar_is_day.get_level().into()
}
fn get_mptt_voltage(&mut self) -> Result<Voltage> {
//if working this is the hardware set mppt voltage
if self.is_day() {
Ok(Voltage::from_volts(15_f64))
} else {
Ok(Voltage::from_volts(0_f64))
}
}
fn get_mptt_current(&mut self) -> Result<Current> {
bail!("Board does not have current sensor")
}
fn get_esp(&mut self) -> &mut ESP<'a> {
&mut self.esp
}
@@ -261,10 +282,6 @@ impl<'a> BoardInteraction<'a> for V3<'a> {
&mut self.battery_monitor
}
fn set_charge_indicator(&mut self, charging: bool) -> Result<()> {
Ok(self.shift_register.decompose()[CHARGING].set_state(charging.into())?)
}
fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
let _ = self.shift_register.decompose()[AWAKE].set_low();
deep_sleep(duration_in_ms)
@@ -366,10 +383,6 @@ impl<'a> BoardInteraction<'a> for V3<'a> {
Ok(())
}
fn is_day(&self) -> bool {
self.solar_is_day.get_level().into()
}
fn water_temperature_c(&mut self) -> Result<f32> {
self.one_wire_bus
.reset(&mut self.esp.delay)

View File

@@ -1,4 +1,4 @@
pub use crate::config::PlantControllerConfig;
use crate::config::PlantControllerConfig;
use crate::hal::battery::BatteryInteraction;
use crate::hal::esp::ESP;
use crate::hal::{
@@ -13,22 +13,24 @@ use ds323x::{DateTimeAccess, Ds323x};
use eeprom24x::{Eeprom24x, Eeprom24xTrait, SlaveAddr};
use embedded_hal::digital::OutputPin;
use embedded_hal_bus::i2c::MutexDevice;
use esp_idf_hal::{
adc::{
attenuation,
oneshot::{config::AdcChannelConfig, AdcChannelDriver, AdcDriver},
Resolution,
},
delay::Delay,
gpio::{AnyInputPin, Gpio5, IOPin, InputOutput, Output, PinDriver, Pull},
i2c::I2cDriver,
pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex},
use esp_idf_hal::adc::oneshot::config::AdcChannelConfig;
use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver};
use esp_idf_hal::adc::{attenuation, Resolution};
use esp_idf_hal::delay::Delay;
use esp_idf_hal::gpio::{AnyInputPin, Gpio5, IOPin, InputOutput, Output, PinDriver, Pull};
use esp_idf_hal::i2c::I2cDriver;
use esp_idf_hal::pcnt::{
PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex,
};
use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, EspError};
use ina219::{address::Address, calibration::UnCalibrated, SyncIna219};
use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError};
use ina219::address::{Address, Pin};
use ina219::calibration::{Calibration, UnCalibrated};
use ina219::SyncIna219;
use measurements::{Current, Resistance, Voltage};
use one_wire_bus::OneWire;
use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
use std::result::Result::Ok as OkStd;
use ina219::configuration::{Configuration, OperatingMode};
const MS0: u8 = 1_u8;
const MS1: u8 = 0_u8;
@@ -37,15 +39,78 @@ const MS3: u8 = 4_u8;
const MS4: u8 = 2_u8;
const SENSOR_ON: u8 = 5_u8;
pub enum Charger<'a> {
SolarMpptV1 {
mppt_ina: SyncIna219<MutexDevice<'a, I2cDriver<'a>>, 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>,
},
}
impl<'a> Charger<'a> {
pub(crate) fn powersave(&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| {
println!(
"Error setting ina mppt configuration during deepsleep 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(),
}
}
fn get_mptt_voltage(&mut self) -> anyhow::Result<Voltage> {
let voltage = match self {
Charger::SolarMpptV1 { mppt_ina, .. } => mppt_ina
.bus_voltage()
.map(|v| Voltage::from_millivolts(v.voltage_mv() as f64))?,
};
Ok(voltage)
}
fn get_mptt_current(&mut self) -> anyhow::Result<Current> {
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)
})?,
};
Ok(current)
}
}
pub struct V4<'a> {
_mppt_ina: SyncIna219<MutexDevice<'a, I2cDriver<'a>>, UnCalibrated>,
esp: ESP<'a>,
charger: Charger<'a>,
battery_monitor: Box<dyn BatteryInteraction + Send>,
config: PlantControllerConfig,
tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, esp_idf_hal::adc::ADC1>>,
solar_is_day: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>,
signal_counter: PcntDriver<'a>,
charge_indicator: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
awake: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, Output>,
light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
@@ -63,12 +128,12 @@ pub struct V4<'a> {
sensor_expander: Pca9535Immediate<MutexDevice<'a, I2cDriver<'a>>>,
}
pub(crate) fn create_v4<'a>(
pub(crate) fn create_v4(
peripherals: FreePeripherals,
esp: ESP<'static>,
config: PlantControllerConfig,
battery_monitor: Box<dyn BatteryInteraction + Send>,
) -> anyhow::Result<Box<dyn BoardInteraction<'static> + Send>> {
) -> anyhow::Result<Box<dyn BoardInteraction + Send + '_>> {
let mut awake = PinDriver::output(peripherals.gpio15.downgrade())?;
awake.set_high()?;
@@ -173,7 +238,21 @@ pub(crate) fn create_v4<'a>(
let _ = sensor_expander.pin_set_low(GPIOBank::Bank1, pin);
}
let mut mppt_ina = SyncIna219::new(MutexDevice::new(&I2C_DRIVER), Address::from_byte(68)?)?;
//TODO error handling is not done nicely here, should not break if ina is not responsive
let mut mppt_ina = SyncIna219::new(
MutexDevice::new(&I2C_DRIVER),
Address::from_pins(Pin::Vcc, Pin::Gnd),
)?;
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(),
})?;
//TODO this is probably laready done untill we are ready first time?, maybee add startup time comparison on access?
esp.delay.delay_ms(
mppt_ina
.configuration()?
@@ -181,18 +260,17 @@ pub(crate) fn create_v4<'a>(
.unwrap()
.as_millis() as u32,
);
println!("Bus Voltage: {}", mppt_ina.bus_voltage()?);
println!("Shunt Voltage: {}", mppt_ina.shunt_voltage()?);
let volt = (mppt_ina.shunt_voltage()?.shunt_voltage_mv()) as f32 / 1000_f32;
let current = volt / 0.05;
println!("Shunt Current: {}", current);
let charger = Charger::SolarMpptV1 {
mppt_ina,
solar_is_day,
charge_indicator,
};
let v = V4 {
_mppt_ina: mppt_ina,
esp,
awake,
tank_channel,
solar_is_day,
signal_counter,
light,
tank_power,
@@ -202,9 +280,9 @@ pub(crate) fn create_v4<'a>(
general_fault,
pump_expander,
sensor_expander,
charge_indicator,
config,
battery_monitor,
charger,
};
Ok(Box::new(v))
}
@@ -223,14 +301,12 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
}
fn set_charge_indicator(&mut self, charging: bool) -> anyhow::Result<()> {
self.charge_indicator
.set_state(charging.into())
.expect("cannot fail");
Ok(())
self.charger.set_charge_indicator(charging)
}
fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
self.awake.set_low().unwrap();
self.charger.powersave();
deep_sleep(duration_in_ms);
}
@@ -331,7 +407,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
}
fn is_day(&self) -> bool {
self.solar_is_day.get_level().into()
self.charger.is_day()
}
fn water_temperature_c(&mut self) -> anyhow::Result<f32> {
@@ -568,4 +644,12 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
self.esp.save_config(&self.config)?;
anyhow::Ok(())
}
fn get_mptt_voltage(&mut self) -> anyhow::Result<Voltage> {
self.charger.get_mptt_voltage()
}
fn get_mptt_current(&mut self) -> anyhow::Result<Current> {
self.charger.get_mptt_current()
}
}

View File

@@ -754,13 +754,24 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
loop {
unsafe {
let mut board = BOARD_ACCESS.lock().unwrap();
if let Ok(charging) = board
//we have mppt controller, ask it for charging current
if let Ok(current) = board.board_hal.get_mptt_current() {
let _ = board.board_hal.set_charge_indicator(current.as_milliamperes() > 20_f64);
}
//fallback to battery controller and ask it instead
else if let Ok(charging) = board
.board_hal
.get_battery_monitor()
.average_current_milli_ampere()
{
let _ = board.board_hal.set_charge_indicator(charging > 20);
}
else {
//who knows
let _ = board.board_hal.set_charge_indicator(false);
}
match wait_type {
WaitType::MissingConfig => {
// Keep existing behavior: circular filling pattern

View File

@@ -40,6 +40,13 @@ struct Moistures {
moisture_b: Vec<std::string::String>,
}
#[derive(Serialize, Debug)]
struct SolarState {
mppt_voltage: f32,
mppt_current: f32,
is_day: bool,
}
#[derive(Deserialize, Debug)]
struct SetTime<'a> {
time: &'a str,
@@ -218,6 +225,18 @@ fn set_config(
anyhow::Ok(Some("saved".to_owned()))
}
fn get_solar_state(
_request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> {
let mut board = BOARD_ACCESS.lock().expect("board access");
let state = SolarState {
mppt_voltage: board.board_hal.get_mptt_voltage()?.as_volts() as f32,
mppt_current: board.board_hal.get_mptt_current()?.as_amperes() as f32,
is_day: board.board_hal.is_day(),
};
anyhow::Ok(Some(serde_json::to_string(&state)?))
}
fn get_battery_state(
_request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> {
@@ -383,6 +402,11 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
handle_error_to500(request, get_battery_state)
})
.unwrap();
server
.fn_handler("/solar", Method::Get, |request| {
handle_error_to500(request, get_solar_state)
})
.unwrap();
server
.fn_handler("/time", Method::Get, |request| {
handle_error_to500(request, get_time)