remove anyhow

This commit is contained in:
2025-09-22 01:49:25 +02:00
parent c94f5bdb45
commit 1791f463b7
21 changed files with 2268 additions and 1982 deletions

View File

@@ -26,25 +26,14 @@ command = [
"partitions.csv" "partitions.csv"
] ]
[profile.dev]
lto = true
strip = false
debug = false
overflow-checks = true
panic = "abort"
incremental = true
opt-level = 3
lto = "fat"
[profile.release]
# Explicitly disable LTO which the Xtensa codegen backend has issues
lto = true
strip = true strip = true
debug = false debug = false
overflow-checks = true overflow-checks = true
panic = "abort" panic = "abort"
incremental = true incremental = true
opt-level = 3 opt-level = "z"
[package.metadata.espflash] [package.metadata.espflash]
@@ -61,7 +50,7 @@ esp-hal = { version = "=1.0.0-rc.0", features = [
] } ] }
log = "0.4.27" log = "0.4.27"
embassy-net = { version = "0.7.0", features = [ embassy-net = { version = "0.7.1", default-features = false, features = [
"dhcpv4", "dhcpv4",
"log", "log",
"medium-ethernet", "medium-ethernet",
@@ -83,9 +72,9 @@ esp-println = { version = "0.15.0", features = ["esp32c6", "log-04"] }
# for more networking protocol support see https://crates.io/crates/edge-net # for more networking protocol support see https://crates.io/crates/edge-net
embassy-executor = { version = "0.7.0", features = [ embassy-executor = { version = "0.7.0", features = [
"log", "log",
"task-arena-size-131072" "task-arena-size-98304"
] } ] }
embassy-time = { version = "0.5.0", features = ["log"] } embassy-time = { version = "0.5.0", features = ["log"], default-features = false }
esp-hal-embassy = { version = "0.9.0", features = ["esp32c6", "log-04"] } esp-hal-embassy = { version = "0.9.0", features = ["esp32c6", "log-04"] }
esp-storage = { version = "0.7.0", features = ["esp32c6"] } esp-storage = { version = "0.7.0", features = ["esp32c6"] }
@@ -117,14 +106,9 @@ heapless = { version = "0.8", features = ["serde"] }
embedded-hal-bus = { version = "0.3.0" } embedded-hal-bus = { version = "0.3.0" }
#Hardware additional driver #Hardware additional driver
#ds18b20 = "0.1.1"
#bq34z100 = { version = "0.3.0", default-features = false }
one-wire-bus = "0.1.1"
ds323x = "0.6.0"
#pure code dependencies #bq34z100 = { version = "0.3.0", default-features = false }
#once_cell = "1.19.0" onewire = "0.4.0"
anyhow = { version = "1.0.75", default-features = false }
#strum = { version = "0.27.0", default-feature = false, features = ["derive"] } #strum = { version = "0.27.0", default-feature = false, features = ["derive"] }
measurements = "0.11.0" measurements = "0.11.0"
@@ -156,7 +140,7 @@ edge-http = { version = "0.6.1", features = ["log"] }
littlefs2 = { version = "0.6.1", features = ["c-stubs", "alloc"] } littlefs2 = { version = "0.6.1", features = ["c-stubs", "alloc"] }
littlefs2-core = "0.1.1" littlefs2-core = "0.1.1"
bytemuck = { version = "1.23.2", features = ["derive", "min_const_generics", "pod_saturating", "extern_crate_alloc"] } bytemuck = { version = "1.23.2", features = ["derive", "min_const_generics", "pod_saturating", "extern_crate_alloc"] }
deranged = "0.5.3"
[patch.crates-io] [patch.crates-io]
#bq34z100 = { path = "../../bq34z100_rust" } #bq34z100 = { path = "../../bq34z100_rust" }

View File

@@ -58,7 +58,7 @@ fn main() {
fn webpack() { fn webpack() {
//println!("cargo:rerun-if-changed=./src/src_webpack"); //println!("cargo:rerun-if-changed=./src/src_webpack");
Command::new("rm") Command::new("rm")
.arg("./src/webserver/bundle.js") .arg("./src/webserver/bundle.js.gz")
.output() .output()
.unwrap(); .unwrap();
@@ -81,14 +81,14 @@ fn webpack() {
let _ = Command::new("cmd") let _ = Command::new("cmd")
.arg("/K") .arg("/K")
.arg("move") .arg("move")
.arg("./src_webpack/bundle.js") .arg("./src_webpack/bundle.js.gz")
.arg("./src/webserver") .arg("./src/webserver")
.output() .output()
.unwrap(); .unwrap();
let _ = Command::new("cmd") let _ = Command::new("cmd")
.arg("/K") .arg("/K")
.arg("move") .arg("move")
.arg("./src_webpack/index.html") .arg("./src_webpack/index.html.gz")
.arg("./src/webserver") .arg("./src/webserver")
.output() .output()
.unwrap(); .unwrap();
@@ -107,12 +107,12 @@ fn webpack() {
// move webpack results to rust webserver src // move webpack results to rust webserver src
let _ = Command::new("mv") let _ = Command::new("mv")
.arg("./src_webpack/bundle.js") .arg("./src_webpack/bundle.js.gz")
.arg("./src/webserver") .arg("./src/webserver")
.output() .output()
.unwrap(); .unwrap();
let _ = Command::new("mv") let _ = Command::new("mv")
.arg("./src_webpack/index.html") .arg("./src_webpack/index.html.gz")
.arg("./src/webserver") .arg("./src/webserver")
.output() .output()
.unwrap(); .unwrap();

View File

@@ -1,3 +1,2 @@
[toolchain] [toolchain]
channel = "nightly" channel = "nightly"
toolchain = "esp"

View File

@@ -1,440 +0,0 @@
use crate::config::PlantControllerConfig;
use crate::hal::battery::BatteryInteraction;
use crate::hal::esp::Esp;
use crate::hal::rtc::RTCModuleInteraction;
use crate::hal::v4_sensor::SensorImpl;
use crate::hal::v4_sensor::SensorInteraction;
use crate::hal::water::TankSensor;
use crate::hal::{
deep_sleep, BoardInteraction, FreePeripherals, Sensor, I2C_DRIVER, PLANT_COUNT
};
use crate::log::{log, LogMessage};
use anyhow::bail;
use embedded_hal::digital::OutputPin;
use embedded_hal_bus::i2c::MutexDevice;
use ina219::address::{Address, Pin};
use ina219::calibration::UnCalibrated;
use ina219::configuration::{Configuration, OperatingMode};
use ina219::SyncIna219;
use measurements::{Current, Resistance, Voltage};
use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
use std::result::Result::Ok as OkStd;
use embedded_can::Frame;
use embedded_can::StandardId;
use alloc::string::ToString;
use alloc::boxed::Box;
use esp_hal::gpio::Pull;
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>,
},
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<Voltage> {
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<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)
})?,
_ => {
bail!("hardware error during init")
}
};
Ok(current)
}
}
pub struct V4<'a> {
esp: Esp<'a>,
tank_sensor: TankSensor<'a>,
charger: Charger<'a>,
rtc_module: Box<dyn RTCModuleInteraction + Send>,
battery_monitor: Box<dyn BatteryInteraction + Send>,
config: PlantControllerConfig,
awake: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, Output>,
light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
pump_expander: Pca9535Immediate<MutexDevice<'a, I2cDriver<'a>>>,
pump_ina: Option<SyncIna219<MutexDevice<'a, I2cDriver<'a>>, UnCalibrated>>,
sensor: SensorImpl<'a>,
extra1: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, Output>,
extra2: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, Output>,
}
pub(crate) fn create_v4(
peripherals: FreePeripherals,
esp: Esp<'static>,
config: PlantControllerConfig,
battery_monitor: Box<dyn BatteryInteraction + Send>,
rtc_module: Box<dyn RTCModuleInteraction + Send>,
) -> anyhow::Result<Box<dyn BoardInteraction<'static> + Send + 'static>> {
log::info!("Start v4");
let mut awake = PinDriver::output(peripherals.gpio21.downgrade())?;
awake.set_high()?;
let mut general_fault = PinDriver::input_output(peripherals.gpio23.downgrade())?;
general_fault.set_pull(Pull::Floating)?;
general_fault.set_low()?;
let mut extra1 = PinDriver::output(peripherals.gpio6.downgrade())?;
extra1.set_low()?;
let mut extra2 = PinDriver::output(peripherals.gpio15.downgrade())?;
extra2.set_low()?;
let one_wire_pin = peripherals.gpio18.downgrade();
let tank_power_pin = peripherals.gpio11.downgrade();
let flow_sensor_pin = peripherals.gpio4.downgrade();
let tank_sensor = TankSensor::create(
one_wire_pin,
peripherals.adc1,
peripherals.gpio5,
tank_power_pin,
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::<AnyInputPin>::None,
Option::<AnyInputPin>::None,
Option::<AnyInputPin>::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 = PinDriver::input(peripherals.gpio7.downgrade())?;
solar_is_day.set_pull(Pull::Floating)?;
let mut light = PinDriver::input_output(peripherals.gpio10.downgrade())?;
light.set_pull(Pull::Floating)?;
let mut charge_indicator = PinDriver::input_output(peripherals.gpio3.downgrade())?;
charge_indicator.set_pull(Pull::Floating)?;
charge_indicator.set_low()?;
let mut pump_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 32);
for pin in 0..8 {
let _ = pump_expander.pin_into_output(GPIOBank::Bank0, pin);
let _ = pump_expander.pin_into_output(GPIOBank::Bank1, pin);
let _ = pump_expander.pin_set_low(GPIOBank::Bank0, pin);
let _ = pump_expander.pin_set_low(GPIOBank::Bank1, pin);
}
let mppt_ina = SyncIna219::new(
MutexDevice::new(&I2C_DRIVER),
Address::from_pins(Pin::Vcc, Pin::Gnd),
);
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 v = V4 {
rtc_module,
esp,
awake,
tank_sensor,
light,
general_fault,
pump_ina,
pump_expander,
config,
battery_monitor,
charger,
extra1,
extra2,
sensor,
};
Ok(Box::new(v))
}
impl<'a> BoardInteraction<'a> for V4<'a> {
fn get_tank_sensor(&mut self) -> Option<&mut TankSensor<'a>> {
Some(&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
}
fn set_charge_indicator(&mut self, charging: bool) -> anyhow::Result<()> {
self.charger.set_charge_indicator(charging)
}
fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
self.awake.set_low().unwrap();
self.charger.power_save();
deep_sleep(duration_in_ms);
}
fn is_day(&self) -> bool {
self.charger.is_day()
}
fn light(&mut self, enable: bool) -> anyhow::Result<()> {
unsafe { gpio_hold_dis(self.light.pin()) };
self.light.set_state(enable.into())?;
unsafe { gpio_hold_en(self.light.pin()) };
anyhow::Ok(())
}
fn pump(&mut self, plant: usize, enable: bool) -> anyhow::Result<()> {
if enable {
self.pump_expander
.pin_set_high(GPIOBank::Bank0, plant.try_into()?)?;
} else {
self.pump_expander
.pin_set_low(GPIOBank::Bank0, plant.try_into()?)?;
}
anyhow::Ok(())
}
fn pump_current(&mut self, _plant: usize) -> anyhow::Result<Current> {
//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)
}
}
}
fn fault(&mut self, plant: usize, enable: bool) -> anyhow::Result<()> {
if enable {
self.pump_expander
.pin_set_high(GPIOBank::Bank1, plant.try_into()?)?
} else {
self.pump_expander
.pin_set_low(GPIOBank::Bank1, plant.try_into()?)?
}
anyhow::Ok(())
}
fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> anyhow::Result<f32> {
self.sensor.measure_moisture_hz(plant, sensor)
}
fn general_fault(&mut self, enable: bool) {
unsafe { gpio_hold_dis(self.general_fault.pin()) };
self.general_fault.set_state(enable.into()).unwrap();
unsafe { gpio_hold_en(self.general_fault.pin()) };
}
fn test(&mut self) -> anyhow::Result<()> {
self.general_fault(true);
self.esp.delay.delay_ms(100);
self.general_fault(false);
self.esp.delay.delay_ms(500);
self.light(true)?;
self.esp.delay.delay_ms(500);
self.light(false)?;
self.esp.delay.delay_ms(500);
for i in 0..PLANT_COUNT {
self.fault(i, true)?;
self.esp.delay.delay_ms(500);
self.fault(i, false)?;
self.esp.delay.delay_ms(500);
}
for i in 0..PLANT_COUNT {
self.pump(i, true)?;
self.esp.delay.delay_ms(100);
self.pump(i, false)?;
self.esp.delay.delay_ms(100);
}
for plant in 0..PLANT_COUNT {
let a = self.measure_moisture_hz(plant, Sensor::A);
let b = self.measure_moisture_hz(plant, Sensor::B);
let aa = match a {
OkStd(a) => a as u32,
Err(_) => u32::MAX,
};
let bb = match b {
OkStd(b) => b as u32,
Err(_) => u32::MAX,
};
log(LogMessage::TestSensor, aa, bb, &plant.to_string(), "");
}
self.esp.delay.delay_ms(10);
anyhow::Ok(())
}
fn set_config(&mut self, config: PlantControllerConfig) -> anyhow::Result<()> {
self.config = config;
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

@@ -1,176 +0,0 @@
use crate::hal::TANK_MULTI_SAMPLE;
use anyhow::{anyhow, bail};
use ds18b20::Ds18b20;
use esp_idf_hal::adc::oneshot::config::AdcChannelConfig;
use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver};
use esp_idf_hal::adc::{attenuation, Resolution, ADC1};
use esp_idf_hal::delay::Delay;
use esp_idf_hal::gpio::{AnyIOPin, AnyInputPin, Gpio5, InputOutput, PinDriver, Pull};
use esp_idf_hal::pcnt::{
PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, PCNT1,
};
use esp_idf_sys::EspError;
use one_wire_bus::OneWire;
pub struct TankSensor<'a> {
// one_wire_bus: OneWire<PinDriver<'a, AnyIOPin, InputOutput>>,
// tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, ADC1>>,
// tank_power: PinDriver<'a, AnyIOPin, InputOutput>,
// flow_counter: PcntDriver<'a>,
// delay: Delay,
}
impl<'a> TankSensor<'a> {
pub(crate) fn create(
// one_wire_pin: AnyIOPin,
// adc1: ADC1,
// gpio5: Gpio5,
// tank_power_pin: AnyIOPin,
// flow_sensor_pin: AnyIOPin,
// pcnt1: PCNT1,
) -> anyhow::Result<TankSensor<'a>> {
// let mut one_wire_pin =
// PinDriver::input_output_od(one_wire_pin).expect("Failed to configure pin");
// one_wire_pin
// .set_pull(Pull::Floating)
// .expect("Failed to set pull");
//
// let adc_config = AdcChannelConfig {
// attenuation: attenuation::DB_11,
// resolution: Resolution::Resolution12Bit,
// calibration: esp_idf_hal::adc::oneshot::config::Calibration::Curve,
// };
// let tank_driver = AdcDriver::new(adc1).expect("Failed to configure ADC");
// let tank_channel = AdcChannelDriver::new(tank_driver, gpio5, &adc_config)
// .expect("Failed to configure ADC channel");
//
// let mut tank_power =
// PinDriver::input_output(tank_power_pin).expect("Failed to configure pin");
// tank_power
// .set_pull(Pull::Floating)
// .expect("Failed to set pull");
//
// let one_wire_bus =
// OneWire::new(one_wire_pin).expect("OneWire bus did not pull up after release");
//
// let mut flow_counter = PcntDriver::new(
// pcnt1,
// Some(flow_sensor_pin),
// Option::<AnyInputPin>::None,
// Option::<AnyInputPin>::None,
// Option::<AnyInputPin>::None,
// )?;
//
// flow_counter.channel_config(
// PcntChannel::Channel1,
// 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,
// },
// )?;
//
// Ok(TankSensor {
// one_wire_bus,
// tank_channel,
// tank_power,
// flow_counter,
// delay: Default::default(),
// })
bail!("Tank sensor not implemented");
}
pub fn reset_flow_meter(&mut self) {
// self.flow_counter.counter_pause().unwrap();
// self.flow_counter.counter_clear().unwrap();
}
pub fn start_flow_meter(&mut self) {
//self.flow_counter.counter_resume().unwrap();
}
pub fn get_flow_meter_value(&mut self) -> i16 {
//self.flow_counter.get_counter_value().unwrap()
5_i16
}
pub fn stop_flow_meter(&mut self) -> i16 {
//self.flow_counter.counter_pause().unwrap();
self.get_flow_meter_value()
}
pub async fn water_temperature_c(&mut self) -> anyhow::Result<f32> {
//multisample should be moved to water_temperature_c
let mut attempt = 1;
let water_temp: Result<f32, anyhow::Error> = loop {
let temp = self.single_temperature_c().await;
match &temp {
Ok(res) => {
log::info!("Water temp is {}", res);
break temp;
}
Err(err) => {
log::info!("Could not get water temp {} attempt {}", err, attempt)
}
}
if attempt == 5 {
break temp;
}
attempt += 1;
};
water_temp
}
async fn single_temperature_c(&mut self) -> anyhow::Result<f32> {
bail!("err");
// self.one_wire_bus
// .reset(&mut self.delay)
// .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
// let first = self.one_wire_bus.devices(false, &mut self.delay).next();
// if first.is_none() {
// bail!("Not found any one wire Ds18b20");
// }
// let device_address = first
// .unwrap()
// .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
// let water_temp_sensor = Ds18b20::new::<EspError>(device_address)
// .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
//
// water_temp_sensor
// .start_temp_measurement(&mut self.one_wire_bus, &mut self.delay)
// .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
// ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut self.delay);
// let sensor_data = water_temp_sensor
// .read_data(&mut self.one_wire_bus, &mut self.delay)
// .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
// if sensor_data.temperature == 85_f32 {
// bail!("Ds18b20 dummy temperature returned");
// }
//anyhow::Ok(sensor_data.temperature / 10_f32)
Ok(13_f32)
}
pub async fn tank_sensor_voltage(&mut self) -> anyhow::Result<f32> {
// self.tank_power.set_high()?;
// //let stabilize
// self.delay.delay_ms(100);
//
// let mut store = [0_u16; TANK_MULTI_SAMPLE];
// for multisample in 0..TANK_MULTI_SAMPLE {
// let value = self.tank_channel.read()?;
// store[multisample] = value;
// }
// self.tank_power.set_low()?;
//
// store.sort();
// let median_mv = store[6] as f32 / 1000_f32;
let median_mv = 10_f32;
anyhow::Ok(median_mv)
}
}

