diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 593cc2c..cbfa9d1 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -88,6 +88,7 @@ text-template = "0.1.0" strum_macros = "0.27.0" esp-ota = { version = "0.2.2", features = ["log"] } unit-enum = "1.4.1" +pca9535 = { version = "2.0.0", features = ["std"] } [patch.crates-io] diff --git a/rust/src/config.rs b/rust/src/config.rs index f103e02..910ce67 100644 --- a/rust/src/config.rs +++ b/rust/src/config.rs @@ -88,11 +88,16 @@ pub enum BoardVersion{ V4 } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] +pub struct BoardHardware { + pub board: BoardVersion, + pub battery: BatteryBoardVersion, +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] #[serde(default)] pub struct PlantControllerConfig { - pub board_hardware: BoardVersion, - pub battery_hardware: BatteryBoardVersion, + pub hardware: BoardHardware, pub network: NetworkConfig, pub tank: TankConfig, pub night_lamp: NightLampConfig, diff --git a/rust/src/plant_hal.rs b/rust/src/plant_hal.rs index b5a76c4..c1201c5 100644 --- a/rust/src/plant_hal.rs +++ b/rust/src/plant_hal.rs @@ -57,7 +57,7 @@ use esp_idf_svc::sntp::{self, SyncStatus}; use esp_idf_svc::systime::EspSystemTime; use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError}; use one_wire_bus::OneWire; - +use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; use crate::config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig}; use crate::log::log; use crate::plant_hal::BoardHal::{Initial, V3, V4}; @@ -78,12 +78,39 @@ const PUMP5_BIT: usize = 5; const PUMP6_BIT: usize = 6; const PUMP7_BIT: usize = 7; -const MS_0: usize = 8; -const MS_4: usize = 9; -const MS_2: usize = 10; -const MS_3: usize = 11; -const SENSOR_ON: usize = 12; -const MS_1: usize = 13; +#[non_exhaustive] +struct V3Constants; + +impl V3Constants { + const MS_0: usize = 8; + const MS_4: usize = 9; + const MS_2: usize = 10; + const MS_3: usize = 11; + const MS_1: usize = 13; + const SENSOR_ON: usize = 12; + + const SENSOR_A_1: u8 = 7; + const SENSOR_A_2: u8 = 6; + const SENSOR_A_3: u8 = 5; + const SENSOR_A_4: u8 = 4; + const SENSOR_A_5: u8 = 3; + const SENSOR_A_6: u8 = 2; + const SENSOR_A_7: u8 = 1; + const SENSOR_A_8: u8 = 0; + + const SENSOR_B_1: u8 = 8; + const SENSOR_B_2: u8 = 9; + const SENSOR_B_3: u8 = 10; + const SENSOR_B_4: u8 = 11; + const SENSOR_B_5: u8 = 12; + const SENSOR_B_6: u8 = 13; + const SENSOR_B_7: u8 = 14; + const SENSOR_B_8: u8 = 15; +} + + + + const CHARGING: usize = 14; const AWAKE: usize = 15; @@ -96,23 +123,7 @@ const FAULT_4: usize = 21; const FAULT_1: usize = 22; const FAULT_2: usize = 23; -const SENSOR_A_1: u8 = 7; -const SENSOR_A_2: u8 = 6; -const SENSOR_A_3: u8 = 5; -const SENSOR_A_4: u8 = 4; -const SENSOR_A_5: u8 = 3; -const SENSOR_A_6: u8 = 2; -const SENSOR_A_7: u8 = 1; -const SENSOR_A_8: u8 = 0; -const SENSOR_B_1: u8 = 8; -const SENSOR_B_2: u8 = 9; -const SENSOR_B_3: u8 = 10; -const SENSOR_B_4: u8 = 11; -const SENSOR_B_5: u8 = 12; -const SENSOR_B_6: u8 = 13; -const SENSOR_B_7: u8 = 14; -const SENSOR_B_8: u8 = 15; const X25: crc::Crc = crc::Crc::::new(&crc::CRC_16_IBM_SDLC); @@ -382,13 +393,9 @@ pub enum BoardHal<'a>{ V4 { tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, esp_idf_hal::adc::ADC1>>, solar_is_day: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, - boot_button: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, signal_counter: PcntDriver<'a>, 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>, - wifi_driver: EspWifi<'a>, one_wire_bus: OneWire>, rtc: Ds323x>>, ds323x::ic::DS3231>, @@ -398,6 +405,9 @@ pub enum BoardHal<'a>{ eeprom24x::addr_size::TwoBytes, eeprom24x::unique_serial::No, >, + general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + pump_expander: Pca9535Immediate>>, + sensor_expander: Pca9535Immediate>>, } } @@ -882,9 +892,9 @@ impl BoardInteraction for HAL<'_> { unsafe { gpio_hold_en(light.pin()) }; Ok(()) } - fn pump(& self, plant: usize, enable: bool) -> Result<()> { - match & self.board_hal { - BoardHal::V3 { shift_register, .. } => { + fn pump(&mut self, plant: usize, enable: bool) -> Result<()> { + match &mut self.board_hal { + V3 { shift_register, .. } => { let index = match plant { 0 => PUMP1_BIT, 1 => PUMP2_BIT, @@ -899,10 +909,14 @@ impl BoardInteraction for HAL<'_> { //currently infallible error, keep for future as result anyway shift_register.decompose()[index].set_state(enable.into())?; } - BoardHal::V4 { .. } => { - bail!("Not yet implemented") + V4 { pump_expander, .. } => { + if enable { + pump_expander.pin_set_high(GPIOBank::Bank0, plant.try_into()?)?; + } else { + pump_expander.pin_set_low(GPIOBank::Bank0, plant.try_into()?)?; + } }, - &plant_hal::BoardHal::Initial { .. } => { + &mut Initial { .. } => { bail!("Board not configured yet") } } @@ -926,8 +940,8 @@ impl BoardInteraction for HAL<'_> { fn consecutive_pump_count(&mut self, plant: usize) -> u32 { unsafe { CONSECUTIVE_WATERING_PLANT[plant] } } - fn fault(& self, plant: usize, enable: bool) -> Result<()>{ - match & self.board_hal { + fn fault(&mut self, plant: usize, enable: bool) -> Result<()>{ + match &mut self.board_hal { V3 { shift_register, .. } => { let index = match plant { 0 => FAULT_1, @@ -942,29 +956,31 @@ impl BoardInteraction for HAL<'_> { }; shift_register.decompose()[index] .set_state(enable.into())?; - Ok(()) + } + V4 { pump_expander, .. } => { + if enable { + pump_expander.pin_set_high(GPIOBank::Bank1, plant.try_into()?)?; + } else { + pump_expander.pin_set_low(GPIOBank::Bank1, plant.try_into()?)?; + } } - BoardHal::V4 { .. } => { - bail!("Not yet implemented") - } - &plant_hal::BoardHal::Initial { .. } => { + &mut Initial { .. } => { bail!("Board not configured yet") } } - + Ok(()) } fn low_voltage_in_cycle(&mut self) -> bool { unsafe { LOW_VOLTAGE_DETECTED } } fn any_pump(&mut self, enable: bool) -> Result<()> { - match &mut self.board_hal { - BoardHal::V3 { main_pump, .. } => { + V3 { main_pump, .. } => { main_pump.set_state(enable.into())?; } - BoardHal::V4 { main_pump, .. } => { - main_pump.set_state(enable.into())?; + V4 { .. } => { + //does not exist in v4, ignore it } &mut plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") @@ -1002,39 +1018,39 @@ impl BoardInteraction for HAL<'_> { signal_counter.counter_pause()?; signal_counter.counter_clear()?; //Disable all - shift_register.decompose()[MS_4].set_high()?; + shift_register.decompose()[V3Constants::MS_4].set_high()?; let sensor_channel = match sensor { Sensor::A => match plant { - 0 => SENSOR_A_1, - 1 => SENSOR_A_2, - 2 => SENSOR_A_3, - 3 => SENSOR_A_4, - 4 => SENSOR_A_5, - 5 => SENSOR_A_6, - 6 => SENSOR_A_7, - 7 => SENSOR_A_8, + 0 => V3Constants::SENSOR_A_1, + 1 => V3Constants::SENSOR_A_2, + 2 => V3Constants::SENSOR_A_3, + 3 => V3Constants::SENSOR_A_4, + 4 => V3Constants::SENSOR_A_5, + 5 => V3Constants::SENSOR_A_6, + 6 => V3Constants::SENSOR_A_7, + 7 => V3Constants::SENSOR_A_8, _ => bail!("Invalid plant id {}", plant), }, Sensor::B => match plant { - 0 => SENSOR_B_1, - 1 => SENSOR_B_2, - 2 => SENSOR_B_3, - 3 => SENSOR_B_4, - 4 => SENSOR_B_5, - 5 => SENSOR_B_6, - 6 => SENSOR_B_7, - 7 => SENSOR_B_8, + 0 => V3Constants::SENSOR_B_1, + 1 => V3Constants::SENSOR_B_2, + 2 => V3Constants::SENSOR_B_3, + 3 => V3Constants::SENSOR_B_4, + 4 => V3Constants::SENSOR_B_5, + 5 => V3Constants::SENSOR_B_6, + 6 => V3Constants::SENSOR_B_7, + 7 => V3Constants::SENSOR_B_8, _ => bail!("Invalid plant id {}", plant), }, }; let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 }; - let pin_0 = &mut shift_register.decompose()[MS_0]; - let pin_1 = &mut shift_register.decompose()[MS_1]; - let pin_2 = &mut shift_register.decompose()[MS_2]; - let pin_3 = &mut shift_register.decompose()[MS_3]; + let pin_0 = &mut shift_register.decompose()[V3Constants::MS_0]; + let pin_1 = &mut shift_register.decompose()[V3Constants::MS_1]; + let pin_2 = &mut shift_register.decompose()[V3Constants::MS_2]; + let pin_3 = &mut shift_register.decompose()[V3Constants::MS_3]; if is_bit_set(0) { pin_0.set_high()?; } else { @@ -1056,8 +1072,8 @@ impl BoardInteraction for HAL<'_> { pin_3.set_low()?; } - shift_register.decompose()[MS_4].set_low()?; - shift_register.decompose()[SENSOR_ON].set_high()?; + shift_register.decompose()[V3Constants::MS_4].set_low()?; + shift_register.decompose()[V3Constants::SENSOR_ON].set_high()?; let delay = Delay::new_default(); let measurement = 100; // TODO what is this scaling factor? what is its purpose? @@ -1068,8 +1084,8 @@ impl BoardInteraction for HAL<'_> { signal_counter.counter_resume()?; delay.delay_ms(measurement); signal_counter.counter_pause()?; - shift_register.decompose()[MS_4].set_high()?; - shift_register.decompose()[SENSOR_ON].set_low()?; + shift_register.decompose()[V3Constants::MS_4].set_high()?; + shift_register.decompose()[V3Constants::SENSOR_ON].set_low()?; delay.delay_ms(10); let unscaled = signal_counter.get_counter_value()? as i32; let hz = unscaled as f32 * factor; @@ -1088,6 +1104,91 @@ impl BoardInteraction for HAL<'_> { let median = results[mid]; Ok(median) }, + V4 {sensor_expander, signal_counter, ..} => { + let mut results = [0_f32; REPEAT_MOIST_MEASURE]; + for repeat in 0..REPEAT_MOIST_MEASURE { + signal_counter.counter_pause()?; + signal_counter.counter_clear()?; + + + const MS0: u8 = 1_u8; + const MS1: u8 = 0_u8; + const MS2: u8 = 3_u8; + const MS3: u8 = 4_u8; + const MS4: u8 = 2_u8; + const SENSOR_ON: u8 = 5_u8; + //Disable all + sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?; + + let sensor_channel = match sensor { + Sensor::A =>{ + plant as u32 + }, + Sensor::B => { + (15 - plant) as u32 + }, + }; + + + + let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 }; + if is_bit_set(0) { + sensor_expander.pin_set_high(GPIOBank::Bank0, MS0)?; + } else { + sensor_expander.pin_set_low(GPIOBank::Bank0, MS0)?; + } + if is_bit_set(1) { + sensor_expander.pin_set_high(GPIOBank::Bank0, MS1)?; + } else { + sensor_expander.pin_set_low(GPIOBank::Bank0, MS1)?; + } + if is_bit_set(2) { + sensor_expander.pin_set_high(GPIOBank::Bank0, MS2)?; + } else { + sensor_expander.pin_set_low(GPIOBank::Bank0, MS2)?; + } + if is_bit_set(3) { + sensor_expander.pin_set_high(GPIOBank::Bank0, MS3)?; + } else { + sensor_expander.pin_set_low(GPIOBank::Bank0, MS3)?; + } + + sensor_expander.pin_set_low(GPIOBank::Bank0, MS4)?; + sensor_expander.pin_set_high(GPIOBank::Bank0, SENSOR_ON)?; + + let delay = Delay::new_default(); + let measurement = 100; // TODO what is this scaling factor? what is its purpose? + let factor = 1000f32 / measurement as f32; + + //give some time to stabilize + delay.delay_ms(10); + signal_counter.counter_resume()?; + delay.delay_ms(measurement); + signal_counter.counter_pause()?; + sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?; + sensor_expander.pin_set_low(GPIOBank::Bank0, SENSOR_ON)?; + sensor_expander.pin_set_low(GPIOBank::Bank0, MS0); + sensor_expander.pin_set_low(GPIOBank::Bank0, MS1); + sensor_expander.pin_set_low(GPIOBank::Bank0, MS2); + sensor_expander.pin_set_low(GPIOBank::Bank0, MS3); + delay.delay_ms(10); + let unscaled = signal_counter.get_counter_value()? as i32; + let hz = unscaled as f32 * factor; + log( + LogMessage::RawMeasure, + unscaled as u32, + hz as u32, + &plant.to_string(), + &format!("{sensor:?}"), + ); + results[repeat] = hz; + } + results.sort_by(|a, b| a.partial_cmp(b).unwrap()); // floats don't seem to implement total_ord + + let mid = results.len() / 2; + let median = results[mid]; + Ok(median) + } _ => { bail!("Not implemented for this board") } @@ -1534,12 +1635,12 @@ pub trait BoardInteraction { fn set_low_voltage_in_cycle(&mut self); fn clear_low_voltage_in_cycle(&mut self); fn light(&mut self, enable: bool) -> Result<()>; - fn pump(&self, plant: usize, enable: bool) -> Result<()>; + fn pump(&mut self, plant: usize, enable: bool) -> Result<()>; fn last_pump_time(&self, plant: usize) -> Option>; fn store_last_pump_time(&mut self, plant: usize, time: DateTime); fn store_consecutive_pump_count(&mut self, plant: usize, count: u32); fn consecutive_pump_count(&mut self, plant: usize) -> u32; - fn fault(&self, plant: usize, enable: bool) -> Result<()>; + fn fault(&mut self, plant: usize, enable: bool) -> Result<()>; fn low_voltage_in_cycle(&mut self) -> bool; fn any_pump(&mut self, enable: bool) -> Result<()>; fn time(&mut self) -> Result>; @@ -1771,8 +1872,7 @@ impl PlantHal { let config = esp.get_config(); let hal = match config { Result::Ok(config) => { - let board_hal : BoardHal = match config.board_hardware { - + let board_hal : BoardHal = match config.hardware.board { BoardVersion::INITIAL => { let mut general_fault = PinDriver::input_output(free_pins.gpio6.downgrade())?; general_fault.set_pull(Pull::Floating)?; @@ -1787,7 +1887,7 @@ impl PlantHal { BoardVersion::V4 => {PlantHal::create_v4(free_pins)?} }; - let battery_monitor : BatteryMonitor = match config.battery_hardware { + let battery_monitor : BatteryMonitor = match config.hardware.battery { BatteryBoardVersion::Disabled => { BatteryMonitor::Disabled {}} BatteryBoardVersion::BQ34Z100G1 => { let mut battery_driver = Bq34z100g1Driver { @@ -1849,15 +1949,124 @@ impl PlantHal { } fn create_v4(peripherals: FreePeripherals) -> Result> { + let mut awake = PinDriver::output(peripherals.gpio15.downgrade())?; + awake.set_high()?; + let mut general_fault = PinDriver::input_output(peripherals.gpio6.downgrade())?; general_fault.set_pull(Pull::Floating)?; general_fault.set_low()?; - //temp remove me - general_fault.set_high()?; - bail!("not implemented"); + 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 one_wire_pin = PinDriver::input_output_od(peripherals.gpio18.downgrade())?; + one_wire_pin.set_pull(Pull::Floating)?; + + 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 mut signal_counter = PcntDriver::new( + peripherals.pcnt0, + Some(peripherals.gpio22), + Option::::None, + Option::::None, + Option::::None, + )?; + + signal_counter.channel_config( + PcntChannel::Channel0, + PinIndex::Pin0, + PinIndex::Pin1, + &PcntChannelConfig { + lctrl_mode: PcntControlMode::Keep, + hctrl_mode: PcntControlMode::Keep, + pos_mode: PcntCountMode::Increment, + neg_mode: PcntCountMode::Hold, + counter_h_lim: i16::MAX, + counter_l_lim: 0, + }, + )?; + + 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 pump_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 32); + + //todo error handing if init error + 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 mut sensor_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 34); + 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); + } + + Ok(V4 { + tank_channel, + solar_is_day, + signal_counter, + light, + tank_power, + one_wire_bus, + rtc, + eeprom, + general_fault, + pump_expander, + sensor_expander + }) } fn create_v3(peripherals: FreePeripherals) -> Result> { @@ -1879,16 +2088,16 @@ impl PlantHal { let charging = &mut shift_register.decompose()[CHARGING]; charging.set_high()?; - let ms0 = &mut shift_register.decompose()[MS_0]; + let ms0 = &mut shift_register.decompose()[V3Constants::MS_0]; ms0.set_low()?; - let ms1 = &mut shift_register.decompose()[MS_1]; + let ms1 = &mut shift_register.decompose()[V3Constants::MS_1]; ms1.set_low()?; - let ms2 = &mut shift_register.decompose()[MS_2]; + let ms2 = &mut shift_register.decompose()[V3Constants::MS_2]; ms2.set_low()?; - let ms3 = &mut shift_register.decompose()[MS_3]; + let ms3 = &mut shift_register.decompose()[V3Constants::MS_3]; ms3.set_low()?; - let ms4 = &mut shift_register.decompose()[MS_4]; + let ms4 = &mut shift_register.decompose()[V3Constants::MS_4]; ms4.set_high()?; println!("Init battery driver"); diff --git a/rust/src/webserver/webserver.rs b/rust/src/webserver/webserver.rs index a76f6d2..5d0b8a1 100644 --- a/rust/src/webserver/webserver.rs +++ b/rust/src/webserver/webserver.rs @@ -216,6 +216,7 @@ fn set_config( ) -> Result, anyhow::Error> { let all = read_up_to_bytes_from_request(request, Some(3072))?; let config: PlantControllerConfig = serde_json::from_slice(&all)?; + let mut board = BOARD_ACCESS.lock().unwrap(); board.esp.set_config(&config)?; @@ -525,7 +526,7 @@ pub fn httpd(reboot_now: Arc) -> Box> { server .fn_handler("/file", Method::Post, move |mut request| { let filename = query_param(request.uri(), "filename").unwrap(); - let lock = BOARD_ACCESS.lock().unwrap(); + let mut lock = BOARD_ACCESS.lock().unwrap(); let file_handle = lock.esp.get_file_handle(&filename, true); match file_handle { //TODO get free filesystem size, check against during write if not to large diff --git a/rust/src_webpack/src/api.ts b/rust/src_webpack/src/api.ts index 531be75..faff2ca 100644 --- a/rust/src_webpack/src/api.ts +++ b/rust/src_webpack/src/api.ts @@ -1,6 +1,6 @@ -interface LogArray extends Array{} +export interface LogArray extends Array{} -interface LogEntry { +export interface LogEntry { timestamp: string, message_id: number, a: number, @@ -9,27 +9,28 @@ interface LogEntry { txt_long: string } -interface LogLocalisation extends Array{} +export interface LogLocalisation extends Array{} -interface LogLocalisationEntry { +export interface LogLocalisationEntry { msg_type: string, message: string } -interface BackupHeader { +export interface BackupHeader { timestamp: string, size: number } -interface NetworkConfig { +export interface NetworkConfig { ap_ssid: string, ssid: string, password: string, mqtt_url: string, - base_topic: string + base_topic: string, + max_wait: number } -interface FileList { +export interface FileList { total: number, used: number, files: FileInfo[], @@ -37,12 +38,12 @@ interface FileList { iter_error: string, } -interface FileInfo{ +export interface FileInfo{ filename: string, size: number, } -interface NightLampConfig { +export interface NightLampConfig { enabled: boolean, night_lamp_hour_start: number, night_lamp_hour_end: number, @@ -51,11 +52,11 @@ interface NightLampConfig { low_soc_restore: number } -interface NightLampCommand { +export interface NightLampCommand { active: boolean } -interface TankConfig { +export interface TankConfig { tank_sensor_enabled: boolean, tank_allow_pumping_if_sensor_error: boolean, tank_useable_ml: number, @@ -64,7 +65,26 @@ interface TankConfig { tank_full_percent: number, } -interface PlantControllerConfig { + +export enum BatteryBoardVersion { + Disabled = "Disabled", + BQ34Z100G1 = "BQ34Z100G1", + WchI2cSlave = "WchI2cSlave" +} +export enum BoardVersion{ + INITIAL = "INITIAL", + V3 = "V3", + V4 = "V4" +} + +export interface BoardHardware { + board: BoardVersion, + battery: BatteryBoardVersion, +} + +export interface PlantControllerConfig { + hardware: BoardHardware, + network: NetworkConfig, tank: TankConfig, night_lamp: NightLampConfig, @@ -72,7 +92,7 @@ interface PlantControllerConfig { timezone?: string, } -interface PlantConfig { +export interface PlantConfig { mode: string, target_moisture: number, pump_time_s: number, @@ -88,35 +108,35 @@ interface PlantConfig { } -interface SSIDList { +export interface SSIDList { ssids: [string] } -interface TestPump { +export interface TestPump { pump: number } -interface SetTime { +export interface SetTime { time: string } -interface GetTime { +export interface GetTime { rtc: string, native: string } -interface Moistures { +export interface Moistures { moisture_a: [string], moisture_b: [string], } -interface VersionInfo { +export interface VersionInfo { git_hash: string, build_time: string, partition: string } -interface BatteryState { +export interface BatteryState { temperature: string voltage_milli_volt: string, current_milli_ampere: string, @@ -127,7 +147,7 @@ interface BatteryState { state_of_health: string } -interface TankInfo { +export interface TankInfo { /// is there enough water in the tank enough_water: boolean, /// warning that water needs to be refilled soon @@ -145,4 +165,4 @@ interface TankInfo { /// water temperature water_temp: number | null, temp_sensor_error: string | null -} \ No newline at end of file +} diff --git a/rust/src_webpack/src/batteryview.ts b/rust/src_webpack/src/batteryview.ts index 5b0b6cf..1dcbaeb 100644 --- a/rust/src_webpack/src/batteryview.ts +++ b/rust/src_webpack/src/batteryview.ts @@ -1,4 +1,5 @@ import { Controller } from "./main"; +import {BatteryState} from "./api"; export class BatteryView{ voltage_milli_volt: HTMLSpanElement; diff --git a/rust/src_webpack/src/fileview.ts b/rust/src_webpack/src/fileview.ts index 2d5e870..4a30f72 100644 --- a/rust/src_webpack/src/fileview.ts +++ b/rust/src_webpack/src/fileview.ts @@ -1,5 +1,5 @@ import {Controller} from "./main"; - +import {FileInfo, FileList} from "./api"; const regex = /[^a-zA-Z0-9_.]/g; function sanitize(str:string){ diff --git a/rust/src_webpack/src/hardware.html b/rust/src_webpack/src/hardware.html new file mode 100644 index 0000000..71a7f32 --- /dev/null +++ b/rust/src_webpack/src/hardware.html @@ -0,0 +1,20 @@ + + +
Hardware:
+
+
BoardRevision
+ +
+
+
BatteryMonitor
+ +
diff --git a/rust/src_webpack/src/hardware.ts b/rust/src_webpack/src/hardware.ts new file mode 100644 index 0000000..ab1819a --- /dev/null +++ b/rust/src_webpack/src/hardware.ts @@ -0,0 +1,45 @@ +import { Controller } from "./main"; +import {BatteryBoardVersion, BoardHardware, BoardVersion} from "./api"; + +export class HardwareConfigView { + private readonly hardware_board_value: HTMLSelectElement; + private readonly hardware_battery_value: HTMLSelectElement; + constructor(controller:Controller){ + (document.getElementById("hardwareview") as HTMLElement).innerHTML = require('./hardware.html') as string; + + this.hardware_board_value = document.getElementById("hardware_board_value") as HTMLSelectElement; + this.hardware_board_value.onchange = controller.configChanged + + Object.keys(BoardVersion).forEach(version => { + let option = document.createElement("option"); + if (version == BoardVersion.INITIAL.toString()){ + option.selected = true + } + option.innerText = version.toString(); + this.hardware_board_value.appendChild(option); + }) + + this.hardware_battery_value = document.getElementById("hardware_battery_value") as HTMLSelectElement; + this.hardware_battery_value.onchange = controller.configChanged + Object.keys(BatteryBoardVersion).forEach(version => { + let option = document.createElement("option"); + if (version == BatteryBoardVersion.Disabled.toString()){ + option.selected = true + } + option.innerText = version.toString(); + this.hardware_battery_value.appendChild(option); + }) + } + + setConfig(hardware: BoardHardware) { + this.hardware_board_value.value = hardware.board.toString() + this.hardware_battery_value.value = hardware.battery.toString() + } + + getConfig(): BoardHardware { + return { + board : BoardVersion[this.hardware_board_value.value as keyof typeof BoardVersion], + battery : BatteryBoardVersion[this.hardware_battery_value.value as keyof typeof BatteryBoardVersion], + } + } + } \ No newline at end of file diff --git a/rust/src_webpack/src/log.ts b/rust/src_webpack/src/log.ts index b8239b6..1e38724 100644 --- a/rust/src_webpack/src/log.ts +++ b/rust/src_webpack/src/log.ts @@ -1,4 +1,5 @@ import { Controller } from "./main"; +import {LogArray, LogLocalisation} from "./api"; export class LogView { private readonly logpanel: HTMLElement; diff --git a/rust/src_webpack/src/main.html b/rust/src_webpack/src/main.html index 8716f63..a706228 100644 --- a/rust/src_webpack/src/main.html +++ b/rust/src_webpack/src/main.html @@ -138,6 +138,10 @@
+
+
+
+
diff --git a/rust/src_webpack/src/main.ts b/rust/src_webpack/src/main.ts index ab3fc44..d34145c 100644 --- a/rust/src_webpack/src/main.ts +++ b/rust/src_webpack/src/main.ts @@ -17,6 +17,19 @@ 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 +} from "./api"; export class Controller { loadTankInfo() : Promise { @@ -66,7 +79,7 @@ export class Controller { } populateTimezones(): Promise { - return fetch('/timezones') + return fetch(PUBLIC_URL+'/timezones') .then(response => response.json()) .then(json => json as string[]) .then(timezones => { @@ -268,6 +281,12 @@ export class Controller { } } + selfTest(){ + fetch(PUBLIC_URL + "/boardtest", { + method: "POST" + }) + } + testNightLamp(active: boolean){ var body: NightLampCommand = { active: active @@ -313,6 +332,7 @@ export class Controller { getConfig(): PlantControllerConfig { return { + hardware: controller.hardwareView.getConfig(), network: controller.networkView.getConfig(), tank: controller.tankView.getConfig(), night_lamp: controller.nightLampView.getConfig(), @@ -360,6 +380,7 @@ export class Controller { this.nightLampView.setConfig(current.night_lamp); this.plantViews.setConfig(current.plants); this.timeView.setTimeZone(current.timezone); + this.hardwareView.setConfig(current.hardware); } measure_moisture() { @@ -437,6 +458,7 @@ export class Controller { readonly timeView: TimeView; readonly plantViews: PlantViews; readonly networkView: NetworkConfigView; + readonly hardwareView: HardwareConfigView; readonly tankView: TankConfigView; readonly nightLampView: NightLampView; readonly submitView: SubmitView; @@ -457,6 +479,7 @@ export class Controller { 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(); @@ -466,6 +489,10 @@ export class Controller { controller.exit(); } } + + selftest() { + + } } const controller = new Controller(); controller.progressview.removeProgress("rebooting"); @@ -505,9 +532,6 @@ executeTasksSequentially().then(r => { controller.progressview.removeProgress("initial") }); - - - controller.progressview.removeProgress("rebooting"); window.addEventListener("beforeunload", (event) => { diff --git a/rust/src_webpack/src/network.html b/rust/src_webpack/src/network.html index 521155f..5bdca8d 100644 --- a/rust/src_webpack/src/network.html +++ b/rust/src_webpack/src/network.html @@ -42,6 +42,11 @@
+ +
+ + +
diff --git a/rust/src_webpack/src/network.ts b/rust/src_webpack/src/network.ts index 8f04063..df1ffbd 100644 --- a/rust/src_webpack/src/network.ts +++ b/rust/src_webpack/src/network.ts @@ -1,4 +1,5 @@ import { Controller } from "./main"; +import {NetworkConfig, SSIDList} from "./api"; export class NetworkConfigView { setScanResult(ssidList: SSIDList) { @@ -14,6 +15,7 @@ export class NetworkConfigView { private readonly password: HTMLInputElement; private readonly mqtt_url: HTMLInputElement; private readonly base_topic: HTMLInputElement; + private readonly max_wait: HTMLInputElement; private readonly ssidlist: HTMLElement; constructor(controller: Controller, publicIp: string) { @@ -28,6 +30,9 @@ export class NetworkConfigView { this.ssid.onchange = controller.configChanged this.password = (document.getElementById("password") as HTMLInputElement); this.password.onchange = controller.configChanged + this.max_wait = (document.getElementById("max_wait") as HTMLInputElement); + this.max_wait.onchange = controller.configChanged + this.mqtt_url = document.getElementById("mqtt_url") as HTMLInputElement; this.mqtt_url.onchange = controller.configChanged this.base_topic = document.getElementById("base_topic") as HTMLInputElement; @@ -47,10 +52,12 @@ export class NetworkConfigView { this.password.value = network.password; this.mqtt_url.value = network.mqtt_url; this.base_topic.value = network.base_topic; + this.max_wait.value = network.max_wait.toString(); } getConfig(): NetworkConfig { return { + max_wait: +this.max_wait.value, ap_ssid: this.ap_ssid.value, ssid: this.ssid.value ?? null, password: this.password.value ?? null, diff --git a/rust/src_webpack/src/nightlightview.ts b/rust/src_webpack/src/nightlightview.ts index d2d6417..f28d723 100644 --- a/rust/src_webpack/src/nightlightview.ts +++ b/rust/src_webpack/src/nightlightview.ts @@ -1,4 +1,5 @@ import { Controller } from "./main"; +import {NightLampConfig} from "./api"; export class NightLampView { private readonly night_lamp_only_when_dark: HTMLInputElement; diff --git a/rust/src_webpack/src/ota.html b/rust/src_webpack/src/ota.html index 32522b9..8584893 100644 --- a/rust/src_webpack/src/ota.html +++ b/rust/src_webpack/src/ota.html @@ -37,5 +37,5 @@
- +
\ No newline at end of file diff --git a/rust/src_webpack/src/ota.ts b/rust/src_webpack/src/ota.ts index 670f9e4..923e5fb 100644 --- a/rust/src_webpack/src/ota.ts +++ b/rust/src_webpack/src/ota.ts @@ -1,4 +1,5 @@ import { Controller } from "./main"; +import {VersionInfo} from "./api"; export class OTAView { readonly file1Upload: HTMLInputElement; @@ -9,6 +10,8 @@ export class OTAView { constructor(controller: Controller) { (document.getElementById("firmwareview") as HTMLElement).innerHTML = require("./ota.html") + let test = document.getElementById("test") as HTMLButtonElement; + this.firmware_buildtime = document.getElementById("firmware_buildtime") as HTMLDivElement; this.firmware_githash = document.getElementById("firmware_githash") as HTMLDivElement; this.firmware_partition = document.getElementById("firmware_partition") as HTMLDivElement; @@ -24,6 +27,10 @@ export class OTAView { } controller.uploadNewFirmware(selectedFile); }; + + test.onclick = () => { + controller.selftest(); + } } setVersion(versionInfo: VersionInfo) { diff --git a/rust/src_webpack/src/plant.ts b/rust/src_webpack/src/plant.ts index d71cf89..d80a120 100644 --- a/rust/src_webpack/src/plant.ts +++ b/rust/src_webpack/src/plant.ts @@ -1,3 +1,5 @@ +import {PlantConfig} from "./api"; + const PLANT_COUNT = 8; diff --git a/rust/src_webpack/src/submitView.ts b/rust/src_webpack/src/submitView.ts index d5ddbb8..e84c0d2 100644 --- a/rust/src_webpack/src/submitView.ts +++ b/rust/src_webpack/src/submitView.ts @@ -1,4 +1,5 @@ import { Controller } from "./main"; +import {BackupHeader} from "./api"; export class SubmitView { json: HTMLDivElement; diff --git a/rust/src_webpack/src/tankview.ts b/rust/src_webpack/src/tankview.ts index f701557..d1f4de8 100644 --- a/rust/src_webpack/src/tankview.ts +++ b/rust/src_webpack/src/tankview.ts @@ -1,4 +1,5 @@ import { Controller } from "./main"; +import {TankConfig, TankInfo} from "./api"; export class TankConfigView { private readonly tank_useable_ml: HTMLInputElement; diff --git a/rust/src_webpack/webpack.config.js b/rust/src_webpack/webpack.config.js index 6386702..00e4ebc 100644 --- a/rust/src_webpack/webpack.config.js +++ b/rust/src_webpack/webpack.config.js @@ -9,7 +9,7 @@ console.log("Dev server is " + isDevServer); var host; if (isDevServer){ //ensure no trailing / - host = 'http://192.168.251.37'; + host = 'http://192.168.71.1'; } else { host = ''; }