diff --git a/board/PlantCtrlESP32.kicad_prl b/board/PlantCtrlESP32.kicad_prl
index c5c3586..34d4d40 100644
--- a/board/PlantCtrlESP32.kicad_prl
+++ b/board/PlantCtrlESP32.kicad_prl
@@ -1,6 +1,6 @@
{
"board": {
- "active_layer": 2,
+ "active_layer": 5,
"active_layer_preset": "All Layers",
"auto_track_width": false,
"hidden_netclasses": [],
diff --git a/rust/.idea/inspectionProfiles/Project_Default.xml b/rust/.idea/inspectionProfiles/Project_Default.xml
index 03d9549..3fe75f2 100644
--- a/rust/.idea/inspectionProfiles/Project_Default.xml
+++ b/rust/.idea/inspectionProfiles/Project_Default.xml
@@ -1,6 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index db37b96..81e29c2 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -14,11 +14,8 @@ debug = true
overflow-checks = true
panic = "abort"
incremental = true
-opt-level = "s"
+opt-level = 2
-[profile.dev.build-override]
-opt-level = 1
-incremental = true
[package.metadata.cargo_runner]
# The string `$TARGET_FILE` will be replaced with the path from cargo.
@@ -86,6 +83,7 @@ esp-ota = { version = "0.2.2", features = ["log"] }
unit-enum = "1.4.1"
pca9535 = { version = "2.0.0", features = ["std"] }
ina219 = { version = "0.2.0", features = ["std"] }
+embedded-storage = "=0.3.1"
[patch.crates-io]
diff --git a/rust/src/hal/initial_hal.rs b/rust/src/hal/initial_hal.rs
index 381125d..7a25adb 100644
--- a/rust/src/hal/initial_hal.rs
+++ b/rust/src/hal/initial_hal.rs
@@ -1,5 +1,6 @@
use crate::hal::esp::Esp;
use crate::hal::rtc::{BackupHeader, RTCModuleInteraction};
+use crate::hal::water::TankSensor;
use crate::hal::{deep_sleep, BoardInteraction, FreePeripherals, Sensor};
use crate::{
config::PlantControllerConfig,
@@ -68,6 +69,10 @@ pub(crate) fn create_initial_board(
}
impl<'a> BoardInteraction<'a> for Initial<'a> {
+ fn get_tank_sensor(&mut self) -> Option<&mut TankSensor<'a>> {
+ None
+ }
+
fn get_esp(&mut self) -> &mut Esp<'a> {
&mut self.esp
}
@@ -91,20 +96,13 @@ impl<'a> BoardInteraction<'a> for Initial<'a> {
fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
deep_sleep(duration_in_ms)
}
-
fn is_day(&self) -> bool {
false
}
- fn water_temperature_c(&mut self) -> Result {
- bail!("Please configure board revision")
- }
-
- fn tank_sensor_voltage(&mut self) -> Result {
- bail!("Please configure board revision")
- }
fn light(&mut self, _enable: bool) -> Result<()> {
bail!("Please configure board revision")
}
+
fn pump(&mut self, _plant: usize, _enable: bool) -> Result<()> {
bail!("Please configure board revision")
}
diff --git a/rust/src/hal/mod.rs b/rust/src/hal/mod.rs
index 81ddb7d..b121b8b 100644
--- a/rust/src/hal/mod.rs
+++ b/rust/src/hal/mod.rs
@@ -4,8 +4,10 @@ mod initial_hal;
mod rtc;
mod v3_hal;
mod v4_hal;
+mod water;
use crate::hal::rtc::{DS3231Module, RTCModuleInteraction};
+use crate::hal::water::TankSensor;
use crate::{
config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig},
hal::{
@@ -18,7 +20,7 @@ use anyhow::{Ok, Result};
use battery::BQ34Z100G1;
use bq34z100::Bq34z100g1Driver;
use ds323x::{DateTimeAccess, Ds323x};
-use eeprom24x::{Eeprom24x, SlaveAddr};
+use eeprom24x::{Eeprom24x, SlaveAddr, Storage};
use embedded_hal_bus::i2c::MutexDevice;
use esp_idf_hal::{
adc::ADC1,
@@ -85,6 +87,7 @@ pub struct HAL<'a> {
}
pub trait BoardInteraction<'a> {
+ fn get_tank_sensor(&mut self) -> Option<&mut TankSensor<'a>>;
fn get_esp(&mut self) -> &mut Esp<'a>;
fn get_config(&mut self) -> &PlantControllerConfig;
fn get_battery_monitor(&mut self) -> &mut Box;
@@ -94,9 +97,6 @@ pub trait BoardInteraction<'a> {
fn is_day(&self) -> bool;
//should be multsampled
- fn water_temperature_c(&mut self) -> Result;
- /// return median tank sensor value in milli volt
- fn tank_sensor_voltage(&mut self) -> Result;
fn light(&mut self, enable: bool) -> Result<()>;
fn pump(&mut self, plant: usize, enable: bool) -> Result<()>;
fn fault(&mut self, plant: usize, enable: bool) -> Result<()>;
@@ -265,7 +265,7 @@ impl PlantHal {
let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER));
println!("Init rtc eeprom driver");
- let mut eeprom = {
+ let eeprom = {
Eeprom24x::new_24x32(
MutexDevice::new(&I2C_DRIVER),
SlaveAddr::Alternative(true, true, true),
@@ -280,17 +280,10 @@ impl PlantHal {
println!("Rtc Module could not be read {:?}", err);
}
}
- match eeprom.read_byte(0) {
- OkStd(byte) => {
- println!("Read first byte with status {}", byte);
- }
- Err(err) => {
- println!("Eeprom could not read first byte {:?}", err);
- }
- }
+ let storage = Storage::new(eeprom, Delay::new(1000));
let rtc_module: Box =
- Box::new(DS3231Module { rtc, eeprom }) as Box;
+ Box::new(DS3231Module { rtc, storage }) as Box;
let hal = match config {
Result::Ok(config) => {
diff --git a/rust/src/hal/rtc.rs b/rust/src/hal/rtc.rs
index a7ae041..e4bc5cb 100644
--- a/rust/src/hal/rtc.rs
+++ b/rust/src/hal/rtc.rs
@@ -1,15 +1,22 @@
use anyhow::{anyhow, bail};
+use bincode::config::Configuration;
use bincode::{config, Decode, Encode};
use chrono::{DateTime, Utc};
use ds323x::{DateTimeAccess, Ds323x};
-use eeprom24x::{Eeprom24x, Eeprom24xTrait};
+use eeprom24x::addr_size::TwoBytes;
+use eeprom24x::page_size::B32;
+use eeprom24x::unique_serial::No;
+use eeprom24x::Storage;
use embedded_hal_bus::i2c::MutexDevice;
+use embedded_storage::ReadStorage as embedded_storage_ReadStorage;
+use embedded_storage::Storage as embedded_storage_Storage;
use esp_idf_hal::delay::Delay;
use esp_idf_hal::i2c::I2cDriver;
use serde::{Deserialize, Serialize};
use std::result::Result::Ok as OkStd;
const X25: crc::Crc = crc::Crc::::new(&crc::CRC_16_IBM_SDLC);
+const CONFIG: Configuration = config::standard();
pub trait RTCModuleInteraction {
fn get_backup_info(&mut self) -> anyhow::Result;
@@ -19,57 +26,49 @@ pub trait RTCModuleInteraction {
fn set_rtc_time(&mut self, time: &DateTime) -> anyhow::Result<()>;
}
+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: usize,
+ pub size: u16,
}
pub struct DS3231Module<'a> {
pub(crate) rtc:
Ds323x>>, ds323x::ic::DS3231>,
- pub(crate) eeprom: Eeprom24x<
- MutexDevice<'a, I2cDriver<'a>>,
- eeprom24x::page_size::B32,
- eeprom24x::addr_size::TwoBytes,
- eeprom24x::unique_serial::No,
- >,
+
+ pub(crate) storage: Storage>, B32, TwoBytes, No, Delay>,
}
impl RTCModuleInteraction for DS3231Module<'_> {
fn get_backup_info(&mut self) -> anyhow::Result {
- let config = config::standard();
- let store = bincode::encode_to_vec(&BackupHeader::default(), config)?.len();
- let mut header_page_buffer = vec![0_u8; store];
+ let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
- self.eeprom
- .read_data(0, &mut header_page_buffer)
+ self.storage
+ .read(0, &mut header_page_buffer)
.map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?;
- println!("Raw header is {:?} with size {}", header_page_buffer, store);
- let (header, _len): (BackupHeader, usize) =
- bincode::decode_from_slice(&header_page_buffer[..], config)?;
+ let (header, len): (BackupHeader, usize) =
+ bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?;
+
+ println!("Raw header is {:?} with size {}", header_page_buffer, len);
anyhow::Ok(header)
}
fn get_backup_config(&mut self) -> anyhow::Result> {
- let config = config::standard();
- let store = bincode::encode_to_vec(&BackupHeader::default(), config)?.len();
- let mut header_page_buffer = vec![0_u8; store];
+ let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
- self.eeprom
- .read_data(0, &mut header_page_buffer)
+ 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 (header, _len): (BackupHeader, usize) =
- bincode::decode_from_slice(&header_page_buffer[..], config)?;
-
- //skip page 0, used by the header
- let data_start_address = self.eeprom.page_size() as u32;
- let mut data_buffer = vec![0_u8; header.size];
- self.eeprom
- .read_data(data_start_address, &mut data_buffer)
+ 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);
@@ -84,55 +83,31 @@ impl RTCModuleInteraction for DS3231Module<'_> {
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 delay = Delay::new_default();
-
let checksum = X25.checksum(bytes);
- let page_size = self.eeprom.page_size();
let header = BackupHeader {
crc16: checksum,
timestamp: time,
- size: bytes.len(),
+ size: bytes.len() as u16,
};
let config = config::standard();
- let encoded = bincode::encode_to_vec(&header, config)?;
- if encoded.len() > page_size {
- bail!(
- "Size limit reached header is {}, but firest page is only {}",
- encoded.len(),
- page_size
- )
- }
- let as_u8: &[u8] = &encoded;
+ let encoded = bincode::encode_into_slice(&header, &mut header_page_buffer, config)?;
+ println!(
+ "Raw header is {:?} with size {}",
+ header_page_buffer, encoded
+ );
+ self.storage
+ .write(0, &header_page_buffer)
+ .map_err(|err| anyhow!("Error writing header {:?}", err))?;
- match self.eeprom.write_page(0, as_u8) {
- OkStd(_) => {}
- Err(err) => bail!("Error writing eeprom {:?}", err),
- };
- delay.delay_ms(5);
+ //write rest after the header
+ self.storage
+ .write(BACKUP_HEADER_MAX_SIZE as u32, &bytes)
+ .map_err(|err| anyhow!("Error writing body {:?}", err))?;
- let to_write = bytes.chunks(page_size);
-
- let mut lastiter = 0;
- let mut current_page = 1;
- for chunk in to_write {
- let address = current_page * page_size as u32;
- self.eeprom
- .write_page(address, chunk)
- .map_err(|err| anyhow!("Error writing eeprom {:?}", err))?;
- current_page += 1;
-
- let iter = (current_page % 8) as usize;
- if iter != lastiter {
- //todo we want to call progress here, how to do this?
- //target.progress();
- lastiter = iter;
- }
-
- delay.delay_ms(5);
- }
anyhow::Ok(())
}
diff --git a/rust/src/hal/v3_hal.rs b/rust/src/hal/v3_hal.rs
index a11fcdd..c795515 100644
--- a/rust/src/hal/v3_hal.rs
+++ b/rust/src/hal/v3_hal.rs
@@ -1,28 +1,21 @@
use crate::hal::rtc::RTCModuleInteraction;
+use crate::hal::water::TankSensor;
use crate::hal::{
deep_sleep, BoardInteraction, FreePeripherals, Sensor, PLANT_COUNT, REPEAT_MOIST_MEASURE,
- TANK_MULTI_SAMPLE,
};
use crate::log::{log, LogMessage};
use crate::{
config::PlantControllerConfig,
hal::{battery::BatteryInteraction, esp::Esp},
};
-use anyhow::{anyhow, bail, Ok, Result};
-use ds18b20::Ds18b20;
+use anyhow::{bail, Ok, Result};
use embedded_hal::digital::OutputPin;
use esp_idf_hal::{
- adc::{
- attenuation,
- oneshot::{config::AdcChannelConfig, AdcChannelDriver, AdcDriver},
- Resolution,
- },
- gpio::{AnyInputPin, Gpio5, IOPin, InputOutput, PinDriver, Pull},
+ gpio::{AnyInputPin, IOPin, InputOutput, PinDriver, Pull},
pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex},
};
-use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError};
+use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay};
use measurements::{Current, Voltage};
-use one_wire_bus::OneWire;
use plant_ctrl2::sipo::ShiftRegister40;
use std::result::Result::Ok as OkStd;
@@ -83,14 +76,12 @@ pub struct V3<'a> {
>,
_shift_register_enable_invert:
PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Output>,
- tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, esp_idf_hal::adc::ADC1>>,
+ tank_sensor: TankSensor<'a>,
solar_is_day: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>,
light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
main_pump: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
- tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
signal_counter: PcntDriver<'a>,
- one_wire_bus: OneWire>,
}
pub(crate) fn create_v3(
@@ -130,8 +121,15 @@ pub(crate) fn create_v3(
let ms4 = &mut shift_register.decompose()[MS_4];
ms4.set_high()?;
- let mut one_wire_pin = PinDriver::input_output_od(peripherals.gpio18.downgrade())?;
- one_wire_pin.set_pull(Pull::Floating)?;
+ let one_wire_pin = peripherals.gpio18.downgrade();
+ let tank_power_pin = peripherals.gpio11.downgrade();
+
+ let tank_sensor = TankSensor::create(
+ one_wire_pin,
+ peripherals.adc1,
+ peripherals.gpio5,
+ tank_power_pin,
+ );
let mut signal_counter = PcntDriver::new(
peripherals.pcnt0,
@@ -155,15 +153,6 @@ pub(crate) fn create_v3(
},
)?;
- 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(peripherals.adc1)?;
- let tank_channel: AdcChannelDriver> =
- AdcChannelDriver::new(tank_driver, peripherals.gpio5, &adc_config)?;
-
let mut solar_is_day = PinDriver::input(peripherals.gpio7.downgrade())?;
solar_is_day.set_pull(Pull::Floating)?;
@@ -173,15 +162,11 @@ pub(crate) fn create_v3(
let mut main_pump = PinDriver::input_output(peripherals.gpio2.downgrade())?;
main_pump.set_pull(Pull::Floating)?;
main_pump.set_low()?;
- let mut tank_power = PinDriver::input_output(peripherals.gpio11.downgrade())?;
- tank_power.set_pull(Pull::Floating)?;
+
let mut general_fault = PinDriver::input_output(peripherals.gpio6.downgrade())?;
general_fault.set_pull(Pull::Floating)?;
general_fault.set_low()?;
- let one_wire_bus = OneWire::new(one_wire_pin)
- .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
-
let mut shift_register_enable_invert = PinDriver::output(peripherals.gpio21.downgrade())?;
unsafe { gpio_hold_dis(shift_register_enable_invert.pin()) };
@@ -195,18 +180,20 @@ pub(crate) fn create_v3(
esp,
shift_register,
_shift_register_enable_invert: shift_register_enable_invert,
- tank_channel,
+ tank_sensor,
solar_is_day,
light,
main_pump,
- tank_power,
general_fault,
signal_counter,
- one_wire_bus,
}))
}
impl<'a> BoardInteraction<'a> for V3<'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
}
@@ -235,51 +222,6 @@ impl<'a> BoardInteraction<'a> for V3<'a> {
self.solar_is_day.get_level().into()
}
- fn water_temperature_c(&mut self) -> Result {
- self.one_wire_bus
- .reset(&mut self.esp.delay)
- .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
- let first = self.one_wire_bus.devices(false, &mut self.esp.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::(device_address)
- .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
-
- water_temp_sensor
- .start_temp_measurement(&mut self.one_wire_bus, &mut self.esp.delay)
- .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
- ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut self.esp.delay);
- let sensor_data = water_temp_sensor
- .read_data(&mut self.one_wire_bus, &mut self.esp.delay)
- .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
- if sensor_data.temperature == 85_f32 {
- bail!("Ds18b20 dummy temperature returned");
- }
- Ok(sensor_data.temperature / 10_f32)
- }
-
- fn tank_sensor_voltage(&mut self) -> Result {
- self.tank_power.set_high()?;
- //let stabilize
- self.esp.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;
- Ok(median_mv)
- }
-
fn light(&mut self, enable: bool) -> Result<()> {
unsafe { gpio_hold_dis(self.light.pin()) };
self.light.set_state(enable.into())?;
diff --git a/rust/src/hal/v4_hal.rs b/rust/src/hal/v4_hal.rs
index b2d0096..491cc64 100644
--- a/rust/src/hal/v4_hal.rs
+++ b/rust/src/hal/v4_hal.rs
@@ -2,33 +2,26 @@ 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::{
deep_sleep, BoardInteraction, FreePeripherals, Sensor, I2C_DRIVER, PLANT_COUNT,
- REPEAT_MOIST_MEASURE, TANK_MULTI_SAMPLE,
+ REPEAT_MOIST_MEASURE,
};
use crate::log::{log, LogMessage};
-use anyhow::{anyhow, bail};
-use ds18b20::Ds18b20;
-use ds323x::{DateTimeAccess, Ds323x};
-use eeprom24x::{Eeprom24x, SlaveAddr};
use embedded_hal::digital::OutputPin;
use embedded_hal_bus::i2c::MutexDevice;
-use esp_idf_hal::adc::oneshot::config::AdcChannelConfig;
-use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver};
-use esp_idf_hal::adc::{attenuation, Resolution};
use esp_idf_hal::delay::Delay;
-use esp_idf_hal::gpio::{AnyInputPin, Gpio5, IOPin, InputOutput, Output, PinDriver, Pull};
+use esp_idf_hal::gpio::{AnyInputPin, IOPin, InputOutput, Output, PinDriver, Pull};
use esp_idf_hal::i2c::I2cDriver;
use esp_idf_hal::pcnt::{
PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex,
};
-use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, EspError};
+use esp_idf_sys::{gpio_hold_dis, gpio_hold_en};
use ina219::address::{Address, Pin};
use ina219::calibration::UnCalibrated;
use ina219::configuration::{Configuration, OperatingMode};
use ina219::SyncIna219;
use measurements::{Current, Resistance, Voltage};
-use one_wire_bus::OneWire;
use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
use std::result::Result::Ok as OkStd;
@@ -110,16 +103,14 @@ impl Charger<'_> {
pub struct V4<'a> {
esp: Esp<'a>,
+ tank_sensor: TankSensor<'a>,
charger: Charger<'a>,
rtc_module: Box,
battery_monitor: Box,
config: PlantControllerConfig,
- tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, esp_idf_hal::adc::ADC1>>,
signal_counter: PcntDriver<'a>,
awake: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, Output>,
light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
- tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
- one_wire_bus: OneWire>,
general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
pump_expander: Pca9535Immediate>>,
sensor_expander: Pca9535Immediate>>,
@@ -141,46 +132,21 @@ pub(crate) fn create_v4(
general_fault.set_pull(Pull::Floating)?;
general_fault.set_low()?;
- println!("Init rtc driver");
- let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER));
-
- println!("Init rtc eeprom driver");
- let mut eeprom = {
- Eeprom24x::new_24x32(
- MutexDevice::new(&I2C_DRIVER),
- SlaveAddr::Alternative(true, true, true),
- )
- };
-
let mut extra1 = PinDriver::output(peripherals.gpio6.downgrade())?;
- extra1.set_high()?;
+ extra1.set_low()?;
let mut extra2 = PinDriver::output(peripherals.gpio15.downgrade())?;
- extra2.set_high()?;
+ extra2.set_low()?;
- let mut one_wire_pin = PinDriver::input_output_od(peripherals.gpio18.downgrade())?;
- one_wire_pin.set_pull(Pull::Floating)?;
+ let one_wire_pin = peripherals.gpio18.downgrade();
+ let tank_power_pin = peripherals.gpio11.downgrade();
- let one_wire_bus = OneWire::new(one_wire_pin)
- .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
-
- let rtc_time = rtc.datetime();
- match rtc_time {
- OkStd(tt) => {
- println!("Rtc Module reports time at UTC {}", tt);
- }
- Err(err) => {
- println!("Rtc Module could not be read {:?}", err);
- }
- }
- match eeprom.read_byte(0) {
- OkStd(byte) => {
- println!("Read first byte with status {}", byte);
- }
- Err(err) => {
- println!("Eeprom could not read first byte {:?}", err);
- }
- }
+ let tank_sensor = TankSensor::create(
+ one_wire_pin,
+ peripherals.adc1,
+ peripherals.gpio5,
+ tank_power_pin,
+ );
let mut signal_counter = PcntDriver::new(
peripherals.pcnt0,
@@ -204,28 +170,15 @@ pub(crate) fn create_v4(
},
)?;
- 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(peripherals.adc1)?;
- let tank_channel: AdcChannelDriver> =
- AdcChannelDriver::new(tank_driver, peripherals.gpio5, &adc_config)?;
-
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 tank_power = PinDriver::input_output(peripherals.gpio11.downgrade())?;
- tank_power.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);
//todo error handing if init error
for pin in 0..8 {
@@ -276,11 +229,9 @@ pub(crate) fn create_v4(
rtc_module,
esp,
awake,
- tank_channel,
+ tank_sensor,
signal_counter,
light,
- tank_power,
- one_wire_bus,
general_fault,
pump_expander,
sensor_expander,
@@ -294,6 +245,10 @@ pub(crate) fn create_v4(
}
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
}
@@ -324,51 +279,6 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
self.charger.is_day()
}
- fn water_temperature_c(&mut self) -> anyhow::Result {
- self.one_wire_bus
- .reset(&mut self.esp.delay)
- .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
- let first = self.one_wire_bus.devices(false, &mut self.esp.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::(device_address)
- .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
-
- water_temp_sensor
- .start_temp_measurement(&mut self.one_wire_bus, &mut self.esp.delay)
- .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
- ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut self.esp.delay);
- let sensor_data = water_temp_sensor
- .read_data(&mut self.one_wire_bus, &mut self.esp.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)
- }
-
- fn tank_sensor_voltage(&mut self) -> anyhow::Result {
- self.tank_power.set_high()?;
- //let stabilize
- self.esp.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;
- anyhow::Ok(median_mv)
- }
-
fn light(&mut self, enable: bool) -> anyhow::Result<()> {
unsafe { gpio_hold_dis(self.light.pin()) };
self.light.set_state(enable.into())?;
diff --git a/rust/src/hal/water.rs b/rust/src/hal/water.rs
new file mode 100644
index 0000000..02f0dc9
--- /dev/null
+++ b/rust/src/hal/water.rs
@@ -0,0 +1,124 @@
+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, Gpio5, InputOutput, PinDriver, Pull};
+use esp_idf_sys::EspError;
+use one_wire_bus::OneWire;
+
+pub struct TankSensor<'a> {
+ one_wire_bus: OneWire>,
+ tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, ADC1>>,
+ tank_power: PinDriver<'a, AnyIOPin, InputOutput>,
+ delay: Delay,
+}
+
+impl<'a> TankSensor<'a> {
+ pub(crate) fn create(
+ one_wire_pin: AnyIOPin,
+ adc1: ADC1,
+ gpio5: Gpio5,
+ tank_power_pin: AnyIOPin,
+ ) -> 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");
+
+ TankSensor {
+ one_wire_bus,
+ tank_channel,
+ tank_power,
+ delay: Default::default(),
+ }
+ }
+
+ pub fn water_temperature_c(&mut self) -> anyhow::Result {
+ //multisample should be moved to water_temperature_c
+ let mut attempt = 1;
+ let water_temp: Result = loop {
+ let temp = self.single_temperature_c();
+ 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
+ }
+
+ fn single_temperature_c(&mut self) -> anyhow::Result {
+ 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::(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)
+ }
+
+ pub fn tank_sensor_voltage(&mut self) -> anyhow::Result {
+ 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;
+ anyhow::Ok(median_mv)
+ }
+}
diff --git a/rust/src/main.rs b/rust/src/main.rs
index 8e57ce4..89338fe 100644
--- a/rust/src/main.rs
+++ b/rust/src/main.rs
@@ -3,7 +3,7 @@ use crate::{
hal::{PlantHal, HAL, PLANT_COUNT},
webserver::httpd,
};
-use anyhow::bail;
+use anyhow::{bail, Context};
use chrono::{DateTime, Datelike, Timelike, Utc};
use chrono_tz::Tz::{self, UTC};
use esp_idf_hal::delay::Delay;
@@ -40,6 +40,12 @@ enum WaitType {
MqttConfig,
}
+#[derive(Serialize, Deserialize, Debug, PartialEq)]
+struct Solar {
+ current_ma: u32,
+ voltage_ma: u32,
+}
+
impl WaitType {
fn blink_pattern(&self) -> u32 {
match self {
@@ -258,6 +264,7 @@ fn safe_main() -> anyhow::Result<()> {
timezone_time,
);
publish_battery_state(&mut board);
+ let _ = publish_mppt_state(&mut board);
}
log(
@@ -326,8 +333,12 @@ fn safe_main() -> anyhow::Result<()> {
}
let mut water_frozen = false;
+ let water_temp = board
+ .board_hal
+ .get_tank_sensor()
+ .context("no sensor")
+ .and_then(|f| f.water_temperature_c());
- let water_temp = obtain_tank_temperature(&mut board);
if let Ok(res) = water_temp {
if res < WATER_FROZEN_THRESH {
water_frozen = true;
@@ -565,28 +576,6 @@ fn update_charge_indicator(board: &mut MutexGuard) {
}
}
-fn obtain_tank_temperature(board: &mut MutexGuard) -> anyhow::Result {
- //multisample should be moved to water_temperature_c
- let mut attempt = 1;
- let water_temp: Result = loop {
- let temp = board.board_hal.water_temperature_c();
- 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
-}
-
fn publish_tank_state(
board: &mut MutexGuard,
tank_state: &TankState,
@@ -743,6 +732,24 @@ fn pump_info(
};
}
+fn publish_mppt_state(board: &mut MutexGuard<'_, HAL<'_>>) -> anyhow::Result<()> {
+ let current = board.board_hal.get_mptt_current()?;
+ let voltage = board.board_hal.get_mptt_voltage()?;
+ let solar_state = Solar {
+ current_ma: current.as_milliamperes() as u32,
+ voltage_ma: voltage.as_millivolts() as u32,
+ };
+ if let Ok(serialized_solar_state_bytes) =
+ serde_json::to_string(&solar_state).map(|s| s.into_bytes())
+ {
+ let _ = board
+ .board_hal
+ .get_esp()
+ .mqtt_publish("/mppt", &serialized_solar_state_bytes);
+ }
+ Ok(())
+}
+
fn publish_battery_state(board: &mut MutexGuard<'_, HAL<'_>>) {
let state = board.board_hal.get_battery_monitor().get_battery_state();
if let Ok(serialized_battery_state_bytes) =
diff --git a/rust/src/tank.rs b/rust/src/tank.rs
index 38e9da0..5325eb6 100644
--- a/rust/src/tank.rs
+++ b/rust/src/tank.rs
@@ -1,4 +1,5 @@
use crate::{config::TankConfig, hal::HAL};
+use anyhow::Context;
use serde::Serialize;
const OPEN_TANK_VOLTAGE: f32 = 3.0;
@@ -151,7 +152,12 @@ 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.tank_sensor_voltage() {
+ 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())),
}
diff --git a/rust/src/webserver/mod.rs b/rust/src/webserver/mod.rs
index 205fe91..b3c6ff1 100644
--- a/rust/src/webserver/mod.rs
+++ b/rust/src/webserver/mod.rs
@@ -8,7 +8,7 @@ use crate::{
plant_state::{MoistureSensorState, PlantState},
BOARD_ACCESS,
};
-use anyhow::bail;
+use anyhow::{bail, Context};
use chrono::DateTime;
use core::result::Result::Ok;
use embedded_svc::http::Method;
@@ -60,7 +60,7 @@ pub struct TestPump {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct WebBackupHeader {
timestamp: std::string::String,
- size: usize,
+ size: u16,
}
#[derive(Deserialize)]
@@ -208,10 +208,9 @@ fn backup_info(
};
serde_json::to_string(&wbh)?
}
- Err(_) => {
- //TODO make better
+ Err(err) => {
let wbh = WebBackupHeader {
- timestamp: "no backup".to_owned(),
+ timestamp: err.to_string(),
size: 0,
};
serde_json::to_string(&wbh)?
@@ -286,7 +285,12 @@ fn tank_info(
let mut board = BOARD_ACCESS.lock().unwrap();
let tank_info = determine_tank_state(&mut board);
//should be multsampled
- let water_temp = board.board_hal.water_temperature_c();
+
+ 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,
diff --git a/rust/src_webpack/src/main.ts b/rust/src_webpack/src/main.ts
index 8463eb2..f930b5d 100644
--- a/rust/src_webpack/src/main.ts
+++ b/rust/src_webpack/src/main.ts
@@ -1,4 +1,4 @@
-import { deepEqual } from 'fast-equals';
+import {deepEqual} from 'fast-equals';
declare var PUBLIC_URL: string;
console.log("Url is " + PUBLIC_URL);
@@ -6,555 +6,571 @@ console.log("Url is " + PUBLIC_URL);
document.body.innerHTML = require('./main.html') as string;
-import { TimeView } from "./timeview";
-import { PlantViews } from "./plant";
-import { NetworkConfigView } from "./network";
-import { NightLampView } from "./nightlightview";
-import { TankConfigView } from "./tankview";
-import { SubmitView } from "./submitView";
-import { ProgressView } from "./progress";
-import { OTAView } from "./ota";
-import { BatteryView } from "./batteryview";
-import { FileView } from './fileview';
-import { LogView } from './log';
+import {TimeView} from "./timeview";
+import {PlantViews} from "./plant";
+import {NetworkConfigView} from "./network";
+import {NightLampView} from "./nightlightview";
+import {TankConfigView} from "./tankview";
+import {SubmitView} from "./submitView";
+import {ProgressView} from "./progress";
+import {OTAView} from "./ota";
+import {BatteryView} from "./batteryview";
+import {FileView} from './fileview';
+import {LogView} from './log';
import {HardwareConfigView} from "./hardware";
import {
- BackupHeader,
- BatteryState,
- GetTime, LogArray, LogLocalisation,
- Moistures,
- NightLampCommand,
- PlantControllerConfig,
- SetTime, SSIDList, TankInfo,
- TestPump,
- VersionInfo,
- FileList, SolarState
+ BackupHeader,
+ BatteryState,
+ GetTime, LogArray, LogLocalisation,
+ Moistures,
+ NightLampCommand,
+ PlantControllerConfig,
+ SetTime, SSIDList, TankInfo,
+ TestPump,
+ VersionInfo,
+ FileList, SolarState
} from "./api";
import {SolarView} from "./solarview";
export class Controller {
- loadTankInfo() : Promise {
- return fetch(PUBLIC_URL + "/tank")
- .then(response => response.json())
- .then(json => json as TankInfo)
- .then(tankinfo => {
- controller.tankView.setTankInfo(tankinfo)
- })
- .catch(error => {
- console.log(error);
- });
- }
+ loadTankInfo(): Promise {
+ return fetch(PUBLIC_URL + "/tank")
+ .then(response => response.json())
+ .then(json => json as TankInfo)
+ .then(tankinfo => {
+ controller.tankView.setTankInfo(tankinfo)
+ })
+ .catch(error => {
+ console.log(error);
+ });
+ }
- loadLogLocaleConfig() {
- return fetch(PUBLIC_URL + "/log_localization")
- .then(response => response.json())
- .then(json => json as LogLocalisation)
- .then(loglocale => {
- controller.logView.setLogLocalisation(loglocale)
- })
- .catch(error => {
- console.log(error);
- });
- }
- loadLog() {
- return fetch(PUBLIC_URL + "/log")
- .then(response => response.json())
- .then(json => json as LogArray)
- .then(logs => {
- controller.logView.setLog(logs)
- })
- .catch(error => {
- console.log(error);
- });
- }
- getBackupInfo() : Promise {
- 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);
- });
- }
+ loadLogLocaleConfig() {
+ return fetch(PUBLIC_URL + "/log_localization")
+ .then(response => response.json())
+ .then(json => json as LogLocalisation)
+ .then(loglocale => {
+ controller.logView.setLogLocalisation(loglocale)
+ })
+ .catch(error => {
+ console.log(error);
+ });
+ }
- populateTimezones(): Promise {
- return fetch(PUBLIC_URL+'/timezones')
- .then(response => response.json())
- .then(json => json as string[])
- .then(timezones => {
- controller.timeView.timezones(timezones)
+ loadLog() {
+ return fetch(PUBLIC_URL + "/log")
+ .then(response => response.json())
+ .then(json => json as LogArray)
+ .then(logs => {
+ controller.logView.setLog(logs)
+ })
+ .catch(error => {
+ console.log(error);
+ });
+ }
+
+ getBackupInfo(): Promise {
+ 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);
+ });
+ }
+
+ populateTimezones(): Promise {
+ 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));
+ }
+
+ updateFileList(): Promise {
+ 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);
+ });
+ }
+
+ uploadFile(file: File, name: string) {
+ var current = 0;
+ var max = 100;
+ controller.progressview.addProgress("file_upload", (current / max) * 100, "Uploading File " + name + "(" + current + "/" + max + ")")
+ var ajax = new XMLHttpRequest();
+ ajax.upload.addEventListener("progress", event => {
+ current = event.loaded / 1000;
+ max = event.total / 1000;
+ controller.progressview.addProgress("file_upload", (current / max) * 100, "Uploading File " + name + "(" + current + "/" + max + ")")
+ }, false);
+ ajax.addEventListener("load", () => {
+ controller.progressview.removeProgress("file_upload")
+ controller.updateFileList()
+ }, false);
+ ajax.addEventListener("error", () => {
+ alert("Error upload")
+ controller.progressview.removeProgress("file_upload")
+ controller.updateFileList()
+ }, false);
+ ajax.addEventListener("abort", () => {
+ alert("abort upload")
+ controller.progressview.removeProgress("file_upload")
+ controller.updateFileList()
+ }, false);
+ ajax.open("POST", PUBLIC_URL + "/file?filename=" + name);
+ ajax.send(file);
+ }
+
+ deleteFile(name: string) {
+ controller.progressview.addIndeterminate("file_delete", "Deleting " + name);
+ var ajax = new XMLHttpRequest();
+ ajax.open("DELETE", PUBLIC_URL + "/file?filename=" + name);
+ ajax.send();
+ ajax.addEventListener("error", () => {
+ controller.progressview.removeProgress("file_delete")
+ alert("Error delete")
+ controller.updateFileList()
+ }, false);
+ ajax.addEventListener("abort", () => {
+ controller.progressview.removeProgress("file_delete")
+ alert("Error upload")
+ controller.updateFileList()
+ }, false);
+ ajax.addEventListener("load", () => {
+ controller.progressview.removeProgress("file_delete")
+ controller.updateFileList()
+ }, false);
+ controller.updateFileList()
+ }
+
+ updateRTCData(): Promise {
+ 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);
+ });
+ }
+
+ updateBatteryData(): Promise {
+ 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);
+ })
+ }
+
+ updateSolarData(): Promise {
+ 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);
+ })
+ }
+
+ uploadNewFirmware(file: File) {
+ var current = 0;
+ var max = 100;
+ controller.progressview.addProgress("ota_upload", (current / max) * 100, "Uploading firmeware (" + current + "/" + max + ")")
+ var ajax = new XMLHttpRequest();
+ ajax.upload.addEventListener("progress", event => {
+ current = event.loaded / 1000;
+ max = event.total / 1000;
+ controller.progressview.addProgress("ota_upload", (current / max) * 100, "Uploading firmeware (" + current + "/" + max + ")")
+ }, false);
+ ajax.addEventListener("load", () => {
+ controller.progressview.removeProgress("ota_upload")
+ controller.reboot();
+ }, false);
+ ajax.addEventListener("error", () => {
+ alert("Error ota")
+ controller.progressview.removeProgress("ota_upload")
+ }, false);
+ ajax.addEventListener("abort", () => {
+ alert("abort ota")
+ controller.progressview.removeProgress("ota_upload")
+ }, false);
+ ajax.open("POST", PUBLIC_URL + "/ota");
+ ajax.send(file);
+ }
+
+ version(): Promise {
+ 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);
+ })
+ }
+
+ getBackupConfig() {
+ controller.progressview.addIndeterminate("get_backup_config", "Downloading Backup")
+ fetch(PUBLIC_URL + "/get_backup_config")
+ .then(response => response.text())
+ .then(loaded => {
+ controller.progressview.removeProgress("get_backup_config")
+ controller.submitView.setBackupJson(loaded);
+ })
+ }
+
+ async downloadConfig(): Promise {
+ 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;
+ controller.setInitialConfig(currentConfig);
+ controller.setConfig(currentConfig);
+ //sync json view initially
+ controller.configChanged();
+ controller.progressview.removeProgress("get_config");
+ }
+
+ setInitialConfig(currentConfig: PlantControllerConfig) {
+ this.initialConfig = currentConfig
+ }
+
+ uploadConfig(json: string, statusCallback: (status: string) => void) {
+ controller.progressview.addIndeterminate("set_config", "Uploading Config")
+ fetch(PUBLIC_URL + "/set_config", {
+ method: "POST",
+ body: json,
})
- .catch(error => console.error('Error fetching timezones:', error));
- }
-
- updateFileList() : Promise {
- 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);
- });
- }
- uploadFile(file: File, name:string) {
- var current = 0;
- var max = 100;
- controller.progressview.addProgress("file_upload", (current / max) * 100, "Uploading File " + name + "(" + current + "/" + max + ")")
- var ajax = new XMLHttpRequest();
- ajax.upload.addEventListener("progress", event => {
- current = event.loaded / 1000;
- max = event.total / 1000;
- controller.progressview.addProgress("file_upload", (current / max) * 100, "Uploading File " + name + "(" + current + "/" + max + ")")
- }, false);
- ajax.addEventListener("load", () => {
- controller.progressview.removeProgress("file_upload")
- controller.updateFileList()
- }, false);
- ajax.addEventListener("error", () => {
- alert("Error upload")
- controller.progressview.removeProgress("file_upload")
- controller.updateFileList()
- }, false);
- ajax.addEventListener("abort", () => {
- alert("abort upload")
- controller.progressview.removeProgress("file_upload")
- controller.updateFileList()
- }, false);
- ajax.open("POST", PUBLIC_URL + "/file?filename="+name);
- ajax.send(file);
- }
- deleteFile(name:string) {
- controller.progressview.addIndeterminate("file_delete", "Deleting " + name);
- var ajax = new XMLHttpRequest();
- ajax.open("DELETE", PUBLIC_URL + "/file?filename="+name);
- ajax.send();
- ajax.addEventListener("error", () => {
- controller.progressview.removeProgress("file_delete")
- alert("Error delete")
- controller.updateFileList()
- }, false);
- ajax.addEventListener("abort", () => {
- controller.progressview.removeProgress("file_delete")
- alert("Error upload")
- controller.updateFileList()
- }, false);
- ajax.addEventListener("load", () => {
- controller.progressview.removeProgress("file_delete")
- controller.updateFileList()
- }, false);
- controller.updateFileList()
- }
-
- updateRTCData() : Promise {
- 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);
- });
- }
- updateBatteryData() {
- 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);
- })
- }
- updateSolarData() {
- 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);
- })
- }
- uploadNewFirmware(file: File) {
- var current = 0;
- var max = 100;
- controller.progressview.addProgress("ota_upload", (current / max) * 100, "Uploading firmeware (" + current + "/" + max + ")")
- var ajax = new XMLHttpRequest();
- ajax.upload.addEventListener("progress", event => {
- current = event.loaded / 1000;
- max = event.total / 1000;
- controller.progressview.addProgress("ota_upload", (current / max) * 100, "Uploading firmeware (" + current + "/" + max + ")")
- }, false);
- ajax.addEventListener("load", () => {
- controller.progressview.removeProgress("ota_upload")
- controller.reboot();
- }, false);
- ajax.addEventListener("error", () => {
- alert("Error ota")
- controller.progressview.removeProgress("ota_upload")
- }, false);
- ajax.addEventListener("abort", () => {
- alert("abort ota")
- controller.progressview.removeProgress("ota_upload")
- }, false);
- ajax.open("POST", PUBLIC_URL + "/ota");
- ajax.send(file);
- }
- version() : Promise {
- 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);
- })
- }
-
- getBackupConfig() {
- controller.progressview.addIndeterminate("get_backup_config", "Downloading Backup")
- fetch(PUBLIC_URL + "/get_backup_config")
- .then(response => response.text())
- .then(loaded => {
- controller.progressview.removeProgress("get_backup_config")
- controller.submitView.setBackupJson(loaded);
- })
- }
-
- async downloadConfig(): Promise {
- 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;
- controller.setInitialConfig(currentConfig);
- controller.setConfig(currentConfig);
- //sync json view initially
- controller.configChanged();
- controller.progressview.removeProgress("get_config");
- }
- setInitialConfig(currentConfig: PlantControllerConfig) {
- this.initialConfig = currentConfig
- }
- uploadConfig(json: string, statusCallback: (status: string) => void) {
- controller.progressview.addIndeterminate("set_config", "Uploading Config")
- fetch(PUBLIC_URL + "/set_config", {
- method: "POST",
- body: json,
- })
- .then(response => response.text())
- .then(text => statusCallback(text))
- controller.progressview.removeProgress("set_config")
- //load from remote to be clean
- controller.downloadConfig()
- }
-
- backupConfig(json: string, statusCallback: (status: string) => void) {
- controller.progressview.addIndeterminate("backup_config", "Backingup Config")
- fetch(PUBLIC_URL + "/backup_config", {
- method: "POST",
- body: json,
- })
- .then(response => response.text())
- .then(text => statusCallback(text))
- controller.progressview.removeProgress("backup_config")
- }
- syncRTCFromBrowser() {
- controller.progressview.addIndeterminate("write_rtc", "Writing RTC")
- var value: SetTime = {
- time: new Date().toISOString()
+ .then(response => response.text())
+ .then(text => statusCallback(text))
+ controller.progressview.removeProgress("set_config")
+ //load from remote to be clean
+ controller.downloadConfig()
}
- var pretty = JSON.stringify(value, undefined, 1);
- fetch(PUBLIC_URL + "/time", {
- method: "POST",
- body: pretty
- }).then(
- _ => controller.progressview.removeProgress("write_rtc")
- )
- }
- configChanged() {
- const current = controller.getConfig();
- var pretty = JSON.stringify(current, undefined, 0);
- controller.submitView.setJson(pretty);
-
-
- if (deepEqual(current, controller.initialConfig)) {
- document.title = "PlantCtrl"
- } else {
- document.title = "*PlantCtrl"
+ backupConfig(json: string): Promise {
+ return fetch(PUBLIC_URL + "/backup_config", {
+ method: "POST",
+ body: json,
+ })
+ .then(response => response.text());
}
- }
- selfTest(){
- fetch(PUBLIC_URL + "/boardtest", {
- method: "POST"
- })
- }
-
- testNightLamp(active: boolean){
- var body: NightLampCommand = {
- active: active
+ syncRTCFromBrowser() {
+ controller.progressview.addIndeterminate("write_rtc", "Writing RTC")
+ const value: SetTime = {
+ time: new Date().toISOString()
+ };
+ const pretty = JSON.stringify(value, undefined, 1);
+ fetch(PUBLIC_URL + "/time", {
+ method: "POST",
+ body: pretty
+ }).then(
+ _ => controller.progressview.removeProgress("write_rtc")
+ )
}
- var pretty = JSON.stringify(body, undefined, 1);
- fetch(PUBLIC_URL + "/lamptest", {
- method: "POST",
- body: pretty
- })
- }
- testPlant(plantId: number) {
- let counter = 0
- let limit = 30
- controller.progressview.addProgress("test_pump", counter / limit * 100, "Testing pump " + (plantId + 1) + " for " + (limit - counter) + "s")
+ configChanged() {
+ const current = controller.getConfig();
+ var pretty = JSON.stringify(current, undefined, 0);
+ controller.submitView.setJson(pretty);
- let timerId: string | number | NodeJS.Timeout | undefined
- function updateProgress() {
- counter++;
- controller.progressview.addProgress("test_pump", counter / limit * 100, "Testing pump " + (plantId + 1) + " for " + (limit - counter) + "s")
- timerId = setTimeout(updateProgress, 1000);
- }
- timerId = setTimeout(updateProgress, 1000);
-
- var body: TestPump = {
- pump: plantId
- }
- var pretty = JSON.stringify(body, undefined, 1);
-
- fetch(PUBLIC_URL + "/pumptest", {
- method: "POST",
- body: pretty
- })
- .then(response => response.text())
- .then(
- _ => {
- clearTimeout(timerId);
- controller.progressview.removeProgress("test_pump");
+ if (deepEqual(current, controller.initialConfig)) {
+ document.title = "PlantCtrl"
+ } else {
+ document.title = "*PlantCtrl"
}
- )
- }
-
- getConfig(): PlantControllerConfig {
- return {
- hardware: controller.hardwareView.getConfig(),
- network: controller.networkView.getConfig(),
- tank: controller.tankView.getConfig(),
- night_lamp: controller.nightLampView.getConfig(),
- plants: controller.plantViews.getConfig(),
- timezone: controller.timeView.getTimeZone()
}
- }
- scanWifi() {
- let counter = 0
- let limit = 5
- controller.progressview.addProgress("scan_ssid", counter / limit * 100, "Scanning for SSIDs for " + (limit - counter) + "s")
+ selfTest() {
+ fetch(PUBLIC_URL + "/boardtest", {
+ method: "POST"
+ })
+ }
- let timerId: string | number | NodeJS.Timeout | undefined
- function updateProgress() {
- counter++;
- controller.progressview.addProgress("scan_ssid", counter / limit * 100, "Scanning for SSIDs for " + (limit - counter) + "s")
- timerId = setTimeout(updateProgress, 1000);
+ testNightLamp(active: boolean) {
+ var body: NightLampCommand = {
+ active: active
+ }
+ var pretty = JSON.stringify(body, undefined, 1);
+ fetch(PUBLIC_URL + "/lamptest", {
+ method: "POST",
+ body: pretty
+ })
+ }
+
+ testPlant(plantId: number) {
+ let counter = 0
+ let limit = 30
+ controller.progressview.addProgress("test_pump", counter / limit * 100, "Testing pump " + (plantId + 1) + " for " + (limit - counter) + "s")
+
+ let timerId: string | number | NodeJS.Timeout | undefined
+
+ function updateProgress() {
+ counter++;
+ controller.progressview.addProgress("test_pump", counter / limit * 100, "Testing pump " + (plantId + 1) + " for " + (limit - counter) + "s")
+ timerId = setTimeout(updateProgress, 1000);
+
+ }
+
+ timerId = setTimeout(updateProgress, 1000);
+
+ var body: TestPump = {
+ pump: plantId
+ }
+ var pretty = JSON.stringify(body, undefined, 1);
+
+ fetch(PUBLIC_URL + "/pumptest", {
+ method: "POST",
+ body: pretty
+ })
+ .then(response => response.text())
+ .then(
+ _ => {
+ clearTimeout(timerId);
+ controller.progressview.removeProgress("test_pump");
+ }
+ )
+ }
+
+ getConfig(): PlantControllerConfig {
+ return {
+ hardware: controller.hardwareView.getConfig(),
+ network: controller.networkView.getConfig(),
+ tank: controller.tankView.getConfig(),
+ night_lamp: controller.nightLampView.getConfig(),
+ plants: controller.plantViews.getConfig(),
+ timezone: controller.timeView.getTimeZone()
+ }
+ }
+
+ scanWifi() {
+ let counter = 0
+ let limit = 5
+ controller.progressview.addProgress("scan_ssid", counter / limit * 100, "Scanning for SSIDs for " + (limit - counter) + "s")
+
+ let timerId: string | number | NodeJS.Timeout | undefined
+
+ function updateProgress() {
+ counter++;
+ controller.progressview.addProgress("scan_ssid", counter / limit * 100, "Scanning for SSIDs for " + (limit - counter) + "s")
+ timerId = setTimeout(updateProgress, 1000);
+
+ }
+
+ timerId = setTimeout(updateProgress, 1000);
+
+
+ var ajax = new XMLHttpRequest();
+ ajax.responseType = 'json';
+ ajax.onreadystatechange = () => {
+ if (ajax.readyState === 4) {
+ clearTimeout(timerId);
+ controller.progressview.removeProgress("scan_ssid");
+ this.networkView.setScanResult(ajax.response as SSIDList)
+ }
+ };
+ ajax.onerror = (evt) => {
+ clearTimeout(timerId);
+ controller.progressview.removeProgress("scan_ssid");
+ alert("Failed to start see console")
+ }
+ ajax.open("POST", PUBLIC_URL + "/wifiscan");
+ ajax.send();
+ }
+
+ setConfig(current: PlantControllerConfig) {
+ this.tankView.setConfig(current.tank);
+ this.networkView.setConfig(current.network);
+ this.nightLampView.setConfig(current.night_lamp);
+ this.plantViews.setConfig(current.plants);
+ this.timeView.setTimeZone(current.timezone);
+ this.hardwareView.setConfig(current.hardware);
+ }
+
+ measure_moisture() {
+ let counter = 0
+ let limit = 2
+ controller.progressview.addProgress("measure_moisture", counter / limit * 100, "Measure Moisture " + (limit - counter) + "s")
+
+ let timerId: string | number | NodeJS.Timeout | undefined
+
+ function updateProgress() {
+ counter++;
+ controller.progressview.addProgress("measure_moisture", counter / limit * 100, "Measure Moisture " + (limit - counter) + "s")
+ timerId = setTimeout(updateProgress, 1000);
+
+ }
+
+ timerId = setTimeout(updateProgress, 1000);
+
+
+ fetch(PUBLIC_URL + "/moisture")
+ .then(response => response.json())
+ .then(json => json as Moistures)
+ .then(time => {
+ controller.plantViews.update(time.moisture_a, time.moisture_b)
+ clearTimeout(timerId);
+ controller.progressview.removeProgress("measure_moisture");
+ })
+ .catch(error => {
+ clearTimeout(timerId);
+ controller.progressview.removeProgress("measure_moisture");
+ console.log(error);
+ });
+ }
+
+ exit() {
+ fetch(PUBLIC_URL + "/exit", {
+ method: "POST",
+ })
+ controller.progressview.addIndeterminate("rebooting", "Returned to normal mode, you can close this site now")
}
- timerId = setTimeout(updateProgress, 1000);
-
- var ajax = new XMLHttpRequest();
- ajax.responseType = 'json';
- ajax.onreadystatechange = () => {
- if (ajax.readyState === 4) {
- clearTimeout(timerId);
- controller.progressview.removeProgress("scan_ssid");
- this.networkView.setScanResult(ajax.response as SSIDList)
- }
- };
- ajax.onerror = (evt) => {
- clearTimeout(timerId);
- controller.progressview.removeProgress("scan_ssid");
- alert("Failed to start see console")
+ waitForReboot() {
+ console.log("Check if controller online again")
+ fetch(PUBLIC_URL + "/version", {
+ method: "GET",
+ signal: AbortSignal.timeout(5000)
+ }).then(response => {
+ if (response.status != 200) {
+ console.log("Not reached yet, retrying")
+ setTimeout(controller.waitForReboot, 1000)
+ } else {
+ console.log("Reached controller, reloading")
+ controller.progressview.addIndeterminate("rebooting", "Reached Controller, reloading")
+ setTimeout(function () {
+ window.location.reload()
+ }, 2000);
+ }
+ })
+ .catch(err => {
+ console.log("Not reached yet, retrying")
+ setTimeout(controller.waitForReboot, 1000)
+ })
}
- ajax.open("POST", PUBLIC_URL + "/wifiscan");
- ajax.send();
- }
-
- setConfig(current: PlantControllerConfig) {
- this.tankView.setConfig(current.tank);
- this.networkView.setConfig(current.network);
- this.nightLampView.setConfig(current.night_lamp);
- this.plantViews.setConfig(current.plants);
- this.timeView.setTimeZone(current.timezone);
- this.hardwareView.setConfig(current.hardware);
- }
-
- measure_moisture() {
- let counter = 0
- let limit = 2
- controller.progressview.addProgress("measure_moisture", counter / limit * 100, "Measure Moisture " + (limit - counter) + "s")
-
- let timerId: string | number | NodeJS.Timeout | undefined
- function updateProgress() {
- counter++;
- controller.progressview.addProgress("measure_moisture", counter / limit * 100, "Measure Moisture " + (limit - counter) + "s")
- timerId = setTimeout(updateProgress, 1000);
+ reboot() {
+ fetch(PUBLIC_URL + "/reboot", {
+ method: "POST",
+ })
+ controller.progressview.addIndeterminate("rebooting", "Rebooting")
+ setTimeout(this.waitForReboot, 1000)
}
- timerId = setTimeout(updateProgress, 1000);
+ initialConfig: PlantControllerConfig | null = null
+ readonly rebootBtn: HTMLButtonElement
+ readonly exitBtn: HTMLButtonElement
+ readonly timeView: TimeView;
+ readonly plantViews: PlantViews;
+ readonly networkView: NetworkConfigView;
+ readonly hardwareView: HardwareConfigView;
+ readonly tankView: TankConfigView;
+ readonly nightLampView: NightLampView;
+ readonly submitView: SubmitView;
+ readonly firmWareView: OTAView;
+ readonly progressview: ProgressView;
+ readonly batteryView: BatteryView;
+ readonly solarView: SolarView;
+ readonly fileview: FileView;
+ readonly logView: LogView
- fetch(PUBLIC_URL + "/moisture")
- .then(response => response.json())
- .then(json => json as Moistures)
- .then(time => {
- controller.plantViews.update(time.moisture_a, time.moisture_b)
- clearTimeout(timerId);
- controller.progressview.removeProgress("measure_moisture");
- })
- .catch(error => {
- clearTimeout(timerId);
- controller.progressview.removeProgress("measure_moisture");
- console.log(error);
- });
- }
-
- exit() {
- fetch(PUBLIC_URL + "/exit", {
- method: "POST",
- })
- controller.progressview.addIndeterminate("rebooting", "Returned to normal mode, you can close this site now")
-
- }
-
- waitForReboot() {
- console.log("Check if controller online again")
- fetch(PUBLIC_URL + "/version", {
- method: "GET",
- signal: AbortSignal.timeout(5000)
- }).then(response => {
- if (response.status != 200){
- console.log("Not reached yet, retrying")
- setTimeout(controller.waitForReboot, 1000)
- } else {
- console.log("Reached controller, reloading")
- controller.progressview.addIndeterminate("rebooting", "Reached Controller, reloading")
- setTimeout(function(){
- window.location.reload()
- }, 2000);
- }
- })
- .catch(err => {
- console.log("Not reached yet, retrying")
- setTimeout(controller.waitForReboot, 1000)
- })
- }
-
- reboot() {
- fetch(PUBLIC_URL + "/reboot", {
- method: "POST",
- })
- controller.progressview.addIndeterminate("rebooting", "Rebooting")
- setTimeout(this.waitForReboot, 1000)
- }
-
- initialConfig: PlantControllerConfig | null = null
- readonly rebootBtn: HTMLButtonElement
- readonly exitBtn: HTMLButtonElement
- readonly timeView: TimeView;
- readonly plantViews: PlantViews;
- readonly networkView: NetworkConfigView;
- readonly hardwareView: HardwareConfigView;
- readonly tankView: TankConfigView;
- readonly nightLampView: NightLampView;
- readonly submitView: SubmitView;
- readonly firmWareView: OTAView;
- readonly progressview: ProgressView;
- readonly batteryView: BatteryView;
- readonly solarView: SolarView;
- readonly fileview: FileView;
- readonly logView: LogView
- constructor() {
- this.timeView = new TimeView(this)
- this.plantViews = new PlantViews(this)
- this.networkView = new NetworkConfigView(this, PUBLIC_URL)
- this.tankView = new TankConfigView(this)
- this.batteryView = new BatteryView(this)
- this.solarView = new SolarView(this)
- this.nightLampView = new NightLampView(this)
- this.submitView = new SubmitView(this)
- this.firmWareView = new OTAView(this)
- this.progressview = new ProgressView(this)
- this.fileview = new FileView(this)
- this.logView = new LogView(this)
- this.hardwareView = new HardwareConfigView(this)
- this.rebootBtn = document.getElementById("reboot") as HTMLButtonElement
- this.rebootBtn.onclick = () => {
- controller.reboot();
+ constructor() {
+ this.timeView = new TimeView(this)
+ this.plantViews = new PlantViews(this)
+ this.networkView = new NetworkConfigView(this, PUBLIC_URL)
+ this.tankView = new TankConfigView(this)
+ this.batteryView = new BatteryView(this)
+ this.solarView = new SolarView(this)
+ this.nightLampView = new NightLampView(this)
+ this.submitView = new SubmitView(this)
+ this.firmWareView = new OTAView(this)
+ this.progressview = new ProgressView(this)
+ this.fileview = new FileView(this)
+ this.logView = new LogView(this)
+ this.hardwareView = new HardwareConfigView(this)
+ this.rebootBtn = document.getElementById("reboot") as HTMLButtonElement
+ this.rebootBtn.onclick = () => {
+ controller.reboot();
+ }
+ this.exitBtn = document.getElementById("exit") as HTMLButtonElement
+ this.exitBtn.onclick = () => {
+ controller.exit();
+ }
}
- this.exitBtn = document.getElementById("exit") as HTMLButtonElement
- this.exitBtn.onclick = () => {
- controller.exit();
- }
- }
}
+
const controller = new Controller();
controller.progressview.removeProgress("rebooting");
const tasks = [
- { task: controller.populateTimezones, displayString: "Populating Timezones" },
- { task: controller.updateRTCData, displayString: "Updating RTC Data" },
- { task: controller.updateBatteryData, displayString: "Updating Battery Data" },
- { task: controller.updateSolarData, displayString: "Updating Solar Data" },
- { task: controller.downloadConfig, displayString: "Downloading Configuration" },
- { task: controller.version, displayString: "Fetching Version Information" },
- { task: controller.updateFileList, displayString: "Updating File List" },
- { task: controller.getBackupInfo, displayString: "Fetching Backup Information" },
- { task: controller.loadLogLocaleConfig, displayString: "Loading Log Localization Config" },
- { task: controller.loadTankInfo, displayString: "Loading Tank Information" },
+ {task: controller.populateTimezones, displayString: "Populating Timezones"},
+ {task: controller.updateRTCData, displayString: "Updating RTC Data"},
+ {task: controller.updateBatteryData, displayString: "Updating Battery Data"},
+ {task: controller.updateSolarData, displayString: "Updating Solar Data"},
+ {task: controller.downloadConfig, displayString: "Downloading Configuration"},
+ {task: controller.version, displayString: "Fetching Version Information"},
+ {task: controller.updateFileList, displayString: "Updating File List"},
+ {task: controller.getBackupInfo, displayString: "Fetching Backup Information"},
+ {task: controller.loadLogLocaleConfig, displayString: "Loading Log Localization Config"},
+ {task: controller.loadTankInfo, displayString: "Loading Tank Information"},
];
async function executeTasksSequentially() {
- let current = 0;
- for (const { task, displayString } of tasks) {
- current++;
- let ratio = current / tasks.length;
- controller.progressview.addProgress("initial", ratio * 100, displayString);
- try {
- await task();
- } catch (error) {
- console.error(`Error executing task '${displayString}':`, error);
- // Optionally, you can decide whether to continue or break on errors
- break;
+ let current = 0;
+ for (const {task, displayString} of tasks) {
+ current++;
+ let ratio = current / tasks.length;
+ controller.progressview.addProgress("initial", ratio * 100, displayString);
+ try {
+ await task();
+ } catch (error) {
+ console.error(`Error executing task '${displayString}':`, error);
+ // Optionally, you can decide whether to continue or break on errors
+ break;
+ }
}
- }
}
executeTasksSequentially().then(r => {
- controller.progressview.removeProgress("initial")
+ controller.progressview.removeProgress("initial")
});
controller.progressview.removeProgress("rebooting");
window.addEventListener("beforeunload", (event) => {
- const currentConfig = controller.getConfig();
-
- // Check if the current state differs from the initial configuration
- if (!deepEqual(currentConfig, controller.initialConfig)) {
- const confirmationMessage = "You have unsaved changes. Are you sure you want to leave this page?";
-
- // Standard behavior for displaying the confirmation dialog
- event.preventDefault();
- event.returnValue = confirmationMessage; // This will trigger the browser's default dialog
- return confirmationMessage;
- }
+ const currentConfig = controller.getConfig();
+
+ // Check if the current state differs from the initial configuration
+ if (!deepEqual(currentConfig, controller.initialConfig)) {
+ const confirmationMessage = "You have unsaved changes. Are you sure you want to leave this page?";
+
+ // Standard behavior for displaying the confirmation dialog
+ event.preventDefault();
+ event.returnValue = confirmationMessage; // This will trigger the browser's default dialog
+ return confirmationMessage;
+ }
});
\ No newline at end of file
diff --git a/rust/src_webpack/src/submitView.ts b/rust/src_webpack/src/submitView.ts
index e84c0d2..250d9c7 100644
--- a/rust/src_webpack/src/submitView.ts
+++ b/rust/src_webpack/src/submitView.ts
@@ -1,61 +1,65 @@
-import { Controller } from "./main";
+import {Controller} from "./main";
import {BackupHeader} from "./api";
export class SubmitView {
- json: HTMLDivElement;
- submitFormBtn: HTMLButtonElement;
- submit_status: HTMLElement;
- backupBtn: HTMLButtonElement;
- restoreBackupBtn: HTMLButtonElement;
- backuptimestamp: HTMLElement;
- backupsize: HTMLElement;
- backupjson: HTMLElement;
+ json: HTMLDivElement;
+ submitFormBtn: HTMLButtonElement;
+ submit_status: HTMLElement;
+ backupBtn: HTMLButtonElement;
+ restoreBackupBtn: HTMLButtonElement;
+ backuptimestamp: HTMLElement;
+ backupsize: HTMLElement;
+ backupjson: HTMLElement;
- constructor(controller: Controller) {
- (document.getElementById("submitview") as HTMLElement).innerHTML = require("./submitview.html")
+ constructor(controller: Controller) {
+ (document.getElementById("submitview") as HTMLElement).innerHTML = require("./submitview.html")
- let showJson = document.getElementById('showJson') as HTMLButtonElement
- let rawdata = document.getElementById('rawdata') as HTMLElement
- this.json = document.getElementById('json') as HTMLDivElement
- this.backupjson = document.getElementById('backupjson') as HTMLDivElement
- this.submitFormBtn = document.getElementById("submit") as HTMLButtonElement
- this.backupBtn = document.getElementById("backup") as HTMLButtonElement
- this.restoreBackupBtn = document.getElementById("restorebackup") as HTMLButtonElement
- this.backuptimestamp = document.getElementById("backuptimestamp") as HTMLElement
- this.backupsize = document.getElementById("backupsize") as HTMLElement
- this.submit_status = document.getElementById("submit_status") as HTMLElement
- this.submitFormBtn.onclick = () => {
- controller.uploadConfig(this.json.textContent as string, (status: string) => {
- this.submit_status.innerHTML = status;
- });
+ let showJson = document.getElementById('showJson') as HTMLButtonElement
+ let rawdata = document.getElementById('rawdata') as HTMLElement
+ this.json = document.getElementById('json') as HTMLDivElement
+ this.backupjson = document.getElementById('backupjson') as HTMLDivElement
+ this.submitFormBtn = document.getElementById("submit") as HTMLButtonElement
+ this.backupBtn = document.getElementById("backup") as HTMLButtonElement
+ this.restoreBackupBtn = document.getElementById("restorebackup") as HTMLButtonElement
+ this.backuptimestamp = document.getElementById("backuptimestamp") as HTMLElement
+ this.backupsize = document.getElementById("backupsize") as HTMLElement
+ this.submit_status = document.getElementById("submit_status") as HTMLElement
+ this.submitFormBtn.onclick = () => {
+ controller.uploadConfig(this.json.textContent as string, (status: string) => {
+ this.submit_status.innerHTML = status;
+ });
+ }
+ this.backupBtn.onclick = () => {
+ controller.progressview.addIndeterminate("backup", "Backup to EEPROM running")
+ controller.backupConfig(this.json.textContent as string).then(saveStatus => {
+ controller.getBackupInfo().then(r => {
+ controller.progressview.removeProgress("backup")
+ this.submit_status.innerHTML = saveStatus;
+ });
+ });
+ }
+ this.restoreBackupBtn.onclick = () => {
+ controller.getBackupConfig();
+ }
+ showJson.onclick = () => {
+ if (rawdata.style.display == "none") {
+ rawdata.style.display = "flex";
+ } else {
+ rawdata.style.display = "none";
+ }
+ }
}
- this.backupBtn.onclick = () => {
- controller.backupConfig(this.json.textContent as string, (status: string) => {
- this.submit_status.innerHTML = status;
- });
- }
- this.restoreBackupBtn.onclick = () => {
- controller.getBackupConfig();
- }
- showJson.onclick = () => {
- if (rawdata.style.display == "none"){
- rawdata.style.display = "flex";
- } else {
- rawdata.style.display = "none";
- }
- }
- }
- setBackupInfo(header: BackupHeader) {
- this.backuptimestamp.innerText = header.timestamp
- this.backupsize.innerText = header.size.toString()
- }
+ setBackupInfo(header: BackupHeader) {
+ this.backuptimestamp.innerText = header.timestamp
+ this.backupsize.innerText = header.size.toString()
+ }
- setJson(pretty: string) {
- this.json.textContent = pretty
- }
+ setJson(pretty: string) {
+ this.json.textContent = pretty
+ }
- setBackupJson(pretty: string) {
- this.backupjson.textContent = pretty
- }
+ setBackupJson(pretty: string) {
+ this.backupjson.textContent = pretty
+ }
}
\ No newline at end of file