152
rust/src/FatError.rs Normal file
View File

@@ -0,0 +1,152 @@
use alloc::string::{String, ToString};
use core::convert::Infallible;
use core::fmt;
use embassy_executor::SpawnError;
use embassy_sync::mutex::TryLockError;
use esp_wifi::wifi::WifiError;
use littlefs2_core::PathError;
use onewire::Error;
//All error superconstruct
#[derive(Debug)]
pub enum FatError {
OneWireError {
error: Error<Infallible>,
},
String {
error: String,
},
LittleFSError {
error: littlefs2_core::Error,
},
PathError {
error: PathError,
},
TryLockError {
error: TryLockError,
},
WifiError {
error: WifiError,
},
SerdeError {
error: serde_json::Error,
},
PreconditionFailed {
error: String,
},
NoBatteryMonitor,
SpawnError {
error: SpawnError,
},
PartitionError {
error: esp_bootloader_esp_idf::partitions::Error,
},
}
pub type FatResult<T> = Result<T, FatError>;
impl fmt::Display for FatError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FatError::SpawnError { error } => {
write!(f, "SpawnError {:?}", error.to_string())
}
FatError::OneWireError { error } => write!(f, "OneWireError {:?}", error),
FatError::String { error } => write!(f, "{}", error),
FatError::LittleFSError { error } => write!(f, "LittleFSError {:?}", error),
FatError::PathError { error } => write!(f, "PathError {:?}", error),
FatError::TryLockError { error } => write!(f, "TryLockError {:?}", error),
FatError::WifiError { error } => write!(f, "WifiError {:?}", error),
FatError::SerdeError { error } => write!(f, "SerdeError {:?}", error),
FatError::PreconditionFailed { error } => write!(f, "PreconditionFailed {:?}", error),
FatError::PartitionError { error } => {
write!(f, "PartitionError {:?}", error)
}
FatError::NoBatteryMonitor => {
write!(f, "No Battery Monitor")
}
}
}
}
#[macro_export]
macro_rules! bail {
($msg:literal $(,)?) => {
return $crate::FatError::fat_bail($msg)
};
($fmt:literal, $($arg:tt)*) => {
return $crate::FatError::fat_bail(&alloc::format!($fmt, $($arg)*))
};
}
pub fn fat_bail<X>(message: &str) -> Result<X, FatError> {
Err(FatError::String {
error: message.to_string(),
})
}
pub trait ContextExt<T> {
fn context<C>(self, context: C) -> Result<T, FatError>
where
C: AsRef<str>;
}
impl<T> ContextExt<T> for Option<T> {
fn context<C>(self, context: C) -> Result<T, FatError>
where
C: AsRef<str>,
{
match self {
Some(value) => Ok(value),
None => Err(FatError::PreconditionFailed {
error: context.as_ref().to_string(),
}),
}
}
}
impl From<Error<Infallible>> for FatError {
fn from(error: Error<Infallible>) -> Self {
FatError::OneWireError { error }
}
}
impl From<littlefs2_core::Error> for FatError {
fn from(value: littlefs2_core::Error) -> Self {
FatError::LittleFSError { error: value }
}
}
impl From<PathError> for FatError {
fn from(value: PathError) -> Self {
FatError::PathError { error: value }
}
}
impl From<TryLockError> for FatError {
fn from(value: TryLockError) -> Self {
FatError::TryLockError { error: value }
}
}
impl From<WifiError> for FatError {
fn from(value: WifiError) -> Self {
FatError::WifiError { error: value }
}
}
impl From<serde_json::error::Error> for FatError {
fn from(value: serde_json::Error) -> Self {
FatError::SerdeError { error: value }
}
}
impl From<SpawnError> for FatError {
fn from(value: SpawnError) -> Self {
FatError::SpawnError { error: value }
}
}
impl From<esp_bootloader_esp_idf::partitions::Error> for FatError {
fn from(value: esp_bootloader_esp_idf::partitions::Error) -> Self {
FatError::PartitionError { error: value }
}
}

View File

