Compare commits
4 Commits
c94f5bdb45
...
b594a02870
| Author | SHA1 | Date | |
|---|---|---|---|
| b594a02870 | |||
| 1f3349c348 | |||
| 5b0e2b6797 | |||
| 1791f463b7 |
@@ -24,6 +24,8 @@ CHRONO_TZ_TIMEZONE_FILTER = "UTC|America/New_York|America/Chicago|America/Los_An
|
||||
CARGO_WORKSPACE_DIR = { value = "", relative = true }
|
||||
ESP_LOG = "info"
|
||||
|
||||
|
||||
|
||||
[unstable]
|
||||
build-std = ["alloc", "core"]
|
||||
|
||||
|
||||
@@ -26,25 +26,14 @@ command = [
|
||||
"partitions.csv"
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
lto = true
|
||||
strip = false
|
||||
debug = false
|
||||
overflow-checks = true
|
||||
panic = "abort"
|
||||
incremental = true
|
||||
opt-level = 3
|
||||
|
||||
|
||||
[profile.release]
|
||||
# Explicitly disable LTO which the Xtensa codegen backend has issues
|
||||
lto = true
|
||||
lto = "fat"
|
||||
strip = true
|
||||
debug = false
|
||||
overflow-checks = true
|
||||
panic = "abort"
|
||||
incremental = true
|
||||
opt-level = 3
|
||||
opt-level = "z"
|
||||
|
||||
|
||||
[package.metadata.espflash]
|
||||
@@ -61,7 +50,7 @@ esp-hal = { version = "=1.0.0-rc.0", features = [
|
||||
] }
|
||||
log = "0.4.27"
|
||||
|
||||
embassy-net = { version = "0.7.0", features = [
|
||||
embassy-net = { version = "0.7.1", default-features = false, features = [
|
||||
"dhcpv4",
|
||||
"log",
|
||||
"medium-ethernet",
|
||||
@@ -83,9 +72,10 @@ esp-println = { version = "0.15.0", features = ["esp32c6", "log-04"] }
|
||||
# for more networking protocol support see https://crates.io/crates/edge-net
|
||||
embassy-executor = { version = "0.7.0", features = [
|
||||
"log",
|
||||
"task-arena-size-131072"
|
||||
"task-arena-size-64",
|
||||
"nightly"
|
||||
] }
|
||||
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-storage = { version = "0.7.0", features = ["esp32c6"] }
|
||||
|
||||
@@ -117,16 +107,12 @@ heapless = { version = "0.8", features = ["serde"] }
|
||||
embedded-hal-bus = { version = "0.3.0" }
|
||||
|
||||
#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
|
||||
#once_cell = "1.19.0"
|
||||
anyhow = { version = "1.0.75", default-features = false }
|
||||
#bq34z100 = { version = "0.3.0", default-features = false }
|
||||
onewire = "0.4.0"
|
||||
#strum = { version = "0.27.0", default-feature = false, features = ["derive"] }
|
||||
measurements = "0.11.0"
|
||||
ds323x = "0.6.0"
|
||||
|
||||
#json
|
||||
serde = { version = "1.0.219", features = ["derive", "alloc"], default-features = false }
|
||||
@@ -156,7 +142,9 @@ edge-http = { version = "0.6.1", features = ["log"] }
|
||||
littlefs2 = { version = "0.6.1", features = ["c-stubs", "alloc"] }
|
||||
littlefs2-core = "0.1.1"
|
||||
bytemuck = { version = "1.23.2", features = ["derive", "min_const_generics", "pod_saturating", "extern_crate_alloc"] }
|
||||
|
||||
deranged = "0.5.3"
|
||||
embassy-embedded-hal = "0.5.0"
|
||||
bincode = { version = "2.0.1", default-features = false, features = ["derive"] }
|
||||
|
||||
[patch.crates-io]
|
||||
#bq34z100 = { path = "../../bq34z100_rust" }
|
||||
|
||||
@@ -58,7 +58,7 @@ fn main() {
|
||||
fn webpack() {
|
||||
//println!("cargo:rerun-if-changed=./src/src_webpack");
|
||||
Command::new("rm")
|
||||
.arg("./src/webserver/bundle.js")
|
||||
.arg("./src/webserver/bundle.js.gz")
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
@@ -81,14 +81,14 @@ fn webpack() {
|
||||
let _ = Command::new("cmd")
|
||||
.arg("/K")
|
||||
.arg("move")
|
||||
.arg("./src_webpack/bundle.js")
|
||||
.arg("./src_webpack/bundle.js.gz")
|
||||
.arg("./src/webserver")
|
||||
.output()
|
||||
.unwrap();
|
||||
let _ = Command::new("cmd")
|
||||
.arg("/K")
|
||||
.arg("move")
|
||||
.arg("./src_webpack/index.html")
|
||||
.arg("./src_webpack/index.html.gz")
|
||||
.arg("./src/webserver")
|
||||
.output()
|
||||
.unwrap();
|
||||
@@ -107,12 +107,12 @@ fn webpack() {
|
||||
|
||||
// move webpack results to rust webserver src
|
||||
let _ = Command::new("mv")
|
||||
.arg("./src_webpack/bundle.js")
|
||||
.arg("./src_webpack/bundle.js.gz")
|
||||
.arg("./src/webserver")
|
||||
.output()
|
||||
.unwrap();
|
||||
let _ = Command::new("mv")
|
||||
.arg("./src_webpack/index.html")
|
||||
.arg("./src_webpack/index.html.gz")
|
||||
.arg("./src/webserver")
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
toolchain = "esp"
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
235
rust/src/FatError.rs
Normal file
235
rust/src/FatError.rs
Normal file
@@ -0,0 +1,235 @@
|
||||
use alloc::format;
|
||||
use alloc::string::{String, ToString};
|
||||
use core::convert::Infallible;
|
||||
use core::fmt;
|
||||
use core::str::Utf8Error;
|
||||
use embassy_embedded_hal::shared_bus::I2cDeviceError;
|
||||
use embassy_executor::SpawnError;
|
||||
use embassy_sync::mutex::TryLockError;
|
||||
use esp_hal::i2c::master::ConfigError;
|
||||
use esp_wifi::wifi::WifiError;
|
||||
use littlefs2_core::PathError;
|
||||
use onewire::Error;
|
||||
use pca9535::ExpanderError;
|
||||
|
||||
//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,
|
||||
},
|
||||
I2CConfigError {
|
||||
error: ConfigError,
|
||||
},
|
||||
DS323 {
|
||||
error: String,
|
||||
},
|
||||
Eeprom24x {
|
||||
error: String,
|
||||
},
|
||||
ExpanderError {
|
||||
error: String,
|
||||
},
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
FatError::I2CConfigError { error } => write!(f, "I2CConfigError {:?}", error),
|
||||
FatError::DS323 { error } => write!(f, "DS323 {:?}", error),
|
||||
FatError::Eeprom24x { error } => write!(f, "Eeprom24x {:?}", error),
|
||||
FatError::ExpanderError { error } => write!(f, "ExpanderError {:?}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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 }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Utf8Error> for FatError {
|
||||
fn from(value: Utf8Error) -> Self {
|
||||
FatError::String {
|
||||
error: value.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: core::fmt::Debug> From<edge_http::io::Error<E>> for FatError {
|
||||
fn from(value: edge_http::io::Error<E>) -> Self {
|
||||
FatError::String {
|
||||
error: format!("{:?}", value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: core::fmt::Debug> From<ds323x::Error<E>> for FatError {
|
||||
fn from(value: ds323x::Error<E>) -> Self {
|
||||
FatError::DS323 {
|
||||
error: format!("{:?}", value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: core::fmt::Debug> From<eeprom24x::Error<E>> for FatError {
|
||||
fn from(value: eeprom24x::Error<E>) -> Self {
|
||||
FatError::Eeprom24x {
|
||||
error: format!("{:?}", value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: core::fmt::Debug> From<ExpanderError<I2cDeviceError<E>>> for FatError {
|
||||
fn from(value: ExpanderError<I2cDeviceError<E>>) -> Self {
|
||||
FatError::ExpanderError {
|
||||
error: format!("{:?}", value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bincode::error::DecodeError> for FatError {
|
||||
fn from(value: bincode::error::DecodeError) -> Self {
|
||||
FatError::Eeprom24x {
|
||||
error: format!("{:?}", value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bincode::error::EncodeError> for FatError {
|
||||
fn from(value: bincode::error::EncodeError) -> Self {
|
||||
FatError::Eeprom24x {
|
||||
error: format!("{:?}", value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigError> for FatError {
|
||||
fn from(value: ConfigError) -> Self {
|
||||
FatError::I2CConfigError { error: value }
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,21 @@
|
||||
use crate::hal::Box;
|
||||
use crate::FatError::{FatError, FatResult};
|
||||
use alloc::string::String;
|
||||
use async_trait::async_trait;
|
||||
use core::error::Error;
|
||||
use serde::Serialize;
|
||||
|
||||
#[async_trait]
|
||||
pub trait BatteryInteraction {
|
||||
async fn state_charge_percent(&mut self) -> Result<f32, BatteryError>;
|
||||
async fn remaining_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>;
|
||||
async fn max_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>;
|
||||
async fn design_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>;
|
||||
async fn voltage_milli_volt(&mut self) -> Result<u16, BatteryError>;
|
||||
async fn average_current_milli_ampere(&mut self) -> Result<i16, BatteryError>;
|
||||
async fn cycle_count(&mut self) -> Result<u16, BatteryError>;
|
||||
async fn state_health_percent(&mut self) -> Result<u16, BatteryError>;
|
||||
async fn bat_temperature(&mut self) -> Result<u16, BatteryError>;
|
||||
async fn get_battery_state(&mut self) -> Result<BatteryState, BatteryError>;
|
||||
}
|
||||
|
||||
impl From<BatteryError> for anyhow::Error {
|
||||
fn from(err: BatteryError) -> Self {
|
||||
anyhow::anyhow!(err)
|
||||
}
|
||||
async fn state_charge_percent(&mut self) -> FatResult<f32>;
|
||||
async fn remaining_milli_ampere_hour(&mut self) -> FatResult<u16>;
|
||||
async fn max_milli_ampere_hour(&mut self) -> FatResult<u16>;
|
||||
async fn design_milli_ampere_hour(&mut self) -> FatResult<u16>;
|
||||
async fn voltage_milli_volt(&mut self) -> FatResult<u16>;
|
||||
async fn average_current_milli_ampere(&mut self) -> FatResult<i16>;
|
||||
async fn cycle_count(&mut self) -> FatResult<u16>;
|
||||
async fn state_health_percent(&mut self) -> FatResult<u16>;
|
||||
async fn bat_temperature(&mut self) -> FatResult<u16>;
|
||||
async fn get_battery_state(&mut self) -> FatResult<BatteryState>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
@@ -60,43 +54,43 @@ pub enum BatteryState {
|
||||
pub struct NoBatteryMonitor {}
|
||||
#[async_trait]
|
||||
impl BatteryInteraction for NoBatteryMonitor {
|
||||
async fn state_charge_percent(&mut self) -> Result<f32, BatteryError> {
|
||||
Err(BatteryError::NoBatteryMonitor)
|
||||
async fn state_charge_percent(&mut self) -> FatResult<f32> {
|
||||
Err(FatError::NoBatteryMonitor)
|
||||
}
|
||||
|
||||
async fn remaining_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> {
|
||||
Err(BatteryError::NoBatteryMonitor)
|
||||
async fn remaining_milli_ampere_hour(&mut self) -> FatResult<u16> {
|
||||
Err(FatError::NoBatteryMonitor)
|
||||
}
|
||||
|
||||
async fn max_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> {
|
||||
Err(BatteryError::NoBatteryMonitor)
|
||||
async fn max_milli_ampere_hour(&mut self) -> FatResult<u16> {
|
||||
Err(FatError::NoBatteryMonitor)
|
||||
}
|
||||
|
||||
async fn design_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> {
|
||||
Err(BatteryError::NoBatteryMonitor)
|
||||
async fn design_milli_ampere_hour(&mut self) -> FatResult<u16> {
|
||||
Err(FatError::NoBatteryMonitor)
|
||||
}
|
||||
|
||||
async fn voltage_milli_volt(&mut self) -> Result<u16, BatteryError> {
|
||||
Err(BatteryError::NoBatteryMonitor)
|
||||
async fn voltage_milli_volt(&mut self) -> FatResult<u16> {
|
||||
Err(FatError::NoBatteryMonitor)
|
||||
}
|
||||
|
||||
async fn average_current_milli_ampere(&mut self) -> Result<i16, BatteryError> {
|
||||
Err(BatteryError::NoBatteryMonitor)
|
||||
async fn average_current_milli_ampere(&mut self) -> FatResult<i16> {
|
||||
Err(FatError::NoBatteryMonitor)
|
||||
}
|
||||
|
||||
async fn cycle_count(&mut self) -> Result<u16, BatteryError> {
|
||||
Err(BatteryError::NoBatteryMonitor)
|
||||
async fn cycle_count(&mut self) -> FatResult<u16> {
|
||||
Err(FatError::NoBatteryMonitor)
|
||||
}
|
||||
|
||||
async fn state_health_percent(&mut self) -> Result<u16, BatteryError> {
|
||||
Err(BatteryError::NoBatteryMonitor)
|
||||
async fn state_health_percent(&mut self) -> FatResult<u16> {
|
||||
Err(FatError::NoBatteryMonitor)
|
||||
}
|
||||
|
||||
async fn bat_temperature(&mut self) -> Result<u16, BatteryError> {
|
||||
Err(BatteryError::NoBatteryMonitor)
|
||||
async fn bat_temperature(&mut self) -> FatResult<u16> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
use crate::config::{NetworkConfig, PlantControllerConfig};
|
||||
use crate::hal::{GW_IP_ADDR_ENV, PLANT_COUNT, TIME_ACCESS};
|
||||
use crate::log::{ LogArray, LogMessage, LOG_ACCESS};
|
||||
use crate::STAY_ALIVE;
|
||||
use anyhow::{anyhow, bail, Context};
|
||||
use chrono::{DateTime, FixedOffset, Utc};
|
||||
use crate::log::{LogMessage, LOG_ACCESS};
|
||||
use crate::{bail, STAY_ALIVE};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem;
|
||||
use crate::FatError::{ContextExt, FatError, FatResult};
|
||||
use alloc::string::ToString;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::{format, string::String, vec::Vec};
|
||||
use core::marker::PhantomData;
|
||||
use core::net::{IpAddr, Ipv4Addr};
|
||||
use core::str::FromStr;
|
||||
use embassy_executor::{Spawner};
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_net::{Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4};
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
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::Storage;
|
||||
use esp_bootloader_esp_idf::ota::{Ota, OtaImageState, Slot};
|
||||
use esp_bootloader_esp_idf::partitions::{Error, FlashRegion, PartitionEntry, PartitionTable};
|
||||
use esp_bootloader_esp_idf::ota::{Ota, OtaImageState};
|
||||
use esp_bootloader_esp_idf::partitions::FlashRegion;
|
||||
use esp_hal::gpio::Input;
|
||||
use esp_hal::rng::Rng;
|
||||
use esp_hal::rtc_cntl::Rtc;
|
||||
use esp_hal::rtc_cntl::sleep::RtcSleepConfig;
|
||||
use esp_hal::system::software_reset;
|
||||
use esp_println::{println};
|
||||
use esp_println::println;
|
||||
use esp_storage::FlashStorage;
|
||||
use esp_wifi::wifi::{
|
||||
AccessPointConfiguration, AccessPointInfo, Configuration, Interfaces, ScanConfig,
|
||||
@@ -35,7 +33,7 @@ use esp_wifi::wifi::{
|
||||
};
|
||||
use littlefs2::fs::Filesystem;
|
||||
use littlefs2_core::{DynFile, FileType, PathBuf, SeekFrom};
|
||||
use log::{info};
|
||||
use log::info;
|
||||
|
||||
#[esp_hal::ram(rtc_fast, persistent)]
|
||||
static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT];
|
||||
@@ -113,12 +111,10 @@ macro_rules! mk_static {
|
||||
}
|
||||
|
||||
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 access = self.fs.lock().await;
|
||||
access
|
||||
.remove(&*file)
|
||||
.map_err(|err| anyhow!("Could not delete file: {:?}", err))?;
|
||||
access.remove(&*file)?;
|
||||
Ok(())
|
||||
}
|
||||
pub(crate) async fn write_file(
|
||||
@@ -126,11 +122,10 @@ impl Esp<'_> {
|
||||
filename: String,
|
||||
offset: u32,
|
||||
buf: &[u8],
|
||||
) -> anyhow::Result<()> {
|
||||
let file = PathBuf::try_from(filename.as_str()).unwrap();
|
||||
) -> Result<(), FatError> {
|
||||
let file = PathBuf::try_from(filename.as_str())?;
|
||||
let access = self.fs.lock().await;
|
||||
info!("write file {} at offset {}", filename, offset);
|
||||
match access.open_file_with_options_and_then(
|
||||
access.open_file_with_options_and_then(
|
||||
|options| options.read(true).write(true).create(true),
|
||||
&*file,
|
||||
|file| {
|
||||
@@ -138,51 +133,45 @@ impl Esp<'_> {
|
||||
file.write(buf)?;
|
||||
Ok(())
|
||||
},
|
||||
) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
bail!(format!("{err:?}"))
|
||||
}
|
||||
}
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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(
|
||||
&mut self,
|
||||
filename: String,
|
||||
chunk: u32,
|
||||
) -> anyhow::Result<([u8; 128], usize)> {
|
||||
) -> FatResult<([u8; 512], usize)> {
|
||||
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 mut buf = [0_u8; 128];
|
||||
let mut buf = [0_u8; 512];
|
||||
let mut read = 0;
|
||||
let offset = chunk * 128;
|
||||
info!("read file {} at offset {}", filename, offset);
|
||||
match access.open_file_with_options_and_then(
|
||||
let offset = chunk * buf.len() as u32;
|
||||
access.open_file_with_options_and_then(
|
||||
|options| options.read(true),
|
||||
&*file,
|
||||
|file| {
|
||||
let length = file.len()? as u32;
|
||||
info!("file length {}", length);
|
||||
if length == 0 {
|
||||
Err(lfs2Error::IO)
|
||||
} else if length > offset {
|
||||
file.seek(SeekFrom::Start(offset))?;
|
||||
info!("seek to {}", offset);
|
||||
read = file.read(&mut buf)?;
|
||||
info!("read {} bytes", read);
|
||||
Ok(())
|
||||
} else {
|
||||
//exactly at end, do nothing
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
bail!(format!("{err:?}"))
|
||||
}
|
||||
}
|
||||
)?;
|
||||
Ok((buf, read))
|
||||
}
|
||||
|
||||
@@ -219,7 +208,7 @@ impl Esp<'_> {
|
||||
pub(crate) fn mode_override_pressed(&mut self) -> bool {
|
||||
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 mut counter = 0;
|
||||
//while sntp.get_sync_status() != SyncStatus::Completed {
|
||||
@@ -233,19 +222,15 @@ impl Esp<'_> {
|
||||
todo!();
|
||||
}
|
||||
|
||||
pub async fn flash_ota(&mut self) -> anyhow::Result<()> {
|
||||
pub async fn flash_ota(&mut self) -> FatResult<()> {
|
||||
let capacity = self.ota_next.capacity();
|
||||
|
||||
bail!("not implemented")
|
||||
}
|
||||
|
||||
|
||||
pub(crate) async fn wifi_scan(&mut self) -> anyhow::Result<Vec<AccessPointInfo>> {
|
||||
pub(crate) async fn wifi_scan(&mut self) -> FatResult<Vec<AccessPointInfo>> {
|
||||
info!("start wifi scan");
|
||||
let mut lock = self
|
||||
.controller
|
||||
.try_lock()
|
||||
.map_err(|_| anyhow!("Could not lock wifi controller, currently in use"))?;
|
||||
let mut lock = self.controller.try_lock()?;
|
||||
info!("start wifi scan lock");
|
||||
let scan_config = ScanConfig {
|
||||
ssid: None,
|
||||
@@ -254,10 +239,7 @@ impl Esp<'_> {
|
||||
show_hidden: false,
|
||||
scan_type: ScanTypeConfig::Passive(core::time::Duration::from_secs(2)),
|
||||
};
|
||||
let rv = lock
|
||||
.scan_with_config_async(scan_config)
|
||||
.await
|
||||
.map_err(|err| anyhow!("Could not scan wifi: {:?}", err))?;
|
||||
let rv = lock.scan_with_config_async(scan_config).await?;
|
||||
info!("end wifi scan lock");
|
||||
Ok(rv)
|
||||
}
|
||||
@@ -294,7 +276,7 @@ impl Esp<'_> {
|
||||
unsafe { CONSECUTIVE_WATERING_PLANT[plant] }
|
||||
}
|
||||
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) {
|
||||
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 {
|
||||
Ok(config) => config.network.ap_ssid.as_str().to_string(),
|
||||
Err(_) => "PlantCtrl Emergency Mode".to_string(),
|
||||
@@ -358,7 +340,7 @@ impl Esp<'_> {
|
||||
.config_v4()
|
||||
.inspect(|c| println!("ipv4 config: {c:?}"));
|
||||
|
||||
anyhow::Ok(stack.clone())
|
||||
Ok(stack.clone())
|
||||
}
|
||||
|
||||
pub async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
|
||||
@@ -376,7 +358,6 @@ impl Esp<'_> {
|
||||
if duration_in_ms == 0 {
|
||||
software_reset();
|
||||
} else {
|
||||
|
||||
loop {
|
||||
info!("todo deepsleep")
|
||||
}
|
||||
@@ -390,11 +371,19 @@ impl Esp<'_> {
|
||||
//};
|
||||
}
|
||||
|
||||
pub(crate) async fn wifi(&mut self, network_config: &NetworkConfig) -> anyhow::Result<IpInfo> {
|
||||
let _ssid = network_config
|
||||
.ssid
|
||||
.clone()
|
||||
.ok_or(anyhow!("No ssid configured"))?;
|
||||
pub(crate) async fn wifi(&mut self, network_config: &NetworkConfig) -> FatResult<IpInfo> {
|
||||
let _ssid = network_config.ssid.clone();
|
||||
match &_ssid {
|
||||
Some(ssid) => {
|
||||
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 _max_wait = network_config.max_wait;
|
||||
bail!("todo")
|
||||
@@ -452,59 +441,32 @@ impl Esp<'_> {
|
||||
// log(LogMessage::WifiInfo, 0, 0, "", &format!("{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();
|
||||
match self.fs.lock().await.read::<4096>(&cfg) {
|
||||
Ok(data) => {
|
||||
let config: PlantControllerConfig = serde_json::from_slice(&data)?;
|
||||
anyhow::Ok(config)
|
||||
}
|
||||
Err(err) => {
|
||||
bail!(format!("{err:?}"))
|
||||
}
|
||||
}
|
||||
let data = self.fs.lock().await.read::<4096>(&cfg)?;
|
||||
let config: PlantControllerConfig = serde_json::from_slice(&data)?;
|
||||
return Ok(config);
|
||||
}
|
||||
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 cfg = PathBuf::try_from(CONFIG_FILE).unwrap();
|
||||
|
||||
match filesystem.write(&cfg, &*config) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
bail!(format!("{err:?}"))
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
let cfg = PathBuf::try_from(CONFIG_FILE)?;
|
||||
filesystem.write(&cfg, &*config)?;
|
||||
Ok(())
|
||||
}
|
||||
async fn file_system_size(&mut self) -> anyhow::Result<FileSystemSizeInfo> {
|
||||
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> {
|
||||
pub(crate) async fn list_files(&self) -> FatResult<FileList> {
|
||||
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 {
|
||||
total: 0,
|
||||
used: 0,
|
||||
total: total_size,
|
||||
used: total_size - free_size,
|
||||
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 {
|
||||
let e = entry?;
|
||||
if e.file_type() == FileType::File {
|
||||
@@ -514,29 +476,11 @@ impl Esp<'_> {
|
||||
});
|
||||
}
|
||||
}
|
||||
Result::Ok(())
|
||||
}) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
bail!(format!("{err:?}"))
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
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(
|
||||
&self,
|
||||
init_rtc_store: bool,
|
||||
@@ -558,22 +502,28 @@ impl Esp<'_> {
|
||||
if to_config_mode {
|
||||
RESTART_TO_CONF = 1;
|
||||
}
|
||||
LOG_ACCESS.lock().await.log(
|
||||
LogMessage::RestartToConfig,
|
||||
RESTART_TO_CONF as u32,
|
||||
0,
|
||||
"",
|
||||
"",
|
||||
).await
|
||||
;
|
||||
LOG_ACCESS.lock().await.log(
|
||||
LogMessage::LowVoltage,
|
||||
LOW_VOLTAGE_DETECTED as u32,
|
||||
0,
|
||||
"",
|
||||
"",
|
||||
).await
|
||||
;
|
||||
LOG_ACCESS
|
||||
.lock()
|
||||
.await
|
||||
.log(
|
||||
LogMessage::RestartToConfig,
|
||||
RESTART_TO_CONF as u32,
|
||||
0,
|
||||
"",
|
||||
"",
|
||||
)
|
||||
.await;
|
||||
LOG_ACCESS
|
||||
.lock()
|
||||
.await
|
||||
.log(
|
||||
LogMessage::LowVoltage,
|
||||
LOW_VOLTAGE_DETECTED as u32,
|
||||
0,
|
||||
"",
|
||||
"",
|
||||
)
|
||||
.await;
|
||||
for i in 0..PLANT_COUNT {
|
||||
log::info!(
|
||||
"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
|
||||
.base_topic
|
||||
.as_ref()
|
||||
@@ -756,11 +706,7 @@ impl Esp<'_> {
|
||||
// }
|
||||
// bail!("Mqtt did not fire connection callback in time");
|
||||
}
|
||||
pub(crate) async fn mqtt_publish(
|
||||
&mut self,
|
||||
_subtopic: &str,
|
||||
_message: &[u8],
|
||||
) -> anyhow::Result<()> {
|
||||
pub(crate) async fn mqtt_publish(&mut self, _subtopic: &str, _message: &[u8]) -> FatResult<()> {
|
||||
bail!("todo");
|
||||
//
|
||||
// if self.mqtt_client.is_none() {
|
||||
|
||||
@@ -3,16 +3,17 @@ use crate::hal::rtc::{BackupHeader, RTCModuleInteraction};
|
||||
use alloc::vec::Vec;
|
||||
//use crate::hal::water::TankSensor;
|
||||
use crate::alloc::boxed::Box;
|
||||
use crate::hal::water::TankSensor;
|
||||
use crate::hal::{BoardInteraction, FreePeripherals, Sensor};
|
||||
use crate::FatError::{FatError, FatResult};
|
||||
use crate::{
|
||||
bail,
|
||||
config::PlantControllerConfig,
|
||||
hal::battery::{BatteryInteraction, NoBatteryMonitor},
|
||||
};
|
||||
use anyhow::{bail, Result};
|
||||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, Utc};
|
||||
use esp_hal::gpio::{Level, Output, OutputConfig};
|
||||
use log::info;
|
||||
use measurements::{Current, Voltage};
|
||||
|
||||
pub struct Initial<'a> {
|
||||
@@ -23,27 +24,31 @@ pub struct Initial<'a> {
|
||||
pub rtc: Box<dyn RTCModuleInteraction + Send>,
|
||||
}
|
||||
|
||||
struct NoRTC {}
|
||||
pub(crate) struct NoRTC {}
|
||||
|
||||
#[async_trait]
|
||||
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")
|
||||
}
|
||||
|
||||
async fn get_backup_config(&mut self) -> Result<Vec<u8>> {
|
||||
async fn get_backup_config(&mut self, chunk: usize) -> FatResult<([u8; 32], usize, u16)> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
|
||||
async fn backup_config(&mut self, _bytes: &[u8]) -> Result<()> {
|
||||
async fn backup_config(&mut self, offset: usize, bytes: &[u8]) -> FatResult<()> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
|
||||
async fn get_rtc_time(&mut self) -> Result<DateTime<Utc>> {
|
||||
async fn backup_config_finalize(&mut self, crc: u16, length: usize) -> FatResult<()> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
|
||||
async fn set_rtc_time(&mut self, _time: &DateTime<Utc>) -> Result<()> {
|
||||
async fn get_rtc_time(&mut self) -> Result<DateTime<Utc>, FatError> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
|
||||
async fn set_rtc_time(&mut self, _time: &DateTime<Utc>) -> Result<(), FatError> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
}
|
||||
@@ -52,7 +57,7 @@ pub(crate) fn create_initial_board(
|
||||
free_pins: FreePeripherals<'static>,
|
||||
config: PlantControllerConfig,
|
||||
esp: Esp<'static>,
|
||||
) -> Result<Box<dyn BoardInteraction<'static> + Send>> {
|
||||
) -> Result<Box<dyn BoardInteraction<'static> + Send>, FatError> {
|
||||
log::info!("Start initial");
|
||||
let general_fault = Output::new(free_pins.gpio23, Level::Low, OutputConfig::default());
|
||||
let v = Initial {
|
||||
@@ -67,9 +72,9 @@ pub(crate) fn create_initial_board(
|
||||
|
||||
#[async_trait]
|
||||
impl<'a> BoardInteraction<'a> for Initial<'a> {
|
||||
// fn get_tank_sensor(&mut self) -> Option<&mut TankSensor<'a>> {
|
||||
// None
|
||||
// }
|
||||
fn get_tank_sensor(&mut self) -> Result<&mut TankSensor<'a>, FatError> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
|
||||
fn get_esp(&mut self) -> &mut Esp<'a> {
|
||||
&mut self.esp
|
||||
@@ -87,7 +92,7 @@ impl<'a> BoardInteraction<'a> for Initial<'a> {
|
||||
&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")
|
||||
}
|
||||
|
||||
@@ -97,23 +102,27 @@ impl<'a> BoardInteraction<'a> for Initial<'a> {
|
||||
fn is_day(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn light(&mut self, _enable: bool) -> Result<()> {
|
||||
fn light(&mut self, _enable: bool) -> Result<(), FatError> {
|
||||
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")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -121,7 +130,7 @@ impl<'a> BoardInteraction<'a> for Initial<'a> {
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -129,11 +138,11 @@ impl<'a> BoardInteraction<'a> for Initial<'a> {
|
||||
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")
|
||||
}
|
||||
|
||||
async fn get_mptt_current(&mut self) -> Result<Current> {
|
||||
async fn get_mptt_current(&mut self) -> Result<Current, FatError> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,9 @@ use esp_storage::FlashStorage;
|
||||
use littlefs2::consts::U512 as lfsCache;
|
||||
use littlefs2::consts::U512 as lfsLookahead;
|
||||
use littlefs2::driver::Storage as lfs2Storage;
|
||||
use littlefs2::fs::Filesystem as lfs2Filesystem;
|
||||
use littlefs2::io::Error as lfs2Error;
|
||||
use littlefs2::io::Result as lfs2Result;
|
||||
use log::{error, info};
|
||||
use log::error;
|
||||
|
||||
pub struct LittleFs2Filesystem {
|
||||
pub(crate) storage: &'static mut FlashRegion<'static, FlashStorage>,
|
||||
@@ -27,7 +26,7 @@ impl lfs2Storage for LittleFs2Filesystem {
|
||||
assert_eq!(off % read_size, 0);
|
||||
assert_eq!(buf.len() % read_size, 0);
|
||||
match self.storage.read(off as u32, buf) {
|
||||
anyhow::Result::Ok(..) => lfs2Result::Ok(buf.len()),
|
||||
Ok(..) => Ok(buf.len()),
|
||||
Err(err) => {
|
||||
error!("Littlefs2Filesystem read error: {:?}", err);
|
||||
Err(lfs2Error::IO)
|
||||
@@ -36,16 +35,11 @@ impl lfs2Storage for LittleFs2Filesystem {
|
||||
}
|
||||
|
||||
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;
|
||||
assert_eq!(off % write_size, 0);
|
||||
assert_eq!(data.len() % write_size, 0);
|
||||
match self.storage.write(off as u32, data) {
|
||||
anyhow::Result::Ok(..) => lfs2Result::Ok(data.len()),
|
||||
Ok(..) => Ok(data.len()),
|
||||
Err(err) => {
|
||||
error!("Littlefs2Filesystem write error: {:?}", err);
|
||||
Err(lfs2Error::IO)
|
||||
@@ -54,10 +48,6 @@ impl lfs2Storage for LittleFs2Filesystem {
|
||||
}
|
||||
|
||||
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;
|
||||
debug_assert!(off % block_size == 0);
|
||||
debug_assert!(len % block_size == 0);
|
||||
|
||||
@@ -2,35 +2,72 @@ pub(crate) mod battery;
|
||||
pub mod esp;
|
||||
mod initial_hal;
|
||||
mod little_fs2storage_adapter;
|
||||
mod rtc;
|
||||
pub(crate) mod rtc;
|
||||
mod v4_hal;
|
||||
mod water;
|
||||
//mod water;
|
||||
|
||||
use crate::alloc::string::ToString;
|
||||
use crate::hal::rtc::RTCModuleInteraction;
|
||||
use crate::hal::rtc::{DS3231Module, RTCModuleInteraction};
|
||||
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::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::GPIO7;
|
||||
use esp_hal::peripherals::GPIO8;
|
||||
|
||||
//use crate::hal::water::TankSensor;
|
||||
use crate::{
|
||||
bail,
|
||||
config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig},
|
||||
hal::{
|
||||
battery::{BatteryInteraction, NoBatteryMonitor},
|
||||
esp::Esp,
|
||||
},
|
||||
log::{LogMessage},
|
||||
log::LogMessage,
|
||||
};
|
||||
use alloc::boxed::Box;
|
||||
use alloc::format;
|
||||
use alloc::sync::Arc;
|
||||
use core::cell::OnceCell;
|
||||
use anyhow::{bail, Ok, Result};
|
||||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, FixedOffset, Utc};
|
||||
use core::cell::RefCell;
|
||||
use ds323x::ic::DS3231;
|
||||
use ds323x::interface::I2cInterface;
|
||||
use ds323x::{DateTimeAccess, Ds323x};
|
||||
use eeprom24x::addr_size::TwoBytes;
|
||||
use eeprom24x::page_size::B32;
|
||||
use eeprom24x::unique_serial::No;
|
||||
use eeprom24x::{Eeprom24x, SlaveAddr, Storage};
|
||||
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_sync::blocking_mutex::{CriticalSectionMutex, NoopMutex};
|
||||
//use battery::BQ34Z100G1;
|
||||
//use bq34z100::Bq34z100g1Driver;
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex};
|
||||
use esp_bootloader_esp_idf::partitions::{
|
||||
AppPartitionSubType, DataPartitionSubType, FlashRegion, PartitionEntry,
|
||||
};
|
||||
@@ -39,21 +76,27 @@ use esp_hal::gpio::{Input, InputConfig, Pull};
|
||||
use measurements::{Current, Voltage};
|
||||
|
||||
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::once_lock::OnceLock;
|
||||
use esp_alloc as _;
|
||||
use esp_backtrace as _;
|
||||
use esp_bootloader_esp_idf::ota::Slot;
|
||||
use esp_hal::delay::Delay;
|
||||
use esp_hal::i2c::master::{BusTimeout, Config, I2c};
|
||||
use esp_hal::rng::Rng;
|
||||
use esp_hal::rtc_cntl::{Rtc, SocResetReason};
|
||||
use esp_hal::system::reset_reason;
|
||||
use esp_hal::time::Rate;
|
||||
use esp_hal::timer::timg::TimerGroup;
|
||||
use esp_hal::Blocking;
|
||||
use esp_storage::FlashStorage;
|
||||
use esp_wifi::{init, EspWifiController};
|
||||
use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem};
|
||||
use littlefs2::object_safe::DynStorage;
|
||||
use log::{info, warn};
|
||||
use crate::log::{LogArray, LOG_ACCESS};
|
||||
|
||||
pub static TIME_ACCESS: OnceLock<Rtc> = OnceLock::new();
|
||||
|
||||
@@ -61,8 +104,9 @@ pub static TIME_ACCESS: OnceLock<Rtc> = OnceLock::new();
|
||||
pub const PLANT_COUNT: usize = 8;
|
||||
|
||||
const TANK_MULTI_SAMPLE: usize = 11;
|
||||
|
||||
//pub static I2C_DRIVER: LazyLock<Mutex<CriticalSectionRawMutex,I2cDriver<'static>>> = LazyLock::new(PlantHal::create_i2c);
|
||||
pub static I2C_DRIVER: OnceLock<
|
||||
embassy_sync::blocking_mutex::Mutex<CriticalSectionRawMutex, RefCell<I2c<Blocking>>>,
|
||||
> = OnceLock::new();
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Sensor {
|
||||
@@ -78,77 +122,80 @@ pub struct HAL<'a> {
|
||||
|
||||
#[async_trait]
|
||||
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_config(&mut self) -> &PlantControllerConfig;
|
||||
fn get_battery_monitor(&mut self) -> &mut Box<dyn BatteryInteraction + 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) -> !;
|
||||
|
||||
fn is_day(&self) -> bool;
|
||||
//should be multsampled
|
||||
fn light(&mut self, enable: bool) -> Result<()>;
|
||||
async fn pump(&mut self, plant: usize, enable: bool) -> Result<()>;
|
||||
async fn pump_current(&mut self, plant: usize) -> Result<Current>;
|
||||
async fn fault(&mut self, plant: usize, enable: bool) -> Result<()>;
|
||||
async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32>;
|
||||
fn light(&mut self, enable: bool) -> Result<(), FatError>;
|
||||
async fn pump(&mut self, plant: usize, enable: bool) -> Result<(), FatError>;
|
||||
async fn pump_current(&mut self, plant: usize) -> Result<Current, FatError>;
|
||||
async fn fault(&mut self, plant: usize, enable: bool) -> Result<(), FatError>;
|
||||
async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32, FatError>;
|
||||
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);
|
||||
async fn get_mptt_voltage(&mut self) -> anyhow::Result<Voltage>;
|
||||
async fn get_mptt_current(&mut self) -> anyhow::Result<Current>;
|
||||
}
|
||||
async fn get_mptt_voltage(&mut self) -> Result<Voltage, FatError>;
|
||||
async fn get_mptt_current(&mut self) -> Result<Current, FatError>;
|
||||
|
||||
impl dyn BoardInteraction<'_> {
|
||||
//the counter is just some arbitrary number that increases whenever some progress was made, try to keep the updates < 10 per second for ux reasons
|
||||
async fn _progress(&mut self, counter: u32) {
|
||||
let even = counter % 2 == 0;
|
||||
let current = counter / (PLANT_COUNT as u32);
|
||||
async fn progress(&mut self, counter: u32) {
|
||||
let current = counter % PLANT_COUNT as u32;
|
||||
for led in 0..PLANT_COUNT {
|
||||
match self.fault(led, current == led as u32).await {
|
||||
Result::Ok(_) => {}
|
||||
Err(err) => {
|
||||
warn!("Fault on plant {}: {:?}", led, err);
|
||||
}
|
||||
if let Err(err) = self.fault(led, current == led as u32).await {
|
||||
warn!("Fault on plant {}: {:?}", led, err);
|
||||
}
|
||||
}
|
||||
let _ = self.general_fault(even.into());
|
||||
let even = counter % 2 == 0;
|
||||
let _ = self.general_fault(even.into()).await;
|
||||
}
|
||||
|
||||
async fn clear_progress(&mut self) {
|
||||
for led in 0..PLANT_COUNT {
|
||||
if let Err(err) = self.fault(led, false).await {
|
||||
warn!("Fault on plant {}: {:?}", led, err);
|
||||
}
|
||||
}
|
||||
let _ = self.general_fault(false).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct FreePeripherals<'a> {
|
||||
// pub gpio0: Gpio0,
|
||||
// pub gpio1: Gpio1,
|
||||
// pub gpio2: Gpio2,
|
||||
// pub gpio3: Gpio3,
|
||||
// pub gpio4: Gpio4,
|
||||
// pub gpio5: Gpio5,
|
||||
pub gpio0: GPIO0<'a>,
|
||||
pub gpio1: GPIO1<'a>,
|
||||
pub gpio2: GPIO2<'a>,
|
||||
pub gpio3: GPIO3<'a>,
|
||||
pub gpio4: GPIO4<'a>,
|
||||
pub gpio5: GPIO5<'a>,
|
||||
pub gpio6: GPIO6<'a>,
|
||||
// pub gpio7: Gpio7,
|
||||
// pub gpio8: Gpio8,
|
||||
pub gpio7: GPIO7<'a>,
|
||||
pub gpio8: GPIO8<'a>,
|
||||
// //config button here
|
||||
// pub gpio10: Gpio10,
|
||||
// pub gpio11: Gpio11,
|
||||
// pub gpio12: Gpio12,
|
||||
// pub gpio13: Gpio13,
|
||||
// pub gpio14: Gpio14,
|
||||
// pub gpio15: Gpio15,
|
||||
// pub gpio16: Gpio16,
|
||||
// pub gpio17: Gpio17,
|
||||
// pub gpio18: Gpio18,
|
||||
pub gpio10: GPIO10<'a>,
|
||||
pub gpio11: GPIO11<'a>,
|
||||
pub gpio12: GPIO12<'a>,
|
||||
pub gpio13: GPIO13<'a>,
|
||||
pub gpio14: GPIO14<'a>,
|
||||
pub gpio15: GPIO15<'a>,
|
||||
pub gpio16: GPIO16<'a>,
|
||||
pub gpio17: GPIO17<'a>,
|
||||
pub gpio18: GPIO18<'a>,
|
||||
// //i2c here
|
||||
// pub gpio21: Gpio21,
|
||||
// pub gpio22: Gpio22,
|
||||
pub gpio21: GPIO21<'a>,
|
||||
pub gpio22: GPIO22<'a>,
|
||||
pub gpio23: GPIO23<'a>,
|
||||
// pub gpio24: Gpio24,
|
||||
// pub gpio25: Gpio25,
|
||||
// pub gpio26: Gpio26,
|
||||
// pub gpio27: Gpio27,
|
||||
// pub gpio28: Gpio28,
|
||||
// pub gpio29: Gpio29,
|
||||
// pub gpio30: Gpio30,
|
||||
pub gpio24: GPIO24<'a>,
|
||||
pub gpio25: GPIO25<'a>,
|
||||
pub gpio26: GPIO26<'a>,
|
||||
pub gpio27: GPIO27<'a>,
|
||||
pub gpio28: GPIO28<'a>,
|
||||
pub gpio29: GPIO29<'a>,
|
||||
pub gpio30: GPIO30<'a>,
|
||||
// pub pcnt0: PCNT0,
|
||||
// pub pcnt1: PCNT1,
|
||||
// pub adc1: ADC1,
|
||||
@@ -167,7 +214,7 @@ macro_rules! mk_static {
|
||||
const GW_IP_ADDR_ENV: Option<&'static str> = option_env!("GATEWAY_IP");
|
||||
|
||||
impl PlantHal {
|
||||
// fn create_i2c() -> Mutex<CriticalSectionRawMutex, I2cDriver<'static>> {
|
||||
//fn create_i2c() -> Mutex<CriticalSectionRawMutex, ()> {
|
||||
// let peripherals = unsafe { Peripherals::new() };
|
||||
//
|
||||
// let config = I2cConfig::new()
|
||||
@@ -181,9 +228,11 @@ impl PlantHal {
|
||||
// let sda = peripherals.pins.gpio20.downgrade();
|
||||
//
|
||||
// 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 peripherals: Peripherals = esp_hal::init(config);
|
||||
|
||||
@@ -191,7 +240,7 @@ impl PlantHal {
|
||||
esp_alloc::heap_allocator!(#[link_section = ".dram2_uninit"] size: 64000);
|
||||
|
||||
let rtc: Rtc = Rtc::new(peripherals.LPWR);
|
||||
match(TIME_ACCESS.init(rtc)){
|
||||
match (TIME_ACCESS.init(rtc)) {
|
||||
Result::Ok(_) => {}
|
||||
Err(_) => {}
|
||||
}
|
||||
@@ -223,36 +272,35 @@ impl PlantHal {
|
||||
// adc1: peripherals.adc1,
|
||||
// pcnt0: peripherals.pcnt0,
|
||||
// pcnt1: peripherals.pcnt1,
|
||||
// gpio0: peripherals.pins.gpio0,
|
||||
// gpio1: peripherals.pins.gpio1,
|
||||
// gpio2: peripherals.pins.gpio2,
|
||||
// gpio3: peripherals.pins.gpio3,
|
||||
// gpio4: peripherals.pins.gpio4,
|
||||
// gpio5: peripherals.pins.gpio5,
|
||||
gpio0: peripherals.GPIO0,
|
||||
gpio1: peripherals.GPIO1,
|
||||
gpio2: peripherals.GPIO2,
|
||||
gpio3: peripherals.GPIO3,
|
||||
gpio4: peripherals.GPIO4,
|
||||
gpio5: peripherals.GPIO5,
|
||||
gpio6: peripherals.GPIO6,
|
||||
// gpio7: peripherals.pins.gpio7,
|
||||
// gpio8: peripherals.pins.gpio8,
|
||||
// gpio10: peripherals.pins.gpio10,
|
||||
// gpio11: peripherals.pins.gpio11,
|
||||
// gpio12: peripherals.pins.gpio12,
|
||||
// gpio13: peripherals.pins.gpio13,
|
||||
// gpio14: peripherals.pins.gpio14,
|
||||
// gpio15: peripherals.pins.gpio15,
|
||||
// gpio16: peripherals.pins.gpio16,
|
||||
// gpio17: peripherals.pins.gpio17,
|
||||
// gpio18: peripherals.pins.gpio18,
|
||||
// gpio21: peripherals.pins.gpio21,
|
||||
// gpio22: peripherals.pins.gpio22,
|
||||
gpio7: peripherals.GPIO7,
|
||||
gpio8: peripherals.GPIO8,
|
||||
gpio10: peripherals.GPIO10,
|
||||
gpio11: peripherals.GPIO11,
|
||||
gpio12: peripherals.GPIO12,
|
||||
gpio13: peripherals.GPIO13,
|
||||
gpio14: peripherals.GPIO14,
|
||||
gpio15: peripherals.GPIO15,
|
||||
gpio16: peripherals.GPIO16,
|
||||
gpio17: peripherals.GPIO17,
|
||||
gpio18: peripherals.GPIO18,
|
||||
gpio21: peripherals.GPIO21,
|
||||
gpio22: peripherals.GPIO22,
|
||||
gpio23: peripherals.GPIO23,
|
||||
// gpio24: peripherals.pins.gpio24,
|
||||
// gpio25: peripherals.pins.gpio25,
|
||||
// gpio26: peripherals.pins.gpio26,
|
||||
// gpio27: peripherals.pins.gpio27,
|
||||
// gpio28: peripherals.pins.gpio28,
|
||||
// gpio29: peripherals.pins.gpio29,
|
||||
// gpio30: peripherals.pins.gpio30,
|
||||
gpio24: peripherals.GPIO24,
|
||||
gpio25: peripherals.GPIO25,
|
||||
gpio26: peripherals.GPIO26,
|
||||
gpio27: peripherals.GPIO27,
|
||||
gpio28: peripherals.GPIO28,
|
||||
gpio29: peripherals.GPIO29,
|
||||
gpio30: peripherals.GPIO30,
|
||||
};
|
||||
//
|
||||
|
||||
let tablebuffer = mk_static!(
|
||||
[u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN],
|
||||
@@ -351,77 +399,45 @@ impl PlantHal {
|
||||
let mut init_rtc_store: bool = false;
|
||||
let mut to_config_mode: bool = false;
|
||||
let reasons = match reset_reason() {
|
||||
None => {
|
||||
"unknown"
|
||||
}
|
||||
Some(reason) => {
|
||||
match reason {
|
||||
SocResetReason::ChipPowerOn => {
|
||||
"power on"
|
||||
}
|
||||
SocResetReason::CoreSw => {
|
||||
"software reset"
|
||||
}
|
||||
SocResetReason::CoreDeepSleep => {
|
||||
"deep sleep"
|
||||
}
|
||||
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"
|
||||
}
|
||||
None => "unknown",
|
||||
Some(reason) => match reason {
|
||||
SocResetReason::ChipPowerOn => "power on",
|
||||
SocResetReason::CoreSw => "software reset",
|
||||
SocResetReason::CoreDeepSleep => "deep sleep",
|
||||
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",
|
||||
},
|
||||
};
|
||||
LOG_ACCESS.lock().await.log(
|
||||
LogMessage::ResetReason,
|
||||
init_rtc_store as u32,
|
||||
to_config_mode as u32,
|
||||
"",
|
||||
&format!("{reasons:?}"),
|
||||
).await;
|
||||
LOG_ACCESS
|
||||
.lock()
|
||||
.await
|
||||
.log(
|
||||
LogMessage::ResetReason,
|
||||
init_rtc_store as u32,
|
||||
to_config_mode as u32,
|
||||
"",
|
||||
&format!("{reasons:?}"),
|
||||
)
|
||||
.await;
|
||||
|
||||
esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode)
|
||||
.await;
|
||||
@@ -429,28 +445,55 @@ impl PlantHal {
|
||||
let config = esp.load_config().await;
|
||||
|
||||
log::info!("Init rtc driver");
|
||||
// let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER));
|
||||
//
|
||||
// log::info!("Init rtc eeprom driver");
|
||||
// let eeprom = {
|
||||
// Eeprom24x::new_24x32(
|
||||
// MutexDevice::new(&I2C_DRIVER),
|
||||
// SlaveAddr::Alternative(true, true, true),
|
||||
// )
|
||||
// };
|
||||
// let rtc_time = rtc.datetime();
|
||||
// match rtc_time {
|
||||
// OkStd(tt) => {
|
||||
// log::info!("Rtc Module reports time at UTC {}", tt);
|
||||
// }
|
||||
// Err(err) => {
|
||||
// log::info!("Rtc Module could not be read {:?}", err);
|
||||
// }
|
||||
// }
|
||||
|
||||
// let storage = Storage::new(eeprom, Delay::new(1000));
|
||||
//let rtc_module: Box<dyn RTCModuleInteraction + Send> =
|
||||
// Box::new(DS3231Module { rtc, storage }) as Box<dyn RTCModuleInteraction + Send>;
|
||||
let sda = peripherals.GPIO20;
|
||||
let scl = peripherals.GPIO19;
|
||||
|
||||
let i2c = I2c::new(
|
||||
peripherals.I2C0,
|
||||
Config::default()
|
||||
.with_frequency(Rate::from_hz(100))
|
||||
.with_timeout(BusTimeout::Maximum),
|
||||
)?
|
||||
.with_scl(scl)
|
||||
.with_sda(sda);
|
||||
let i2c_bus: embassy_sync::blocking_mutex::Mutex<
|
||||
CriticalSectionRawMutex,
|
||||
RefCell<I2c<Blocking>>,
|
||||
> = CriticalSectionMutex::new(RefCell::new(i2c));
|
||||
|
||||
I2C_DRIVER.init(i2c_bus).expect("Could not init i2c driver");
|
||||
|
||||
let i2c_bus = I2C_DRIVER.get().await;
|
||||
let rtc_device = I2cDevice::new(&i2c_bus);
|
||||
let eeprom_device = I2cDevice::new(&i2c_bus);
|
||||
|
||||
let mut rtc: Ds323x<
|
||||
I2cInterface<I2cDevice<CriticalSectionRawMutex, I2c<Blocking>>>,
|
||||
DS3231,
|
||||
> = Ds323x::new_ds3231(rtc_device);
|
||||
|
||||
info!("Init rtc eeprom driver");
|
||||
let eeprom = Eeprom24x::new_24x32(eeprom_device, SlaveAddr::Alternative(true, true, true));
|
||||
let rtc_time = rtc.datetime();
|
||||
match rtc_time {
|
||||
Ok(tt) => {
|
||||
log::info!("Rtc Module reports time at UTC {}", tt);
|
||||
}
|
||||
Err(err) => {
|
||||
log::info!("Rtc Module could not be read {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
let storage: Storage<
|
||||
I2cDevice<'static, CriticalSectionRawMutex, I2c<Blocking>>,
|
||||
B32,
|
||||
TwoBytes,
|
||||
No,
|
||||
Delay,
|
||||
> = Storage::new(eeprom, Delay::new());
|
||||
let rtc_module: Box<dyn RTCModuleInteraction + Send> =
|
||||
Box::new(DS3231Module { rtc, storage }) as Box<dyn RTCModuleInteraction + Send>;
|
||||
|
||||
let hal = match config {
|
||||
Result::Ok(config) => {
|
||||
@@ -487,32 +530,36 @@ impl PlantHal {
|
||||
}
|
||||
};
|
||||
|
||||
let board_hal: Box<dyn BoardInteraction + Send> = //match config.hardware.board {
|
||||
//BoardVersion::INITIAL => {
|
||||
let board_hal: Box<dyn BoardInteraction + Send> = match config.hardware.board {
|
||||
BoardVersion::INITIAL => {
|
||||
initial_hal::create_initial_board(free_pins, config, esp)?
|
||||
;
|
||||
//}
|
||||
}
|
||||
// BoardVersion::V3 => {
|
||||
// v3_hal::create_v3(free_pins, esp, config, battery_interaction, rtc_module)?
|
||||
// }
|
||||
//BoardVersion::V4 => {
|
||||
// v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)?
|
||||
//}
|
||||
//_ => {
|
||||
// todo!()
|
||||
//}
|
||||
//};
|
||||
BoardVersion::V4 => {
|
||||
v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)
|
||||
.await?
|
||||
}
|
||||
_ => {
|
||||
todo!()
|
||||
}
|
||||
};
|
||||
|
||||
HAL { board_hal }
|
||||
}
|
||||
Err(err) => {
|
||||
LOG_ACCESS.lock().await.log(
|
||||
LogMessage::ConfigModeMissingConfig,
|
||||
0,
|
||||
0,
|
||||
"",
|
||||
&err.to_string(),
|
||||
).await;
|
||||
LOG_ACCESS
|
||||
.lock()
|
||||
.await
|
||||
.log(
|
||||
LogMessage::ConfigModeMissingConfig,
|
||||
0,
|
||||
0,
|
||||
"",
|
||||
&err.to_string(),
|
||||
)
|
||||
.await;
|
||||
HAL {
|
||||
board_hal: initial_hal::create_initial_board(
|
||||
free_pins,
|
||||
@@ -527,11 +574,13 @@ impl PlantHal {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn esp_time() -> DateTime<Utc> {
|
||||
DateTime::from_timestamp_micros(TIME_ACCESS.get().await.current_time_us() as i64).unwrap()
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,138 +1,138 @@
|
||||
use crate::bail;
|
||||
use crate::hal::Box;
|
||||
use crate::FatError::FatResult;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use async_trait::async_trait;
|
||||
use bincode::config::Configuration;
|
||||
use bincode::{config, Decode, Encode};
|
||||
use chrono::{DateTime, Utc};
|
||||
use crc::Digest;
|
||||
use ds323x::ic::DS3231;
|
||||
use ds323x::interface::I2cInterface;
|
||||
use ds323x::{DateTimeAccess, Ds323x};
|
||||
use eeprom24x::addr_size::TwoBytes;
|
||||
use eeprom24x::page_size::B32;
|
||||
use eeprom24x::unique_serial::No;
|
||||
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
||||
use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex};
|
||||
use embedded_storage::{ReadStorage, Storage};
|
||||
use esp_hal::delay::Delay;
|
||||
use esp_hal::i2c::master::I2c;
|
||||
use esp_hal::Blocking;
|
||||
use littlefs2_core::{PathBuf, SeekFrom};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// use crate::hal::Box;
|
||||
// use alloc::vec::Vec;
|
||||
// use anyhow::{anyhow, bail};
|
||||
// use async_trait::async_trait;
|
||||
// use bincode::config::Configuration;
|
||||
// use bincode::{config, Decode, Encode};
|
||||
// use chrono::{DateTime, Utc};
|
||||
// use ds323x::{DateTimeAccess, Ds323x};
|
||||
// use eeprom24x::addr_size::TwoBytes;
|
||||
// use eeprom24x::page_size::B32;
|
||||
// use eeprom24x::unique_serial::No;
|
||||
// use eeprom24x::Storage;
|
||||
// use embedded_storage::ReadStorage as embedded_storage_ReadStorage;
|
||||
// use embedded_storage::Storage as embedded_storage_Storage;
|
||||
// use serde::{Deserialize, Serialize};
|
||||
//
|
||||
// const X25: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC);
|
||||
// const CONFIG: Configuration = config::standard();
|
||||
pub const X25: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC);
|
||||
const CONFIG: Configuration = config::standard();
|
||||
//
|
||||
#[async_trait]
|
||||
pub trait RTCModuleInteraction {
|
||||
async fn get_backup_info(&mut self) -> anyhow::Result<BackupHeader>;
|
||||
async fn get_backup_config(&mut self) -> anyhow::Result<Vec<u8>>;
|
||||
async fn backup_config(&mut self, bytes: &[u8]) -> anyhow::Result<()>;
|
||||
async fn get_rtc_time(&mut self) -> anyhow::Result<DateTime<Utc>>;
|
||||
async fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> anyhow::Result<()>;
|
||||
async fn get_backup_info(&mut self) -> FatResult<BackupHeader>;
|
||||
async fn get_backup_config(&mut self, chunk: usize) -> FatResult<([u8; 32], usize, u16)>;
|
||||
async fn backup_config(&mut self, offset: usize, bytes: &[u8]) -> FatResult<()>;
|
||||
async fn backup_config_finalize(&mut self, crc: u16, length: usize) -> FatResult<()>;
|
||||
async fn get_rtc_time(&mut self) -> FatResult<DateTime<Utc>>;
|
||||
async fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> FatResult<()>;
|
||||
}
|
||||
//
|
||||
// const BACKUP_HEADER_MAX_SIZE: usize = 64;
|
||||
// #[derive(Serialize, Deserialize, PartialEq, Debug, Default, Encode, Decode)]
|
||||
const BACKUP_HEADER_MAX_SIZE: usize = 64;
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Default, Encode, Decode)]
|
||||
pub struct BackupHeader {
|
||||
pub timestamp: i64,
|
||||
crc16: u16,
|
||||
pub size: u16,
|
||||
}
|
||||
//
|
||||
// pub struct DS3231Module<'a> {
|
||||
// pub(crate) rtc:
|
||||
// Ds323x<ds323x::interface::I2cInterface<MutexDevice<'a, I2cDriver<'a>>>, ds323x::ic::DS3231>,
|
||||
//
|
||||
// pub(crate) storage: Storage<MutexDevice<'a, I2cDriver<'a>>, B32, TwoBytes, No, Delay>,
|
||||
// }
|
||||
//
|
||||
// impl RTCModuleInteraction for DS3231Module<'_> {
|
||||
// fn get_backup_info(&mut self) -> anyhow::Result<BackupHeader> {
|
||||
// let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
|
||||
//
|
||||
// self.storage
|
||||
// .read(0, &mut header_page_buffer)
|
||||
// .map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?;
|
||||
//
|
||||
// let (header, len): (BackupHeader, usize) =
|
||||
// bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?;
|
||||
//
|
||||
// log::info!("Raw header is {:?} with size {}", header_page_buffer, len);
|
||||
// anyhow::Ok(header)
|
||||
// }
|
||||
//
|
||||
// fn get_backup_config(&mut self) -> anyhow::Result<Vec<u8>> {
|
||||
// let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
|
||||
//
|
||||
// self.storage
|
||||
// .read(0, &mut header_page_buffer)
|
||||
// .map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?;
|
||||
// let (header, _header_size): (BackupHeader, usize) =
|
||||
// bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?;
|
||||
//
|
||||
// let mut data_buffer = vec![0_u8; header.size as usize];
|
||||
// //read the specified number of bytes after the header
|
||||
// self.storage
|
||||
// .read(BACKUP_HEADER_MAX_SIZE as u32, &mut data_buffer)
|
||||
// .map_err(|err| anyhow!("Error reading eeprom data {:?}", err))?;
|
||||
//
|
||||
// let checksum = X25.checksum(&data_buffer);
|
||||
// if checksum != header.crc16 {
|
||||
// bail!(
|
||||
// "Invalid checksum, got {} but expected {}",
|
||||
// checksum,
|
||||
// header.crc16
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// anyhow::Ok(data_buffer)
|
||||
// }
|
||||
// fn backup_config(&mut self, bytes: &[u8]) -> anyhow::Result<()> {
|
||||
// let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
|
||||
//
|
||||
// let time = self.get_rtc_time()?.timestamp_millis();
|
||||
// let checksum = X25.checksum(bytes);
|
||||
//
|
||||
// let header = BackupHeader {
|
||||
// crc16: checksum,
|
||||
// timestamp: time,
|
||||
// size: bytes.len() as u16,
|
||||
// };
|
||||
// let config = config::standard();
|
||||
// let encoded = bincode::encode_into_slice(&header, &mut header_page_buffer, config)?;
|
||||
// log::info!(
|
||||
// "Raw header is {:?} with size {}",
|
||||
// header_page_buffer,
|
||||
// encoded
|
||||
// );
|
||||
// self.storage
|
||||
// .write(0, &header_page_buffer)
|
||||
// .map_err(|err| anyhow!("Error writing header {:?}", err))?;
|
||||
//
|
||||
// //write rest after the header
|
||||
// self.storage
|
||||
// .write(BACKUP_HEADER_MAX_SIZE as u32, &bytes)
|
||||
// .map_err(|err| anyhow!("Error writing body {:?}", err))?;
|
||||
//
|
||||
// anyhow::Ok(())
|
||||
// }
|
||||
//
|
||||
// fn get_rtc_time(&mut self) -> anyhow::Result<DateTime<Utc>> {
|
||||
// match self.rtc.datetime() {
|
||||
// OkStd(rtc_time) => anyhow::Ok(rtc_time.and_utc()),
|
||||
// Err(err) => {
|
||||
// bail!("Error getting rtc time {:?}", err)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> anyhow::Result<()> {
|
||||
// let naive_time = time.naive_utc();
|
||||
// match self.rtc.set_datetime(&naive_time) {
|
||||
// OkStd(_) => anyhow::Ok(()),
|
||||
// Err(err) => {
|
||||
// bail!("Error getting rtc time {:?}", err)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
pub struct DS3231Module {
|
||||
pub(crate) rtc: Ds323x<
|
||||
I2cInterface<I2cDevice<'static, CriticalSectionRawMutex, I2c<'static, Blocking>>>,
|
||||
DS3231,
|
||||
>,
|
||||
|
||||
pub(crate) storage: eeprom24x::Storage<
|
||||
I2cDevice<'static, CriticalSectionRawMutex, I2c<'static, Blocking>>,
|
||||
B32,
|
||||
TwoBytes,
|
||||
No,
|
||||
Delay,
|
||||
>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RTCModuleInteraction for DS3231Module {
|
||||
async fn get_backup_info(&mut self) -> FatResult<BackupHeader> {
|
||||
let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
|
||||
|
||||
self.storage.read(0, &mut header_page_buffer)?;
|
||||
|
||||
let (header, len): (BackupHeader, usize) =
|
||||
bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?;
|
||||
|
||||
log::info!("Raw header is {:?} with size {}", header_page_buffer, len);
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
async fn get_backup_config(&mut self, chunk: usize) -> FatResult<([u8; 32], usize, u16)> {
|
||||
let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
|
||||
|
||||
self.storage.read(0, &mut header_page_buffer)?;
|
||||
let (header, _header_size): (BackupHeader, usize) =
|
||||
bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?;
|
||||
|
||||
let mut buf = [0_u8; 32];
|
||||
let offset = chunk * buf.len() + BACKUP_HEADER_MAX_SIZE;
|
||||
|
||||
let end: usize = header.size as usize + BACKUP_HEADER_MAX_SIZE;
|
||||
let current_end = offset + buf.len();
|
||||
let chunk_size = if current_end > end {
|
||||
end - offset
|
||||
} else {
|
||||
buf.len()
|
||||
};
|
||||
if chunk_size == 0 {
|
||||
Ok((buf, 0, header.crc16))
|
||||
} else {
|
||||
self.storage.read(offset as u32, &mut buf)?;
|
||||
let data = &buf[..chunk_size];
|
||||
Ok((buf, chunk_size, header.crc16))
|
||||
}
|
||||
}
|
||||
async fn backup_config(&mut self, offset: usize, bytes: &[u8]) -> FatResult<()> {
|
||||
//skip header and write after
|
||||
self.storage
|
||||
.write((BACKUP_HEADER_MAX_SIZE + offset) as u32, &bytes)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn backup_config_finalize(&mut self, crc: u16, length: usize) -> FatResult<()> {
|
||||
let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
|
||||
|
||||
let time = self.get_rtc_time().await?.timestamp_millis();
|
||||
let header = BackupHeader {
|
||||
crc16: crc,
|
||||
timestamp: time,
|
||||
size: length as u16,
|
||||
};
|
||||
let config = config::standard();
|
||||
let encoded = bincode::encode_into_slice(&header, &mut header_page_buffer, config)?;
|
||||
log::info!(
|
||||
"Raw header is {:?} with size {}",
|
||||
header_page_buffer,
|
||||
encoded
|
||||
);
|
||||
self.storage.write(0, &header_page_buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_rtc_time(&mut self) -> FatResult<DateTime<Utc>> {
|
||||
Ok(self.rtc.datetime()?.and_utc())
|
||||
}
|
||||
|
||||
async fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> FatResult<()> {
|
||||
let naive_time = time.naive_utc();
|
||||
Ok(self.rtc.set_datetime(&naive_time)?)
|
||||
}
|
||||
}
|
||||
|
||||
430
rust/src/hal/v4_hal.rs
Normal file
430
rust/src/hal/v4_hal.rs
Normal file
@@ -0,0 +1,430 @@
|
||||
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, I2C_DRIVER};
|
||||
use alloc::boxed::Box;
|
||||
use async_trait::async_trait;
|
||||
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use esp_hal::Blocking;
|
||||
//use embedded_hal_bus::i2c::MutexDevice;
|
||||
use crate::bail;
|
||||
use crate::FatError::{FatError, FatResult};
|
||||
use esp_hal::gpio::{Flex, Level, Output, OutputConfig};
|
||||
use esp_hal::i2c::master::I2c;
|
||||
use ina219::address::{Address, Pin};
|
||||
use ina219::SyncIna219;
|
||||
use measurements::{Current, Voltage};
|
||||
use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
|
||||
// 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<I2cDevice<'a, CriticalSectionRawMutex, I2c<'static, Blocking>>>,
|
||||
//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) async 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 pump_device = I2cDevice::new(I2C_DRIVER.get().await);
|
||||
let mut pump_expander = Pca9535Immediate::new(pump_device, 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_current = I2cDevice::new(I2C_DRIVER.get().await);
|
||||
let mppt_ina = SyncIna219::new(mppt_current, 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) -> FatResult<()> {
|
||||
if enable {
|
||||
self.pump_expander
|
||||
.pin_set_high(GPIOBank::Bank0, plant as u8)?;
|
||||
} else {
|
||||
self.pump_expander
|
||||
.pin_set_low(GPIOBank::Bank0, plant as u8)?;
|
||||
}
|
||||
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) -> FatResult<()> {
|
||||
if enable {
|
||||
self.pump_expander
|
||||
.pin_set_high(GPIOBank::Bank1, plant as u8)?;
|
||||
} else {
|
||||
self.pump_expander
|
||||
.pin_set_low(GPIOBank::Bank1, plant as u8)?;
|
||||
}
|
||||
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
172
rust/src/hal/water.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,48 @@
|
||||
use crate::hal::TIME_ACCESS;
|
||||
use crate::vec;
|
||||
use alloc::string::ToString;
|
||||
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::mutex::Mutex;
|
||||
use embassy_time::Instant;
|
||||
use esp_hal::Persistable;
|
||||
use log::info;
|
||||
use serde::Serialize;
|
||||
use strum_macros::IntoStaticStr;
|
||||
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)]
|
||||
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,
|
||||
};
|
||||
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_LONG_LENGTH: usize = 32;
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Copy, AnyBitPattern)]
|
||||
#[repr(C)]
|
||||
pub struct LogArray{
|
||||
buffer: [LogEntryInner; (u8::MAX_VALUE as usize) +1],
|
||||
head: u8
|
||||
pub struct LogArray {
|
||||
buffer: [LogEntryInner; LOG_ARRAY_SIZE as usize],
|
||||
head: u8,
|
||||
}
|
||||
|
||||
unsafe impl Persistable for LogArray {}
|
||||
unsafe impl Zeroable for LogEntryInner {}
|
||||
|
||||
unsafe impl Pod for LogEntryInner{}
|
||||
unsafe impl Pod for LogEntryInner {}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct LogEntryInner {
|
||||
@@ -57,7 +66,7 @@ pub struct LogEntry {
|
||||
|
||||
impl From<LogEntryInner> for LogEntry {
|
||||
fn from(value: LogEntryInner) -> Self {
|
||||
LogEntry{
|
||||
LogEntry {
|
||||
timestamp: value.timestamp,
|
||||
message_id: value.message_id,
|
||||
a: value.a,
|
||||
@@ -70,10 +79,13 @@ impl From<LogEntryInner> for LogEntry {
|
||||
|
||||
impl LogArray {
|
||||
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 index = self.head.wrapping_sub(1);
|
||||
let mut index = head.wrapping_sub(1);
|
||||
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() {
|
||||
rv.push(entry.into());
|
||||
}
|
||||
@@ -90,13 +102,16 @@ impl LogArray {
|
||||
txt_short: &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_long_stack: heapless::String<TXT_LONG_LENGTH> = heapless::String::new();
|
||||
|
||||
limit_length(txt_short, &mut txt_short_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 template: &str = message_key.into();
|
||||
@@ -108,14 +123,19 @@ impl LogArray {
|
||||
|
||||
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.message_id = ordinal;
|
||||
to_modify.a = number_a;
|
||||
to_modify.b = number_b;
|
||||
to_modify.txt_short.clone_from_slice(&txt_short_stack.as_bytes());
|
||||
to_modify.txt_long.clone_from_slice(&txt_long_stack.as_bytes());
|
||||
self.head = self.head.wrapping_add(1);
|
||||
to_modify
|
||||
.txt_short
|
||||
.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)]
|
||||
pub enum LogMessage {
|
||||
#[strum(
|
||||
serialize = ""
|
||||
)]
|
||||
#[strum(serialize = "")]
|
||||
Empty,
|
||||
#[strum(
|
||||
serialize = "Reset due to ${txt_long} requires rtc clear ${number_a} and force config mode ${number_b}"
|
||||
|
||||
193
rust/src/main.rs
193
rust/src/main.rs
@@ -2,6 +2,7 @@
|
||||
#![no_main]
|
||||
#![feature(never_type)]
|
||||
#![feature(string_from_utf8_lossy_owned)]
|
||||
#![feature(impl_trait_in_assoc_type)]
|
||||
#![deny(
|
||||
clippy::mem_forget,
|
||||
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
|
||||
@@ -12,8 +13,11 @@ esp_bootloader_esp_idf::esp_app_desc!();
|
||||
use esp_backtrace as _;
|
||||
|
||||
use crate::config::PlantConfig;
|
||||
use crate::hal::esp_time;
|
||||
use crate::log::LOG_ACCESS;
|
||||
use crate::tank::WATER_FROZEN_THRESH;
|
||||
use crate::webserver::httpd;
|
||||
use crate::FatError::FatResult;
|
||||
use crate::{
|
||||
config::BoardVersion::INITIAL,
|
||||
hal::{PlantHal, HAL, PLANT_COUNT},
|
||||
@@ -32,15 +36,14 @@ use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_sync::mutex::{Mutex, MutexGuard};
|
||||
use embassy_sync::once_lock::OnceLock;
|
||||
use embassy_time::Timer;
|
||||
use esp_bootloader_esp_idf::ota::OtaImageState;
|
||||
use esp_hal::rom::ets_delay_us;
|
||||
use esp_hal::system::software_reset;
|
||||
use esp_println::{logger, println};
|
||||
use hal::battery::BatteryState;
|
||||
use log::{ LogMessage};
|
||||
use log::LogMessage;
|
||||
use plant_state::PlantState;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::hal::esp_time;
|
||||
use crate::log::LOG_ACCESS;
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn custom_halt() -> ! {
|
||||
@@ -56,6 +59,7 @@ extern "C" fn custom_halt() -> ! {
|
||||
}
|
||||
|
||||
//use tank::*;
|
||||
mod FatError;
|
||||
mod config;
|
||||
mod hal;
|
||||
mod log;
|
||||
@@ -146,7 +150,7 @@ enum NetworkMode {
|
||||
OFFLINE,
|
||||
}
|
||||
|
||||
async fn safe_main(spawner: Spawner) -> anyhow::Result<()> {
|
||||
async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
||||
info!("Startup Rust");
|
||||
|
||||
let mut to_config = false;
|
||||
@@ -183,28 +187,34 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> {
|
||||
let _ota_state_string = "unknown";
|
||||
|
||||
board.board_hal.general_fault(false).await;
|
||||
let time = esp_time().await;
|
||||
let cur = board
|
||||
.board_hal
|
||||
.get_rtc_module()
|
||||
.get_rtc_time()
|
||||
.await
|
||||
.or_else(|err| {
|
||||
let cur = match board.board_hal.get_rtc_module().get_rtc_time().await {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
info!("rtc module error: {:?}", err);
|
||||
board.board_hal.general_fault(true);
|
||||
anyhow::Ok(time)
|
||||
})?;
|
||||
board.board_hal.general_fault(true).await;
|
||||
esp_time().await
|
||||
}
|
||||
};
|
||||
|
||||
//check if we know the time current > 2020 (plausibility checks, this code is newer than 2020)
|
||||
if cur.year() < 2020 {
|
||||
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);
|
||||
update_charge_indicator(&mut board).await;
|
||||
println!("faul led3");
|
||||
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 {
|
||||
board.board_hal.general_fault(true).await;
|
||||
Timer::after_millis(100).await;
|
||||
@@ -216,7 +226,11 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> {
|
||||
board.board_hal.get_esp().set_restart_to_conf(false);
|
||||
} else if board.board_hal.get_esp().mode_override_pressed() {
|
||||
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 {
|
||||
board.board_hal.general_fault(true).await;
|
||||
Timer::after_millis(100).await;
|
||||
@@ -297,22 +311,25 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> {
|
||||
let _ = publish_mppt_state(&mut board).await;
|
||||
}
|
||||
|
||||
LOG_ACCESS.lock().await.log(
|
||||
LogMessage::StartupInfo,
|
||||
matches!(network_mode, NetworkMode::WIFI { .. }) as u32,
|
||||
matches!(
|
||||
network_mode,
|
||||
NetworkMode::WIFI {
|
||||
sntp: SntpMode::SYNC { .. },
|
||||
..
|
||||
}
|
||||
) as u32,
|
||||
matches!(network_mode, NetworkMode::WIFI { mqtt: true, .. })
|
||||
.to_string()
|
||||
.as_str(),
|
||||
"",
|
||||
).await
|
||||
;
|
||||
LOG_ACCESS
|
||||
.lock()
|
||||
.await
|
||||
.log(
|
||||
LogMessage::StartupInfo,
|
||||
matches!(network_mode, NetworkMode::WIFI { .. }) as u32,
|
||||
matches!(
|
||||
network_mode,
|
||||
NetworkMode::WIFI {
|
||||
sntp: SntpMode::SYNC { .. },
|
||||
..
|
||||
}
|
||||
) as u32,
|
||||
matches!(network_mode, NetworkMode::WIFI { mqtt: true, .. })
|
||||
.to_string()
|
||||
.as_str(),
|
||||
"",
|
||||
)
|
||||
.await;
|
||||
|
||||
if to_config {
|
||||
//check if client or ap mode and init Wi-Fi
|
||||
@@ -323,7 +340,11 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> {
|
||||
let board = BOARD_ACCESS.get().await.lock().await;
|
||||
wait_infinity(board, WaitType::ConfigButton, reboot_now.clone()).await;
|
||||
} 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;
|
||||
@@ -364,13 +385,10 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> {
|
||||
// }
|
||||
|
||||
let mut _water_frozen = false;
|
||||
//TODO
|
||||
let water_temp = anyhow::Ok(12_f32);
|
||||
// board
|
||||
// .board_hal
|
||||
// .get_tank_sensor()
|
||||
// .context("no sensor")
|
||||
// .and_then(async |f| f.water_temperature_c().await);
|
||||
let water_temp: FatResult<f32> = match board.board_hal.get_tank_sensor() {
|
||||
Ok(sensor) => sensor.water_temperature_c().await,
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
|
||||
if let Ok(res) = water_temp {
|
||||
if res < WATER_FROZEN_THRESH {
|
||||
@@ -615,7 +633,7 @@ pub async fn do_secure_pump(
|
||||
plant_id: usize,
|
||||
plant_config: &PlantConfig,
|
||||
dry_run: bool,
|
||||
) -> anyhow::Result<PumpResult> {
|
||||
) -> FatResult<PumpResult> {
|
||||
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 error = false;
|
||||
@@ -662,14 +680,17 @@ pub async fn do_secure_pump(
|
||||
let high_current = current_ma > plant_config.max_pump_current_ma;
|
||||
if high_current {
|
||||
if first_error {
|
||||
LOG_ACCESS.lock().await.log(
|
||||
LogMessage::PumpOverCurrent,
|
||||
plant_id as u32 + 1,
|
||||
current_ma as u32,
|
||||
plant_config.max_pump_current_ma.to_string().as_str(),
|
||||
step.to_string().as_str(),
|
||||
).await
|
||||
;
|
||||
LOG_ACCESS
|
||||
.lock()
|
||||
.await
|
||||
.log(
|
||||
LogMessage::PumpOverCurrent,
|
||||
plant_id as u32 + 1,
|
||||
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.fault(plant_id, true).await?;
|
||||
if !plant_config.ignore_current_error {
|
||||
@@ -682,14 +703,17 @@ pub async fn do_secure_pump(
|
||||
let low_current = current_ma < plant_config.min_pump_current_ma;
|
||||
if low_current {
|
||||
if first_error {
|
||||
LOG_ACCESS.lock().await.log(
|
||||
LogMessage::PumpOpenLoopCurrent,
|
||||
plant_id as u32 + 1,
|
||||
current_ma as u32,
|
||||
plant_config.min_pump_current_ma.to_string().as_str(),
|
||||
step.to_string().as_str(),
|
||||
).await
|
||||
;
|
||||
LOG_ACCESS
|
||||
.lock()
|
||||
.await
|
||||
.log(
|
||||
LogMessage::PumpOpenLoopCurrent,
|
||||
plant_id as u32 + 1,
|
||||
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.fault(plant_id, true).await?;
|
||||
if !plant_config.ignore_current_error {
|
||||
@@ -703,14 +727,17 @@ pub async fn do_secure_pump(
|
||||
Err(err) => {
|
||||
if !plant_config.ignore_current_error {
|
||||
info!("Error getting pump current: {}", err);
|
||||
LOG_ACCESS.lock().await.log(
|
||||
LogMessage::PumpMissingSensorCurrent,
|
||||
plant_id as u32,
|
||||
0,
|
||||
"",
|
||||
"",
|
||||
).await
|
||||
;
|
||||
LOG_ACCESS
|
||||
.lock()
|
||||
.await
|
||||
.log(
|
||||
LogMessage::PumpMissingSensorCurrent,
|
||||
plant_id as u32,
|
||||
0,
|
||||
"",
|
||||
"",
|
||||
)
|
||||
.await;
|
||||
error = true;
|
||||
break;
|
||||
} else {
|
||||
@@ -957,7 +984,7 @@ async fn pump_info(
|
||||
|
||||
async fn publish_mppt_state(
|
||||
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> FatResult<()> {
|
||||
let current = board.board_hal.get_mptt_current().await?;
|
||||
let voltage = board.board_hal.get_mptt_voltage().await?;
|
||||
let solar_state = Solar {
|
||||
@@ -983,13 +1010,18 @@ async fn publish_battery_state(
|
||||
.get_battery_monitor()
|
||||
.get_battery_state()
|
||||
.await;
|
||||
if let Ok(serialized_battery_state_bytes) =
|
||||
serde_json::to_string(&state).map(|s| s.into_bytes())
|
||||
let value = match state {
|
||||
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
|
||||
.get_esp()
|
||||
.mqtt_publish("/battery", &serialized_battery_state_bytes)
|
||||
.mqtt_publish("/battery", &*value)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
@@ -1062,13 +1094,14 @@ async fn wait_infinity(
|
||||
.lock()
|
||||
.await
|
||||
.board_hal
|
||||
.deep_sleep(0).await;
|
||||
.deep_sleep(0)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[esp_hal_embassy::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
async fn main(spawner: Spawner) -> ! {
|
||||
// intialize embassy
|
||||
logger::init_logger_from_env();
|
||||
//force init here!
|
||||
@@ -1082,21 +1115,13 @@ async fn main(spawner: Spawner) {
|
||||
println!("Hal init done, starting logic");
|
||||
|
||||
match safe_main(spawner).await {
|
||||
// this should not get triggered, safe_main should not return but go into deep sleep with sensible
|
||||
// timeout, this is just a fallback
|
||||
// this should not get triggered, safe_main should not return but go into deep sleep or reboot
|
||||
Ok(_) => {
|
||||
warn!("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);
|
||||
panic!("Main app finished, but should never do, restarting");
|
||||
}
|
||||
// if safe_main exists with an error, rollback to a known good ota version
|
||||
Err(err) => {
|
||||
error!("Failed main {}", err);
|
||||
//TODO
|
||||
//let _rollback_successful = rollback_and_reboot();
|
||||
//panic!("Failed to rollback :(");
|
||||
panic!("Failed main {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
use crate::alloc::string::{String, ToString};
|
||||
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;
|
||||
|
||||
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 left_ml = match self.left_ml(config) {
|
||||
Err(err) => {
|
||||
@@ -150,40 +154,41 @@ impl TankState {
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn determine_tank_state(board: &mut std::sync::MutexGuard<'_, HAL<'_>>) -> TankState {
|
||||
// if board.board_hal.get_config().tank.tank_sensor_enabled {
|
||||
// match board
|
||||
// .board_hal
|
||||
// .get_tank_sensor()
|
||||
// .context("no sensor")
|
||||
// .and_then(|f| f.tank_sensor_voltage())
|
||||
// {
|
||||
// Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv),
|
||||
// Err(err) => TankState::Error(TankError::BoardError(err.to_string())),
|
||||
// }
|
||||
// } else {
|
||||
// TankState::Disabled
|
||||
// }
|
||||
// }
|
||||
pub async fn determine_tank_state(
|
||||
board: &mut MutexGuard<'static, CriticalSectionRawMutex, HAL<'static>>,
|
||||
) -> TankState {
|
||||
if board.board_hal.get_config().tank.tank_sensor_enabled {
|
||||
match board
|
||||
.board_hal
|
||||
.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.await.unwrap()),
|
||||
Err(err) => TankState::Error(TankError::BoardError(err.to_string())),
|
||||
}
|
||||
} else {
|
||||
TankState::Disabled
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
/// Information structure send to mqtt for monitoring purposes
|
||||
pub struct TankInfo {
|
||||
/// there is enough water in the tank
|
||||
enough_water: bool,
|
||||
pub(crate) enough_water: bool,
|
||||
/// 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
|
||||
left_ml: Option<f32>,
|
||||
pub(crate) left_ml: Option<f32>,
|
||||
/// 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: Option<f32>,
|
||||
pub(crate) raw: Option<f32>,
|
||||
/// percent value
|
||||
percent: Option<f32>,
|
||||
pub(crate) percent: Option<f32>,
|
||||
/// water in the tank might be frozen
|
||||
water_frozen: bool,
|
||||
pub(crate) water_frozen: bool,
|
||||
/// water temperature
|
||||
water_temp: Option<f32>,
|
||||
temp_sensor_error: Option<String>,
|
||||
pub(crate) water_temp: Option<f32>,
|
||||
pub(crate) temp_sensor_error: Option<String>,
|
||||
}
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
//offer ota and config mode
|
||||
|
||||
use crate::config::PlantControllerConfig;
|
||||
use crate::{get_version, log::LogMessage, BOARD_ACCESS};
|
||||
use crate::hal::rtc::X25;
|
||||
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::format;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use anyhow::{bail};
|
||||
use chrono::DateTime;
|
||||
use core::fmt::{Debug, Display};
|
||||
use core::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use core::result::Result::Ok;
|
||||
use core::str::from_utf8;
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
use chrono::DateTime;
|
||||
use edge_http::io::server::{Connection, Handler, Server};
|
||||
use edge_http::io::Error;
|
||||
use edge_http::Method;
|
||||
use edge_nal::{TcpBind};
|
||||
use edge_nal::TcpBind;
|
||||
use edge_nal_embassy::{Tcp, TcpBuffers};
|
||||
use embassy_net::Stack;
|
||||
use embassy_time::Instant;
|
||||
@@ -25,8 +28,6 @@ use embedded_io_async::{Read, Write};
|
||||
use esp_println::println;
|
||||
use log::info;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::hal::{esp_set_time, esp_time};
|
||||
use crate::log::{LOG_ACCESS};
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct SSIDList {
|
||||
@@ -123,56 +124,9 @@ pub struct NightLampCommand {
|
||||
// anyhow::Ok(Some(json))
|
||||
// }
|
||||
//
|
||||
|
||||
//
|
||||
// fn backup_config(
|
||||
// request: &mut Request<&mut EspHttpConnection>,
|
||||
// ) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
// let all = read_up_to_bytes_from_request(request, Some(3072))?;
|
||||
// let mut board = BOARD_ACCESS.lock().expect("board access");
|
||||
//
|
||||
// //TODO how to handle progress here? prior versions animated the fault leds while running
|
||||
// board.board_hal.get_rtc_module().backup_config(&all)?;
|
||||
// anyhow::Ok(Some("saved".to_owned()))
|
||||
// }
|
||||
//
|
||||
// fn get_backup_config(
|
||||
// _request: &mut Request<&mut EspHttpConnection>,
|
||||
// ) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
// let mut board = BOARD_ACCESS.lock().expect("board access");
|
||||
// let json = match board.board_hal.get_rtc_module().get_backup_config() {
|
||||
// Ok(config) => from_utf8(&config)?.to_owned(),
|
||||
// Err(err) => {
|
||||
// log::info!("Error get backup config {:?}", err);
|
||||
// err.to_string()
|
||||
// }
|
||||
// };
|
||||
// anyhow::Ok(Some(json))
|
||||
// }
|
||||
//
|
||||
// fn backup_info(
|
||||
// _request: &mut Request<&mut EspHttpConnection>,
|
||||
// ) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
// let mut board = BOARD_ACCESS.lock().expect("Should never fail");
|
||||
// let header = board.board_hal.get_rtc_module().get_backup_info();
|
||||
// let json = match header {
|
||||
// Ok(h) => {
|
||||
// let timestamp = DateTime::from_timestamp_millis(h.timestamp).unwrap();
|
||||
// let wbh = WebBackupHeader {
|
||||
// timestamp: timestamp.to_rfc3339(),
|
||||
// size: h.size,
|
||||
// };
|
||||
// serde_json::to_string(&wbh)?
|
||||
// }
|
||||
// Err(err) => {
|
||||
// let wbh = WebBackupHeader {
|
||||
// timestamp: err.to_string(),
|
||||
// size: 0,
|
||||
// };
|
||||
// serde_json::to_string(&wbh)?
|
||||
// }
|
||||
// };
|
||||
// anyhow::Ok(Some(json))
|
||||
// }
|
||||
|
||||
//
|
||||
|
||||
//
|
||||
@@ -191,23 +145,6 @@ pub struct NightLampCommand {
|
||||
// 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(
|
||||
// request: &mut Request<&mut EspHttpConnection>,
|
||||
@@ -285,12 +222,12 @@ struct 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>(
|
||||
&self,
|
||||
_task_id: impl Display + Copy,
|
||||
conn: &mut Connection<'a, T, N>,
|
||||
) -> anyhow::Result<(), Self::Error<T::Error>>
|
||||
) -> Result<(), FatError>
|
||||
where
|
||||
T: Read + Write,
|
||||
{
|
||||
@@ -303,61 +240,84 @@ impl Handler for HttpHandler {
|
||||
let prefix = "/file?filename=";
|
||||
let status = if path.starts_with(prefix) {
|
||||
let filename = &path[prefix.len()..];
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
info!("file request for {} with method {}", filename, method);
|
||||
match method {
|
||||
Method::Delete => {
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
board
|
||||
.board_hal
|
||||
.get_esp()
|
||||
.delete_file(filename.to_owned())
|
||||
.await
|
||||
.unwrap();
|
||||
.await?;
|
||||
Some(200)
|
||||
}
|
||||
Method::Get => {
|
||||
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(
|
||||
200,
|
||||
Some("OK"),
|
||||
&[
|
||||
("Content-Type", "application/octet-stream"),
|
||||
("Content-Disposition", disp.as_str()),
|
||||
("Content-Length", &format!("{}", size)),
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut chunk = 0;
|
||||
loop {
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
board.board_hal.progress(chunk as u32).await;
|
||||
let read_chunk = board
|
||||
.board_hal
|
||||
.get_esp()
|
||||
.get_file(filename.to_owned(), chunk)
|
||||
.await
|
||||
.unwrap();
|
||||
.await?;
|
||||
let length = read_chunk.1;
|
||||
info!("read {} bytes for file request for {}", length, filename);
|
||||
if length == 0 {
|
||||
info!("file request for {} finished", filename);
|
||||
break;
|
||||
}
|
||||
let data = &read_chunk.0[0..length];
|
||||
conn.write_all(data).await?;
|
||||
if length < 128 {
|
||||
if length < read_chunk.0.len() {
|
||||
info!("file request for {} finished", filename);
|
||||
break;
|
||||
}
|
||||
chunk = chunk + 1;
|
||||
}
|
||||
BOARD_ACCESS
|
||||
.get()
|
||||
.await
|
||||
.lock()
|
||||
.await
|
||||
.board_hal
|
||||
.clear_progress()
|
||||
.await;
|
||||
Some(200)
|
||||
}
|
||||
Method::Post => {
|
||||
//ensure file is deleted, otherwise we would need to truncate the file which will not work with streaming
|
||||
let _ = board
|
||||
.board_hal
|
||||
.get_esp()
|
||||
.delete_file(filename.to_owned())
|
||||
.await;
|
||||
{
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
//ensure file is deleted, otherwise we would need to truncate the file which will not work with streaming
|
||||
let _ = board
|
||||
.board_hal
|
||||
.get_esp()
|
||||
.delete_file(filename.to_owned())
|
||||
.await;
|
||||
}
|
||||
|
||||
let mut offset = 0_usize;
|
||||
let mut chunk = 0;
|
||||
loop {
|
||||
let mut buf = [0_u8; 1024];
|
||||
let to_write = conn.read(&mut buf).await?;
|
||||
@@ -365,20 +325,25 @@ impl Handler for HttpHandler {
|
||||
info!("file request for {} finished", filename);
|
||||
break;
|
||||
} else {
|
||||
info!(
|
||||
"writing {} bytes for file request for {}",
|
||||
to_write, filename
|
||||
);
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
board.board_hal.progress(chunk as u32).await;
|
||||
board
|
||||
.board_hal
|
||||
.get_esp()
|
||||
.write_file(filename.to_owned(), offset as u32, &buf[0..to_write])
|
||||
.await
|
||||
.unwrap();
|
||||
.await?;
|
||||
}
|
||||
offset = offset + to_write
|
||||
offset = offset + to_write;
|
||||
chunk = chunk + 1;
|
||||
}
|
||||
|
||||
BOARD_ACCESS
|
||||
.get()
|
||||
.await
|
||||
.lock()
|
||||
.await
|
||||
.board_hal
|
||||
.clear_progress()
|
||||
.await;
|
||||
Some(200)
|
||||
}
|
||||
_ => None,
|
||||
@@ -397,25 +362,36 @@ impl Handler for HttpHandler {
|
||||
Some(200)
|
||||
}
|
||||
"/" => {
|
||||
conn.initiate_response(200, Some("OK"), &[("Content-Type", "text/html")])
|
||||
.await?;
|
||||
conn.write_all(include_bytes!("index.html")).await?;
|
||||
conn.initiate_response(
|
||||
200,
|
||||
Some("OK"),
|
||||
&[("Content-Type", "text/html"), ("Content-Encoding", "gzip")],
|
||||
)
|
||||
.await?;
|
||||
conn.write_all(include_bytes!("index.html.gz")).await?;
|
||||
Some(200)
|
||||
}
|
||||
"/bundle.js" => {
|
||||
conn.initiate_response(
|
||||
200,
|
||||
Some("OK"),
|
||||
&[("Content-Type", "text/javascript")],
|
||||
&[
|
||||
("Content-Type", "text/javascript"),
|
||||
("Content-Encoding", "gzip"),
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
conn.write_all(include_bytes!("bundle.js")).await?;
|
||||
conn.write_all(include_bytes!("bundle.js.gz")).await?;
|
||||
Some(200)
|
||||
}
|
||||
"/log" => {
|
||||
let buf = get_log(conn).await;
|
||||
Some(200)
|
||||
},
|
||||
}
|
||||
"/get_backup_config" => {
|
||||
get_backup_config(conn).await?;
|
||||
Some(200)
|
||||
}
|
||||
&_ => {
|
||||
let json = match path {
|
||||
"/version" => Some(get_version_web(conn).await),
|
||||
@@ -425,7 +401,8 @@ impl Handler for HttpHandler {
|
||||
"/get_config" => Some(get_config(conn).await),
|
||||
"/files" => Some(list_files(conn).await),
|
||||
"/log_localization" => Some(get_log_localization_config(conn).await),
|
||||
"/wifiscan" => Some(wifi_scan(conn).await),
|
||||
"/tank" => Some(tank_info(conn).await),
|
||||
"/backup_info" => Some(backup_info(conn).await),
|
||||
_ => None,
|
||||
};
|
||||
match json {
|
||||
@@ -439,6 +416,7 @@ impl Handler for HttpHandler {
|
||||
"/wifiscan" => Some(wifi_scan(conn).await),
|
||||
"/set_config" => Some(set_config(conn).await),
|
||||
"/time" => Some(write_time(conn).await),
|
||||
"/backup_config" => Some(backup_config(conn).await),
|
||||
"/reboot" => {
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
board.board_hal.get_esp().set_restart_to_conf(true);
|
||||
@@ -472,105 +450,197 @@ impl Handler for HttpHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// .fn_handler("/file", Method::Get, move |request| {
|
||||
// let filename = query_param(request.uri(), "filename").unwrap();
|
||||
// let file_handle = BOARD_ACCESS
|
||||
// .lock()
|
||||
// .unwrap()
|
||||
// .board_hal
|
||||
// .get_esp()
|
||||
// .get_file_handle(&filename, false);
|
||||
// match file_handle {
|
||||
// Ok(mut file_handle) => {
|
||||
// let headers = [("Access-Control-Allow-Origin", "*")];
|
||||
// let mut response = request.into_response(200, None, &headers)?;
|
||||
// const BUFFER_SIZE: usize = 512;
|
||||
// let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
|
||||
// let mut total_read: usize = 0;
|
||||
// loop {
|
||||
// unsafe { vTaskDelay(1) };
|
||||
// let read = std::io::Read::read(&mut file_handle, &mut buffer)?;
|
||||
// total_read += read;
|
||||
// let to_write = &buffer[0..read];
|
||||
// response.write(to_write)?;
|
||||
// if read == 0 {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// log::info!("wrote {total_read} for file {filename}");
|
||||
// drop(file_handle);
|
||||
// response.flush()?;
|
||||
// }
|
||||
// Err(err) => {
|
||||
// //todo set headers here for filename to be error
|
||||
// let error_text = err.to_string();
|
||||
// log::info!("error handling get file {}", error_text);
|
||||
// cors_response(request, 500, &error_text)?;
|
||||
// }
|
||||
// }
|
||||
// anyhow::Ok(())
|
||||
// })
|
||||
// .unwrap();
|
||||
// server
|
||||
// .fn_handler("/file", Method::Post, move |mut request| {
|
||||
// let filename = query_param(request.uri(), "filename").unwrap();
|
||||
// let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
// let file_handle = board.board_hal.get_esp().get_file_handle(&filename, true);
|
||||
// match file_handle {
|
||||
// //TODO get free filesystem size, check against during write if not to large
|
||||
// Ok(mut file_handle) => {
|
||||
// const BUFFER_SIZE: usize = 512;
|
||||
// let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
|
||||
// let mut total_read: usize = 0;
|
||||
// let mut lastiter = 0;
|
||||
// loop {
|
||||
// let iter = (total_read / 1024) % 8;
|
||||
// if iter != lastiter {
|
||||
// for i in 0..PLANT_COUNT {
|
||||
// let _ = board.board_hal.fault(i, iter == i);
|
||||
// }
|
||||
// lastiter = iter;
|
||||
// }
|
||||
//
|
||||
// let read = request.read(&mut buffer)?;
|
||||
// total_read += read;
|
||||
// let to_write = &buffer[0..read];
|
||||
// std::io::Write::write(&mut file_handle, to_write)?;
|
||||
// if read == 0 {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// cors_response(request, 200, &format!("saved {total_read} bytes"))?;
|
||||
// }
|
||||
// Err(err) => {
|
||||
// //todo set headers here for filename to be error
|
||||
// let error_text = err.to_string();
|
||||
// log::info!("error handling get file {}", error_text);
|
||||
// cors_response(request, 500, &error_text)?;
|
||||
// }
|
||||
// }
|
||||
// drop(board);
|
||||
// anyhow::Ok(())
|
||||
// })
|
||||
// .unwrap();
|
||||
async fn get_backup_config<T, const N: usize>(
|
||||
conn: &mut Connection<'_, T, { N }>,
|
||||
) -> Result<(), FatError>
|
||||
where
|
||||
T: Read + Write,
|
||||
{
|
||||
// First pass: verify checksum without sending data
|
||||
let mut checksum = X25.digest();
|
||||
let mut chunk = 0_usize;
|
||||
loop {
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
board.board_hal.progress(chunk as u32).await;
|
||||
let (buf, len, expected_crc) = board
|
||||
.board_hal
|
||||
.get_rtc_module()
|
||||
.get_backup_config(chunk)
|
||||
.await?;
|
||||
|
||||
// Update checksum with the actual data bytes of this chunk
|
||||
checksum.update(&buf[..len]);
|
||||
|
||||
let is_last = len == 0 || len < buf.len();
|
||||
if is_last {
|
||||
let actual_crc = checksum.finalize();
|
||||
if actual_crc != expected_crc {
|
||||
BOARD_ACCESS
|
||||
.get()
|
||||
.await
|
||||
.lock()
|
||||
.await
|
||||
.board_hal
|
||||
.clear_progress()
|
||||
.await;
|
||||
conn.initiate_response(
|
||||
409,
|
||||
Some(
|
||||
format!(
|
||||
"Checksum mismatch expected {} got {}",
|
||||
expected_crc, actual_crc
|
||||
)
|
||||
.as_str(),
|
||||
),
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
break;
|
||||
}
|
||||
chunk += 1;
|
||||
}
|
||||
|
||||
// Second pass: stream data
|
||||
conn.initiate_response(200, Some("OK"), &[]).await?;
|
||||
|
||||
let mut chunk = 0_usize;
|
||||
loop {
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
board.board_hal.progress(chunk as u32).await;
|
||||
let (buf, len, _expected_crc) = board
|
||||
.board_hal
|
||||
.get_rtc_module()
|
||||
.get_backup_config(chunk)
|
||||
.await?;
|
||||
|
||||
if len == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
conn.write_all(&buf[..len]).await?;
|
||||
|
||||
if len < buf.len() {
|
||||
break;
|
||||
}
|
||||
chunk += 1;
|
||||
}
|
||||
BOARD_ACCESS
|
||||
.get()
|
||||
.await
|
||||
.lock()
|
||||
.await
|
||||
.board_hal
|
||||
.clear_progress()
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn backup_config<T, const N: usize>(
|
||||
conn: &mut Connection<'_, T, N>,
|
||||
) -> FatResult<Option<String>>
|
||||
where
|
||||
T: Read + Write,
|
||||
{
|
||||
let mut offset = 0_usize;
|
||||
let mut buf = [0_u8; 32];
|
||||
|
||||
let mut checksum = crate::hal::rtc::X25.digest();
|
||||
|
||||
let mut counter = 0;
|
||||
loop {
|
||||
let to_write = conn.read(&mut buf).await?;
|
||||
if to_write == 0 {
|
||||
info!("backup finished");
|
||||
break;
|
||||
} else {
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
board.board_hal.progress(counter).await;
|
||||
|
||||
counter = counter + 1;
|
||||
board
|
||||
.board_hal
|
||||
.get_rtc_module()
|
||||
.backup_config(offset, &buf[0..to_write])
|
||||
.await?;
|
||||
checksum.update(&buf[0..to_write]);
|
||||
}
|
||||
offset = offset + to_write;
|
||||
}
|
||||
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
board
|
||||
.board_hal
|
||||
.get_rtc_module()
|
||||
.backup_config_finalize(checksum.finalize(), offset)
|
||||
.await?;
|
||||
board.board_hal.clear_progress().await;
|
||||
Ok(Some("saved".to_owned()))
|
||||
}
|
||||
|
||||
async fn backup_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;
|
||||
let header = board.board_hal.get_rtc_module().get_backup_info().await;
|
||||
let json = match header {
|
||||
Ok(h) => {
|
||||
let timestamp = DateTime::from_timestamp_millis(h.timestamp).unwrap();
|
||||
let wbh = WebBackupHeader {
|
||||
timestamp: timestamp.to_rfc3339(),
|
||||
size: h.size,
|
||||
};
|
||||
serde_json::to_string(&wbh)?
|
||||
}
|
||||
Err(err) => {
|
||||
let wbh = WebBackupHeader {
|
||||
timestamp: err.to_string(),
|
||||
size: 0,
|
||||
};
|
||||
serde_json::to_string(&wbh)?
|
||||
}
|
||||
};
|
||||
Ok(Some(json))
|
||||
}
|
||||
|
||||
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;
|
||||
let tank_state = determine_tank_state(&mut board).await;
|
||||
//should be multisampled
|
||||
let sensor = board.board_hal.get_tank_sensor()?;
|
||||
|
||||
let water_temp: FatResult<f32> = sensor.water_temperature_c().await;
|
||||
Ok(Some(serde_json::to_string(&tank_state.as_mqtt_info(
|
||||
&board.board_hal.get_config().tank,
|
||||
&water_temp,
|
||||
))?))
|
||||
}
|
||||
|
||||
async fn write_time<T, const N: usize>(
|
||||
request: &mut Connection<'_, T, N>,
|
||||
) -> Result<Option<String>, anyhow::Error>
|
||||
) -> FatResult<Option<String>>
|
||||
where
|
||||
T: Read + Write
|
||||
T: Read + Write,
|
||||
{
|
||||
let actual_data = read_up_to_bytes_from_request(request, None).await?;
|
||||
let time: SetTime = serde_json::from_slice(&actual_data)?;
|
||||
let parsed = DateTime::parse_from_rfc3339(time.time).unwrap();
|
||||
esp_set_time(parsed).await;
|
||||
anyhow::Ok(None)
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
|
||||
async fn set_config<T, const N: usize>(
|
||||
request: &mut Connection<'_, T, N>,
|
||||
) -> Result<Option<String>, anyhow::Error>
|
||||
) -> FatResult<Option<String>>
|
||||
where
|
||||
T: Read + Write,
|
||||
{
|
||||
@@ -582,13 +652,13 @@ where
|
||||
board.board_hal.get_esp().save_config(all).await?;
|
||||
log::info!("Wrote config config {:?} with size {}", config, length);
|
||||
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>(
|
||||
request: &mut Connection<'_, T, N>,
|
||||
limit: Option<usize>,
|
||||
) -> Result<Vec<u8>, anyhow::Error>
|
||||
) -> FatResult<Vec<u8>>
|
||||
where
|
||||
T: Read + Write,
|
||||
{
|
||||
@@ -597,10 +667,7 @@ where
|
||||
let mut total_read = 0;
|
||||
loop {
|
||||
let mut buf = [0_u8; 64];
|
||||
let read = match request.read(&mut buf).await {
|
||||
Ok(read) => read,
|
||||
Err(e) => bail!("Error reading request {:?}", e),
|
||||
};
|
||||
let read = request.read(&mut buf).await?;
|
||||
if read == 0 {
|
||||
break;
|
||||
}
|
||||
@@ -618,7 +685,7 @@ where
|
||||
|
||||
async fn wifi_scan<T, const N: usize>(
|
||||
_request: &mut Connection<'_, T, N>,
|
||||
) -> Result<Option<String>, anyhow::Error> {
|
||||
) -> FatResult<Option<String>> {
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
info!("start wifi scan");
|
||||
//let scan_result = board.board_hal.get_esp().wifi_scan().await?
|
||||
@@ -629,111 +696,105 @@ async fn wifi_scan<T, const N: usize>(
|
||||
//.for_each(|s| ssids.push(s.ssid.to_string()));
|
||||
let ssid_json = serde_json::to_string(&SSIDList { ssids })?;
|
||||
info!("Sending ssid list {}", &ssid_json);
|
||||
anyhow::Ok(Some(ssid_json))
|
||||
Ok(Some(ssid_json))
|
||||
}
|
||||
|
||||
async fn get_log<T, const N: usize>(
|
||||
conn: &mut Connection<'_, T, N>,
|
||||
) -> anyhow::Result<()>
|
||||
async fn get_log<T, const N: usize>(conn: &mut Connection<'_, T, N>) -> FatResult<()>
|
||||
where
|
||||
T: Read + Write,{
|
||||
T: Read + Write,
|
||||
{
|
||||
let log = LOG_ACCESS.lock().await.get();
|
||||
conn.initiate_response(
|
||||
200,
|
||||
Some("OK"),
|
||||
&[("Content-Type", "text/javascript")],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
conn.write_all("[".as_bytes()).await.unwrap();
|
||||
conn.initiate_response(200, Some("OK"), &[("Content-Type", "text/javascript")])
|
||||
.await?;
|
||||
conn.write_all("[".as_bytes()).await?;
|
||||
let mut append = false;
|
||||
for entry in log {
|
||||
for entry in log {
|
||||
if append {
|
||||
conn.write_all(",".as_bytes()).await.unwrap();
|
||||
conn.write_all(",".as_bytes()).await?;
|
||||
}
|
||||
append = true;
|
||||
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(())
|
||||
}
|
||||
|
||||
async fn get_log_localization_config<T, const N: usize>(
|
||||
_request: &mut Connection<'_, T, N>,
|
||||
) -> Result<Option<String>, anyhow::Error> {
|
||||
anyhow::Ok(Some(serde_json::to_string(
|
||||
) -> FatResult<Option<String>> {
|
||||
Ok(Some(serde_json::to_string(
|
||||
&LogMessage::to_log_localisation_config(),
|
||||
)?))
|
||||
}
|
||||
async fn list_files<T, const N: usize>(
|
||||
_request: &mut Connection<'_, T, N>,
|
||||
) -> Result<Option<String>, anyhow::Error> {
|
||||
) -> FatResult<Option<String>> {
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
let result = board.board_hal.get_esp().list_files().await?;
|
||||
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>(
|
||||
_request: &mut Connection<'_, T, N>,
|
||||
) -> Result<Option<String>, anyhow::Error> {
|
||||
) -> FatResult<Option<String>> {
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
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>(
|
||||
_request: &mut Connection<'_, T, N>,
|
||||
) -> Result<Option<String>, anyhow::Error> {
|
||||
) -> FatResult<Option<String>> {
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
let state = SolarState {
|
||||
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,
|
||||
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>(
|
||||
_request: &mut Connection<'_, T, N>,
|
||||
) -> Result<Option<String>, anyhow::Error> {
|
||||
) -> FatResult<Option<String>> {
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
let battery_state = board
|
||||
.board_hal
|
||||
.get_battery_monitor()
|
||||
.get_battery_state()
|
||||
.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>(
|
||||
_request: &mut Connection<'_, T, N>,
|
||||
) -> Result<Option<String>, anyhow::Error> {
|
||||
) -> FatResult<Option<String>> {
|
||||
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>(
|
||||
_request: &mut Connection<'_, T, N>,
|
||||
) -> Result<Option<String>, anyhow::Error> {
|
||||
) -> FatResult<Option<String>> {
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
//TODO do not fail if rtc module is missing
|
||||
|
||||
let native = esp_time().await.to_rfc3339();
|
||||
let rtc = "todo";
|
||||
// board
|
||||
// .board_hal
|
||||
// .get_rtc_module()
|
||||
// .get_rtc_time()
|
||||
// .await?
|
||||
// .to_rfc3339();
|
||||
|
||||
let rtc = match board.board_hal.get_rtc_module().get_rtc_time().await {
|
||||
Ok(time) => time.to_rfc3339(),
|
||||
Err(err) => {
|
||||
format!("Error getting time: {}", err)
|
||||
}
|
||||
};
|
||||
|
||||
let data = LoadData {
|
||||
rtc,
|
||||
rtc: rtc.as_str(),
|
||||
native: native.as_str(),
|
||||
};
|
||||
let json = serde_json::to_string(&data)?;
|
||||
|
||||
anyhow::Ok(Some(json))
|
||||
Ok(Some(json))
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
@@ -833,32 +894,6 @@ pub async fn httpd(reboot_now: Arc<AtomicBool>, stack: Stack<'static>) {
|
||||
// cors_response(request, 200, "")
|
||||
// })
|
||||
// .unwrap();
|
||||
// server
|
||||
// .fn_handler("/get_config", Method::Get, move |request| {
|
||||
// handle_error_to500(request, get_config)
|
||||
// })
|
||||
// .unwrap();
|
||||
// server
|
||||
// .fn_handler("/get_backup_config", Method::Get, move |request| {
|
||||
// handle_error_to500(request, get_backup_config)
|
||||
// })
|
||||
// .unwrap();
|
||||
//
|
||||
// server
|
||||
// .fn_handler("/backup_config", Method::Post, move |request| {
|
||||
// handle_error_to500(request, backup_config)
|
||||
// })
|
||||
// .unwrap();
|
||||
// server
|
||||
// .fn_handler("/backup_info", Method::Get, move |request| {
|
||||
// handle_error_to500(request, backup_info)
|
||||
// })
|
||||
// .unwrap();
|
||||
// server
|
||||
// .fn_handler("/files", Method::Get, move |request| {
|
||||
// handle_error_to500(request, list_files)
|
||||
// })
|
||||
// .unwrap();
|
||||
// let reboot_now_for_reboot = reboot_now.clone();
|
||||
// server
|
||||
// .fn_handler("/reboot", Method::Post, move |_| {
|
||||
@@ -885,29 +920,6 @@ pub async fn httpd(reboot_now: Arc<AtomicBool>, stack: Stack<'static>) {
|
||||
// server
|
||||
|
||||
//
|
||||
// server
|
||||
// .fn_handler("/file", Method::Delete, move |request| {
|
||||
// let filename = query_param(request.uri(), "filename").unwrap();
|
||||
// let copy = filename.clone();
|
||||
// let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
// match board.board_hal.get_esp().delete_file(&filename) {
|
||||
// Ok(_) => {
|
||||
// let info = format!("Deleted file {copy}");
|
||||
// cors_response(request, 200, &info)?;
|
||||
// }
|
||||
// Err(err) => {
|
||||
// let info = format!("Could not delete file {copy} {err:?}");
|
||||
// cors_response(request, 400, &info)?;
|
||||
// }
|
||||
// }
|
||||
// anyhow::Ok(())
|
||||
// })
|
||||
// .unwrap();
|
||||
// server
|
||||
// .fn_handler("/file", Method::Options, |request| {
|
||||
// cors_response(request, 200, "")
|
||||
// })
|
||||
// .unwrap();
|
||||
// unsafe { vTaskDelay(1) };
|
||||
// server
|
||||
// .fn_handler("/", Method::Get, move |request| {
|
||||
@@ -943,8 +955,8 @@ pub async fn httpd(reboot_now: Arc<AtomicBool>, stack: Stack<'static>) {
|
||||
|
||||
async fn handle_json<'a, T, const N: usize>(
|
||||
conn: &mut Connection<'a, T, N>,
|
||||
chain: anyhow::Result<Option<String>>,
|
||||
) -> anyhow::Result<u32, Error<T::Error>>
|
||||
chain: FatResult<Option<String>>,
|
||||
) -> FatResult<u32>
|
||||
where
|
||||
T: Read + Write,
|
||||
<T as embedded_io_async::ErrorType>::Error: Debug,
|
||||
|
||||
1540
rust/src_webpack/package-lock.json
generated
1540
rust/src_webpack/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"compression-webpack-plugin": "^11.1.0",
|
||||
"html-webpack-harddisk-plugin": "^2.0.0",
|
||||
"html-webpack-plugin": "^5.6.3",
|
||||
"raw-loader": "^4.0.2",
|
||||
|
||||
@@ -69,45 +69,44 @@ export class Controller {
|
||||
});
|
||||
}
|
||||
|
||||
getBackupInfo(): Promise<void> {
|
||||
return fetch(PUBLIC_URL + "/backup_info")
|
||||
.then(response => response.json())
|
||||
.then(json => json as BackupHeader)
|
||||
.then(header => {
|
||||
controller.submitView.setBackupInfo(header)
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
});
|
||||
async getBackupInfo(): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(PUBLIC_URL + "/backup_info");
|
||||
const json = await response.json();
|
||||
const header = json as BackupHeader;
|
||||
controller.submitView.setBackupInfo(header);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
populateTimezones(): Promise<void> {
|
||||
return fetch(PUBLIC_URL + '/timezones')
|
||||
.then(response => response.json())
|
||||
.then(json => json as string[])
|
||||
.then(timezones => {
|
||||
controller.timeView.timezones(timezones)
|
||||
})
|
||||
.catch(error => console.error('Error fetching timezones:', error));
|
||||
async populateTimezones(): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(PUBLIC_URL + '/timezones');
|
||||
const json = await response.json();
|
||||
const timezones = json as string[];
|
||||
controller.timeView.timezones(timezones);
|
||||
} catch (error) {
|
||||
return console.error('Error fetching timezones:', error);
|
||||
}
|
||||
}
|
||||
|
||||
updateFileList(): Promise<void> {
|
||||
return fetch(PUBLIC_URL + "/files")
|
||||
.then(response => response.json())
|
||||
.then(json => json as FileList)
|
||||
.then(filelist => {
|
||||
controller.fileview.setFileList(filelist, PUBLIC_URL)
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
});
|
||||
async updateFileList(): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(PUBLIC_URL + "/files");
|
||||
const json = await response.json();
|
||||
const filelist = json as FileList;
|
||||
controller.fileview.setFileList(filelist, PUBLIC_URL);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
uploadFile(file: File, name: string) {
|
||||
var current = 0;
|
||||
var max = 100;
|
||||
let current = 0;
|
||||
let max = 100;
|
||||
controller.progressview.addProgress("file_upload", (current / max) * 100, "Uploading File " + name + "(" + current + "/" + max + ")")
|
||||
var ajax = new XMLHttpRequest();
|
||||
const ajax = new XMLHttpRequest();
|
||||
ajax.upload.addEventListener("progress", event => {
|
||||
current = event.loaded / 1000;
|
||||
max = event.total / 1000;
|
||||
@@ -133,7 +132,7 @@ export class Controller {
|
||||
|
||||
deleteFile(name: string) {
|
||||
controller.progressview.addIndeterminate("file_delete", "Deleting " + name);
|
||||
var ajax = new XMLHttpRequest();
|
||||
const ajax = new XMLHttpRequest();
|
||||
ajax.open("DELETE", PUBLIC_URL + "/file?filename=" + name);
|
||||
ajax.send();
|
||||
ajax.addEventListener("error", () => {
|
||||
@@ -153,50 +152,47 @@ export class Controller {
|
||||
controller.updateFileList()
|
||||
}
|
||||
|
||||
updateRTCData(): Promise<void> {
|
||||
return fetch(PUBLIC_URL + "/time")
|
||||
.then(response => response.json())
|
||||
.then(json => json as GetTime)
|
||||
.then(time => {
|
||||
controller.timeView.update(time.native, time.rtc)
|
||||
})
|
||||
.catch(error => {
|
||||
controller.timeView.update("n/a", "n/a")
|
||||
console.log(error);
|
||||
});
|
||||
async updateRTCData(): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(PUBLIC_URL + "/time");
|
||||
const json = await response.json();
|
||||
const time = json as GetTime;
|
||||
controller.timeView.update(time.native, time.rtc);
|
||||
} catch (error) {
|
||||
controller.timeView.update("n/a", "n/a");
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
updateBatteryData(): Promise<void> {
|
||||
return fetch(PUBLIC_URL + "/battery")
|
||||
.then(response => response.json())
|
||||
.then(json => json as BatteryState)
|
||||
.then(battery => {
|
||||
controller.batteryView.update(battery)
|
||||
})
|
||||
.catch(error => {
|
||||
controller.batteryView.update(null)
|
||||
console.log(error);
|
||||
})
|
||||
async updateBatteryData(): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(PUBLIC_URL + "/battery");
|
||||
const json = await response.json();
|
||||
const battery = json as BatteryState;
|
||||
controller.batteryView.update(battery);
|
||||
} catch (error) {
|
||||
controller.batteryView.update(null);
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
updateSolarData(): Promise<void> {
|
||||
return fetch(PUBLIC_URL + "/solar")
|
||||
.then(response => response.json())
|
||||
.then(json => json as SolarState)
|
||||
.then(solar => {
|
||||
controller.solarView.update(solar)
|
||||
})
|
||||
.catch(error => {
|
||||
controller.solarView.update(null)
|
||||
console.log(error);
|
||||
})
|
||||
async updateSolarData(): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(PUBLIC_URL + "/solar");
|
||||
const json = await response.json();
|
||||
const solar = json as SolarState;
|
||||
controller.solarView.update(solar);
|
||||
} catch (error) {
|
||||
controller.solarView.update(null);
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
uploadNewFirmware(file: File) {
|
||||
var current = 0;
|
||||
var max = 100;
|
||||
let current = 0;
|
||||
let max = 100;
|
||||
controller.progressview.addProgress("ota_upload", (current / max) * 100, "Uploading firmeware (" + current + "/" + max + ")")
|
||||
var ajax = new XMLHttpRequest();
|
||||
const ajax = new XMLHttpRequest();
|
||||
ajax.upload.addEventListener("progress", event => {
|
||||
current = event.loaded / 1000;
|
||||
max = event.total / 1000;
|
||||
@@ -218,15 +214,13 @@ export class Controller {
|
||||
ajax.send(file);
|
||||
}
|
||||
|
||||
version(): Promise<void> {
|
||||
async version(): Promise<void> {
|
||||
controller.progressview.addIndeterminate("version", "Getting buildVersion")
|
||||
return fetch(PUBLIC_URL + "/version")
|
||||
.then(response => response.json())
|
||||
.then(json => json as VersionInfo)
|
||||
.then(versionInfo => {
|
||||
controller.progressview.removeProgress("version")
|
||||
controller.firmWareView.setVersion(versionInfo);
|
||||
})
|
||||
const response = await fetch(PUBLIC_URL + "/version");
|
||||
const json = await response.json();
|
||||
const versionInfo = json as VersionInfo;
|
||||
controller.progressview.removeProgress("version");
|
||||
controller.firmWareView.setVersion(versionInfo);
|
||||
}
|
||||
|
||||
getBackupConfig() {
|
||||
@@ -243,7 +237,7 @@ export class Controller {
|
||||
controller.progressview.addIndeterminate("get_config", "Downloading Config")
|
||||
const response = await fetch(PUBLIC_URL + "/get_config");
|
||||
const loaded = await response.json();
|
||||
var currentConfig = loaded as PlantControllerConfig;
|
||||
const currentConfig = loaded as PlantControllerConfig;
|
||||
controller.setInitialConfig(currentConfig);
|
||||
controller.setConfig(currentConfig);
|
||||
//sync json view initially
|
||||
@@ -268,12 +262,12 @@ export class Controller {
|
||||
controller.downloadConfig()
|
||||
}
|
||||
|
||||
backupConfig(json: string): Promise<string> {
|
||||
return fetch(PUBLIC_URL + "/backup_config", {
|
||||
async backupConfig(json: string): Promise<string> {
|
||||
const response = await fetch(PUBLIC_URL + "/backup_config", {
|
||||
method: "POST",
|
||||
body: json,
|
||||
})
|
||||
.then(response => response.text());
|
||||
});
|
||||
return await response.text();
|
||||
}
|
||||
|
||||
syncRTCFromBrowser() {
|
||||
@@ -310,9 +304,9 @@ export class Controller {
|
||||
}
|
||||
|
||||
testNightLamp(active: boolean) {
|
||||
var body: NightLampCommand = {
|
||||
const body: NightLampCommand = {
|
||||
active: active
|
||||
}
|
||||
};
|
||||
var pretty = JSON.stringify(body, undefined, 1);
|
||||
fetch(PUBLIC_URL + "/lamptest", {
|
||||
method: "POST",
|
||||
|
||||
@@ -3,54 +3,60 @@ const path = require('path');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin');
|
||||
const CopyPlugin = require("copy-webpack-plugin");
|
||||
const CompressionPlugin = require("compression-webpack-plugin");
|
||||
|
||||
const isDevServer = process.env.WEBPACK_SERVE;
|
||||
console.log("Dev server is " + isDevServer);
|
||||
var host;
|
||||
if (isDevServer){
|
||||
//ensure no trailing /
|
||||
host = 'http://10.23.44.186';
|
||||
if (isDevServer) {
|
||||
//ensure no trailing /
|
||||
host = 'http://10.23.44.186';
|
||||
} else {
|
||||
host = '';
|
||||
host = '';
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
entry: ['./src/main.ts'],
|
||||
devtool: 'inline-source-map',
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
PUBLIC_URL: JSON.stringify(host),
|
||||
}),
|
||||
new webpack.EnvironmentPlugin({
|
||||
redirect: 'true'
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
alwaysWriteToDisk: true,
|
||||
title: "PlantCtrl",
|
||||
}),
|
||||
new HtmlWebpackHarddiskPlugin(),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.html$/,
|
||||
type: 'asset/source',
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.js', '.html'],
|
||||
},
|
||||
output: {
|
||||
filename: 'bundle.js',
|
||||
path: path.resolve(__dirname, '.'),
|
||||
},
|
||||
devServer: {
|
||||
}
|
||||
mode: "development",
|
||||
entry: ['./src/main.ts'],
|
||||
devtool: 'inline-source-map',
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
PUBLIC_URL: JSON.stringify(host),
|
||||
}),
|
||||
new webpack.EnvironmentPlugin({
|
||||
redirect: 'true'
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
alwaysWriteToDisk: true,
|
||||
title: "PlantCtrl",
|
||||
}),
|
||||
new HtmlWebpackHarddiskPlugin(),
|
||||
new CompressionPlugin({
|
||||
algorithm: "gzip",
|
||||
test: /\.js$|\.css$|\.html$/,
|
||||
threshold: 0,
|
||||
minRatio: 0.8
|
||||
})
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.html$/,
|
||||
type: 'asset/source',
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.js', '.html'],
|
||||
},
|
||||
output: {
|
||||
filename: 'bundle.js',
|
||||
path: path.resolve(__dirname, '.'),
|
||||
},
|
||||
devServer: {}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user