From d33b05e1d736afb179e5800e6b9de6c8908c6b01 Mon Sep 17 00:00:00 2001 From: Empire Date: Sun, 4 Jan 2026 18:41:38 +0100 Subject: [PATCH] remove HAL implementation files for v3 and v4, and the build script --- Software/MainBoard/rust/.idea/vcs.xml | 2 - Software/MainBoard/rust/src/config.rs | 3 +- Software/MainBoard/rust/src/fat_error.rs | 14 +- Software/MainBoard/rust/src/hal/esp.rs | 11 +- .../rust/src/hal/little_fs2storage_adapter.rs | 10 +- Software/MainBoard/rust/src/hal/mod.rs | 28 +- .../MainBoard/rust/src/hal/shared_flash.rs | 6 +- Software/MainBoard/rust/src/hal/v3_hal.rs | 458 ----------------- .../rust/src/hal/v3_shift_register.rs | 154 ------ Software/MainBoard/rust/src/hal/v4_hal.rs | 238 ++++++--- Software/MainBoard/rust/src/hal/v4_sensor.rs | 334 ------------- Software/MainBoard/rust/src/log/mod.rs | 4 +- Software/MainBoard/rust/src/main.rs | 10 +- .../MainBoard/rust/src/webserver/get_json.rs | 2 +- .../rust/src_webpack/package-lock.json | 466 +++++++++--------- .../MainBoard/rust/src_webpack/src/api.ts | 7 +- .../MainBoard/rust/src_webpack/src/main.ts | 8 +- .../MainBoard/rust/src_webpack/src/plant.ts | 36 +- .../rust/src_webpack/src/tankview.ts | 6 +- .../MainBoard/rust/src_webpack/src/toast.ts | 1 + rust/build.rs | 124 ----- 21 files changed, 504 insertions(+), 1418 deletions(-) delete mode 100644 Software/MainBoard/rust/src/hal/v3_hal.rs delete mode 100644 Software/MainBoard/rust/src/hal/v3_shift_register.rs delete mode 100644 Software/MainBoard/rust/src/hal/v4_sensor.rs delete mode 100644 rust/build.rs diff --git a/Software/MainBoard/rust/.idea/vcs.xml b/Software/MainBoard/rust/.idea/vcs.xml index c654893..c2365ab 100644 --- a/Software/MainBoard/rust/.idea/vcs.xml +++ b/Software/MainBoard/rust/.idea/vcs.xml @@ -2,7 +2,5 @@ - - \ No newline at end of file diff --git a/Software/MainBoard/rust/src/config.rs b/Software/MainBoard/rust/src/config.rs index 947e0c0..f437d44 100644 --- a/Software/MainBoard/rust/src/config.rs +++ b/Software/MainBoard/rust/src/config.rs @@ -88,9 +88,8 @@ pub enum BatteryBoardVersion { } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] pub enum BoardVersion { - #[default] Initial, - V3, + #[default] V4, } diff --git a/Software/MainBoard/rust/src/fat_error.rs b/Software/MainBoard/rust/src/fat_error.rs index f5b3306..96bbe11 100644 --- a/Software/MainBoard/rust/src/fat_error.rs +++ b/Software/MainBoard/rust/src/fat_error.rs @@ -194,7 +194,7 @@ impl From for FatError { } } -impl From> for FatError { +impl From> for FatError { fn from(value: edge_http::io::Error) -> Self { FatError::String { error: format!("{value:?}"), @@ -202,7 +202,7 @@ impl From> for FatError { } } -impl From> for FatError { +impl From> for FatError { fn from(value: ds323x::Error) -> Self { FatError::DS323 { error: format!("{value:?}"), @@ -210,7 +210,7 @@ impl From> for FatError { } } -impl From> for FatError { +impl From> for FatError { fn from(value: eeprom24x::Error) -> Self { FatError::Eeprom24x { error: format!("{value:?}"), @@ -218,7 +218,7 @@ impl From> for FatError { } } -impl From>> for FatError { +impl From>> for FatError { fn from(value: ExpanderError>) -> Self { FatError::ExpanderError { error: format!("{value:?}"), @@ -248,7 +248,7 @@ impl From for FatError { } } -impl From> for FatError { +impl From> for FatError { fn from(value: I2cDeviceError) -> Self { FatError::String { error: format!("{value:?}"), @@ -256,14 +256,14 @@ impl From> for FatError { } } -impl From>> for FatError { +impl From>> for FatError { fn from(value: BusVoltageReadError>) -> Self { FatError::String { error: format!("{value:?}"), } } } -impl From>> for FatError { +impl From>> for FatError { fn from(value: ShuntVoltageReadError>) -> Self { FatError::String { error: format!("{value:?}"), diff --git a/Software/MainBoard/rust/src/hal/esp.rs b/Software/MainBoard/rust/src/hal/esp.rs index 4985e1a..25e006b 100644 --- a/Software/MainBoard/rust/src/hal/esp.rs +++ b/Software/MainBoard/rust/src/hal/esp.rs @@ -560,14 +560,11 @@ impl Esp<'_> { duration_in_ms: u64, mut rtc: MutexGuard, ) -> ! { - // Configure and enter deep sleep using esp-hal. Also keep prior behavior where - // duration_in_ms == 0 triggers an immediate reset. - // Mark the current OTA image as valid if we reached here while in pending verify. if let Ok(cur) = self.ota.current_ota_state() { if cur == OtaImageState::PendingVerify { self.ota - .set_current_ota_state(OtaImageState::Valid) + .set_current_ota_state(Valid) .expect("Could not set image to valid"); } } @@ -673,12 +670,12 @@ impl Esp<'_> { // is executed before main, no other code will alter these values during printing #[allow(static_mut_refs)] for (i, time) in LAST_WATERING_TIMESTAMP.iter().enumerate() { - log::info!("LAST_WATERING_TIMESTAMP[{i}] = UTC {time}"); + info!("LAST_WATERING_TIMESTAMP[{i}] = UTC {time}"); } // is executed before main, no other code will alter these values during printing #[allow(static_mut_refs)] for (i, item) in CONSECUTIVE_WATERING_PLANT.iter().enumerate() { - log::info!("CONSECUTIVE_WATERING_PLANT[{i}] = {item}"); + info!("CONSECUTIVE_WATERING_PLANT[{i}] = {item}"); } } } @@ -963,7 +960,7 @@ async fn run_dhcp(stack: Stack<'static>, gw_ip_addr: &'static str) { &mut buf, ) .await - .inspect_err(|e| log::warn!("DHCP server error: {e:?}")); + .inspect_err(|e| warn!("DHCP server error: {e:?}")); Timer::after(Duration::from_millis(500)).await; } } diff --git a/Software/MainBoard/rust/src/hal/little_fs2storage_adapter.rs b/Software/MainBoard/rust/src/hal/little_fs2storage_adapter.rs index 987a7b7..c3b7a80 100644 --- a/Software/MainBoard/rust/src/hal/little_fs2storage_adapter.rs +++ b/Software/MainBoard/rust/src/hal/little_fs2storage_adapter.rs @@ -63,25 +63,25 @@ impl lfs2Storage for LittleFs2Filesystem { let block_size: usize = Self::BLOCK_SIZE; if off % block_size != 0 { error!("Littlefs2Filesystem erase error: offset not aligned to block size offset: {off} block_size: {block_size}"); - return lfs2Result::Err(lfs2Error::IO); + return Err(lfs2Error::IO); } if len % block_size != 0 { error!("Littlefs2Filesystem erase error: length not aligned to block size length: {len} block_size: {block_size}"); - return lfs2Result::Err(lfs2Error::IO); + return Err(lfs2Error::IO); } match check_erase(self.storage, off as u32, (off + len) as u32) { Ok(_) => {} Err(err) => { error!("Littlefs2Filesystem check erase error: {err:?}"); - return lfs2Result::Err(lfs2Error::IO); + return Err(lfs2Error::IO); } } match self.storage.erase(off as u32, (off + len) as u32) { - Ok(..) => lfs2Result::Ok(len), + Ok(..) => Ok(len), Err(err) => { error!("Littlefs2Filesystem erase error: {err:?}"); - lfs2Result::Err(lfs2Error::IO) + Err(lfs2Error::IO) } } } diff --git a/Software/MainBoard/rust/src/hal/mod.rs b/Software/MainBoard/rust/src/hal/mod.rs index c995dcf..04e2175 100644 --- a/Software/MainBoard/rust/src/hal/mod.rs +++ b/Software/MainBoard/rust/src/hal/mod.rs @@ -5,10 +5,7 @@ mod initial_hal; mod little_fs2storage_adapter; pub(crate) mod rtc; mod shared_flash; -mod v3_hal; -mod v3_shift_register; mod v4_hal; -pub(crate) mod v4_sensor; mod water; use crate::alloc::string::ToString; @@ -169,7 +166,7 @@ pub trait BoardInteraction<'a> { async fn progress(&mut self, counter: u32) { // Indicate progress is active to suppress default wait_infinity blinking - crate::hal::PROGRESS_ACTIVE.store(true, core::sync::atomic::Ordering::Relaxed); + PROGRESS_ACTIVE.store(true, core::sync::atomic::Ordering::Relaxed); let current = counter % PLANT_COUNT as u32; for led in 0..PLANT_COUNT { @@ -190,7 +187,7 @@ pub trait BoardInteraction<'a> { let _ = self.general_fault(false).await; // Reset progress active flag so wait_infinity can resume blinking - crate::hal::PROGRESS_ACTIVE.store(false, core::sync::atomic::Ordering::Relaxed); + PROGRESS_ACTIVE.store(false, core::sync::atomic::Ordering::Relaxed); } } @@ -345,7 +342,7 @@ impl PlantHal { info!("Slot0 state: {state_0:?}"); info!("Slot1 state: {state_1:?}"); - //obtain current_state and next_state here! + //get current_state and next_state here! let ota_target = match target { AppPartitionSubType::Ota0 => pt .find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App( @@ -382,11 +379,11 @@ impl PlantHal { let lfs2filesystem = mk_static!(LittleFs2Filesystem, LittleFs2Filesystem { storage: data }); let alloc = mk_static!(Allocation, lfs2Filesystem::allocate()); if lfs2filesystem.is_mountable() { - log::info!("Littlefs2 filesystem is mountable"); + info!("Littlefs2 filesystem is mountable"); } else { match lfs2filesystem.format() { - Result::Ok(..) => { - log::info!("Littlefs2 filesystem is formatted"); + Ok(..) => { + info!("Littlefs2 filesystem is formatted"); } Err(err) => { error!("Littlefs2 filesystem could not be formatted: {err:?}"); @@ -466,7 +463,7 @@ impl PlantHal { let config = esp.load_config().await; - log::info!("Init rtc driver"); + info!("Init rtc driver"); let sda = peripherals.GPIO20; let scl = peripherals.GPIO19; @@ -500,10 +497,10 @@ impl PlantHal { let rtc_time = rtc.datetime(); match rtc_time { Ok(tt) => { - log::info!("Rtc Module reports time at UTC {tt}"); + info!("Rtc Module reports time at UTC {tt}"); } Err(err) => { - log::info!("Rtc Module could not be read {err:?}"); + info!("Rtc Module could not be read {err:?}"); } } @@ -518,7 +515,7 @@ impl PlantHal { Box::new(DS3231Module { rtc, storage }) as Box; let hal = match config { - Result::Ok(config) => { + Ok(config) => { let battery_interaction: Box = match config.hardware.battery { BatteryBoardVersion::Disabled => Box::new(NoBatteryMonitor {}), @@ -558,9 +555,6 @@ impl PlantHal { BoardVersion::Initial => { initial_hal::create_initial_board(free_pins, config, esp)? } - BoardVersion::V3 => { - v3_hal::create_v3(free_pins, esp, config, battery_interaction, rtc_module)? - } BoardVersion::V4 => { v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module) .await? @@ -705,7 +699,7 @@ pub struct Moistures { #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)] pub struct DetectionResult { - plant: [DetectionSensorResult; crate::hal::PLANT_COUNT], + plant: [DetectionSensorResult; PLANT_COUNT], } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)] pub struct DetectionSensorResult { diff --git a/Software/MainBoard/rust/src/hal/shared_flash.rs b/Software/MainBoard/rust/src/hal/shared_flash.rs index 69bd76e..b206ea5 100644 --- a/Software/MainBoard/rust/src/hal/shared_flash.rs +++ b/Software/MainBoard/rust/src/hal/shared_flash.rs @@ -36,7 +36,7 @@ impl ErrorType for MutexFlashStorage { } impl ReadNorFlash for MutexFlashStorage { - const READ_SIZE: usize = 0; + const READ_SIZE: usize = 1; fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { ReadStorage::read(self, offset, bytes) @@ -48,8 +48,8 @@ impl ReadNorFlash for MutexFlashStorage { } impl NorFlash for MutexFlashStorage { - const WRITE_SIZE: usize = 0; - const ERASE_SIZE: usize = 0; + const WRITE_SIZE: usize = 1; + const ERASE_SIZE: usize = 4096; fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { self.inner diff --git a/Software/MainBoard/rust/src/hal/v3_hal.rs b/Software/MainBoard/rust/src/hal/v3_hal.rs deleted file mode 100644 index 244bcbe..0000000 --- a/Software/MainBoard/rust/src/hal/v3_hal.rs +++ /dev/null @@ -1,458 +0,0 @@ -use crate::bail; -use crate::fat_error::FatError; -use crate::hal::esp::{hold_disable, hold_enable}; -use crate::hal::rtc::RTCModuleInteraction; -use crate::hal::v3_shift_register::ShiftRegister40; -use crate::hal::water::TankSensor; -use crate::hal::{BoardInteraction, FreePeripherals, Moistures, Sensor, PLANT_COUNT, TIME_ACCESS}; -use crate::log::{LogMessage, LOG_ACCESS}; -use crate::{ - config::PlantControllerConfig, - hal::{battery::BatteryInteraction, esp::Esp}, -}; -use alloc::boxed::Box; -use alloc::format; -use alloc::string::ToString; -use async_trait::async_trait; -use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use embassy_sync::mutex::Mutex; -use embassy_time::Timer; -use embedded_hal::digital::OutputPin as _; -use esp_hal::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig, Pull}; -use esp_hal::pcnt::channel::CtrlMode::Keep; -use esp_hal::pcnt::channel::EdgeMode::{Hold, Increment}; -use esp_hal::pcnt::unit::Unit; -use measurements::{Current, Voltage}; - -const PUMP8_BIT: usize = 0; -const PUMP1_BIT: usize = 1; -const PUMP2_BIT: usize = 2; -const PUMP3_BIT: usize = 3; -const PUMP4_BIT: usize = 4; -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 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; - -const FAULT_3: usize = 16; -const FAULT_8: usize = 17; -const FAULT_7: usize = 18; -const FAULT_6: usize = 19; -const FAULT_5: usize = 20; -const FAULT_4: usize = 21; -const FAULT_1: usize = 22; -const FAULT_2: usize = 23; - -const REPEAT_MOIST_MEASURE: usize = 1; - -pub struct V3<'a> { - config: PlantControllerConfig, - battery_monitor: Box, - rtc_module: Box, - esp: Esp<'a>, - shift_register: - Mutex, Output<'a>, Output<'a>>>, - _shift_register_enable_invert: Output<'a>, - tank_sensor: TankSensor<'a>, - solar_is_day: Input<'a>, - light: Output<'a>, - main_pump: Output<'a>, - general_fault: Output<'a>, - pub signal_counter: Unit<'static, 0>, -} - -pub(crate) fn create_v3( - peripherals: FreePeripherals<'static>, - esp: Esp<'static>, - config: PlantControllerConfig, - battery_monitor: Box, - rtc_module: Box, -) -> Result + Send + 'static>, FatError> { - log::info!("Start v3"); - let clock = Output::new(peripherals.gpio15, Level::Low, OutputConfig::default()); - let latch = Output::new(peripherals.gpio3, Level::Low, OutputConfig::default()); - let data = Output::new(peripherals.gpio23, Level::Low, OutputConfig::default()); - let shift_register = ShiftRegister40::new(clock, latch, data); - //disable all - for mut pin in shift_register.decompose() { - let _ = pin.set_low(); - } - - // Set always-on status bits - let _ = shift_register.decompose()[AWAKE].set_high(); - let _ = shift_register.decompose()[CHARGING].set_high(); - - // Multiplexer defaults: ms0..ms3 low, ms4 high (disabled) - let _ = shift_register.decompose()[MS_0].set_low(); - let _ = shift_register.decompose()[MS_1].set_low(); - let _ = shift_register.decompose()[MS_2].set_low(); - let _ = shift_register.decompose()[MS_3].set_low(); - let _ = shift_register.decompose()[MS_4].set_high(); - - let one_wire_pin = Flex::new(peripherals.gpio18); - let tank_power_pin = Output::new(peripherals.gpio11, Level::Low, OutputConfig::default()); - - let flow_sensor_pin = Input::new( - peripherals.gpio4, - InputConfig::default().with_pull(Pull::Up), - ); - - let tank_sensor = TankSensor::create( - one_wire_pin, - peripherals.adc1, - peripherals.gpio5, - tank_power_pin, - flow_sensor_pin, - peripherals.pcnt1, - )?; - - let solar_is_day = Input::new(peripherals.gpio7, InputConfig::default()); - let light = Output::new(peripherals.gpio10, Level::Low, OutputConfig::default()); - let mut main_pump = Output::new(peripherals.gpio2, Level::Low, OutputConfig::default()); - main_pump.set_low(); - let mut general_fault = Output::new(peripherals.gpio6, Level::Low, OutputConfig::default()); - general_fault.set_low(); - - let mut shift_register_enable_invert = - Output::new(peripherals.gpio21, Level::Low, OutputConfig::default()); - shift_register_enable_invert.set_low(); - - let signal_counter = peripherals.pcnt0; - - signal_counter.set_high_limit(Some(i16::MAX))?; - - let ch0 = &signal_counter.channel0; - let edge_pin = Input::new(peripherals.gpio22, InputConfig::default()); - ch0.set_edge_signal(edge_pin.peripheral_input()); - ch0.set_input_mode(Hold, Increment); - ch0.set_ctrl_mode(Keep, Keep); - signal_counter.listen(); - - Ok(Box::new(V3 { - config, - battery_monitor, - rtc_module, - esp, - shift_register: Mutex::new(shift_register), - _shift_register_enable_invert: shift_register_enable_invert, - tank_sensor, - solar_is_day, - light, - main_pump, - general_fault, - signal_counter, - })) -} - -impl V3<'_> { - async fn inner_measure_moisture_hz( - &mut self, - plant: usize, - sensor: Sensor, - ) -> Result { - let mut results = [0_f32; REPEAT_MOIST_MEASURE]; - for sample in results.iter_mut() { - self.signal_counter.pause(); - self.signal_counter.clear(); - //Disable all - { - let shift_register = self.shift_register.lock().await; - shift_register.decompose()[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, - _ => 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, - _ => bail!("Invalid plant id {}", plant), - }, - }; - - let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 }; - { - let shift_register = self.shift_register.lock().await; - 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]; - if is_bit_set(0) { - pin_0.set_high()?; - } else { - pin_0.set_low()?; - } - if is_bit_set(1) { - pin_1.set_high()?; - } else { - pin_1.set_low()?; - } - if is_bit_set(2) { - pin_2.set_high()?; - } else { - pin_2.set_low()?; - } - if is_bit_set(3) { - pin_3.set_high()?; - } else { - pin_3.set_low()?; - } - - shift_register.decompose()[MS_4].set_low()?; - shift_register.decompose()[SENSOR_ON].set_high()?; - } - let measurement = 100; //how long to measure and then extrapolate to hz - let factor = 1000f32 / measurement as f32; //scale raw cound by this number to get hz - - //give some time to stabilize - Timer::after_millis(10).await; - self.signal_counter.resume(); - Timer::after_millis(measurement).await; - self.signal_counter.pause(); - { - let shift_register = self.shift_register.lock().await; - shift_register.decompose()[MS_4].set_high()?; - shift_register.decompose()[SENSOR_ON].set_low()?; - } - Timer::after_millis(10).await; - let unscaled = self.signal_counter.value(); - let hz = unscaled as f32 * factor; - LOG_ACCESS - .lock() - .await - .log( - LogMessage::RawMeasure, - unscaled as u32, - hz as u32, - &plant.to_string(), - &format!("{sensor:?}"), - ) - .await; - *sample = 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) - } -} - -#[async_trait(?Send)] -impl<'a> BoardInteraction<'a> for V3<'a> { - fn get_tank_sensor(&mut self) -> Result<&mut TankSensor<'a>, FatError> { - Ok(&mut self.tank_sensor) - } - - fn get_esp(&mut self) -> &mut Esp<'a> { - &mut self.esp - } - - fn get_config(&mut self) -> &PlantControllerConfig { - &self.config - } - - fn get_battery_monitor(&mut self) -> &mut Box { - &mut self.battery_monitor - } - - fn get_rtc_module(&mut self) -> &mut Box { - &mut self.rtc_module - } - async fn set_charge_indicator(&mut self, charging: bool) -> Result<(), FatError> { - let shift_register = self.shift_register.lock().await; - if charging { - let _ = shift_register.decompose()[CHARGING].set_high(); - } else { - let _ = shift_register.decompose()[CHARGING].set_low(); - } - Ok(()) - } - - async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { - let _ = self.shift_register.lock().await.decompose()[AWAKE].set_low(); - let guard = TIME_ACCESS.get().await.lock().await; - self.esp.deep_sleep(duration_in_ms, guard) - } - - fn is_day(&self) -> bool { - self.solar_is_day.is_high() - } - - async fn light(&mut self, enable: bool) -> Result<(), FatError> { - hold_disable(10); - if enable { - self.light.set_high(); - } else { - self.light.set_low(); - } - hold_enable(10); - Ok(()) - } - async fn pump(&mut self, plant: usize, enable: bool) -> Result<(), FatError> { - if enable { - self.main_pump.set_high(); - } - - let index = match plant { - 0 => PUMP1_BIT, - 1 => PUMP2_BIT, - 2 => PUMP3_BIT, - 3 => PUMP4_BIT, - 4 => PUMP5_BIT, - 5 => PUMP6_BIT, - 6 => PUMP7_BIT, - 7 => PUMP8_BIT, - _ => bail!("Invalid pump {plant}"), - }; - let shift_register = self.shift_register.lock().await; - if enable { - let _ = shift_register.decompose()[index].set_high(); - } else { - let _ = shift_register.decompose()[index].set_low(); - } - - if !enable { - self.main_pump.set_low(); - } - Ok(()) - } - - async fn pump_current(&mut self, _plant: usize) -> Result { - bail!("Not implemented in v3") - } - - async fn fault(&mut self, plant: usize, enable: bool) -> Result<(), FatError> { - let index = match plant { - 0 => FAULT_1, - 1 => FAULT_2, - 2 => FAULT_3, - 3 => FAULT_4, - 4 => FAULT_5, - 5 => FAULT_6, - 6 => FAULT_7, - 7 => FAULT_8, - _ => panic!("Invalid plant id {}", plant), - }; - let shift_register = self.shift_register.lock().await; - if enable { - let _ = shift_register.decompose()[index].set_high(); - } else { - let _ = shift_register.decompose()[index].set_low(); - } - Ok(()) - } - - async fn measure_moisture_hz(&mut self) -> Result { - let mut result = Moistures::default(); - for plant in 0..PLANT_COUNT { - let a = self.inner_measure_moisture_hz(plant, Sensor::A).await; - let b = self.inner_measure_moisture_hz(plant, Sensor::B).await; - let aa = a.unwrap_or(u32::MAX as f32); - let bb = b.unwrap_or(u32::MAX as f32); - LOG_ACCESS - .lock() - .await - .log( - LogMessage::TestSensor, - aa as u32, - bb as u32, - &plant.to_string(), - "", - ) - .await; - result.sensor_a_hz[plant] = aa; - result.sensor_b_hz[plant] = bb; - } - Ok(result) - } - - async fn general_fault(&mut self, enable: bool) { - hold_disable(6); - if enable { - self.general_fault.set_high(); - } else { - self.general_fault.set_low(); - } - hold_enable(6); - } - - async fn test(&mut self) -> Result<(), FatError> { - self.general_fault(true).await; - Timer::after_millis(100).await; - self.general_fault(false).await; - Timer::after_millis(100).await; - self.light(true).await?; - Timer::after_millis(500).await; - - self.light(false).await?; - Timer::after_millis(500).await; - for i in 0..PLANT_COUNT { - self.fault(i, true).await?; - Timer::after_millis(500).await; - self.fault(i, false).await?; - Timer::after_millis(500).await; - } - for i in 0..PLANT_COUNT { - self.pump(i, true).await?; - Timer::after_millis(100).await; - self.pump(i, false).await?; - Timer::after_millis(100).await; - } - self.measure_moisture_hz().await?; - Timer::after_millis(10).await; - Ok(()) - } - - fn set_config(&mut self, config: PlantControllerConfig) { - self.config = config; - } - - async fn get_mptt_voltage(&mut self) -> Result { - bail!("Not implemented in v3") - } - async fn get_mptt_current(&mut self) -> Result { - bail!("Not implemented in v3") - } -} diff --git a/Software/MainBoard/rust/src/hal/v3_shift_register.rs b/Software/MainBoard/rust/src/hal/v3_shift_register.rs deleted file mode 100644 index f931e4e..0000000 --- a/Software/MainBoard/rust/src/hal/v3_shift_register.rs +++ /dev/null @@ -1,154 +0,0 @@ -//! Serial-in parallel-out shift register -#![allow(warnings)] -use core::cell::RefCell; -use core::convert::Infallible; -use core::iter::Iterator; -use core::mem::{self, MaybeUninit}; -use core::result::{Result, Result::Ok}; -use embedded_hal::digital::OutputPin; - -trait ShiftRegisterInternal: Send { - fn update(&self, index: usize, command: bool) -> Result<(), ()>; -} - -/// Output pin of the shift register -pub struct ShiftRegisterPin<'a> { - shift_register: &'a dyn ShiftRegisterInternal, - index: usize, -} - -impl<'a> ShiftRegisterPin<'a> { - fn new(shift_register: &'a dyn ShiftRegisterInternal, index: usize) -> Self { - ShiftRegisterPin { - shift_register, - index, - } - } -} - -impl embedded_hal::digital::ErrorType for ShiftRegisterPin<'_> { - type Error = Infallible; -} - -impl OutputPin for ShiftRegisterPin<'_> { - fn set_low(&mut self) -> Result<(), Infallible> { - self.shift_register.update(self.index, false).unwrap(); - Ok(()) - } - - fn set_high(&mut self) -> Result<(), Infallible> { - self.shift_register.update(self.index, true).unwrap(); - Ok(()) - } -} - -macro_rules! ShiftRegisterBuilder { - ($name: ident, $size: expr) => { - /// Serial-in parallel-out shift register - pub struct $name - where - Pin1: OutputPin + Send, - Pin2: OutputPin + Send, - Pin3: OutputPin + Send, - { - clock: RefCell, - latch: RefCell, - data: RefCell, - output_state: RefCell<[bool; $size]>, - } - - impl ShiftRegisterInternal for $name - where - Pin1: OutputPin + Send, - Pin2: OutputPin + Send, - Pin3: OutputPin + Send, - { - /// Sets the value of the shift register output at `index` to value `command` - fn update(&self, index: usize, command: bool) -> Result<(), ()> { - self.output_state.borrow_mut()[index] = command; - let output_state = self.output_state.borrow(); - self.latch.borrow_mut().set_low().map_err(|_e| ())?; - - for i in 1..=output_state.len() { - if output_state[output_state.len() - i] { - self.data.borrow_mut().set_high().map_err(|_e| ())?; - } else { - self.data.borrow_mut().set_low().map_err(|_e| ())?; - } - self.clock.borrow_mut().set_high().map_err(|_e| ())?; - self.clock.borrow_mut().set_low().map_err(|_e| ())?; - } - - self.latch.borrow_mut().set_high().map_err(|_e| ())?; - Ok(()) - } - } - - impl $name - where - Pin1: OutputPin + Send, - Pin2: OutputPin + Send, - Pin3: OutputPin + Send, - { - /// Creates a new SIPO shift register from clock, latch, and data output pins - pub fn new(clock: Pin1, latch: Pin2, data: Pin3) -> Self { - $name { - clock: RefCell::new(clock), - latch: RefCell::new(latch), - data: RefCell::new(data), - output_state: RefCell::new([false; $size]), - } - } - - /// Get embedded-hal output pins to control the shift register outputs - pub fn decompose(&self) -> [ShiftRegisterPin<'_>; $size] { - // Create an uninitialized array of `MaybeUninit`. The `assume_init` is - // safe because the type we are claiming to have initialized here is a - // bunch of `MaybeUninit`s, which do not require initialization. - let mut pins: [MaybeUninit; $size] = - unsafe { MaybeUninit::uninit().assume_init() }; - - // Dropping a `MaybeUninit` does nothing, so if there is a panic during this loop, - // we have a memory leak, but there is no memory safety issue. - for (index, elem) in pins.iter_mut().enumerate() { - elem.write(ShiftRegisterPin::new(self, index)); - } - - // Everything is initialized. Transmute the array to the - // initialized type. - unsafe { mem::transmute::<_, [ShiftRegisterPin; $size]>(pins) } - } - - /// Consume the shift register and return the original clock, latch, and data output pins - pub fn release(self) -> (Pin1, Pin2, Pin3) { - let Self { - clock, - latch, - data, - output_state: _, - } = self; - (clock.into_inner(), latch.into_inner(), data.into_inner()) - } - } - }; -} - -ShiftRegisterBuilder!(ShiftRegister8, 8); -ShiftRegisterBuilder!(ShiftRegister16, 16); -ShiftRegisterBuilder!(ShiftRegister24, 24); -ShiftRegisterBuilder!(ShiftRegister32, 32); -ShiftRegisterBuilder!(ShiftRegister40, 40); -ShiftRegisterBuilder!(ShiftRegister48, 48); -ShiftRegisterBuilder!(ShiftRegister56, 56); -ShiftRegisterBuilder!(ShiftRegister64, 64); -ShiftRegisterBuilder!(ShiftRegister72, 72); -ShiftRegisterBuilder!(ShiftRegister80, 80); -ShiftRegisterBuilder!(ShiftRegister88, 88); -ShiftRegisterBuilder!(ShiftRegister96, 96); -ShiftRegisterBuilder!(ShiftRegister104, 104); -ShiftRegisterBuilder!(ShiftRegister112, 112); -ShiftRegisterBuilder!(ShiftRegister120, 120); -ShiftRegisterBuilder!(ShiftRegister128, 128); - -/// 8 output serial-in parallel-out shift register -pub type ShiftRegister = ShiftRegister8; diff --git a/Software/MainBoard/rust/src/hal/v4_hal.rs b/Software/MainBoard/rust/src/hal/v4_hal.rs index 30b507f..08807a4 100644 --- a/Software/MainBoard/rust/src/hal/v4_hal.rs +++ b/Software/MainBoard/rust/src/hal/v4_hal.rs @@ -1,32 +1,33 @@ use crate::bail; use crate::config::PlantControllerConfig; -use crate::fat_error::{FatError, FatResult}; +use crate::fat_error::{ContextExt, FatError, FatResult}; use crate::hal::battery::BatteryInteraction; use crate::hal::esp::{hold_disable, hold_enable, Esp}; use crate::hal::rtc::RTCModuleInteraction; -use crate::hal::v4_sensor::{SensorImpl, SensorInteraction}; use crate::hal::water::TankSensor; use crate::hal::{ - BoardInteraction, DetectionResult, FreePeripherals, Moistures, I2C_DRIVER, PLANT_COUNT, + BoardInteraction, DetectionResult, FreePeripherals, Moistures, Sensor, I2C_DRIVER, PLANT_COUNT, TIME_ACCESS, }; use crate::log::{LogMessage, LOG_ACCESS}; use alloc::boxed::Box; use alloc::string::ToString; use async_trait::async_trait; +use canapi::id::{classify, plant_id, MessageKind, IDENTIFY_CMD_OFFSET}; +use canapi::SensorSlot; use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use embassy_time::Timer; +use embassy_time::{Duration, Timer, WithTimeout}; +use embedded_can::{Frame, Id}; use esp_hal::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig, Pull}; use esp_hal::i2c::master::I2c; -use esp_hal::pcnt::channel::CtrlMode::Keep; -use esp_hal::pcnt::channel::EdgeMode::{Hold, Increment}; -use esp_hal::twai::TwaiMode; -use esp_hal::{twai, Blocking}; +use esp_hal::twai::{EspTwaiError, EspTwaiFrame, StandardId, Twai, TwaiConfiguration, TwaiMode}; +use esp_hal::{twai, Async, Blocking}; use ina219::address::{Address, Pin}; use ina219::calibration::UnCalibrated; use ina219::configuration::{Configuration, OperatingMode, Resolution}; use ina219::SyncIna219; +use log::{error, info, warn}; use measurements::Resistance; use measurements::{Current, Voltage}; use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; @@ -88,7 +89,7 @@ impl Charger<'_> { operating_mode: OperatingMode::PowerDown, }) .map_err(|e| { - log::info!( + info!( "Error setting ina mppt configuration during deep sleep preparation{e:?}" ); }); @@ -127,7 +128,8 @@ pub struct V4<'a> { pump_ina: Option< SyncIna219>, UnCalibrated>, >, - sensor: SensorImpl, + twai_config: Option>, + can_power: Output<'static>, extra1: Output<'a>, extra2: Output<'a>, } @@ -139,7 +141,7 @@ pub(crate) async fn create_v4( battery_monitor: Box, rtc_module: Box, ) -> Result + Send + 'static>, FatError> { - log::info!("Start v4"); + info!("Start v4"); let mut awake = Output::new(peripherals.gpio21, Level::High, OutputConfig::default()); awake.set_high(); @@ -165,53 +167,14 @@ pub(crate) async fn create_v4( peripherals.pcnt1, )?; - let sensor_expander_device = I2cDevice::new(I2C_DRIVER.get().await); - let mut sensor_expander = Pca9535Immediate::new(sensor_expander_device, 34); - let sensor = match sensor_expander.pin_into_output(GPIOBank::Bank0, 0) { - Ok(_) => { - log::info!("SensorExpander answered"); - - let signal_counter = peripherals.pcnt0; - - signal_counter.set_high_limit(Some(i16::MAX))?; - - let ch0 = &signal_counter.channel0; - let edge_pin = Input::new(peripherals.gpio22, InputConfig::default()); - ch0.set_edge_signal(edge_pin.peripheral_input()); - ch0.set_input_mode(Hold, Increment); - ch0.set_ctrl_mode(Keep, Keep); - signal_counter.listen(); - - for pin in 0..8 { - let _ = sensor_expander.pin_into_output(GPIOBank::Bank0, pin); - let _ = sensor_expander.pin_into_output(GPIOBank::Bank1, pin); - let _ = sensor_expander.pin_set_low(GPIOBank::Bank0, pin); - let _ = sensor_expander.pin_set_low(GPIOBank::Bank1, pin); - } - - SensorImpl::PulseCounter { - signal_counter, - sensor_expander, - } - } - Err(_) => { - log::info!("Can bus mode "); - let twai_config = Some(twai::TwaiConfiguration::new( - peripherals.twai, - peripherals.gpio2, - peripherals.gpio0, - TWAI_BAUDRATE, - TwaiMode::Normal, - )); - let can_power = Output::new(peripherals.gpio22, Level::Low, OutputConfig::default()); - - //can bus version - SensorImpl::CanBus { - twai_config, - can_power, - } - } - }; + let twai_config = Some(TwaiConfiguration::new( + peripherals.twai, + peripherals.gpio2, + peripherals.gpio0, + TWAI_BAUDRATE, + TwaiMode::Normal, + )); + let can_power = Output::new(peripherals.gpio22, Level::Low, OutputConfig::default()); let solar_is_day = Input::new(peripherals.gpio7, InputConfig::default()); let light = Output::new(peripherals.gpio10, Level::Low, Default::default()); @@ -241,7 +204,7 @@ pub(crate) async fn create_v4( Some(ina) } Err(err) => { - log::info!("Error creating mppt ina: {err:?}"); + info!("Error creating mppt ina: {err:?}"); None } }; @@ -250,7 +213,7 @@ pub(crate) async fn create_v4( let pump_ina = match SyncIna219::new(pump_current_dev, Address::from_pins(Pin::Gnd, Pin::Sda)) { Ok(ina) => Some(ina), Err(err) => { - log::info!("Error creating pump ina: {err:?}"); + info!("Error creating pump ina: {err:?}"); None } }; @@ -262,7 +225,7 @@ pub(crate) async fn create_v4( bus_voltage_range: Default::default(), shunt_voltage_range: Default::default(), bus_resolution: Default::default(), - shunt_resolution: ina219::configuration::Resolution::Avg128, + shunt_resolution: Resolution::Avg128, operating_mode: Default::default(), })?; @@ -275,6 +238,7 @@ pub(crate) async fn create_v4( None => Charger::ErrorInit {}, }; + info!("Assembling final v4 board interaction object"); let v = V4 { rtc_module, esp, @@ -286,10 +250,11 @@ pub(crate) async fn create_v4( config, battery_monitor, pump_ina, + twai_config, charger, extra1, extra2, - sensor, + can_power, }; Ok(Box::new(v)) } @@ -383,9 +348,35 @@ impl<'a> BoardInteraction<'a> for V4<'a> { } Ok(()) } + async fn measure_moisture_hz(&mut self) -> FatResult { + self.can_power.set_high(); + let config = self.twai_config.take().expect("twai config not set"); + let mut twai = config.into_async().start(); - async fn measure_moisture_hz(&mut self) -> Result { - self.sensor.measure_moisture_hz().await + loop { + let rec = twai.receive(); + match rec { + Ok(_) => {} + Err(err) => { + info!("Error receiving CAN message: {err:?}"); + break; + } + } + } + + Timer::after_millis(10).await; + + let mut moistures = Moistures::default(); + let _ = wait_for_can_measurements(&mut twai, &mut moistures) + .with_timeout(Duration::from_millis(2000)) + .await; + + self.can_power.set_low(); + + let config = twai.stop().into_blocking(); + self.twai_config.replace(config); + + Ok(moistures) } async fn general_fault(&mut self, enable: bool) { @@ -450,6 +441,123 @@ impl<'a> BoardInteraction<'a> for V4<'a> { } async fn detect_sensors(&mut self) -> FatResult { - self.sensor.autodetect().await + // Power on CAN transceiver and start controller + self.can_power.set_high(); + let config = self.twai_config.take().expect("twai config not set"); + info!("convert can"); + let mut as_async = config.into_async().start(); + // Give CAN some time to stabilize + Timer::after_millis(10).await; + + info!("Sending info messages now"); + // Send a few test messages per potential sensor node + for plant in 0..PLANT_COUNT { + for sensor in [Sensor::A, Sensor::B] { + let target = + StandardId::new(plant_id(IDENTIFY_CMD_OFFSET, sensor.into(), plant as u16)) + .context(">> Could not create address for sensor! (plant: {}) <<")?; + let can_buffer = [0_u8; 0]; + if let Some(frame) = EspTwaiFrame::new(target, &can_buffer) { + // Try a few times; we intentionally ignore rx here and rely on stub logic + let resu = as_async + .transmit_async(&frame) + .with_timeout(Duration::from_millis(1000)) + .await; + match resu { + Ok(_) => { + info!("Sent test message to plant {plant} sensor {sensor:?}"); + } + Err(err) => { + info!( + "Error sending test message to plant {plant} sensor {sensor:?}: {err:?}" + ); + } + } + } else { + info!("Error building CAN frame"); + } + } + } + + let mut moistures = Moistures::default(); + let _ = wait_for_can_measurements(&mut as_async, &mut moistures) + .with_timeout(Duration::from_millis(1000)) + .await; + + let config = as_async.stop().into_blocking(); + self.can_power.set_low(); + self.twai_config.replace(config); + + let result = moistures.into(); + + info!("Autodetection result: {result:?}"); + Ok(result) + } +} + +async fn wait_for_can_measurements( + as_async: &mut Twai<'_, Async>, + moistures: &mut Moistures, +) -> FatResult<()> { + loop { + match as_async.receive_async().await { + Ok(can_frame) => match can_frame.id() { + Id::Standard(id) => { + info!("Received CAN message: {id:?}"); + let rawid = id.as_raw(); + match classify(rawid) { + None => {} + Some(msg) => { + info!( + "received message of kind {:?} (plant: {}, sensor: {:?})", + msg.0, msg.1, msg.2 + ); + if msg.0 == MessageKind::MoistureData { + let plant = msg.1 as usize; + let sensor = msg.2; + let data = can_frame.data(); + if data.len() == 2 { + let frequency = u16::from_be_bytes([data[0], data[1]]); + match sensor { + SensorSlot::A => { + moistures.sensor_a_hz[plant] = frequency as f32; + } + SensorSlot::B => { + moistures.sensor_b_hz[plant] = frequency as f32; + } + } + } + } + } + } + } + Id::Extended(ext) => { + warn!("Received extended ID: {ext:?}"); + } + }, + Err(err) => { + match err { + EspTwaiError::BusOff => { + bail!("Bus offline") + } + EspTwaiError::NonCompliantDlc(_) => {} + EspTwaiError::EmbeddedHAL(_) => {} + } + error!("Error receiving CAN message: {err:?}"); + } + } + } +} + +impl From for DetectionResult { + fn from(value: Moistures) -> Self { + let mut result = DetectionResult::default(); + for (plant, sensor) in value.sensor_a_hz.iter().enumerate() { + result.plant[plant].sensor_a = *sensor > 1.0_f32; + } + for (plant, sensor) in value.sensor_b_hz.iter().enumerate() { + result.plant[plant].sensor_b = *sensor > 1.0_f32; + } + result } } diff --git a/Software/MainBoard/rust/src/hal/v4_sensor.rs b/Software/MainBoard/rust/src/hal/v4_sensor.rs deleted file mode 100644 index 2bc1ccd..0000000 --- a/Software/MainBoard/rust/src/hal/v4_sensor.rs +++ /dev/null @@ -1,334 +0,0 @@ -use crate::bail; -use crate::fat_error::{ContextExt, FatResult}; -use crate::hal::Box; -use crate::hal::{DetectionResult, Moistures, Sensor}; -use crate::log::{LogMessage, LOG_ACCESS}; -use alloc::format; -use alloc::string::ToString; -use async_trait::async_trait; -use canapi::id::{classify, plant_id, MessageKind, IDENTIFY_CMD_OFFSET}; -use canapi::SensorSlot; -use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; -use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use embassy_time::{Duration, Instant, Timer, WithTimeout}; -use embedded_can::{Frame, Id}; -use esp_hal::gpio::Output; -use esp_hal::i2c::master::I2c; -use esp_hal::pcnt::unit::Unit; -use esp_hal::twai::{EspTwaiError, EspTwaiFrame, StandardId, Twai, TwaiConfiguration}; -use esp_hal::{Async, Blocking}; -use log::{error, info, warn}; -use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; - -const REPEAT_MOIST_MEASURE: usize = 10; - -#[async_trait(?Send)] -pub trait SensorInteraction { - async fn measure_moisture_hz(&mut self) -> FatResult; -} - -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; - -pub enum SensorImpl { - PulseCounter { - signal_counter: Unit<'static, 0>, - sensor_expander: - Pca9535Immediate>>, - }, - CanBus { - twai_config: Option>, - can_power: Output<'static>, - }, -} - -#[async_trait(?Send)] -impl SensorInteraction for SensorImpl { - async fn measure_moisture_hz(&mut self) -> FatResult { - match self { - SensorImpl::PulseCounter { - signal_counter, - sensor_expander, - .. - } => { - let mut result = Moistures::default(); - for plant in 0..crate::hal::PLANT_COUNT { - result.sensor_a_hz[plant] = - Self::inner_pulse(plant, Sensor::A, signal_counter, sensor_expander) - .await?; - info!( - "Sensor {} {:?}: {}", - plant, - Sensor::A, - result.sensor_a_hz[plant] - ); - result.sensor_b_hz[plant] = - Self::inner_pulse(plant, Sensor::B, signal_counter, sensor_expander) - .await?; - info!( - "Sensor {} {:?}: {}", - plant, - Sensor::B, - result.sensor_b_hz[plant] - ); - } - Ok(result) - } - - SensorImpl::CanBus { - twai_config, - can_power, - } => { - can_power.set_high(); - let config = twai_config.take().expect("twai config not set"); - let mut twai = config.into_async().start(); - - loop { - let rec = twai.receive(); - match rec { - Ok(_) => {} - Err(err) => { - info!("Error receiving CAN message: {err:?}"); - break; - } - } - } - - Timer::after_millis(10).await; - - let mut moistures = Moistures::default(); - let _ = Self::wait_for_can_measurements(&mut twai, &mut moistures) - .with_timeout(Duration::from_millis(5000)) - .await; - - can_power.set_low(); - - let config = twai.stop().into_blocking(); - twai_config.replace(config); - - Ok(moistures) - } - } - } -} - -impl SensorImpl { - pub async fn autodetect(&mut self) -> FatResult { - match self { - SensorImpl::PulseCounter { .. } => { - bail!("Only CAN bus implementation supports autodetection") - } - SensorImpl::CanBus { - twai_config, - can_power, - } => { - // Power on CAN transceiver and start controller - can_power.set_high(); - let config = twai_config.take().expect("twai config not set"); - info!("convert can"); - let mut as_async = config.into_async().start(); - // Give CAN some time to stabilize - Timer::after_millis(10).await; - - info!("Sending info messages now"); - // Send a few test messages per potential sensor node - for plant in 0..crate::hal::PLANT_COUNT { - for sensor in [Sensor::A, Sensor::B] { - let target = StandardId::new(plant_id( - IDENTIFY_CMD_OFFSET, - sensor.into(), - plant as u16, - )) - .context(">> Could not create address for sensor! (plant: {}) <<")?; - let can_buffer = [0_u8; 0]; - if let Some(frame) = EspTwaiFrame::new(target, &can_buffer) { - // Try a few times; we intentionally ignore rx here and rely on stub logic - let resu = as_async.transmit_async(&frame).with_timeout(Duration::from_millis(1000)).await; - match resu { - Ok(_) => { - info!("Sent test message to plant {plant} sensor {sensor:?}"); - } - Err(err) => { - info!( - "Error sending test message to plant {plant} sensor {sensor:?}: {err:?}" - ); - } - } - } else { - info!("Error building CAN frame"); - } - } - } - - let mut moistures = Moistures::default(); - let _ = Self::wait_for_can_measurements(&mut as_async, &mut moistures) - .with_timeout(Duration::from_millis(1000)) - .await; - - let config = as_async.stop().into_blocking(); - can_power.set_low(); - twai_config.replace(config); - - let result = moistures.into(); - - info!("Autodetection result: {result:?}"); - Ok(result) - } - } - } - - async fn wait_for_can_measurements( - as_async: &mut Twai<'_, Async>, - moistures: &mut Moistures, - ) -> FatResult<()> { - loop { - match as_async.receive_async().await { - Ok(can_frame) => match can_frame.id() { - Id::Standard(id) => { - info!("Received CAN message: {id:?}"); - let rawid = id.as_raw(); - match classify(rawid) { - None => {} - Some(msg) => { - info!( - "received message of kind {:?} (plant: {}, sensor: {:?})", - msg.0, msg.1, msg.2 - ); - if msg.0 == MessageKind::MoistureData { - let plant = msg.1 as usize; - let sensor = msg.2; - let data = can_frame.data(); - if data.len() == 2 { - let frequency = u16::from_be_bytes([data[0], data[1]]); - match sensor { - SensorSlot::A => { - moistures.sensor_a_hz[plant] = frequency as f32; - } - SensorSlot::B => { - moistures.sensor_b_hz[plant] = frequency as f32; - } - } - } - - } - } - } - } - Id::Extended(ext) => { - warn!("Received extended ID: {ext:?}"); - } - }, - Err(err) => { - match err { - EspTwaiError::BusOff => { - bail!("Bus offline") - } - EspTwaiError::NonCompliantDlc(_) => {} - EspTwaiError::EmbeddedHAL(_) => {} - } - error!("Error receiving CAN message: {err:?}"); - } - } - } - } - - pub async fn inner_pulse( - plant: usize, - sensor: Sensor, - signal_counter: &mut Unit<'_, 0>, - sensor_expander: &mut Pca9535Immediate< - I2cDevice<'static, CriticalSectionRawMutex, I2c<'static, Blocking>>, - >, - ) -> FatResult { - let mut results = [0_f32; REPEAT_MOIST_MEASURE]; - for sample in results.iter_mut() { - signal_counter.pause(); - signal_counter.clear(); - - //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 measurement = 100; // TODO what is this scaling factor? what is its purpose? - let factor = 1000f32 / measurement as f32; - - //give some time to stabilize - Timer::after_millis(10).await; - signal_counter.resume(); - Timer::after_millis(measurement).await; - signal_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)?; - Timer::after_millis(10).await; - let unscaled = 1337; //signal_counter.get_counter_value()? as i32; - let hz = unscaled as f32 * factor; - LOG_ACCESS - .lock() - .await - .log( - LogMessage::RawMeasure, - unscaled as u32, - hz as u32, - &plant.to_string(), - &format!("{sensor:?}"), - ) - .await; - *sample = 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) - } -} - -impl From for DetectionResult { - fn from(value: Moistures) -> Self { - let mut result = DetectionResult::default(); - for (plant, sensor) in value.sensor_a_hz.iter().enumerate() { - result.plant[plant].sensor_a = *sensor > 1.0_f32; - } - for (plant, sensor) in value.sensor_b_hz.iter().enumerate() { - result.plant[plant].sensor_b = *sensor > 1.0_f32; - } - result - } -} diff --git a/Software/MainBoard/rust/src/log/mod.rs b/Software/MainBoard/rust/src/log/mod.rs index 881fb4b..a340ace 100644 --- a/Software/MainBoard/rust/src/log/mod.rs +++ b/Software/MainBoard/rust/src/log/mod.rs @@ -27,7 +27,7 @@ static mut LOG_ARRAY: LogArray = LogArray { head: 0, }; -// this is the only reference that is created for LOG_ARRAY and the only way to access it +// this is the only reference created for LOG_ARRAY and the only way to access it #[allow(static_mut_refs)] pub static LOG_ACCESS: Mutex = unsafe { Mutex::new(&mut LOG_ARRAY) }; @@ -298,7 +298,7 @@ impl From<&LogMessage> for MessageTranslation { } impl LogMessage { - pub fn to_log_localisation_config() -> Vec { + pub fn log_localisation_config() -> Vec { Vec::from_iter((0..LogMessage::len()).map(|i| { let msg_type = LogMessage::from_ordinal(i).unwrap(); (&msg_type).into() diff --git a/Software/MainBoard/rust/src/main.rs b/Software/MainBoard/rust/src/main.rs index f558886..19c311a 100644 --- a/Software/MainBoard/rust/src/main.rs +++ b/Software/MainBoard/rust/src/main.rs @@ -38,7 +38,7 @@ use embassy_net::Stack; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::mutex::{Mutex, MutexGuard}; use embassy_sync::once_lock::OnceLock; -use embassy_time::Timer; +use embassy_time::{Duration, Timer, WithTimeout}; use esp_hal::rom::ets_delay_us; use esp_hal::system::software_reset; use esp_println::{logger, println}; @@ -1063,7 +1063,13 @@ async fn main(spawner: Spawner) -> ! { // intialize embassy logger::init_logger_from_env(); //force init here! - match BOARD_ACCESS.init(PlantHal::create().await.unwrap()) { + match BOARD_ACCESS.init( + PlantHal::create() + .with_timeout(Duration::from_secs(10)) + .await + .unwrap() + .unwrap(), + ) { Ok(_) => {} Err(_) => { panic!("Could not set hal to static") diff --git a/Software/MainBoard/rust/src/webserver/get_json.rs b/Software/MainBoard/rust/src/webserver/get_json.rs index 553e9e4..186449f 100644 --- a/Software/MainBoard/rust/src/webserver/get_json.rs +++ b/Software/MainBoard/rust/src/webserver/get_json.rs @@ -175,6 +175,6 @@ pub(crate) async fn get_log_localization_config( _request: &mut Connection<'_, T, N>, ) -> FatResult> { Ok(Some(serde_json::to_string( - &LogMessage::to_log_localisation_config(), + &LogMessage::log_localisation_config(), )?)) } diff --git a/Software/MainBoard/rust/src_webpack/package-lock.json b/Software/MainBoard/rust/src_webpack/package-lock.json index 26340b5..e7934f4 100644 --- a/Software/MainBoard/rust/src_webpack/package-lock.json +++ b/Software/MainBoard/rust/src_webpack/package-lock.json @@ -94,9 +94,9 @@ } }, "node_modules/@jsonjoy.com/buffers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.0.0.tgz", - "integrity": "sha512-NDigYR3PHqCnQLXYyoLbnEdzMMvzeiCWo1KOut7Q0CoIqg9tUAPKJ1iq/2nFhc5kZtexzutNY0LFjdwWL3Dw3Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -128,19 +128,20 @@ } }, "node_modules/@jsonjoy.com/json-pack": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.14.0.tgz", - "integrity": "sha512-LpWbYgVnKzphN5S6uss4M25jJ/9+m6q6UJoeN6zTkK4xAGhKsiBRPVeF7OYMWonn5repMQbE5vieRXcMUrKDKw==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/base64": "^1.1.2", - "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/buffers": "^1.2.0", "@jsonjoy.com/codegen": "^1.0.0", - "@jsonjoy.com/json-pointer": "^1.0.1", + "@jsonjoy.com/json-pointer": "^1.0.2", "@jsonjoy.com/util": "^1.9.0", "hyperdyperid": "^1.2.0", - "thingies": "^2.5.0" + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" }, "engines": { "node": ">=10.0" @@ -318,22 +319,22 @@ "license": "MIT" }, "node_modules/@types/express": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", - "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", - "@types/serve-static": "*" + "@types/serve-static": "^1" } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "version": "4.19.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", + "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", "dev": true, "license": "MIT", "dependencies": { @@ -358,9 +359,9 @@ "license": "MIT" }, "node_modules/@types/http-proxy": { - "version": "1.17.16", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", - "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", "dev": true, "license": "MIT", "dependencies": { @@ -381,12 +382,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.5.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", - "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", + "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", "license": "MIT", "dependencies": { - "undici-types": "~7.12.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/node-forge": { @@ -421,13 +422,12 @@ "license": "MIT" }, "node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/mime": "^1", "@types/node": "*" } }, @@ -442,15 +442,26 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", - "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", "@types/node": "*", - "@types/send": "*" + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" } }, "node_modules/@types/sockjs": { @@ -832,9 +843,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.6.tgz", - "integrity": "sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==", + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -871,24 +882,24 @@ } }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -939,9 +950,9 @@ } }, "node_modules/browserslist": { - "version": "4.26.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", - "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "funding": [ { "type": "opencollective", @@ -958,11 +969,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.3", - "caniuse-lite": "^1.0.30001741", - "electron-to-chromium": "^1.5.218", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -1046,9 +1057,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001743", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz", - "integrity": "sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==", + "version": "1.0.30001761", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz", + "integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==", "funding": [ { "type": "opencollective", @@ -1281,9 +1292,9 @@ } }, "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, "license": "MIT", "engines": { @@ -1291,9 +1302,9 @@ } }, "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "dev": true, "license": "MIT" }, @@ -1384,9 +1395,9 @@ } }, "node_modules/default-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", - "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", + "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==", "dev": true, "license": "MIT", "dependencies": { @@ -1401,9 +1412,9 @@ } }, "node_modules/default-browser-id": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", - "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", "dev": true, "license": "MIT", "engines": { @@ -1570,9 +1581,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.222", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.222.tgz", - "integrity": "sha512-gA7psSwSwQRE60CEoLz6JBCQPIxNeuzB2nL8vE03GK/OHxlvykbLyeiumQy1iH5C2f3YbRAZpGCMT12a/9ih9w==", + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", "license": "ISC" }, "node_modules/emojis-list": { @@ -1596,9 +1607,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -1619,9 +1630,9 @@ } }, "node_modules/envinfo": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", - "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", "dev": true, "license": "MIT", "bin": { @@ -1652,9 +1663,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "license": "MIT" }, "node_modules/es-object-atoms": { @@ -1756,40 +1767,40 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -1809,9 +1820,9 @@ "license": "MIT" }, "node_modules/fast-equals": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", - "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1879,9 +1890,9 @@ } }, "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -1913,18 +1924,18 @@ } }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "statuses": "2.0.1", + "statuses": "~2.0.2", "unpipe": "~1.0.0" }, "engines": { @@ -2073,9 +2084,9 @@ } }, "node_modules/glob-to-regex.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.0.1.tgz", - "integrity": "sha512-CG/iEvgQqfzoVsMUbxSJcwbG2JwyZ3naEqPkeltwl0BSS8Bp83k3xlGms+0QdWFUAwV+uvo80wNswKF6FWEkKg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", + "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2269,9 +2280,9 @@ } }, "node_modules/html-webpack-plugin": { - "version": "5.6.4", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.4.tgz", - "integrity": "sha512-V/PZeWsqhfpE27nKeX9EO2sbR+D17A+tLf6qU+ht66jdUsN0QLKJN27Z+1+gHrVMKgndBahes0PU6rRihDgHTw==", + "version": "5.6.5", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.5.tgz", + "integrity": "sha512-4xynFbKNNk+WlzXeQQ+6YYsH2g7mpfPszQZUi3ovKlj+pDmngQ7vRXjrrmGROabmKwyQkcgcX5hqfOwHbFmK5g==", "dev": true, "license": "MIT", "dependencies": { @@ -2329,20 +2340,24 @@ "license": "MIT" }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/http-parser-js": { @@ -2461,9 +2476,9 @@ } }, "node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", "dev": true, "license": "MIT", "engines": { @@ -2708,9 +2723,9 @@ } }, "node_modules/launch-editor": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz", - "integrity": "sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz", + "integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==", "dev": true, "license": "MIT", "dependencies": { @@ -2719,12 +2734,16 @@ } }, "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "license": "MIT", "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/loader-utils": { @@ -2793,9 +2812,9 @@ } }, "node_modules/memfs": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.42.0.tgz", - "integrity": "sha512-RG+4HMGyIVp6UWDWbFmZ38yKrSzblPnfJu0PyPt0hw52KW4PPlPp+HdV4qZBG0hLDuYVnf8wfQT4NymKXnlQjA==", + "version": "4.51.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.51.1.tgz", + "integrity": "sha512-Eyt3XrufitN2ZL9c/uIRMyDwXanLI88h/L3MoWqNY747ha3dMR9dWqp8cRT5ntjZ0U1TNuq4U91ZXK0sMBjYOQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2806,9 +2825,6 @@ "tree-dump": "^1.0.3", "tslib": "^2.0.0" }, - "engines": { - "node": ">= 4.0.0" - }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" @@ -2952,9 +2968,9 @@ } }, "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", + "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", "dev": true, "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { @@ -2962,9 +2978,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", - "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "license": "MIT" }, "node_modules/normalize-path": { @@ -3270,13 +3286,13 @@ } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -3325,16 +3341,16 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" @@ -3509,13 +3525,13 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -3635,9 +3651,9 @@ "license": "MIT" }, "node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", @@ -3675,9 +3691,9 @@ } }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -3688,40 +3704,30 @@ } }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3808,16 +3814,16 @@ } }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "dev": true, "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.19.0" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.0" @@ -4110,9 +4116,9 @@ "license": "MIT" }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, "license": "MIT", "engines": { @@ -4169,9 +4175,9 @@ } }, "node_modules/tapable": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", - "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "license": "MIT", "engines": { "node": ">=6" @@ -4182,9 +4188,9 @@ } }, "node_modules/terser": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", - "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", + "version": "5.44.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -4200,9 +4206,9 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", @@ -4355,9 +4361,9 @@ } }, "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4369,9 +4375,9 @@ } }, "node_modules/undici-types": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", - "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "license": "MIT" }, "node_modules/unicorn-magic": { @@ -4397,9 +4403,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "funding": [ { "type": "opencollective", @@ -4481,9 +4487,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", - "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.0.tgz", + "integrity": "sha512-e6vZvY6xboSwLz2GD36c16+O/2Z6fKvIf4pOXptw2rY9MVwE/TXc6RGqxD3I3x0a28lwBY7DE+76uTPSsBrrCA==", "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", @@ -4504,9 +4510,9 @@ } }, "node_modules/webpack": { - "version": "5.101.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz", - "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", + "version": "5.104.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", + "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", @@ -4517,22 +4523,22 @@ "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.15.0", "acorn-import-phases": "^1.0.3", - "browserslist": "^4.24.0", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.3", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.17.4", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", + "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.4.4", "webpack-sources": "^3.3.3" }, "bin": { @@ -4608,14 +4614,14 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.3.tgz", - "integrity": "sha512-5kA/PzpZzDz5mNOkcNLmU1UdjGeSSxd7rt1akWpI70jMNHLASiBPRaQZn0hgyhvhawfIwSnnLfDABIxL3ueyFg==", + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz", + "integrity": "sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==", "dev": true, "license": "MIT", "dependencies": { "colorette": "^2.0.10", - "memfs": "^4.6.0", + "memfs": "^4.43.1", "mime-types": "^3.0.1", "on-finished": "^2.4.1", "range-parser": "^1.2.1", @@ -4648,16 +4654,20 @@ } }, "node_modules/webpack-dev-middleware/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "dev": true, "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/webpack-dev-server": { diff --git a/Software/MainBoard/rust/src_webpack/src/api.ts b/Software/MainBoard/rust/src_webpack/src/api.ts index 52261bc..7087375 100644 --- a/Software/MainBoard/rust/src_webpack/src/api.ts +++ b/Software/MainBoard/rust/src_webpack/src/api.ts @@ -175,12 +175,12 @@ export interface BatteryState { } export interface DetectionPlant { - a: boolean, - b: boolean + sensor_a: boolean, + sensor_b: boolean } export interface DetectionResult { - plants: DetectionPlant[] + plant: DetectionPlant[] } export interface TankInfo { @@ -202,3 +202,4 @@ export interface TankInfo { water_temp: number | null, temp_sensor_error: string | null } + diff --git a/Software/MainBoard/rust/src_webpack/src/main.ts b/Software/MainBoard/rust/src_webpack/src/main.ts index 5b0bac5..e4b5149 100644 --- a/Software/MainBoard/rust/src_webpack/src/main.ts +++ b/Software/MainBoard/rust/src_webpack/src/main.ts @@ -2,6 +2,7 @@ import {deepEqual} from 'fast-equals'; declare var PUBLIC_URL: string; console.log("Url is " + PUBLIC_URL); +console.log("Public url is " + PUBLIC_URL); document.body.innerHTML = require('./main.html') as string; @@ -28,7 +29,7 @@ import { SetTime, SSIDList, TankInfo, TestPump, VersionInfo, - FileList, SolarState, PumpTestResult + FileList, SolarState, PumpTestResult, DetectionResult } from "./api"; import {SolarView} from "./solarview"; import {toast} from "./toast"; @@ -289,6 +290,7 @@ export class Controller { method: "POST", body: pretty }).then( + _ => controller.progressview.removeProgress("write_rtc") ) } @@ -376,11 +378,15 @@ export class Controller { fetch(PUBLIC_URL + "/detect_sensors", { method: "POST" }) .then(response => response.json()) + .then (json => json as DetectionResult) .then(json => { clearTimeout(timerId); controller.progressview.removeProgress("detect_sensors"); const pretty = JSON.stringify(json); toast.info("Detection result: " + pretty); + console.log(pretty); + this.plantViews.applyDetectionResult(json); + }) .catch(error => { clearTimeout(timerId); diff --git a/Software/MainBoard/rust/src_webpack/src/plant.ts b/Software/MainBoard/rust/src_webpack/src/plant.ts index 7318dfa..f25b6c1 100644 --- a/Software/MainBoard/rust/src_webpack/src/plant.ts +++ b/Software/MainBoard/rust/src_webpack/src/plant.ts @@ -1,4 +1,4 @@ -import {PlantConfig, PumpTestResult} from "./api"; +import {DetectionPlant, DetectionResult, PlantConfig, PumpTestResult} from "./api"; const PLANT_COUNT = 8; @@ -47,6 +47,13 @@ export class PlantViews { const plantView = this.plants[plantId]; plantView.setTestResult(response) } + + applyDetectionResult(json: DetectionResult) { + for (let i = 0; i < PLANT_COUNT; i++) { + var plantResult = json.plant[i]; + this.plants[i].setDetectionResult(plantResult); + } + } } @@ -80,9 +87,12 @@ export class PlantView { private readonly pump_test_pump_time: HTMLElement; private readonly pump_test_flow_ml: HTMLElement; private readonly pump_test_flow_raw: HTMLElement; + private showDisabled: boolean = false; + private readonly controller: Controller; constructor(plantId: number, parent: HTMLDivElement, controller: Controller) { + this.controller = controller; this.plantId = plantId; this.plantDiv = document.createElement("div")! as HTMLDivElement const template = require('./plant.html') as string; @@ -217,6 +227,14 @@ export class PlantView { let showTarget = plantConfig.mode === "TargetMoisture" let showMin = plantConfig.mode === "MinMoisture" + if(this.showDisabled || plantConfig.sensor_a || plantConfig.sensor_b) { + console.log("Showing plant " + this.plantId); + this.plantDiv.style.display = "block"; + } else { + console.log("Hiding plant " + this.plantId); + this.plantDiv.style.display = "none"; + } + console.log("updateVisibility showsensor: " + showSensor + " pump " + showPump + " target " +showTarget + " min " + showMin) for (const element of Array.from(sensorOnly)) { @@ -315,4 +333,20 @@ export class PlantView { this.updateVisibility(conv); return conv; } + + setDetectionResult(plantResult: DetectionPlant) { + var changed = false; + if (this.sensorAInstalled.checked != plantResult.sensor_b){ + changed = true; + this.sensorAInstalled.checked = plantResult.sensor_a; + } + if (this.sensorBInstalled.checked != plantResult.sensor_b){ + changed = true; + this.sensorBInstalled.checked = plantResult.sensor_b; + } + if (changed) { + this.controller.configChanged(); + } + + } } \ No newline at end of file diff --git a/Software/MainBoard/rust/src_webpack/src/tankview.ts b/Software/MainBoard/rust/src_webpack/src/tankview.ts index 29165fe..24337ba 100644 --- a/Software/MainBoard/rust/src_webpack/src/tankview.ts +++ b/Software/MainBoard/rust/src_webpack/src/tankview.ts @@ -1,5 +1,5 @@ import { Controller } from "./main"; -import {TankConfig, TankInfo} from "./api"; +import {DetectionResult, TankConfig, TankInfo} from "./api"; export class TankConfigView { private readonly tank_useable_ml: HTMLInputElement; @@ -157,4 +157,6 @@ export class TankConfigView { ml_per_pulse: this.ml_per_pulse.valueAsNumber } } - } \ No newline at end of file + + +} \ No newline at end of file diff --git a/Software/MainBoard/rust/src_webpack/src/toast.ts b/Software/MainBoard/rust/src_webpack/src/toast.ts index 3313ab5..bca6b71 100644 --- a/Software/MainBoard/rust/src_webpack/src/toast.ts +++ b/Software/MainBoard/rust/src_webpack/src/toast.ts @@ -20,6 +20,7 @@ class ToastService { } error(message: string) { + console.error(message); const el = this.createToast(message, 'error'); this.container.appendChild(el); // Only dismiss on click diff --git a/rust/build.rs b/rust/build.rs deleted file mode 100644 index 735bf51..0000000 --- a/rust/build.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::{collections::VecDeque, env, process::Command}; - -use vergen::EmitBuilder; - -fn linker_be_nice() { - let args: Vec = std::env::args().collect(); - if args.len() > 1 { - let kind = &args[1]; - let what = &args[2]; - - match kind.as_str() { - "undefined-symbol" => match what.as_str() { - "_defmt_timestamp" => { - eprintln!(); - eprintln!("💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`"); - eprintln!(); - } - "_stack_start" => { - eprintln!(); - eprintln!("💡 Is the linker script `linkall.x` missing?"); - eprintln!(); - } - "esp_wifi_preempt_enable" - | "esp_wifi_preempt_yield_task" - | "esp_wifi_preempt_task_create" => { - eprintln!(); - eprintln!("💡 `esp-wifi` has no scheduler enabled. Make sure you have the `builtin-scheduler` feature enabled, or that you provide an external scheduler."); - eprintln!(); - } - "embedded_test_linker_file_not_added_to_rustflags" => { - eprintln!(); - eprintln!("💡 `embedded-test` not found - make sure `embedded-test.x` is added as a linker script for tests"); - eprintln!(); - } - _ => (), - }, - // we don't have anything helpful for "missing-lib" yet - _ => { - std::process::exit(1); - } - } - - std::process::exit(0); - } - - println!( - "cargo:rustc-link-arg=--error-handling-script={}", - std::env::current_exe().unwrap().display() - ); -} - -fn main() { - if Command::new("podman").arg("--version").output().is_err() { - println!("Could not find `podman` installation, assuming the developer has setup all required tool for build manually! … ") - } - webpack(); - linker_be_nice(); - let _ = EmitBuilder::builder().all_git().all_build().emit(); -} - -fn webpack() { - //println!("cargo:rerun-if-changed=./src/src_webpack"); - Command::new("rm") - .arg("./src/webserver/bundle.js.gz") - .output() - .unwrap(); - - match Command::new("cmd").spawn() { - Ok(_) => { - println!("Assuming build on windows"); - let output = Command::new("cmd") - .arg("/K") - .arg("npx") - .arg("webpack") - .current_dir("./src_webpack") - .output() - .unwrap(); - println!("status: {}", output.status); - println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); - println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); - assert!(output.status.success()); - - // move webpack results to rust webserver src - let _ = Command::new("cmd") - .arg("/K") - .arg("move") - .arg("./src_webpack/bundle.js.gz") - .arg("./src/webserver") - .output() - .unwrap(); - let _ = Command::new("cmd") - .arg("/K") - .arg("move") - .arg("./src_webpack/index.html.gz") - .arg("./src/webserver") - .output() - .unwrap(); - } - Err(_) => { - println!("Assuming build on linux"); - let output = Command::new("npx") - .arg("webpack") - .current_dir("./src_webpack") - .output() - .unwrap(); - println!("status: {}", output.status); - println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); - println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); - assert!(output.status.success()); - - // move webpack results to rust webserver src - let _ = Command::new("mv") - .arg("./src_webpack/bundle.js.gz") - .arg("./src/webserver") - .output() - .unwrap(); - let _ = Command::new("mv") - .arg("./src_webpack/index.html.gz") - .arg("./src/webserver") - .output() - .unwrap(); - } - } -}