@@ -1,27 +1,20 @@
use crate::hal::Box; use crate::FatError::{FatError, FatResult};
use alloc::string::String; use alloc::string::String;
use async_trait::async_trait; use async_trait::async_trait;
use core::error::Error;
use serde::Serialize; use serde::Serialize;
#[async_trait] #[async_trait]
pub trait BatteryInteraction { pub trait BatteryInteraction {
async fn state_charge_percent(&mut self) -> Result<f32, BatteryError>; async fn state_charge_percent(&mut self) -> FatResult<f32>;
async fn remaining_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>; async fn remaining_milli_ampere_hour(&mut self) -> FatResult<u16>;
async fn max_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>; async fn max_milli_ampere_hour(&mut self) -> FatResult<u16>;
async fn design_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>; async fn design_milli_ampere_hour(&mut self) -> FatResult<u16>;
async fn voltage_milli_volt(&mut self) -> Result<u16, BatteryError>; async fn voltage_milli_volt(&mut self) -> FatResult<u16>;
async fn average_current_milli_ampere(&mut self) -> Result<i16, BatteryError>; async fn average_current_milli_ampere(&mut self) -> FatResult<i16>;
async fn cycle_count(&mut self) -> Result<u16, BatteryError>; async fn cycle_count(&mut self) -> FatResult<u16>;
async fn state_health_percent(&mut self) -> Result<u16, BatteryError>; async fn state_health_percent(&mut self) -> FatResult<u16>;
async fn bat_temperature(&mut self) -> Result<u16, BatteryError>; async fn bat_temperature(&mut self) -> FatResult<u16>;
async fn get_battery_state(&mut self) -> Result<BatteryState, BatteryError>; async fn get_battery_state(&mut self) -> FatResult<BatteryState>;
}
impl From<BatteryError> for anyhow::Error {
fn from(err: BatteryError) -> Self {
anyhow::anyhow!(err)
}
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
@@ -60,43 +53,43 @@ pub enum BatteryState {
pub struct NoBatteryMonitor {} pub struct NoBatteryMonitor {}
#[async_trait] #[async_trait]
impl BatteryInteraction for NoBatteryMonitor { impl BatteryInteraction for NoBatteryMonitor {
async fn state_charge_percent(&mut self) -> Result<f32, BatteryError> { async fn state_charge_percent(&mut self) -> FatResult<f32> {
Err(BatteryError::NoBatteryMonitor) Err(FatError::NoBatteryMonitor)
} }
async fn remaining_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> { async fn remaining_milli_ampere_hour(&mut self) -> FatResult<u16> {
Err(BatteryError::NoBatteryMonitor) Err(FatError::NoBatteryMonitor)
} }
async fn max_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> { async fn max_milli_ampere_hour(&mut self) -> FatResult<u16> {
Err(BatteryError::NoBatteryMonitor) Err(FatError::NoBatteryMonitor)
} }
async fn design_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> { async fn design_milli_ampere_hour(&mut self) -> FatResult<u16> {
Err(BatteryError::NoBatteryMonitor) Err(FatError::NoBatteryMonitor)
} }
async fn voltage_milli_volt(&mut self) -> Result<u16, BatteryError> { async fn voltage_milli_volt(&mut self) -> FatResult<u16> {
Err(BatteryError::NoBatteryMonitor) Err(FatError::NoBatteryMonitor)
} }
async fn average_current_milli_ampere(&mut self) -> Result<i16, BatteryError> { async fn average_current_milli_ampere(&mut self) -> FatResult<i16> {
Err(BatteryError::NoBatteryMonitor) Err(FatError::NoBatteryMonitor)
} }
async fn cycle_count(&mut self) -> Result<u16, BatteryError> { async fn cycle_count(&mut self) -> FatResult<u16> {
Err(BatteryError::NoBatteryMonitor) Err(FatError::NoBatteryMonitor)
} }
async fn state_health_percent(&mut self) -> Result<u16, BatteryError> { async fn state_health_percent(&mut self) -> FatResult<u16> {
Err(BatteryError::NoBatteryMonitor) Err(FatError::NoBatteryMonitor)
} }
async fn bat_temperature(&mut self) -> Result<u16, BatteryError> { async fn bat_temperature(&mut self) -> FatResult<u16> {
Err(BatteryError::NoBatteryMonitor) Err(FatError::NoBatteryMonitor)
} }
async fn get_battery_state(&mut self) -> Result<BatteryState, BatteryError> { async fn get_battery_state(&mut self) -> FatResult<BatteryState> {
Ok(BatteryState::Unknown) Ok(BatteryState::Unknown)
} }
} }

View File

@@ -1,33 +1,31 @@
use crate::config::{NetworkConfig, PlantControllerConfig}; use crate::config::{NetworkConfig, PlantControllerConfig};
use crate::hal::{GW_IP_ADDR_ENV, PLANT_COUNT, TIME_ACCESS}; use crate::hal::{GW_IP_ADDR_ENV, PLANT_COUNT, TIME_ACCESS};
use crate::log::{ LogArray, LogMessage, LOG_ACCESS}; use crate::log::{LogMessage, LOG_ACCESS};
use crate::STAY_ALIVE; use crate::{bail, STAY_ALIVE};
use anyhow::{anyhow, bail, Context}; use chrono::{DateTime, Utc};
use chrono::{DateTime, FixedOffset, Utc};
use serde::Serialize; use serde::Serialize;
use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem; use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem;
use crate::FatError::{ContextExt, FatError, FatResult};
use alloc::string::ToString; use alloc::string::ToString;
use alloc::sync::Arc; use alloc::sync::Arc;
use alloc::{format, string::String, vec::Vec}; use alloc::{format, string::String, vec::Vec};
use core::marker::PhantomData; use core::marker::PhantomData;
use core::net::{IpAddr, Ipv4Addr}; use core::net::{IpAddr, Ipv4Addr};
use core::str::FromStr; use core::str::FromStr;
use embassy_executor::{Spawner}; use embassy_executor::Spawner;
use embassy_net::{Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4}; use embassy_net::{Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::Mutex; use embassy_sync::mutex::Mutex;
use embassy_time::{Duration, Instant, Timer}; use embassy_time::{Duration, Timer};
use embedded_storage::nor_flash::ReadNorFlash; use embedded_storage::nor_flash::ReadNorFlash;
use embedded_storage::Storage; use esp_bootloader_esp_idf::ota::{Ota, OtaImageState};
use esp_bootloader_esp_idf::ota::{Ota, OtaImageState, Slot}; use esp_bootloader_esp_idf::partitions::FlashRegion;
use esp_bootloader_esp_idf::partitions::{Error, FlashRegion, PartitionEntry, PartitionTable};
use esp_hal::gpio::Input; use esp_hal::gpio::Input;
use esp_hal::rng::Rng; use esp_hal::rng::Rng;
use esp_hal::rtc_cntl::Rtc;
use esp_hal::rtc_cntl::sleep::RtcSleepConfig; use esp_hal::rtc_cntl::sleep::RtcSleepConfig;
use esp_hal::system::software_reset; use esp_hal::system::software_reset;
use esp_println::{println}; use esp_println::println;
use esp_storage::FlashStorage; use esp_storage::FlashStorage;
use esp_wifi::wifi::{ use esp_wifi::wifi::{
AccessPointConfiguration, AccessPointInfo, Configuration, Interfaces, ScanConfig, AccessPointConfiguration, AccessPointInfo, Configuration, Interfaces, ScanConfig,
@@ -35,7 +33,7 @@ use esp_wifi::wifi::{
}; };
use littlefs2::fs::Filesystem; use littlefs2::fs::Filesystem;
use littlefs2_core::{DynFile, FileType, PathBuf, SeekFrom}; use littlefs2_core::{DynFile, FileType, PathBuf, SeekFrom};
use log::{info}; use log::info;
#[esp_hal::ram(rtc_fast, persistent)] #[esp_hal::ram(rtc_fast, persistent)]
static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT]; static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT];
@@ -113,12 +111,10 @@ macro_rules! mk_static {
} }
impl Esp<'_> { impl Esp<'_> {
pub(crate) async fn delete_file(&self, filename: String) -> anyhow::Result<()> { pub(crate) async fn delete_file(&self, filename: String) -> FatResult<()> {
let file = PathBuf::try_from(filename.as_str()).unwrap(); let file = PathBuf::try_from(filename.as_str()).unwrap();
let access = self.fs.lock().await; let access = self.fs.lock().await;
access access.remove(&*file)?;
.remove(&*file)
.map_err(|err| anyhow!("Could not delete file: {:?}", err))?;
Ok(()) Ok(())
} }
pub(crate) async fn write_file( pub(crate) async fn write_file(
@@ -126,11 +122,10 @@ impl Esp<'_> {
filename: String, filename: String,
offset: u32, offset: u32,
buf: &[u8], buf: &[u8],
) -> anyhow::Result<()> { ) -> Result<(), FatError> {
let file = PathBuf::try_from(filename.as_str()).unwrap(); let file = PathBuf::try_from(filename.as_str())?;
let access = self.fs.lock().await; let access = self.fs.lock().await;
info!("write file {} at offset {}", filename, offset); access.open_file_with_options_and_then(
match access.open_file_with_options_and_then(
|options| options.read(true).write(true).create(true), |options| options.read(true).write(true).create(true),
&*file, &*file,
|file| { |file| {
@@ -138,51 +133,45 @@ impl Esp<'_> {
file.write(buf)?; file.write(buf)?;
Ok(()) Ok(())
}, },
) { )?;
Ok(_) => Ok(()), Ok(())
Err(err) => { }
bail!(format!("{err:?}"))
} pub async fn get_size(&mut self, filename: String) -> FatResult<usize> {
} let file = PathBuf::try_from(filename.as_str())?;
let access = self.fs.lock().await;
let data = access.metadata(&*file)?;
Ok(data.len())
} }
pub(crate) async fn get_file( pub(crate) async fn get_file(
&mut self, &mut self,
filename: String, filename: String,
chunk: u32, chunk: u32,
) -> anyhow::Result<([u8; 128], usize)> { ) -> FatResult<([u8; 512], usize)> {
use littlefs2::io::Error as lfs2Error; use littlefs2::io::Error as lfs2Error;
let file = PathBuf::try_from(filename.as_str()).unwrap(); let file = PathBuf::try_from(filename.as_str())?;
let access = self.fs.lock().await; let access = self.fs.lock().await;
let mut buf = [0_u8; 128]; let mut buf = [0_u8; 512];
let mut read = 0; let mut read = 0;
let offset = chunk * 128; let offset = chunk * buf.len() as u32;
info!("read file {} at offset {}", filename, offset); access.open_file_with_options_and_then(
match access.open_file_with_options_and_then(
|options| options.read(true), |options| options.read(true),
&*file, &*file,
|file| { |file| {
let length = file.len()? as u32; let length = file.len()? as u32;
info!("file length {}", length);
if length == 0 { if length == 0 {
Err(lfs2Error::IO) Err(lfs2Error::IO)
} else if length > offset { } else if length > offset {
file.seek(SeekFrom::Start(offset))?; file.seek(SeekFrom::Start(offset))?;
info!("seek to {}", offset);
read = file.read(&mut buf)?; read = file.read(&mut buf)?;
info!("read {} bytes", read);
Ok(()) Ok(())
} else { } else {
//exactly at end, do nothing //exactly at end, do nothing
Ok(()) Ok(())
} }
}, },
) { )?;
Ok(_) => {}
Err(err) => {
bail!(format!("{err:?}"))
}
}
Ok((buf, read)) Ok((buf, read))
} }
@@ -219,7 +208,7 @@ impl Esp<'_> {
pub(crate) fn mode_override_pressed(&mut self) -> bool { pub(crate) fn mode_override_pressed(&mut self) -> bool {
self.boot_button.is_low() self.boot_button.is_low()
} }
pub(crate) async fn sntp(&mut self, _max_wait_ms: u32) -> anyhow::Result<DateTime<Utc>> { pub(crate) async fn sntp(&mut self, _max_wait_ms: u32) -> FatResult<DateTime<Utc>> {
//let sntp = sntp::EspSntp::new_default()?; //let sntp = sntp::EspSntp::new_default()?;
//let mut counter = 0; //let mut counter = 0;
//while sntp.get_sync_status() != SyncStatus::Completed { //while sntp.get_sync_status() != SyncStatus::Completed {
@@ -233,19 +222,15 @@ impl Esp<'_> {
todo!(); todo!();
} }
pub async fn flash_ota(&mut self) -> anyhow::Result<()> { pub async fn flash_ota(&mut self) -> FatResult<()> {
let capacity = self.ota_next.capacity(); let capacity = self.ota_next.capacity();
bail!("not implemented") bail!("not implemented")
} }
pub(crate) async fn wifi_scan(&mut self) -> FatResult<Vec<AccessPointInfo>> {
pub(crate) async fn wifi_scan(&mut self) -> anyhow::Result<Vec<AccessPointInfo>> {
info!("start wifi scan"); info!("start wifi scan");
let mut lock = self let mut lock = self.controller.try_lock()?;
.controller
.try_lock()
.map_err(|_| anyhow!("Could not lock wifi controller, currently in use"))?;
info!("start wifi scan lock"); info!("start wifi scan lock");
let scan_config = ScanConfig { let scan_config = ScanConfig {
ssid: None, ssid: None,
@@ -254,10 +239,7 @@ impl Esp<'_> {
show_hidden: false, show_hidden: false,
scan_type: ScanTypeConfig::Passive(core::time::Duration::from_secs(2)), scan_type: ScanTypeConfig::Passive(core::time::Duration::from_secs(2)),
}; };
let rv = lock let rv = lock.scan_with_config_async(scan_config).await?;
.scan_with_config_async(scan_config)
.await
.map_err(|err| anyhow!("Could not scan wifi: {:?}", err))?;
info!("end wifi scan lock"); info!("end wifi scan lock");
Ok(rv) Ok(rv)
} }
@@ -294,7 +276,7 @@ impl Esp<'_> {
unsafe { CONSECUTIVE_WATERING_PLANT[plant] } unsafe { CONSECUTIVE_WATERING_PLANT[plant] }
} }
pub(crate) fn get_restart_to_conf(&mut self) -> bool { pub(crate) fn get_restart_to_conf(&mut self) -> bool {
unsafe { RESTART_TO_CONF == 1} unsafe { RESTART_TO_CONF == 1 }
} }
pub(crate) fn set_restart_to_conf(&mut self, to_conf: bool) { pub(crate) fn set_restart_to_conf(&mut self, to_conf: bool) {
unsafe { unsafe {
@@ -306,7 +288,7 @@ impl Esp<'_> {
} }
} }
pub(crate) async fn wifi_ap(&mut self) -> anyhow::Result<Stack<'static>> { pub(crate) async fn wifi_ap(&mut self) -> FatResult<Stack<'static>> {
let ssid = match self.load_config().await { let ssid = match self.load_config().await {
Ok(config) => config.network.ap_ssid.as_str().to_string(), Ok(config) => config.network.ap_ssid.as_str().to_string(),
Err(_) => "PlantCtrl Emergency Mode".to_string(), Err(_) => "PlantCtrl Emergency Mode".to_string(),
@@ -358,7 +340,7 @@ impl Esp<'_> {
.config_v4() .config_v4()
.inspect(|c| println!("ipv4 config: {c:?}")); .inspect(|c| println!("ipv4 config: {c:?}"));
anyhow::Ok(stack.clone()) Ok(stack.clone())
} }
pub async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { pub async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
@@ -376,7 +358,6 @@ impl Esp<'_> {
if duration_in_ms == 0 { if duration_in_ms == 0 {
software_reset(); software_reset();
} else { } else {
loop { loop {
info!("todo deepsleep") info!("todo deepsleep")
} }
@@ -390,11 +371,19 @@ impl Esp<'_> {
//}; //};
} }
pub(crate) async fn wifi(&mut self, network_config: &NetworkConfig) -> anyhow::Result<IpInfo> { pub(crate) async fn wifi(&mut self, network_config: &NetworkConfig) -> FatResult<IpInfo> {
let _ssid = network_config let _ssid = network_config.ssid.clone();
.ssid match &_ssid {
.clone() Some(ssid) => {
.ok_or(anyhow!("No ssid configured"))?; if ssid.is_empty() {
bail!("Wifi ssid was empty")
}
}
None => {
bail!("Wifi ssid was empty")
}
}
let _ssid = _ssid.unwrap();
let _password = network_config.password.clone(); let _password = network_config.password.clone();
let _max_wait = network_config.max_wait; let _max_wait = network_config.max_wait;
bail!("todo") bail!("todo")
@@ -452,59 +441,32 @@ impl Esp<'_> {
// log(LogMessage::WifiInfo, 0, 0, "", &format!("{address:?}")); // log(LogMessage::WifiInfo, 0, 0, "", &format!("{address:?}"));
// anyhow::Ok(address) // anyhow::Ok(address)
} }
pub(crate) async fn load_config(&mut self) -> anyhow::Result<PlantControllerConfig> { pub(crate) async fn load_config(&mut self) -> FatResult<PlantControllerConfig> {
let cfg = PathBuf::try_from(CONFIG_FILE).unwrap(); let cfg = PathBuf::try_from(CONFIG_FILE).unwrap();
match self.fs.lock().await.read::<4096>(&cfg) { let data = self.fs.lock().await.read::<4096>(&cfg)?;
Ok(data) => { let config: PlantControllerConfig = serde_json::from_slice(&data)?;
let config: PlantControllerConfig = serde_json::from_slice(&data)?; return Ok(config);
anyhow::Ok(config)
}
Err(err) => {
bail!(format!("{err:?}"))
}
}
} }
pub(crate) async fn save_config(&mut self, config: Vec<u8>) -> anyhow::Result<()> { pub(crate) async fn save_config(&mut self, config: Vec<u8>) -> FatResult<()> {
let filesystem = self.fs.lock().await; let filesystem = self.fs.lock().await;
let cfg = PathBuf::try_from(CONFIG_FILE).unwrap(); let cfg = PathBuf::try_from(CONFIG_FILE)?;
filesystem.write(&cfg, &*config)?;
match filesystem.write(&cfg, &*config) { Ok(())
Ok(_) => {}
Err(err) => {
bail!(format!("{err:?}"))
}
}
anyhow::Ok(())
} }
async fn file_system_size(&mut self) -> anyhow::Result<FileSystemSizeInfo> { pub(crate) async fn list_files(&self) -> FatResult<FileList> {
bail!("fail");
// let storage = CString::new(Self::SPIFFS_PARTITION_NAME)?;
// let mut total_size = 0;
// let mut used_size = 0;
// unsafe {
// esp_idf_sys::esp!(esp_spiffs_info(
// storage.as_ptr(),
// &mut total_size,
// &mut used_size
// ))?;
// }
// anyhow::Ok(FileSystemSizeInfo {
// total_size,
// used_size,
// free_size: total_size - used_size,
// })
}
pub(crate) async fn list_files(&self) -> anyhow::Result<FileList> {
let path = PathBuf::new(); let path = PathBuf::new();
let fs = self.fs.lock().await;
let free_size = fs.available_space()?;
let total_size = fs.total_space();
let mut result = FileList { let mut result = FileList {
total: 0, total: total_size,
used: 0, used: total_size - free_size,
files: Vec::new(), files: Vec::new(),
}; };
match self.fs.lock().await.read_dir_and_then(&path, |dir| { fs.read_dir_and_then(&path, |dir| {
for entry in dir { for entry in dir {
let e = entry?; let e = entry?;
if e.file_type() == FileType::File { if e.file_type() == FileType::File {
@@ -514,29 +476,11 @@ impl Esp<'_> {
}); });
} }
} }
Result::Ok(()) Ok(())
}) { })?;
Ok(_) => {}
Err(err) => {
bail!(format!("{err:?}"))
}
}
Ok(result) Ok(result)
} }
// pub(crate) async fn get_file_handle(
// &self,
// filename: &str,
// write: bool,
// ) -> anyhow::Result<File> {
// let filepath = Path::new(Self::BASE_PATH).join(Path::new(filename));
// anyhow::Ok(if write {
// File::create(filepath)?
// } else {
// File::open(filepath)?
// })
// }
pub(crate) async fn init_rtc_deepsleep_memory( pub(crate) async fn init_rtc_deepsleep_memory(
&self, &self,
init_rtc_store: bool, init_rtc_store: bool,
@@ -558,22 +502,28 @@ impl Esp<'_> {
if to_config_mode { if to_config_mode {
RESTART_TO_CONF = 1; RESTART_TO_CONF = 1;
} }
LOG_ACCESS.lock().await.log( LOG_ACCESS
LogMessage::RestartToConfig, .lock()
RESTART_TO_CONF as u32, .await
0, .log(
"", LogMessage::RestartToConfig,
"", RESTART_TO_CONF as u32,
).await 0,
; "",
LOG_ACCESS.lock().await.log( "",
LogMessage::LowVoltage, )
LOW_VOLTAGE_DETECTED as u32, .await;
0, LOG_ACCESS
"", .lock()
"", .await
).await .log(
; LogMessage::LowVoltage,
LOW_VOLTAGE_DETECTED as u32,
0,
"",
"",
)
.await;
for i in 0..PLANT_COUNT { for i in 0..PLANT_COUNT {
log::info!( log::info!(
"LAST_WATERING_TIMESTAMP[{}] = UTC {}", "LAST_WATERING_TIMESTAMP[{}] = UTC {}",
@@ -592,7 +542,7 @@ impl Esp<'_> {
} }
} }
pub(crate) async fn mqtt(&mut self, network_config: &NetworkConfig) -> anyhow::Result<()> { pub(crate) async fn mqtt(&mut self, network_config: &NetworkConfig) -> FatResult<()> {
let base_topic = network_config let base_topic = network_config
.base_topic .base_topic
.as_ref() .as_ref()
@@ -756,11 +706,7 @@ impl Esp<'_> {
// } // }
// bail!("Mqtt did not fire connection callback in time"); // bail!("Mqtt did not fire connection callback in time");
} }
pub(crate) async fn mqtt_publish( pub(crate) async fn mqtt_publish(&mut self, _subtopic: &str, _message: &[u8]) -> FatResult<()> {
&mut self,
_subtopic: &str,
_message: &[u8],
) -> anyhow::Result<()> {
bail!("todo"); bail!("todo");
// //
// if self.mqtt_client.is_none() { // if self.mqtt_client.is_none() {

View File

@@ -3,16 +3,17 @@ use crate::hal::rtc::{BackupHeader, RTCModuleInteraction};
use alloc::vec::Vec; use alloc::vec::Vec;
//use crate::hal::water::TankSensor; //use crate::hal::water::TankSensor;
use crate::alloc::boxed::Box; use crate::alloc::boxed::Box;
use crate::hal::water::TankSensor;
use crate::hal::{BoardInteraction, FreePeripherals, Sensor}; use crate::hal::{BoardInteraction, FreePeripherals, Sensor};
use crate::FatError::FatError;
use crate::{ use crate::{
bail,
config::PlantControllerConfig, config::PlantControllerConfig,
hal::battery::{BatteryInteraction, NoBatteryMonitor}, hal::battery::{BatteryInteraction, NoBatteryMonitor},
}; };
use anyhow::{bail, Result};
use async_trait::async_trait; use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use esp_hal::gpio::{Level, Output, OutputConfig}; use esp_hal::gpio::{Level, Output, OutputConfig};
use log::info;
use measurements::{Current, Voltage}; use measurements::{Current, Voltage};
pub struct Initial<'a> { pub struct Initial<'a> {
@@ -23,27 +24,27 @@ pub struct Initial<'a> {
pub rtc: Box<dyn RTCModuleInteraction + Send>, pub rtc: Box<dyn RTCModuleInteraction + Send>,
} }
struct NoRTC {} pub(crate) struct NoRTC {}
#[async_trait] #[async_trait]
impl RTCModuleInteraction for NoRTC { impl RTCModuleInteraction for NoRTC {
async fn get_backup_info(&mut self) -> Result<BackupHeader> { async fn get_backup_info(&mut self) -> Result<BackupHeader, FatError> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
async fn get_backup_config(&mut self) -> Result<Vec<u8>> { async fn get_backup_config(&mut self) -> Result<Vec<u8>, FatError> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
async fn backup_config(&mut self, _bytes: &[u8]) -> Result<()> { async fn backup_config(&mut self, _bytes: &[u8]) -> Result<(), FatError> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
async fn get_rtc_time(&mut self) -> Result<DateTime<Utc>> { async fn get_rtc_time(&mut self) -> Result<DateTime<Utc>, FatError> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
async fn set_rtc_time(&mut self, _time: &DateTime<Utc>) -> Result<()> { async fn set_rtc_time(&mut self, _time: &DateTime<Utc>) -> Result<(), FatError> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
} }
@@ -52,7 +53,7 @@ pub(crate) fn create_initial_board(
free_pins: FreePeripherals<'static>, free_pins: FreePeripherals<'static>,
config: PlantControllerConfig, config: PlantControllerConfig,
esp: Esp<'static>, esp: Esp<'static>,
) -> Result<Box<dyn BoardInteraction<'static> + Send>> { ) -> Result<Box<dyn BoardInteraction<'static> + Send>, FatError> {
log::info!("Start initial"); log::info!("Start initial");
let general_fault = Output::new(free_pins.gpio23, Level::Low, OutputConfig::default()); let general_fault = Output::new(free_pins.gpio23, Level::Low, OutputConfig::default());
let v = Initial { let v = Initial {
@@ -67,9 +68,9 @@ pub(crate) fn create_initial_board(
#[async_trait] #[async_trait]
impl<'a> BoardInteraction<'a> for Initial<'a> { impl<'a> BoardInteraction<'a> for Initial<'a> {
// fn get_tank_sensor(&mut self) -> Option<&mut TankSensor<'a>> { fn get_tank_sensor(&mut self) -> Result<&mut TankSensor<'a>, FatError> {
// None bail!("Please configure board revision")
// } }
fn get_esp(&mut self) -> &mut Esp<'a> { fn get_esp(&mut self) -> &mut Esp<'a> {
&mut self.esp &mut self.esp
@@ -87,7 +88,7 @@ impl<'a> BoardInteraction<'a> for Initial<'a> {
&mut self.rtc &mut self.rtc
} }
fn set_charge_indicator(&mut self, _charging: bool) -> Result<()> { fn set_charge_indicator(&mut self, _charging: bool) -> Result<(), FatError> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
@@ -97,23 +98,27 @@ impl<'a> BoardInteraction<'a> for Initial<'a> {
fn is_day(&self) -> bool { fn is_day(&self) -> bool {
false false
} }
fn light(&mut self, _enable: bool) -> Result<()> { fn light(&mut self, _enable: bool) -> Result<(), FatError> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
async fn pump(&mut self, _plant: usize, _enable: bool) -> Result<()> { async fn pump(&mut self, _plant: usize, _enable: bool) -> Result<(), FatError> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
async fn pump_current(&mut self, _plant: usize) -> Result<Current> { async fn pump_current(&mut self, _plant: usize) -> Result<Current, FatError> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
async fn fault(&mut self, _plant: usize, _enable: bool) -> Result<()> { async fn fault(&mut self, _plant: usize, _enable: bool) -> Result<(), FatError> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
async fn measure_moisture_hz(&mut self, _plant: usize, _sensor: Sensor) -> Result<f32> { async fn measure_moisture_hz(
&mut self,
_plant: usize,
_sensor: Sensor,
) -> Result<f32, FatError> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
@@ -121,7 +126,7 @@ impl<'a> BoardInteraction<'a> for Initial<'a> {
self.general_fault.set_level(enable.into()); self.general_fault.set_level(enable.into());
} }
async fn test(&mut self) -> Result<()> { async fn test(&mut self) -> Result<(), FatError> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
@@ -129,11 +134,11 @@ impl<'a> BoardInteraction<'a> for Initial<'a> {
self.config = config; self.config = config;
} }
async fn get_mptt_voltage(&mut self) -> Result<Voltage> { async fn get_mptt_voltage(&mut self) -> Result<Voltage, FatError> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
async fn get_mptt_current(&mut self) -> Result<Current> { async fn get_mptt_current(&mut self) -> Result<Current, FatError> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
} }

View File

@@ -4,10 +4,9 @@ use esp_storage::FlashStorage;
use littlefs2::consts::U512 as lfsCache; use littlefs2::consts::U512 as lfsCache;
use littlefs2::consts::U512 as lfsLookahead; use littlefs2::consts::U512 as lfsLookahead;
use littlefs2::driver::Storage as lfs2Storage; use littlefs2::driver::Storage as lfs2Storage;
use littlefs2::fs::Filesystem as lfs2Filesystem;
use littlefs2::io::Error as lfs2Error; use littlefs2::io::Error as lfs2Error;
use littlefs2::io::Result as lfs2Result; use littlefs2::io::Result as lfs2Result;
use log::{error, info}; use log::error;
pub struct LittleFs2Filesystem { pub struct LittleFs2Filesystem {
pub(crate) storage: &'static mut FlashRegion<'static, FlashStorage>, pub(crate) storage: &'static mut FlashRegion<'static, FlashStorage>,
@@ -27,7 +26,7 @@ impl lfs2Storage for LittleFs2Filesystem {
assert_eq!(off % read_size, 0); assert_eq!(off % read_size, 0);
assert_eq!(buf.len() % read_size, 0); assert_eq!(buf.len() % read_size, 0);
match self.storage.read(off as u32, buf) { match self.storage.read(off as u32, buf) {
anyhow::Result::Ok(..) => lfs2Result::Ok(buf.len()), Ok(..) => Ok(buf.len()),
Err(err) => { Err(err) => {
error!("Littlefs2Filesystem read error: {:?}", err); error!("Littlefs2Filesystem read error: {:?}", err);
Err(lfs2Error::IO) Err(lfs2Error::IO)
@@ -36,16 +35,11 @@ impl lfs2Storage for LittleFs2Filesystem {
} }
fn write(&mut self, off: usize, data: &[u8]) -> lfs2Result<usize> { fn write(&mut self, off: usize, data: &[u8]) -> lfs2Result<usize> {
info!(
"Littlefs2Filesystem write at offset {} with len {}",
off,
data.len()
);
let write_size: usize = Self::WRITE_SIZE; let write_size: usize = Self::WRITE_SIZE;
assert_eq!(off % write_size, 0); assert_eq!(off % write_size, 0);
assert_eq!(data.len() % write_size, 0); assert_eq!(data.len() % write_size, 0);
match self.storage.write(off as u32, data) { match self.storage.write(off as u32, data) {
anyhow::Result::Ok(..) => lfs2Result::Ok(data.len()), Ok(..) => Ok(data.len()),
Err(err) => { Err(err) => {
error!("Littlefs2Filesystem write error: {:?}", err); error!("Littlefs2Filesystem write error: {:?}", err);
Err(lfs2Error::IO) Err(lfs2Error::IO)
@@ -54,10 +48,6 @@ impl lfs2Storage for LittleFs2Filesystem {
} }
fn erase(&mut self, off: usize, len: usize) -> lfs2Result<usize> { fn erase(&mut self, off: usize, len: usize) -> lfs2Result<usize> {
info!(
"Littlefs2Filesystem erase at offset {} with len {}",
off, len
);
let block_size: usize = Self::BLOCK_SIZE; let block_size: usize = Self::BLOCK_SIZE;
debug_assert!(off % block_size == 0); debug_assert!(off % block_size == 0);
debug_assert!(len % block_size == 0); debug_assert!(len % block_size == 0);

View File

@@ -3,28 +3,55 @@ pub mod esp;
mod initial_hal; mod initial_hal;
mod little_fs2storage_adapter; mod little_fs2storage_adapter;
mod rtc; mod rtc;
mod v4_hal;
mod water;
//mod water; //mod water;
use crate::alloc::string::ToString; use crate::alloc::string::ToString;
use crate::hal::rtc::RTCModuleInteraction; use crate::hal::rtc::RTCModuleInteraction;
use esp_hal::peripherals::Peripherals; use esp_hal::peripherals::Peripherals;
use esp_hal::peripherals::GPIO0;
use esp_hal::peripherals::GPIO1;
use esp_hal::peripherals::GPIO10;
use esp_hal::peripherals::GPIO11;
use esp_hal::peripherals::GPIO12;
use esp_hal::peripherals::GPIO13;
use esp_hal::peripherals::GPIO14;
use esp_hal::peripherals::GPIO15;
use esp_hal::peripherals::GPIO16;
use esp_hal::peripherals::GPIO17;
use esp_hal::peripherals::GPIO18;
use esp_hal::peripherals::GPIO2;
use esp_hal::peripherals::GPIO21;
use esp_hal::peripherals::GPIO22;
use esp_hal::peripherals::GPIO23; use esp_hal::peripherals::GPIO23;
use esp_hal::peripherals::GPIO24;
use esp_hal::peripherals::GPIO25;
use esp_hal::peripherals::GPIO26;
use esp_hal::peripherals::GPIO27;
use esp_hal::peripherals::GPIO28;
use esp_hal::peripherals::GPIO29;
use esp_hal::peripherals::GPIO3;
use esp_hal::peripherals::GPIO30;
use esp_hal::peripherals::GPIO4;
use esp_hal::peripherals::GPIO5;
use esp_hal::peripherals::GPIO6; use esp_hal::peripherals::GPIO6;
use esp_hal::peripherals::GPIO7;
use esp_hal::peripherals::GPIO8;
//use crate::hal::water::TankSensor; //use crate::hal::water::TankSensor;
use crate::{ use crate::{
bail,
config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig}, config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig},
hal::{ hal::{
battery::{BatteryInteraction, NoBatteryMonitor}, battery::{BatteryInteraction, NoBatteryMonitor},
esp::Esp, esp::Esp,
}, },
log::{LogMessage}, log::LogMessage,
}; };
use alloc::boxed::Box; use alloc::boxed::Box;
use alloc::format; use alloc::format;
use alloc::sync::Arc; use alloc::sync::Arc;
use core::cell::OnceCell;
use anyhow::{bail, Ok, Result};
use async_trait::async_trait; use async_trait::async_trait;
use chrono::{DateTime, FixedOffset, Utc}; use chrono::{DateTime, FixedOffset, Utc};
use embassy_executor::Spawner; use embassy_executor::Spawner;
@@ -39,6 +66,9 @@ use esp_hal::gpio::{Input, InputConfig, Pull};
use measurements::{Current, Voltage}; use measurements::{Current, Voltage};
use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem; use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem;
use crate::hal::water::TankSensor;
use crate::log::LOG_ACCESS;
use crate::FatError::FatError;
use embassy_sync::mutex::Mutex; use embassy_sync::mutex::Mutex;
use embassy_sync::once_lock::OnceLock; use embassy_sync::once_lock::OnceLock;
use esp_alloc as _; use esp_alloc as _;
@@ -53,7 +83,6 @@ use esp_wifi::{init, EspWifiController};
use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem}; use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem};
use littlefs2::object_safe::DynStorage; use littlefs2::object_safe::DynStorage;
use log::{info, warn}; use log::{info, warn};
use crate::log::{LogArray, LOG_ACCESS};
pub static TIME_ACCESS: OnceLock<Rtc> = OnceLock::new(); pub static TIME_ACCESS: OnceLock<Rtc> = OnceLock::new();
@@ -78,26 +107,26 @@ pub struct HAL<'a> {
#[async_trait] #[async_trait]
pub trait BoardInteraction<'a> { pub trait BoardInteraction<'a> {
//fn get_tank_sensor(&mut self) -> Option<&mut TankSensor>; fn get_tank_sensor(&mut self) -> Result<&mut TankSensor<'a>, FatError>;
fn get_esp(&mut self) -> &mut Esp<'a>; fn get_esp(&mut self) -> &mut Esp<'a>;
fn get_config(&mut self) -> &PlantControllerConfig; fn get_config(&mut self) -> &PlantControllerConfig;
fn get_battery_monitor(&mut self) -> &mut Box<dyn BatteryInteraction + Send>; fn get_battery_monitor(&mut self) -> &mut Box<dyn BatteryInteraction + Send>;
fn get_rtc_module(&mut self) -> &mut Box<dyn RTCModuleInteraction + Send>; fn get_rtc_module(&mut self) -> &mut Box<dyn RTCModuleInteraction + Send>;
fn set_charge_indicator(&mut self, charging: bool) -> Result<()>; fn set_charge_indicator(&mut self, charging: bool) -> Result<(), FatError>;
async fn deep_sleep(&mut self, duration_in_ms: u64) -> !; async fn deep_sleep(&mut self, duration_in_ms: u64) -> !;
fn is_day(&self) -> bool; fn is_day(&self) -> bool;
//should be multsampled //should be multsampled
fn light(&mut self, enable: bool) -> Result<()>; fn light(&mut self, enable: bool) -> Result<(), FatError>;
async fn pump(&mut self, plant: usize, enable: bool) -> Result<()>; async fn pump(&mut self, plant: usize, enable: bool) -> Result<(), FatError>;
async fn pump_current(&mut self, plant: usize) -> Result<Current>; async fn pump_current(&mut self, plant: usize) -> Result<Current, FatError>;
async fn fault(&mut self, plant: usize, enable: bool) -> Result<()>; async fn fault(&mut self, plant: usize, enable: bool) -> Result<(), FatError>;
async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32>; async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32, FatError>;
async fn general_fault(&mut self, enable: bool); async fn general_fault(&mut self, enable: bool);
async fn test(&mut self) -> Result<()>; async fn test(&mut self) -> Result<(), FatError>;
fn set_config(&mut self, config: PlantControllerConfig); fn set_config(&mut self, config: PlantControllerConfig);
async fn get_mptt_voltage(&mut self) -> anyhow::Result<Voltage>; async fn get_mptt_voltage(&mut self) -> Result<Voltage, FatError>;
async fn get_mptt_current(&mut self) -> anyhow::Result<Current>; async fn get_mptt_current(&mut self) -> Result<Current, FatError>;
} }
impl dyn BoardInteraction<'_> { impl dyn BoardInteraction<'_> {
@@ -119,36 +148,36 @@ impl dyn BoardInteraction<'_> {
#[allow(dead_code)] #[allow(dead_code)]
pub struct FreePeripherals<'a> { pub struct FreePeripherals<'a> {
// pub gpio0: Gpio0, pub gpio0: GPIO0<'a>,
// pub gpio1: Gpio1, pub gpio1: GPIO1<'a>,
// pub gpio2: Gpio2, pub gpio2: GPIO2<'a>,
// pub gpio3: Gpio3, pub gpio3: GPIO3<'a>,
// pub gpio4: Gpio4, pub gpio4: GPIO4<'a>,
// pub gpio5: Gpio5, pub gpio5: GPIO5<'a>,
pub gpio6: GPIO6<'a>, pub gpio6: GPIO6<'a>,
// pub gpio7: Gpio7, pub gpio7: GPIO7<'a>,
// pub gpio8: Gpio8, pub gpio8: GPIO8<'a>,
// //config button here // //config button here
// pub gpio10: Gpio10, pub gpio10: GPIO10<'a>,
// pub gpio11: Gpio11, pub gpio11: GPIO11<'a>,
// pub gpio12: Gpio12, pub gpio12: GPIO12<'a>,
// pub gpio13: Gpio13, pub gpio13: GPIO13<'a>,
// pub gpio14: Gpio14, pub gpio14: GPIO14<'a>,
// pub gpio15: Gpio15, pub gpio15: GPIO15<'a>,
// pub gpio16: Gpio16, pub gpio16: GPIO16<'a>,
// pub gpio17: Gpio17, pub gpio17: GPIO17<'a>,
// pub gpio18: Gpio18, pub gpio18: GPIO18<'a>,
// //i2c here // //i2c here
// pub gpio21: Gpio21, pub gpio21: GPIO21<'a>,
// pub gpio22: Gpio22, pub gpio22: GPIO22<'a>,
pub gpio23: GPIO23<'a>, pub gpio23: GPIO23<'a>,
// pub gpio24: Gpio24, pub gpio24: GPIO24<'a>,
// pub gpio25: Gpio25, pub gpio25: GPIO25<'a>,
// pub gpio26: Gpio26, pub gpio26: GPIO26<'a>,
// pub gpio27: Gpio27, pub gpio27: GPIO27<'a>,
// pub gpio28: Gpio28, pub gpio28: GPIO28<'a>,
// pub gpio29: Gpio29, pub gpio29: GPIO29<'a>,
// pub gpio30: Gpio30, pub gpio30: GPIO30<'a>,
// pub pcnt0: PCNT0, // pub pcnt0: PCNT0,
// pub pcnt1: PCNT1, // pub pcnt1: PCNT1,
// pub adc1: ADC1, // pub adc1: ADC1,
@@ -183,7 +212,9 @@ impl PlantHal {
// Mutex::new(I2cDriver::new(i2c, sda, scl, &config).unwrap()) // Mutex::new(I2cDriver::new(i2c, sda, scl, &config).unwrap())
// } // }
pub async fn create(spawner: Spawner) -> Result<Mutex<CriticalSectionRawMutex, HAL<'static>>> { pub async fn create(
spawner: Spawner,
) -> Result<Mutex<CriticalSectionRawMutex, HAL<'static>>, FatError> {
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
let peripherals: Peripherals = esp_hal::init(config); let peripherals: Peripherals = esp_hal::init(config);
@@ -191,7 +222,7 @@ impl PlantHal {
esp_alloc::heap_allocator!(#[link_section = ".dram2_uninit"] size: 64000); esp_alloc::heap_allocator!(#[link_section = ".dram2_uninit"] size: 64000);
let rtc: Rtc = Rtc::new(peripherals.LPWR); let rtc: Rtc = Rtc::new(peripherals.LPWR);
match(TIME_ACCESS.init(rtc)){ match (TIME_ACCESS.init(rtc)) {
Result::Ok(_) => {} Result::Ok(_) => {}
Err(_) => {} Err(_) => {}
} }
@@ -223,36 +254,35 @@ impl PlantHal {
// adc1: peripherals.adc1, // adc1: peripherals.adc1,
// pcnt0: peripherals.pcnt0, // pcnt0: peripherals.pcnt0,
// pcnt1: peripherals.pcnt1, // pcnt1: peripherals.pcnt1,
// gpio0: peripherals.pins.gpio0, gpio0: peripherals.GPIO0,
// gpio1: peripherals.pins.gpio1, gpio1: peripherals.GPIO1,
// gpio2: peripherals.pins.gpio2, gpio2: peripherals.GPIO2,
// gpio3: peripherals.pins.gpio3, gpio3: peripherals.GPIO3,
// gpio4: peripherals.pins.gpio4, gpio4: peripherals.GPIO4,
// gpio5: peripherals.pins.gpio5, gpio5: peripherals.GPIO5,
gpio6: peripherals.GPIO6, gpio6: peripherals.GPIO6,
// gpio7: peripherals.pins.gpio7, gpio7: peripherals.GPIO7,
// gpio8: peripherals.pins.gpio8, gpio8: peripherals.GPIO8,
// gpio10: peripherals.pins.gpio10, gpio10: peripherals.GPIO10,
// gpio11: peripherals.pins.gpio11, gpio11: peripherals.GPIO11,
// gpio12: peripherals.pins.gpio12, gpio12: peripherals.GPIO12,
// gpio13: peripherals.pins.gpio13, gpio13: peripherals.GPIO13,
// gpio14: peripherals.pins.gpio14, gpio14: peripherals.GPIO14,
// gpio15: peripherals.pins.gpio15, gpio15: peripherals.GPIO15,
// gpio16: peripherals.pins.gpio16, gpio16: peripherals.GPIO16,
// gpio17: peripherals.pins.gpio17, gpio17: peripherals.GPIO17,
// gpio18: peripherals.pins.gpio18, gpio18: peripherals.GPIO18,
// gpio21: peripherals.pins.gpio21, gpio21: peripherals.GPIO21,
// gpio22: peripherals.pins.gpio22, gpio22: peripherals.GPIO22,
gpio23: peripherals.GPIO23, gpio23: peripherals.GPIO23,
// gpio24: peripherals.pins.gpio24, gpio24: peripherals.GPIO24,
// gpio25: peripherals.pins.gpio25, gpio25: peripherals.GPIO25,
// gpio26: peripherals.pins.gpio26, gpio26: peripherals.GPIO26,
// gpio27: peripherals.pins.gpio27, gpio27: peripherals.GPIO27,
// gpio28: peripherals.pins.gpio28, gpio28: peripherals.GPIO28,
// gpio29: peripherals.pins.gpio29, gpio29: peripherals.GPIO29,
// gpio30: peripherals.pins.gpio30, gpio30: peripherals.GPIO30,
}; };
//
let tablebuffer = mk_static!( let tablebuffer = mk_static!(
[u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN], [u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN],
@@ -351,77 +381,45 @@ impl PlantHal {
let mut init_rtc_store: bool = false; let mut init_rtc_store: bool = false;
let mut to_config_mode: bool = false; let mut to_config_mode: bool = false;
let reasons = match reset_reason() { let reasons = match reset_reason() {
None => { None => "unknown",
"unknown" Some(reason) => match reason {
} SocResetReason::ChipPowerOn => "power on",
Some(reason) => { SocResetReason::CoreSw => "software reset",
match reason { SocResetReason::CoreDeepSleep => "deep sleep",
SocResetReason::ChipPowerOn => { SocResetReason::CoreSDIO => "sdio reset",
"power on" SocResetReason::CoreMwdt0 => "Watchdog Main",
} SocResetReason::CoreMwdt1 => "Watchdog 1",
SocResetReason::CoreSw => { SocResetReason::CoreRtcWdt => "Watchdog RTC",
"software reset" SocResetReason::Cpu0Mwdt0 => "Watchdog MCpu0",
} SocResetReason::Cpu0Sw => "software reset cpu0",
SocResetReason::CoreDeepSleep => { SocResetReason::Cpu0RtcWdt => {
"deep sleep" init_rtc_store = true;
} "Watchdog RTC cpu0"
SocResetReason::CoreSDIO => {
"sdio reset"
}
SocResetReason::CoreMwdt0 => {
"Watchdog Main"
}
SocResetReason::CoreMwdt1 => {
"Watchdog 1"
}
SocResetReason::CoreRtcWdt => {
"Watchdog RTC"
}
SocResetReason::Cpu0Mwdt0 => {
"Watchdog MCpu0"
}
SocResetReason::Cpu0Sw => {
"software reset cpu0"
}
SocResetReason::Cpu0RtcWdt => {
init_rtc_store = true;
"Watchdog RTC cpu0"
}
SocResetReason::SysBrownOut => {
"sys brown out"
}
SocResetReason::SysRtcWdt => {
"Watchdog Sys rtc"
}
SocResetReason::Cpu0Mwdt1 => {
"cpu0 mwdt1"
}
SocResetReason::SysSuperWdt => {
"Watchdog Super"
}
SocResetReason::CoreEfuseCrc => {
"core efuse crc"
}
SocResetReason::CoreUsbUart => {
to_config_mode = true;
"core usb uart"
}
SocResetReason::CoreUsbJtag => {
"core usb jtag"
}
SocResetReason::Cpu0JtagCpu => {
"cpu0 jtag cpu"
}
} }
} SocResetReason::SysBrownOut => "sys brown out",
SocResetReason::SysRtcWdt => "Watchdog Sys rtc",
SocResetReason::Cpu0Mwdt1 => "cpu0 mwdt1",
SocResetReason::SysSuperWdt => "Watchdog Super",
SocResetReason::CoreEfuseCrc => "core efuse crc",
SocResetReason::CoreUsbUart => {
to_config_mode = true;
"core usb uart"
}
SocResetReason::CoreUsbJtag => "core usb jtag",
SocResetReason::Cpu0JtagCpu => "cpu0 jtag cpu",
},
}; };
LOG_ACCESS.lock().await.log( LOG_ACCESS
LogMessage::ResetReason, .lock()
init_rtc_store as u32, .await
to_config_mode as u32, .log(
"", LogMessage::ResetReason,
&format!("{reasons:?}"), init_rtc_store as u32,
).await; to_config_mode as u32,
"",
&format!("{reasons:?}"),
)
.await;
esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode) esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode)
.await; .await;
@@ -449,7 +447,7 @@ impl PlantHal {
// } // }
// let storage = Storage::new(eeprom, Delay::new(1000)); // let storage = Storage::new(eeprom, Delay::new(1000));
//let rtc_module: Box<dyn RTCModuleInteraction + Send> = let rtc_module: Box<dyn RTCModuleInteraction + Send> = Box::new(initial_hal::NoRTC {});
// Box::new(DS3231Module { rtc, storage }) as Box<dyn RTCModuleInteraction + Send>; // Box::new(DS3231Module { rtc, storage }) as Box<dyn RTCModuleInteraction + Send>;
let hal = match config { let hal = match config {
@@ -487,32 +485,35 @@ impl PlantHal {
} }
}; };
let board_hal: Box<dyn BoardInteraction + Send> = //match config.hardware.board { let board_hal: Box<dyn BoardInteraction + Send> = match config.hardware.board {
//BoardVersion::INITIAL => { BoardVersion::INITIAL => {
initial_hal::create_initial_board(free_pins, config, esp)? initial_hal::create_initial_board(free_pins, config, esp)?
; }
//}
// BoardVersion::V3 => { // BoardVersion::V3 => {
// v3_hal::create_v3(free_pins, esp, config, battery_interaction, rtc_module)? // v3_hal::create_v3(free_pins, esp, config, battery_interaction, rtc_module)?
// } // }
//BoardVersion::V4 => { BoardVersion::V4 => {
// v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)? v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)?
//} }
//_ => { _ => {
// todo!() todo!()
//} }
//}; };
HAL { board_hal } HAL { board_hal }
} }
Err(err) => { Err(err) => {
LOG_ACCESS.lock().await.log( LOG_ACCESS
LogMessage::ConfigModeMissingConfig, .lock()
0, .await
0, .log(
"", LogMessage::ConfigModeMissingConfig,
&err.to_string(), 0,
).await; 0,
"",
&err.to_string(),
)
.await;
HAL { HAL {
board_hal: initial_hal::create_initial_board( board_hal: initial_hal::create_initial_board(
free_pins, free_pins,
@@ -527,11 +528,13 @@ impl PlantHal {
} }
} }
pub async fn esp_time() -> DateTime<Utc> { pub async fn esp_time() -> DateTime<Utc> {
DateTime::from_timestamp_micros(TIME_ACCESS.get().await.current_time_us() as i64).unwrap() DateTime::from_timestamp_micros(TIME_ACCESS.get().await.current_time_us() as i64).unwrap()
} }
pub async fn esp_set_time(time: DateTime<FixedOffset>) { pub async fn esp_set_time(time: DateTime<FixedOffset>) {
TIME_ACCESS.get().await.set_current_time_us(time.timestamp_micros() as u64); TIME_ACCESS
.get()
.await
.set_current_time_us(time.timestamp_micros() as u64);
} }

View File

@@ -1,4 +1,5 @@
use crate::hal::Box; use crate::hal::Box;
use crate::FatError::FatResult;
use alloc::vec::Vec; use alloc::vec::Vec;
use async_trait::async_trait; use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
@@ -24,11 +25,11 @@ use chrono::{DateTime, Utc};
// //
#[async_trait] #[async_trait]
pub trait RTCModuleInteraction { pub trait RTCModuleInteraction {
async fn get_backup_info(&mut self) -> anyhow::Result<BackupHeader>; async fn get_backup_info(&mut self) -> FatResult<BackupHeader>;
async fn get_backup_config(&mut self) -> anyhow::Result<Vec<u8>>; async fn get_backup_config(&mut self) -> FatResult<Vec<u8>>;
async fn backup_config(&mut self, bytes: &[u8]) -> anyhow::Result<()>; async fn backup_config(&mut self, bytes: &[u8]) -> FatResult<()>;
async fn get_rtc_time(&mut self) -> anyhow::Result<DateTime<Utc>>; async fn get_rtc_time(&mut self) -> FatResult<DateTime<Utc>>;
async fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> anyhow::Result<()>; async fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> FatResult<()>;
} }
// //
// const BACKUP_HEADER_MAX_SIZE: usize = 64; // const BACKUP_HEADER_MAX_SIZE: usize = 64;

426
rust/src/hal/v4_hal.rs Normal file
View File

@@ -0,0 +1,426 @@
use crate::config::PlantControllerConfig;
use crate::hal::battery::BatteryInteraction;
use crate::hal::esp::Esp;
use crate::hal::rtc::RTCModuleInteraction;
use crate::hal::water::TankSensor;
use crate::hal::{BoardInteraction, FreePeripherals, Sensor};
use alloc::boxed::Box;
use async_trait::async_trait;
//use embedded_hal_bus::i2c::MutexDevice;
use crate::bail;
use crate::FatError::FatError;
use esp_hal::gpio::{Flex, Level, Output, OutputConfig};
use measurements::{Current, Voltage};
// 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>,
// },
// 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<Voltage> {
// 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<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)
// })?,
// _ => {
// bail!("hardware error during init")
// }
// };
// Ok(current)
// }
// }
pub struct V4<'a> {
esp: Esp<'a>,
tank_sensor: TankSensor<'a>,
//charger: Charger<'a>,
rtc_module: Box<dyn RTCModuleInteraction + Send>,
battery_monitor: Box<dyn BatteryInteraction + Send>,
config: PlantControllerConfig,
awake: Output<'a>,
light: Output<'a>,
general_fault: Output<'a>,
//pump_expander: Pca9535Immediate<MutexDevice<'a, I2cDriver<'a>>>,
//pump_ina: Option<SyncIna219<MutexDevice<'a, I2cDriver<'a>>, UnCalibrated>>,
//sensor: SensorImpl<'a>,
extra1: Output<'a>,
extra2: Output<'a>,
}
struct InputOutput<'a> {
pin: Flex<'a>,
}
pub(crate) fn create_v4(
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 v4");
let mut awake = Output::new(peripherals.gpio21, Level::High, OutputConfig::default());
awake.set_high();
let mut general_fault = Output::new(peripherals.gpio23, Level::Low, OutputConfig::default());
general_fault.set_low();
let mut extra1 = Output::new(peripherals.gpio6, Level::Low, OutputConfig::default());
let mut extra2 = Output::new(peripherals.gpio15, Level::Low, OutputConfig::default());
let one_wire_pin = Flex::new(peripherals.gpio18);
let tank_power_pin = peripherals.gpio11;
let flow_sensor_pin = peripherals.gpio4;
let tank_sensor = TankSensor::create(
one_wire_pin,
//peripherals.adc1,
//peripherals.gpio5,
//tank_power_pin,
//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::<AnyInputPin>::None,
// Option::<AnyInputPin>::None,
// Option::<AnyInputPin>::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 mut pump_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 32);
// for pin in 0..8 {
// let _ = pump_expander.pin_into_output(GPIOBank::Bank0, pin);
// let _ = pump_expander.pin_into_output(GPIOBank::Bank1, pin);
// let _ = pump_expander.pin_set_low(GPIOBank::Bank0, pin);
// let _ = pump_expander.pin_set_low(GPIOBank::Bank1, pin);
// }
//
// let mppt_ina = SyncIna219::new(
// MutexDevice::new(&I2C_DRIVER),
// Address::from_pins(Pin::Vcc, Pin::Gnd),
// );
//
// 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 v = V4 {
rtc_module,
esp,
awake,
tank_sensor,
light,
general_fault,
//pump_ina,
//pump_expander,
config,
battery_monitor,
//charger,
extra1,
extra2,
//sensor,
};
Ok(Box::new(v))
}
#[async_trait]
impl<'a> BoardInteraction<'a> for V4<'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
}
fn set_charge_indicator(&mut self, charging: bool) -> Result<(), FatError> {
bail!("not implemented");
// self.charger.set_charge_indicator(charging)
}
async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
self.awake.set_low();
//self.charger.power_save();
self.esp.deep_sleep(duration_in_ms).await;
}
fn is_day(&self) -> bool {
false
//self.charger.is_day()
}
fn light(&mut self, enable: bool) -> Result<(), FatError> {
bail!("not implemented");
// unsafe { gpio_hold_dis(self.light.pin()) };
// self.light.set_state(enable.into())?;
// unsafe { gpio_hold_en(self.light.pin()) };
// anyhow::Ok(())
}
async fn pump(&mut self, plant: usize, enable: bool) -> Result<(), FatError> {
bail!("not implemented");
// if enable {
// self.pump_expander
// .pin_set_high(GPIOBank::Bank0, plant.try_into()?)?;
// } else {
// self.pump_expander
// .pin_set_low(GPIOBank::Bank0, plant.try_into()?)?;
// }
// anyhow::Ok(())
}
async fn pump_current(&mut self, _plant: usize) -> Result<Current, FatError> {
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)
// }
// }
}
async fn fault(&mut self, plant: usize, enable: bool) -> Result<(), FatError> {
bail!("not implemented");
// if enable {
// self.pump_expander
// .pin_set_high(GPIOBank::Bank1, plant.try_into()?)?
// } else {
// self.pump_expander
// .pin_set_low(GPIOBank::Bank1, plant.try_into()?)?
// }
// anyhow::Ok(())
}
async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32, FatError> {
bail!("not implemented");
//self.sensor.measure_moisture_hz(plant, sensor)
}
async fn general_fault(&mut self, enable: bool) {
//FIXME unsafe { gpio_hold_dis(self.general_fault.pin()) };
self.general_fault.set_level(enable.into());
//FIXME unsafe { gpio_hold_en(self.general_fault.pin()) };
}
async fn test(&mut self) -> Result<(), FatError> {
// self.general_fault(true);
// self.esp.delay.delay_ms(100);
// self.general_fault(false);
// self.esp.delay.delay_ms(500);
// self.light(true)?;
// self.esp.delay.delay_ms(500);
// self.light(false)?;
// self.esp.delay.delay_ms(500);
// for i in 0..PLANT_COUNT {
// self.fault(i, true)?;
// self.esp.delay.delay_ms(500);
// self.fault(i, false)?;
// self.esp.delay.delay_ms(500);
// }
// for i in 0..PLANT_COUNT {
// self.pump(i, true)?;
// self.esp.delay.delay_ms(100);
// self.pump(i, false)?;
// self.esp.delay.delay_ms(100);
// }
// for plant in 0..PLANT_COUNT {
// let a = self.measure_moisture_hz(plant, Sensor::A);
// let b = self.measure_moisture_hz(plant, Sensor::B);
// let aa = match a {
// OkStd(a) => a as u32,
// Err(_) => u32::MAX,
// };
// let bb = match b {
// OkStd(b) => b as u32,
// Err(_) => u32::MAX,
// };
// log(LogMessage::TestSensor, aa, bb, &plant.to_string(), "");
// }
// self.esp.delay.delay_ms(10);
Ok(())
}
fn set_config(&mut self, config: PlantControllerConfig) {
self.config = config;
}
async fn get_mptt_voltage(&mut self) -> Result<Voltage, FatError> {
bail!("not implemented");
//self.charger.get_mptt_voltage()
}
async fn get_mptt_current(&mut self) -> Result<Current, FatError> {
bail!("not implemented");
//self.charger.get_mptt_current()
}
}

172
rust/src/hal/water.rs Normal file
View File

@@ -0,0 +1,172 @@
use crate::bail;
use crate::FatError::FatError;
use embassy_time::Timer;
use esp_hal::delay::Delay;
use esp_hal::gpio::{Flex, OutputConfig, Pull};
use esp_println::println;
use onewire::{ds18b20, Device, DeviceSearch, OneWire, DS18B20};
pub struct TankSensor<'a> {
one_wire_bus: OneWire<Flex<'a>>,
// tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, ADC1>>,
// tank_power: PinDriver<'a, AnyIOPin, InputOutput>,
// flow_counter: PcntDriver<'a>,
// delay: Delay,
}
impl<'a> TankSensor<'a> {
pub(crate) fn create(
mut one_wire_pin: Flex,
// adc1: ADC1,
// gpio5: Gpio5,
// tank_power_pin: AnyIOPin,
// flow_sensor_pin: AnyIOPin,
// pcnt1: PCNT1,
) -> Result<TankSensor, FatError> {
one_wire_pin.apply_output_config(&OutputConfig::default().with_pull(Pull::None));
// let adc_config = AdcChannelConfig {
// attenuation: attenuation::DB_11,
// resolution: Resolution::Resolution12Bit,
// calibration: esp_idf_hal::adc::oneshot::config::Calibration::Curve,
// };
// let tank_driver = AdcDriver::new(adc1).expect("Failed to configure ADC");
// let tank_channel = AdcChannelDriver::new(tank_driver, gpio5, &adc_config)
// .expect("Failed to configure ADC channel");
//
// let mut tank_power =
// PinDriver::input_output(tank_power_pin).expect("Failed to configure pin");
// tank_power
// .set_pull(Pull::Floating)
// .expect("Failed to set pull");
//
let one_wire_bus = OneWire::new(one_wire_pin, false);
//
// let mut flow_counter = PcntDriver::new(
// pcnt1,
// Some(flow_sensor_pin),
// Option::<AnyInputPin>::None,
// Option::<AnyInputPin>::None,
// Option::<AnyInputPin>::None,
// )?;
//
// flow_counter.channel_config(
// PcntChannel::Channel1,
// 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,
// },
// )?;
//
Ok(TankSensor {
one_wire_bus,
// tank_channel,
// tank_power,
// flow_counter,
// delay: Default::default(),
})
}
pub fn reset_flow_meter(&mut self) {
// self.flow_counter.counter_pause().unwrap();
// self.flow_counter.counter_clear().unwrap();
}
pub fn start_flow_meter(&mut self) {
//self.flow_counter.counter_resume().unwrap();
}
pub fn get_flow_meter_value(&mut self) -> i16 {
//self.flow_counter.get_counter_value().unwrap()
5_i16
}
pub fn stop_flow_meter(&mut self) -> i16 {
//self.flow_counter.counter_pause().unwrap();
self.get_flow_meter_value()
}
pub async fn water_temperature_c(&mut self) -> Result<f32, FatError> {
//multisample should be moved to water_temperature_c
let mut attempt = 1;
let mut delay = Delay::new();
self.one_wire_bus.reset(&mut delay)?;
let mut search = DeviceSearch::new();
let mut water_temp_sensor: Option<Device> = None;
while let Some(device) = self.one_wire_bus.search_next(&mut search, &mut delay)? {
if device.address[0] == ds18b20::FAMILY_CODE {
water_temp_sensor = Some(device);
break;
}
}
match water_temp_sensor {
Some(device) => {
println!("Found one wire device: {:?}", device);
let mut water_temp_sensor = DS18B20::new(device)?;
let water_temp: Result<f32, FatError> = loop {
let temp = self
.single_temperature_c(&mut water_temp_sensor, &mut delay)
.await;
match &temp {
Ok(res) => {
println!("Water temp is {}", res);
break temp;
}
Err(err) => {
println!("Could not get water temp {} attempt {}", err, attempt)
}
}
if attempt == 5 {
break temp;
}
attempt += 1;
};
water_temp
}
None => {
bail!("Not found any one wire Ds18b20");
}
}
}
async fn single_temperature_c(
&mut self,
sensor: &mut DS18B20,
delay: &mut Delay,
) -> Result<f32, FatError> {
let resolution = sensor.measure_temperature(&mut self.one_wire_bus, delay)?;
Timer::after_millis(resolution.time_ms() as u64).await;
let temperature = sensor.read_temperature(&mut self.one_wire_bus, delay)? as f32;
if temperature == 85_f32 {
bail!("Ds18b20 dummy temperature returned");
}
Ok(temperature / 10_f32)
}
pub async fn tank_sensor_voltage(&mut self) -> Result<f32, FatError> {
// self.tank_power.set_high()?;
// //let stabilize
// self.delay.delay_ms(100);
//
// let mut store = [0_u16; TANK_MULTI_SAMPLE];
// for multisample in 0..TANK_MULTI_SAMPLE {
// let value = self.tank_channel.read()?;
// store[multisample] = value;
// }
// self.tank_power.set_low()?;
//
// store.sort();
// let median_mv = store[6] as f32 / 1000_f32;
let median_mv = 10_f32;
Ok(median_mv)
}
}

View File

@@ -1,39 +1,48 @@
use crate::hal::TIME_ACCESS;
use crate::vec; use crate::vec;
use alloc::string::ToString; use alloc::string::ToString;
use alloc::vec::Vec; use alloc::vec::Vec;
use bytemuck::{AnyBitPattern, Contiguous, Pod, Zeroable}; use bytemuck::{AnyBitPattern, Pod, Zeroable};
use deranged::RangedU8;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::Mutex; use embassy_sync::mutex::Mutex;
use embassy_time::Instant;
use esp_hal::Persistable; use esp_hal::Persistable;
use log::info; use log::info;
use serde::Serialize; use serde::Serialize;
use strum_macros::IntoStaticStr; use strum_macros::IntoStaticStr;
use unit_enum::UnitEnum; use unit_enum::UnitEnum;
use crate::hal::TIME_ACCESS;
const LOG_ARRAY_SIZE: u8 = 220;
const MAX_LOG_ARRAY_INDEX: u8 = LOG_ARRAY_SIZE - 1;
#[esp_hal::ram(rtc_fast, persistent)] #[esp_hal::ram(rtc_fast, persistent)]
static mut LOG_ARRAY: LogArray = LogArray { static mut LOG_ARRAY: LogArray = LogArray {
buffer: [LogEntryInner { timestamp: 0, message_id: 0, a: 0, b: 0, txt_short: [0;TXT_SHORT_LENGTH], txt_long: [0;TXT_LONG_LENGTH] }; 256], buffer: [LogEntryInner {
timestamp: 0,
message_id: 0,
a: 0,
b: 0,
txt_short: [0; TXT_SHORT_LENGTH],
txt_long: [0; TXT_LONG_LENGTH],
}; LOG_ARRAY_SIZE as usize],
head: 0, head: 0,
}; };
pub static LOG_ACCESS: Mutex<CriticalSectionRawMutex, LogArray> = Mutex::new(unsafe { LOG_ARRAY }); pub static LOG_ACCESS: Mutex<CriticalSectionRawMutex, &'static mut LogArray> =
unsafe { Mutex::new(&mut *&raw mut LOG_ARRAY) };
const TXT_SHORT_LENGTH: usize = 8; const TXT_SHORT_LENGTH: usize = 8;
const TXT_LONG_LENGTH: usize = 32; const TXT_LONG_LENGTH: usize = 32;
#[derive(Debug, Clone, Copy, AnyBitPattern)] #[derive(Debug, Clone, Copy, AnyBitPattern)]
#[repr(C)] #[repr(C)]
pub struct LogArray{ pub struct LogArray {
buffer: [LogEntryInner; (u8::MAX_VALUE as usize) +1], buffer: [LogEntryInner; LOG_ARRAY_SIZE as usize],
head: u8 head: u8,
} }
unsafe impl Persistable for LogArray {} unsafe impl Persistable for LogArray {}
unsafe impl Zeroable for LogEntryInner {} unsafe impl Zeroable for LogEntryInner {}
unsafe impl Pod for LogEntryInner{} unsafe impl Pod for LogEntryInner {}
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
struct LogEntryInner { struct LogEntryInner {
@@ -57,7 +66,7 @@ pub struct LogEntry {
impl From<LogEntryInner> for LogEntry { impl From<LogEntryInner> for LogEntry {
fn from(value: LogEntryInner) -> Self { fn from(value: LogEntryInner) -> Self {
LogEntry{ LogEntry {
timestamp: value.timestamp, timestamp: value.timestamp,
message_id: value.message_id, message_id: value.message_id,
a: value.a, a: value.a,
@@ -70,10 +79,13 @@ impl From<LogEntryInner> for LogEntry {
impl LogArray { impl LogArray {
pub fn get(&mut self) -> Vec<LogEntry> { pub fn get(&mut self) -> Vec<LogEntry> {
let head: RangedU8<0, MAX_LOG_ARRAY_INDEX> =
RangedU8::new(self.head).unwrap_or(RangedU8::new(0).unwrap());
let mut rv: Vec<LogEntry> = Vec::new(); let mut rv: Vec<LogEntry> = Vec::new();
let mut index = self.head.wrapping_sub(1); let mut index = head.wrapping_sub(1);
for _ in 0..self.buffer.len() { for _ in 0..self.buffer.len() {
let entry = self.buffer[index as usize]; let entry = self.buffer[index.get() as usize];
if (entry.message_id as usize) != LogMessage::Empty.ordinal() { if (entry.message_id as usize) != LogMessage::Empty.ordinal() {
rv.push(entry.into()); rv.push(entry.into());
} }
@@ -90,13 +102,16 @@ impl LogArray {
txt_short: &str, txt_short: &str,
txt_long: &str, txt_long: &str,
) { ) {
let mut head: RangedU8<0, MAX_LOG_ARRAY_INDEX> =
RangedU8::new(self.head).unwrap_or(RangedU8::new(0).unwrap());
let mut txt_short_stack: heapless::String<TXT_SHORT_LENGTH> = heapless::String::new(); let mut txt_short_stack: heapless::String<TXT_SHORT_LENGTH> = heapless::String::new();
let mut txt_long_stack: heapless::String<TXT_LONG_LENGTH> = heapless::String::new(); let mut txt_long_stack: heapless::String<TXT_LONG_LENGTH> = heapless::String::new();
limit_length(txt_short, &mut txt_short_stack); limit_length(txt_short, &mut txt_short_stack);
limit_length(txt_long, &mut txt_long_stack); limit_length(txt_long, &mut txt_long_stack);
let time = TIME_ACCESS.get().await.current_time_us()/1000; let time = TIME_ACCESS.get().await.current_time_us() / 1000;
let ordinal = message_key.ordinal() as u16; let ordinal = message_key.ordinal() as u16;
let template: &str = message_key.into(); let template: &str = message_key.into();
@@ -108,14 +123,19 @@ impl LogArray {
info!("{}", template_string); info!("{}", template_string);
let to_modify = &mut self.buffer[self.head as usize]; let to_modify = &mut self.buffer[head.get() as usize];
to_modify.timestamp = time; to_modify.timestamp = time;
to_modify.message_id = ordinal; to_modify.message_id = ordinal;
to_modify.a = number_a; to_modify.a = number_a;
to_modify.b = number_b; to_modify.b = number_b;
to_modify.txt_short.clone_from_slice(&txt_short_stack.as_bytes()); to_modify
to_modify.txt_long.clone_from_slice(&txt_long_stack.as_bytes()); .txt_short
self.head = self.head.wrapping_add(1); .clone_from_slice(&txt_short_stack.as_bytes());
to_modify
.txt_long
.clone_from_slice(&txt_long_stack.as_bytes());
head = head.wrapping_add(1);
self.head = head.get();
} }
} }
@@ -140,13 +160,9 @@ fn limit_length<const LIMIT: usize>(input: &str, target: &mut heapless::String<L
} }
} }
#[derive(IntoStaticStr, Serialize, PartialEq, Eq, PartialOrd, Ord, Clone, UnitEnum)] #[derive(IntoStaticStr, Serialize, PartialEq, Eq, PartialOrd, Ord, Clone, UnitEnum)]
pub enum LogMessage { pub enum LogMessage {
#[strum( #[strum(serialize = "")]
serialize = ""
)]
Empty, Empty,
#[strum( #[strum(
serialize = "Reset due to ${txt_long} requires rtc clear ${number_a} and force config mode ${number_b}" serialize = "Reset due to ${txt_long} requires rtc clear ${number_a} and force config mode ${number_b}"

View File

@@ -12,8 +12,11 @@ esp_bootloader_esp_idf::esp_app_desc!();
use esp_backtrace as _; use esp_backtrace as _;
use crate::config::PlantConfig; use crate::config::PlantConfig;
use crate::hal::esp_time;
use crate::log::LOG_ACCESS;
use crate::tank::WATER_FROZEN_THRESH; use crate::tank::WATER_FROZEN_THRESH;
use crate::webserver::httpd; use crate::webserver::httpd;
use crate::FatError::FatResult;
use crate::{ use crate::{
config::BoardVersion::INITIAL, config::BoardVersion::INITIAL,
hal::{PlantHal, HAL, PLANT_COUNT}, hal::{PlantHal, HAL, PLANT_COUNT},
@@ -32,15 +35,14 @@ use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::{Mutex, MutexGuard}; use embassy_sync::mutex::{Mutex, MutexGuard};
use embassy_sync::once_lock::OnceLock; use embassy_sync::once_lock::OnceLock;
use embassy_time::Timer; use embassy_time::Timer;
use esp_bootloader_esp_idf::ota::OtaImageState;
use esp_hal::rom::ets_delay_us; use esp_hal::rom::ets_delay_us;
use esp_hal::system::software_reset; use esp_hal::system::software_reset;
use esp_println::{logger, println}; use esp_println::{logger, println};
use hal::battery::BatteryState; use hal::battery::BatteryState;
use log::{ LogMessage}; use log::LogMessage;
use plant_state::PlantState; use plant_state::PlantState;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::hal::esp_time;
use crate::log::LOG_ACCESS;
#[no_mangle] #[no_mangle]
extern "C" fn custom_halt() -> ! { extern "C" fn custom_halt() -> ! {
@@ -56,6 +58,7 @@ extern "C" fn custom_halt() -> ! {
} }
//use tank::*; //use tank::*;
mod FatError;
mod config; mod config;
mod hal; mod hal;
mod log; mod log;
@@ -146,7 +149,7 @@ enum NetworkMode {
OFFLINE, OFFLINE,
} }
async fn safe_main(spawner: Spawner) -> anyhow::Result<()> { async fn safe_main(spawner: Spawner) -> FatResult<()> {
info!("Startup Rust"); info!("Startup Rust");
let mut to_config = false; let mut to_config = false;
@@ -183,28 +186,34 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> {
let _ota_state_string = "unknown"; let _ota_state_string = "unknown";
board.board_hal.general_fault(false).await; board.board_hal.general_fault(false).await;
let time = esp_time().await; let cur = match board.board_hal.get_rtc_module().get_rtc_time().await {
let cur = board Ok(value) => value,
.board_hal Err(err) => {
.get_rtc_module()
.get_rtc_time()
.await
.or_else(|err| {
info!("rtc module error: {:?}", err); info!("rtc module error: {:?}", err);
board.board_hal.general_fault(true); board.board_hal.general_fault(true).await;
anyhow::Ok(time) esp_time().await
})?; }
};
//check if we know the time current > 2020 (plausibility checks, this code is newer than 2020) //check if we know the time current > 2020 (plausibility checks, this code is newer than 2020)
if cur.year() < 2020 { if cur.year() < 2020 {
to_config = true; to_config = true;
LOG_ACCESS.lock().await.log(LogMessage::YearInplausibleForceConfig, 0, 0, "", "").await; LOG_ACCESS
.lock()
.await
.log(LogMessage::YearInplausibleForceConfig, 0, 0, "", "")
.await;
} }
info!("cur is {}", cur); info!("cur is {}", cur);
update_charge_indicator(&mut board).await; update_charge_indicator(&mut board).await;
println!("faul led3"); println!("faul led3");
if board.board_hal.get_esp().get_restart_to_conf() { if board.board_hal.get_esp().get_restart_to_conf() {
LOG_ACCESS.lock().await.log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", "").await; LOG_ACCESS
.lock()
.await
.log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", "")
.await;
for _i in 0..2 { for _i in 0..2 {
board.board_hal.general_fault(true).await; board.board_hal.general_fault(true).await;
Timer::after_millis(100).await; Timer::after_millis(100).await;
@@ -216,7 +225,11 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> {
board.board_hal.get_esp().set_restart_to_conf(false); board.board_hal.get_esp().set_restart_to_conf(false);
} else if board.board_hal.get_esp().mode_override_pressed() { } else if board.board_hal.get_esp().mode_override_pressed() {
board.board_hal.general_fault(true).await; board.board_hal.general_fault(true).await;
LOG_ACCESS.lock().await.log(LogMessage::ConfigModeButtonOverride, 0, 0, "", "").await; LOG_ACCESS
.lock()
.await
.log(LogMessage::ConfigModeButtonOverride, 0, 0, "", "")
.await;
for _i in 0..5 { for _i in 0..5 {
board.board_hal.general_fault(true).await; board.board_hal.general_fault(true).await;
Timer::after_millis(100).await; Timer::after_millis(100).await;
@@ -297,22 +310,25 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> {
let _ = publish_mppt_state(&mut board).await; let _ = publish_mppt_state(&mut board).await;
} }
LOG_ACCESS.lock().await.log( LOG_ACCESS
LogMessage::StartupInfo, .lock()
matches!(network_mode, NetworkMode::WIFI { .. }) as u32, .await
matches!( .log(
network_mode, LogMessage::StartupInfo,
NetworkMode::WIFI { matches!(network_mode, NetworkMode::WIFI { .. }) as u32,
sntp: SntpMode::SYNC { .. }, matches!(
.. network_mode,
} NetworkMode::WIFI {
) as u32, sntp: SntpMode::SYNC { .. },
matches!(network_mode, NetworkMode::WIFI { mqtt: true, .. }) ..
.to_string() }
.as_str(), ) as u32,
"", matches!(network_mode, NetworkMode::WIFI { mqtt: true, .. })
).await .to_string()
; .as_str(),
"",
)
.await;
if to_config { if to_config {
//check if client or ap mode and init Wi-Fi //check if client or ap mode and init Wi-Fi
@@ -323,7 +339,11 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> {
let board = BOARD_ACCESS.get().await.lock().await; let board = BOARD_ACCESS.get().await.lock().await;
wait_infinity(board, WaitType::ConfigButton, reboot_now.clone()).await; wait_infinity(board, WaitType::ConfigButton, reboot_now.clone()).await;
} else { } else {
LOG_ACCESS.lock().await.log(LogMessage::NormalRun, 0, 0, "", "").await; LOG_ACCESS
.lock()
.await
.log(LogMessage::NormalRun, 0, 0, "", "")
.await;
} }
let _dry_run = false; let _dry_run = false;
@@ -364,13 +384,10 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> {
// } // }
let mut _water_frozen = false; let mut _water_frozen = false;
//TODO let water_temp = board
let water_temp = anyhow::Ok(12_f32); .board_hal
// board .get_tank_sensor()
// .board_hal .and_then(async |sensor| sensor.water_temperature_c().await);
// .get_tank_sensor()
// .context("no sensor")
// .and_then(async |f| f.water_temperature_c().await);
if let Ok(res) = water_temp { if let Ok(res) = water_temp {
if res < WATER_FROZEN_THRESH { if res < WATER_FROZEN_THRESH {
@@ -615,7 +632,7 @@ pub async fn do_secure_pump(
plant_id: usize, plant_id: usize,
plant_config: &PlantConfig, plant_config: &PlantConfig,
dry_run: bool, dry_run: bool,
) -> anyhow::Result<PumpResult> { ) -> FatResult<PumpResult> {
let mut current_collector = vec![0_u16; plant_config.pump_time_s.into()]; let mut current_collector = vec![0_u16; plant_config.pump_time_s.into()];
let mut flow_collector = vec![0_i16; plant_config.pump_time_s.into()]; let mut flow_collector = vec![0_i16; plant_config.pump_time_s.into()];
let mut error = false; let mut error = false;
@@ -662,14 +679,17 @@ pub async fn do_secure_pump(
let high_current = current_ma > plant_config.max_pump_current_ma; let high_current = current_ma > plant_config.max_pump_current_ma;
if high_current { if high_current {
if first_error { if first_error {
LOG_ACCESS.lock().await.log( LOG_ACCESS
LogMessage::PumpOverCurrent, .lock()
plant_id as u32 + 1, .await
current_ma as u32, .log(
plant_config.max_pump_current_ma.to_string().as_str(), LogMessage::PumpOverCurrent,
step.to_string().as_str(), plant_id as u32 + 1,
).await current_ma as u32,
; plant_config.max_pump_current_ma.to_string().as_str(),
step.to_string().as_str(),
)
.await;
board.board_hal.general_fault(true).await; board.board_hal.general_fault(true).await;
board.board_hal.fault(plant_id, true).await?; board.board_hal.fault(plant_id, true).await?;
if !plant_config.ignore_current_error { if !plant_config.ignore_current_error {
@@ -682,14 +702,17 @@ pub async fn do_secure_pump(
let low_current = current_ma < plant_config.min_pump_current_ma; let low_current = current_ma < plant_config.min_pump_current_ma;
if low_current { if low_current {
if first_error { if first_error {
LOG_ACCESS.lock().await.log( LOG_ACCESS
LogMessage::PumpOpenLoopCurrent, .lock()
plant_id as u32 + 1, .await
current_ma as u32, .log(
plant_config.min_pump_current_ma.to_string().as_str(), LogMessage::PumpOpenLoopCurrent,
step.to_string().as_str(), plant_id as u32 + 1,
).await current_ma as u32,
; plant_config.min_pump_current_ma.to_string().as_str(),
step.to_string().as_str(),
)
.await;
board.board_hal.general_fault(true).await; board.board_hal.general_fault(true).await;
board.board_hal.fault(plant_id, true).await?; board.board_hal.fault(plant_id, true).await?;
if !plant_config.ignore_current_error { if !plant_config.ignore_current_error {
@@ -703,14 +726,17 @@ pub async fn do_secure_pump(
Err(err) => { Err(err) => {
if !plant_config.ignore_current_error { if !plant_config.ignore_current_error {
info!("Error getting pump current: {}", err); info!("Error getting pump current: {}", err);
LOG_ACCESS.lock().await.log( LOG_ACCESS
LogMessage::PumpMissingSensorCurrent, .lock()
plant_id as u32, .await
0, .log(
"", LogMessage::PumpMissingSensorCurrent,
"", plant_id as u32,
).await 0,
; "",
"",
)
.await;
error = true; error = true;
break; break;
} else { } else {
@@ -957,7 +983,7 @@ async fn pump_info(
async fn publish_mppt_state( async fn publish_mppt_state(
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>, board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
) -> anyhow::Result<()> { ) -> FatResult<()> {
let current = board.board_hal.get_mptt_current().await?; let current = board.board_hal.get_mptt_current().await?;
let voltage = board.board_hal.get_mptt_voltage().await?; let voltage = board.board_hal.get_mptt_voltage().await?;
let solar_state = Solar { let solar_state = Solar {
@@ -983,13 +1009,18 @@ async fn publish_battery_state(
.get_battery_monitor() .get_battery_monitor()
.get_battery_state() .get_battery_state()
.await; .await;
if let Ok(serialized_battery_state_bytes) = let value = match state {
serde_json::to_string(&state).map(|s| s.into_bytes()) Ok(state) => {
let json = serde_json::to_string(&state).unwrap().to_owned();
json.as_bytes().to_owned()
}
Err(_) => "error".as_bytes().to_owned(),
};
{ {
board let _ = board
.board_hal .board_hal
.get_esp() .get_esp()
.mqtt_publish("/battery", &serialized_battery_state_bytes) .mqtt_publish("/battery", &*value)
.await; .await;
} }
} }
@@ -1062,13 +1093,14 @@ async fn wait_infinity(
.lock() .lock()
.await .await
.board_hal .board_hal
.deep_sleep(0).await; .deep_sleep(0)
.await;
} }
} }
} }
#[esp_hal_embassy::main] #[esp_hal_embassy::main]
async fn main(spawner: Spawner) { async fn main(spawner: Spawner) -> ! {
// intialize embassy // intialize embassy
logger::init_logger_from_env(); logger::init_logger_from_env();
//force init here! //force init here!
@@ -1082,21 +1114,13 @@ async fn main(spawner: Spawner) {
println!("Hal init done, starting logic"); println!("Hal init done, starting logic");
match safe_main(spawner).await { match safe_main(spawner).await {
// this should not get triggered, safe_main should not return but go into deep sleep with sensible // this should not get triggered, safe_main should not return but go into deep sleep or reboot
// timeout, this is just a fallback
Ok(_) => { Ok(_) => {
warn!("Main app finished, but should never do, restarting"); panic!("Main app finished, but should never do, restarting");
let board = &mut BOARD_ACCESS.get().await.lock().await.board_hal;
board.get_esp().set_restart_to_conf(false);
board.deep_sleep(1);
} }
// if safe_main exists with an error, rollback to a known good ota version // if safe_main exists with an error, rollback to a known good ota version
Err(err) => { Err(err) => {
error!("Failed main {}", err); panic!("Failed main {}", err);
//TODO
//let _rollback_successful = rollback_and_reboot();
//panic!("Failed to rollback :(");
} }
} }
} }

View File

@@ -1,5 +1,9 @@
use crate::alloc::string::{String, ToString}; use crate::alloc::string::{String, ToString};
use crate::config::TankConfig; use crate::config::TankConfig;
use crate::hal::HAL;
use crate::FatError::FatResult;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::MutexGuard;
use serde::Serialize; use serde::Serialize;
const OPEN_TANK_VOLTAGE: f32 = 3.0; const OPEN_TANK_VOLTAGE: f32 = 3.0;
@@ -113,7 +117,7 @@ impl TankState {
} }
} }
pub fn as_mqtt_info(&self, config: &TankConfig, water_temp: &anyhow::Result<f32>) -> TankInfo { pub fn as_mqtt_info(&self, config: &TankConfig, water_temp: &FatResult<f32>) -> TankInfo {
let mut tank_err: Option<TankError> = None; let mut tank_err: Option<TankError> = None;
let left_ml = match self.left_ml(config) { let left_ml = match self.left_ml(config) {
Err(err) => { Err(err) => {
@@ -150,40 +154,41 @@ impl TankState {
} }
} }
// pub fn determine_tank_state(board: &mut std::sync::MutexGuard<'_, HAL<'_>>) -> TankState { pub async fn determine_tank_state(
// if board.board_hal.get_config().tank.tank_sensor_enabled { board: &mut MutexGuard<'static, CriticalSectionRawMutex, HAL<'static>>,
// match board ) -> TankState {
// .board_hal if board.board_hal.get_config().tank.tank_sensor_enabled {
// .get_tank_sensor() match board
// .context("no sensor") .board_hal
// .and_then(|f| f.tank_sensor_voltage()) .get_tank_sensor()
// { .and_then(|f| core::prelude::v1::Ok(f.tank_sensor_voltage()))
// Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv), {
// Err(err) => TankState::Error(TankError::BoardError(err.to_string())), Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv.await.unwrap()),
// } Err(err) => TankState::Error(TankError::BoardError(err.to_string())),
// } else { }
// TankState::Disabled } else {
// } TankState::Disabled
// } }
}
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
/// Information structure send to mqtt for monitoring purposes /// Information structure send to mqtt for monitoring purposes
pub struct TankInfo { pub struct TankInfo {
/// there is enough water in the tank /// there is enough water in the tank
enough_water: bool, pub(crate) enough_water: bool,
/// warning that water needs to be refilled soon /// warning that water needs to be refilled soon
warn_level: bool, pub(crate) warn_level: bool,
/// estimation how many ml are still in the tank /// estimation how many ml are still in the tank
left_ml: Option<f32>, pub(crate) left_ml: Option<f32>,
/// if there is an issue with the water level sensor /// if there is an issue with the water level sensor
sensor_error: Option<TankError>, pub(crate) sensor_error: Option<TankError>,
/// raw water sensor value /// raw water sensor value
raw: Option<f32>, pub(crate) raw: Option<f32>,
/// percent value /// percent value
percent: Option<f32>, pub(crate) percent: Option<f32>,
/// water in the tank might be frozen /// water in the tank might be frozen
water_frozen: bool, pub(crate) water_frozen: bool,
/// water temperature /// water temperature
water_temp: Option<f32>, pub(crate) water_temp: Option<f32>,
temp_sensor_error: Option<String>, pub(crate) temp_sensor_error: Option<String>,
} }

View File

@@ -1,23 +1,25 @@
//offer ota and config mode //offer ota and config mode
use crate::config::PlantControllerConfig; use crate::config::PlantControllerConfig;
use crate::{get_version, log::LogMessage, BOARD_ACCESS}; use crate::hal::{esp_set_time, esp_time};
use crate::log::LOG_ACCESS;
use crate::tank::{determine_tank_state, TankInfo};
use crate::FatError::{FatError, FatResult};
use crate::{bail, get_version, log::LogMessage, BOARD_ACCESS};
use alloc::borrow::ToOwned; use alloc::borrow::ToOwned;
use alloc::format; use alloc::format;
use alloc::string::{String, ToString}; use alloc::string::{String, ToString};
use alloc::sync::Arc; use alloc::sync::Arc;
use alloc::vec::Vec; use alloc::vec::Vec;
use anyhow::{bail}; use chrono::DateTime;
use core::fmt::{Debug, Display}; use core::fmt::{Debug, Display};
use core::net::{IpAddr, Ipv4Addr, SocketAddr}; use core::net::{IpAddr, Ipv4Addr, SocketAddr};
use core::result::Result::Ok; use core::result::Result::Ok;
use core::str::from_utf8; use core::str::from_utf8;
use core::sync::atomic::{AtomicBool, Ordering}; use core::sync::atomic::{AtomicBool, Ordering};
use chrono::DateTime;
use edge_http::io::server::{Connection, Handler, Server}; use edge_http::io::server::{Connection, Handler, Server};
use edge_http::io::Error;
use edge_http::Method; use edge_http::Method;
use edge_nal::{TcpBind}; use edge_nal::TcpBind;
use edge_nal_embassy::{Tcp, TcpBuffers}; use edge_nal_embassy::{Tcp, TcpBuffers};
use embassy_net::Stack; use embassy_net::Stack;
use embassy_time::Instant; use embassy_time::Instant;
@@ -25,8 +27,6 @@ use embedded_io_async::{Read, Write};
use esp_println::println; use esp_println::println;
use log::info; use log::info;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::hal::{esp_set_time, esp_time};
use crate::log::{LOG_ACCESS};
#[derive(Serialize, Debug)] #[derive(Serialize, Debug)]
struct SSIDList { struct SSIDList {
@@ -191,23 +191,6 @@ pub struct NightLampCommand {
// anyhow::Ok(Some(serde_json::to_string(&pump_result)?)) // anyhow::Ok(Some(serde_json::to_string(&pump_result)?))
// } // }
// //
// fn tank_info(
// _request: &mut Request<&mut EspHttpConnection>,
// ) -> Result<Option<std::string::String>, anyhow::Error> {
// let mut board = BOARD_ACCESS.lock().unwrap();
// let tank_info = determine_tank_state(&mut board);
// //should be multsampled
//
// let water_temp = board
// .board_hal
// .get_tank_sensor()
// .context("no sensor")
// .and_then(|f| f.water_temperature_c());
// Ok(Some(serde_json::to_string(&tank_info.as_mqtt_info(
// &board.board_hal.get_config().tank,
// &water_temp,
// ))?))
// }
// //
// fn night_lamp_test( // fn night_lamp_test(
// request: &mut Request<&mut EspHttpConnection>, // request: &mut Request<&mut EspHttpConnection>,
@@ -285,12 +268,12 @@ struct HttpHandler {
} }
impl Handler for HttpHandler { impl Handler for HttpHandler {
type Error<E: core::fmt::Debug> = Error<E>; type Error<E: Debug> = FatError;
async fn handle<'a, T, const N: usize>( async fn handle<'a, T, const N: usize>(
&self, &self,
_task_id: impl Display + Copy, _task_id: impl Display + Copy,
conn: &mut Connection<'a, T, N>, conn: &mut Connection<'a, T, N>,
) -> anyhow::Result<(), Self::Error<T::Error>> ) -> Result<(), FatError>
where where
T: Read + Write, T: Read + Write,
{ {
@@ -303,46 +286,55 @@ impl Handler for HttpHandler {
let prefix = "/file?filename="; let prefix = "/file?filename=";
let status = if path.starts_with(prefix) { let status = if path.starts_with(prefix) {
let filename = &path[prefix.len()..]; let filename = &path[prefix.len()..];
let mut board = BOARD_ACCESS.get().await.lock().await;
info!("file request for {} with method {}", filename, method); info!("file request for {} with method {}", filename, method);
match method { match method {
Method::Delete => { Method::Delete => {
let mut board = BOARD_ACCESS.get().await.lock().await;
board board
.board_hal .board_hal
.get_esp() .get_esp()
.delete_file(filename.to_owned()) .delete_file(filename.to_owned())
.await .await?;
.unwrap();
Some(200) Some(200)
} }
Method::Get => { Method::Get => {
let disp = format!("attachment; filename=\"{filename}\""); let disp = format!("attachment; filename=\"{filename}\"");
let size = {
let mut board = BOARD_ACCESS.get().await.lock().await;
board
.board_hal
.get_esp()
.get_size(filename.to_owned())
.await?
};
conn.initiate_response( conn.initiate_response(
200, 200,
Some("OK"), Some("OK"),
&[ &[
("Content-Type", "application/octet-stream"), ("Content-Type", "application/octet-stream"),
("Content-Disposition", disp.as_str()), ("Content-Disposition", disp.as_str()),
("Content-Length", &format!("{}", size)),
], ],
) )
.await?; .await?;
let mut chunk = 0; let mut chunk = 0;
loop { loop {
let mut board = BOARD_ACCESS.get().await.lock().await;
let read_chunk = board let read_chunk = board
.board_hal .board_hal
.get_esp() .get_esp()
.get_file(filename.to_owned(), chunk) .get_file(filename.to_owned(), chunk)
.await .await?;
.unwrap();
let length = read_chunk.1; let length = read_chunk.1;
info!("read {} bytes for file request for {}", length, filename);
if length == 0 { if length == 0 {
info!("file request for {} finished", filename); info!("file request for {} finished", filename);
break; break;
} }
let data = &read_chunk.0[0..length]; let data = &read_chunk.0[0..length];
conn.write_all(data).await?; conn.write_all(data).await?;
if length < 128 { if length < read_chunk.0.len() {
info!("file request for {} finished", filename); info!("file request for {} finished", filename);
break; break;
} }
@@ -351,12 +343,16 @@ impl Handler for HttpHandler {
Some(200) Some(200)
} }
Method::Post => { Method::Post => {
//ensure file is deleted, otherwise we would need to truncate the file which will not work with streaming {
let _ = board let mut board = BOARD_ACCESS.get().await.lock().await;
.board_hal //ensure file is deleted, otherwise we would need to truncate the file which will not work with streaming
.get_esp() let _ = board
.delete_file(filename.to_owned()) .board_hal
.await; .get_esp()
.delete_file(filename.to_owned())
.await;
}
let mut offset = 0_usize; let mut offset = 0_usize;
loop { loop {
let mut buf = [0_u8; 1024]; let mut buf = [0_u8; 1024];
@@ -365,16 +361,12 @@ impl Handler for HttpHandler {
info!("file request for {} finished", filename); info!("file request for {} finished", filename);
break; break;
} else { } else {
info!( let mut board = BOARD_ACCESS.get().await.lock().await;
"writing {} bytes for file request for {}",
to_write, filename
);
board board
.board_hal .board_hal
.get_esp() .get_esp()
.write_file(filename.to_owned(), offset as u32, &buf[0..to_write]) .write_file(filename.to_owned(), offset as u32, &buf[0..to_write])
.await .await?;
.unwrap();
} }
offset = offset + to_write offset = offset + to_write
} }
@@ -397,25 +389,32 @@ impl Handler for HttpHandler {
Some(200) Some(200)
} }
"/" => { "/" => {
conn.initiate_response(200, Some("OK"), &[("Content-Type", "text/html")]) conn.initiate_response(
.await?; 200,
conn.write_all(include_bytes!("index.html")).await?; Some("OK"),
&[("Content-Type", "text/html"), ("Content-Encoding", "gzip")],
)
.await?;
conn.write_all(include_bytes!("index.html.gz")).await?;
Some(200) Some(200)
} }
"/bundle.js" => { "/bundle.js" => {
conn.initiate_response( conn.initiate_response(
200, 200,
Some("OK"), Some("OK"),
&[("Content-Type", "text/javascript")], &[
("Content-Type", "text/javascript"),
("Content-Encoding", "gzip"),
],
) )
.await?; .await?;
conn.write_all(include_bytes!("bundle.js")).await?; conn.write_all(include_bytes!("bundle.js.gz")).await?;
Some(200) Some(200)
} }
"/log" => { "/log" => {
let buf = get_log(conn).await; let buf = get_log(conn).await;
Some(200) Some(200)
}, }
&_ => { &_ => {
let json = match path { let json = match path {
"/version" => Some(get_version_web(conn).await), "/version" => Some(get_version_web(conn).await),
@@ -554,23 +553,40 @@ impl Handler for HttpHandler {
// }) // })
// .unwrap(); // .unwrap();
async fn tank_info<T, const N: usize>(
request: &mut Connection<'_, T, N>,
) -> Result<Option<String>, FatError>
where
T: Read + Write,
{
let mut board = BOARD_ACCESS.get().await.lock().await;
determine_tank_state(&mut board);
//should be multsampled
let sensor = board.board_hal.get_tank_sensor()?;
let water_temp = sensor.water_temperature_c().await?;
Ok(Some(serde_json::to_string(&tank_info.as_mqtt_info(
&board.board_hal.get_config().tank,
&water_temp,
))?))
}
async fn write_time<T, const N: usize>( async fn write_time<T, const N: usize>(
request: &mut Connection<'_, T, N>, request: &mut Connection<'_, T, N>,
) -> Result<Option<String>, anyhow::Error> ) -> FatResult<Option<String>>
where where
T: Read + Write T: Read + Write,
{ {
let actual_data = read_up_to_bytes_from_request(request, None).await?; let actual_data = read_up_to_bytes_from_request(request, None).await?;
let time: SetTime = serde_json::from_slice(&actual_data)?; let time: SetTime = serde_json::from_slice(&actual_data)?;
let parsed = DateTime::parse_from_rfc3339(time.time).unwrap(); let parsed = DateTime::parse_from_rfc3339(time.time).unwrap();
esp_set_time(parsed).await; esp_set_time(parsed).await;
anyhow::Ok(None) Ok(None)
} }
async fn set_config<T, const N: usize>( async fn set_config<T, const N: usize>(
request: &mut Connection<'_, T, N>, request: &mut Connection<'_, T, N>,
) -> Result<Option<String>, anyhow::Error> ) -> FatResult<Option<String>>
where where
T: Read + Write, T: Read + Write,
{ {
@@ -582,13 +598,13 @@ where
board.board_hal.get_esp().save_config(all).await?; board.board_hal.get_esp().save_config(all).await?;
log::info!("Wrote config config {:?} with size {}", config, length); log::info!("Wrote config config {:?} with size {}", config, length);
board.board_hal.set_config(config); board.board_hal.set_config(config);
anyhow::Ok(Some("saved".to_string())) Ok(Some("saved".to_string()))
} }
async fn read_up_to_bytes_from_request<T, const N: usize>( async fn read_up_to_bytes_from_request<T, const N: usize>(
request: &mut Connection<'_, T, N>, request: &mut Connection<'_, T, N>,
limit: Option<usize>, limit: Option<usize>,
) -> Result<Vec<u8>, anyhow::Error> ) -> FatResult<Vec<u8>>
where where
T: Read + Write, T: Read + Write,
{ {
@@ -597,10 +613,7 @@ where
let mut total_read = 0; let mut total_read = 0;
loop { loop {
let mut buf = [0_u8; 64]; let mut buf = [0_u8; 64];
let read = match request.read(&mut buf).await { let read = request.read(&mut buf).await?;
Ok(read) => read,
Err(e) => bail!("Error reading request {:?}", e),
};
if read == 0 { if read == 0 {
break; break;
} }
@@ -618,7 +631,7 @@ where
async fn wifi_scan<T, const N: usize>( async fn wifi_scan<T, const N: usize>(
_request: &mut Connection<'_, T, N>, _request: &mut Connection<'_, T, N>,
) -> Result<Option<String>, anyhow::Error> { ) -> FatResult<Option<String>> {
let mut board = BOARD_ACCESS.get().await.lock().await; let mut board = BOARD_ACCESS.get().await.lock().await;
info!("start wifi scan"); info!("start wifi scan");
//let scan_result = board.board_hal.get_esp().wifi_scan().await? //let scan_result = board.board_hal.get_esp().wifi_scan().await?
@@ -629,93 +642,87 @@ async fn wifi_scan<T, const N: usize>(
//.for_each(|s| ssids.push(s.ssid.to_string())); //.for_each(|s| ssids.push(s.ssid.to_string()));
let ssid_json = serde_json::to_string(&SSIDList { ssids })?; let ssid_json = serde_json::to_string(&SSIDList { ssids })?;
info!("Sending ssid list {}", &ssid_json); info!("Sending ssid list {}", &ssid_json);
anyhow::Ok(Some(ssid_json)) Ok(Some(ssid_json))
} }
async fn get_log<T, const N: usize>( async fn get_log<T, const N: usize>(conn: &mut Connection<'_, T, N>) -> FatResult<()>
conn: &mut Connection<'_, T, N>,
) -> anyhow::Result<()>
where where
T: Read + Write,{ T: Read + Write,
{
let log = LOG_ACCESS.lock().await.get(); let log = LOG_ACCESS.lock().await.get();
conn.initiate_response( conn.initiate_response(200, Some("OK"), &[("Content-Type", "text/javascript")])
200, .await?;
Some("OK"), conn.write_all("[".as_bytes()).await?;
&[("Content-Type", "text/javascript")],
)
.await
.unwrap();
conn.write_all("[".as_bytes()).await.unwrap();
let mut append = false; let mut append = false;
for entry in log { for entry in log {
if append { if append {
conn.write_all(",".as_bytes()).await.unwrap(); conn.write_all(",".as_bytes()).await?;
} }
append = true; append = true;
let json = serde_json::to_string(&entry)?; let json = serde_json::to_string(&entry)?;
conn.write_all(json.as_bytes()).await.unwrap(); conn.write_all(json.as_bytes()).await?;
} }
conn.write_all("]".as_bytes()).await.unwrap(); conn.write_all("]".as_bytes()).await?;
Ok(()) Ok(())
} }
async fn get_log_localization_config<T, const N: usize>( async fn get_log_localization_config<T, const N: usize>(
_request: &mut Connection<'_, T, N>, _request: &mut Connection<'_, T, N>,
) -> Result<Option<String>, anyhow::Error> { ) -> FatResult<Option<String>> {
anyhow::Ok(Some(serde_json::to_string( Ok(Some(serde_json::to_string(
&LogMessage::to_log_localisation_config(), &LogMessage::to_log_localisation_config(),
)?)) )?))
} }
async fn list_files<T, const N: usize>( async fn list_files<T, const N: usize>(
_request: &mut Connection<'_, T, N>, _request: &mut Connection<'_, T, N>,
) -> Result<Option<String>, anyhow::Error> { ) -> FatResult<Option<String>> {
let mut board = BOARD_ACCESS.get().await.lock().await; let mut board = BOARD_ACCESS.get().await.lock().await;
let result = board.board_hal.get_esp().list_files().await?; let result = board.board_hal.get_esp().list_files().await?;
let file_list_json = serde_json::to_string(&result)?; let file_list_json = serde_json::to_string(&result)?;
anyhow::Ok(Some(file_list_json)) Ok(Some(file_list_json))
} }
async fn get_config<T, const N: usize>( async fn get_config<T, const N: usize>(
_request: &mut Connection<'_, T, N>, _request: &mut Connection<'_, T, N>,
) -> Result<Option<String>, anyhow::Error> { ) -> FatResult<Option<String>> {
let mut board = BOARD_ACCESS.get().await.lock().await; let mut board = BOARD_ACCESS.get().await.lock().await;
let json = serde_json::to_string(&board.board_hal.get_config())?; let json = serde_json::to_string(&board.board_hal.get_config())?;
anyhow::Ok(Some(json)) Ok(Some(json))
} }
async fn get_solar_state<T, const N: usize>( async fn get_solar_state<T, const N: usize>(
_request: &mut Connection<'_, T, N>, _request: &mut Connection<'_, T, N>,
) -> Result<Option<String>, anyhow::Error> { ) -> FatResult<Option<String>> {
let mut board = BOARD_ACCESS.get().await.lock().await; let mut board = BOARD_ACCESS.get().await.lock().await;
let state = SolarState { let state = SolarState {
mppt_voltage: board.board_hal.get_mptt_voltage().await?.as_millivolts() as f32, mppt_voltage: board.board_hal.get_mptt_voltage().await?.as_millivolts() as f32,
mppt_current: board.board_hal.get_mptt_current().await?.as_milliamperes() as f32, mppt_current: board.board_hal.get_mptt_current().await?.as_milliamperes() as f32,
is_day: board.board_hal.is_day(), is_day: board.board_hal.is_day(),
}; };
anyhow::Ok(Some(serde_json::to_string(&state)?)) Ok(Some(serde_json::to_string(&state)?))
} }
async fn get_battery_state<T, const N: usize>( async fn get_battery_state<T, const N: usize>(
_request: &mut Connection<'_, T, N>, _request: &mut Connection<'_, T, N>,
) -> Result<Option<String>, anyhow::Error> { ) -> FatResult<Option<String>> {
let mut board = BOARD_ACCESS.get().await.lock().await; let mut board = BOARD_ACCESS.get().await.lock().await;
let battery_state = board let battery_state = board
.board_hal .board_hal
.get_battery_monitor() .get_battery_monitor()
.get_battery_state() .get_battery_state()
.await?; .await?;
anyhow::Ok(Some(serde_json::to_string(&battery_state)?)) Ok(Some(serde_json::to_string(&battery_state)?))
} }
async fn get_version_web<T, const N: usize>( async fn get_version_web<T, const N: usize>(
_request: &mut Connection<'_, T, N>, _request: &mut Connection<'_, T, N>,
) -> Result<Option<String>, anyhow::Error> { ) -> FatResult<Option<String>> {
let mut board = BOARD_ACCESS.get().await.lock().await; let mut board = BOARD_ACCESS.get().await.lock().await;
anyhow::Ok(Some(serde_json::to_string(&get_version(&mut board).await)?)) Ok(Some(serde_json::to_string(&get_version(&mut board).await)?))
} }
async fn get_time<T, const N: usize>( async fn get_time<T, const N: usize>(
_request: &mut Connection<'_, T, N>, _request: &mut Connection<'_, T, N>,
) -> Result<Option<String>, anyhow::Error> { ) -> FatResult<Option<String>> {
let mut board = BOARD_ACCESS.get().await.lock().await; let mut board = BOARD_ACCESS.get().await.lock().await;
//TODO do not fail if rtc module is missing //TODO do not fail if rtc module is missing
let native = esp_time().await.to_rfc3339(); let native = esp_time().await.to_rfc3339();
@@ -733,7 +740,7 @@ async fn get_time<T, const N: usize>(
}; };
let json = serde_json::to_string(&data)?; let json = serde_json::to_string(&data)?;
anyhow::Ok(Some(json)) Ok(Some(json))
} }
#[embassy_executor::task] #[embassy_executor::task]
@@ -943,8 +950,8 @@ pub async fn httpd(reboot_now: Arc<AtomicBool>, stack: Stack<'static>) {
async fn handle_json<'a, T, const N: usize>( async fn handle_json<'a, T, const N: usize>(
conn: &mut Connection<'a, T, N>, conn: &mut Connection<'a, T, N>,
chain: anyhow::Result<Option<String>>, chain: FatResult<Option<String>>,
) -> anyhow::Result<u32, Error<T::Error>> ) -> FatResult<u32>
where where
T: Read + Write, T: Read + Write,
<T as embedded_io_async::ErrorType>::Error: Debug, <T as embedded_io_async::ErrorType>::Error: Debug,

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
{ {
"devDependencies": { "devDependencies": {
"compression-webpack-plugin": "^11.1.0",
"html-webpack-harddisk-plugin": "^2.0.0", "html-webpack-harddisk-plugin": "^2.0.0",
"html-webpack-plugin": "^5.6.3", "html-webpack-plugin": "^5.6.3",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",

View File

@@ -3,54 +3,60 @@ const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin'); const HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin');
const CopyPlugin = require("copy-webpack-plugin"); const CopyPlugin = require("copy-webpack-plugin");
const CompressionPlugin = require("compression-webpack-plugin");
const isDevServer = process.env.WEBPACK_SERVE; const isDevServer = process.env.WEBPACK_SERVE;
console.log("Dev server is " + isDevServer); console.log("Dev server is " + isDevServer);
var host; var host;
if (isDevServer){ if (isDevServer) {
//ensure no trailing / //ensure no trailing /
host = 'http://10.23.44.186'; host = 'http://10.23.44.186';
} else { } else {
host = ''; host = '';
} }
module.exports = { module.exports = {
mode: "development", mode: "development",
entry: ['./src/main.ts'], entry: ['./src/main.ts'],
devtool: 'inline-source-map', devtool: 'inline-source-map',
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
PUBLIC_URL: JSON.stringify(host), PUBLIC_URL: JSON.stringify(host),
}), }),
new webpack.EnvironmentPlugin({ new webpack.EnvironmentPlugin({
redirect: 'true' redirect: 'true'
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
alwaysWriteToDisk: true, alwaysWriteToDisk: true,
title: "PlantCtrl", title: "PlantCtrl",
}), }),
new HtmlWebpackHarddiskPlugin(), new HtmlWebpackHarddiskPlugin(),
], new CompressionPlugin({
module: { algorithm: "gzip",
rules: [ test: /\.js$|\.css$|\.html$/,
{ threshold: 0,
test: /\.html$/, minRatio: 0.8
type: 'asset/source', })
}, ],
{ module: {
test: /\.tsx?$/, rules: [
use: 'ts-loader', {
exclude: /node_modules/, test: /\.html$/,
} type: 'asset/source',
] },
}, {
resolve: { test: /\.tsx?$/,
extensions: ['.tsx', '.ts', '.js', '.html'], use: 'ts-loader',
}, exclude: /node_modules/,
output: { }
filename: 'bundle.js', ]
path: path.resolve(__dirname, '.'), },
}, resolve: {
devServer: { extensions: ['.tsx', '.ts', '.js', '.html'],
} },
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, '.'),
},
devServer: {}
}; };