diff --git a/Software/MainBoard/rust/src/hal/battery.rs b/Software/MainBoard/rust/src/hal/battery.rs index 8de40c4..d723fb9 100644 --- a/Software/MainBoard/rust/src/hal/battery.rs +++ b/Software/MainBoard/rust/src/hal/battery.rs @@ -18,7 +18,7 @@ pub trait BatteryInteraction { async fn reset(&mut self) -> FatResult<()>; } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Copy, Clone)] pub struct BatteryInfo { pub voltage_milli_volt: u32, pub average_current_milli_ampere: i32, diff --git a/Software/MainBoard/rust/src/hal/v4_hal.rs b/Software/MainBoard/rust/src/hal/v4_hal.rs index 6d82f4f..ff8d87c 100644 --- a/Software/MainBoard/rust/src/hal/v4_hal.rs +++ b/Software/MainBoard/rust/src/hal/v4_hal.rs @@ -358,6 +358,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> { } async fn measure_moisture_hz(&mut self) -> FatResult { self.can_power.set_high(); + Timer::after_millis(1000).await; let config = self.twai_config.take().expect("twai config not set"); let mut twai = config.into_async().start(); @@ -377,6 +378,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> { async fn detect_sensors(&mut self, request: Detection) -> FatResult { self.can_power.set_high(); + Timer::after_millis(1000).await; let config = self.twai_config.take().expect("twai config not set"); let mut twai = config.into_async().start(); @@ -409,7 +411,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> { // Try a few times; we intentionally ignore rx here and rely on stub logic let resu = twai .transmit_async(&frame) - .with_timeout(Duration::from_millis(3000)) + .with_timeout(Duration::from_millis(500)) .await; match resu { Ok(_) => {} diff --git a/Software/MainBoard/rust/src/main.rs b/Software/MainBoard/rust/src/main.rs index a7d02f6..af2942f 100644 --- a/Software/MainBoard/rust/src/main.rs +++ b/Software/MainBoard/rust/src/main.rs @@ -530,13 +530,22 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { board.board_hal.get_config().night_lamp.night_lamp_hour_end, ); - if state_of_charge < board.board_hal.get_config().night_lamp.low_soc_cutoff { - board.board_hal.get_esp().set_low_voltage_in_cycle(); - info!("Set low voltage in cycle"); - } else if state_of_charge > board.board_hal.get_config().night_lamp.low_soc_restore { - board.board_hal.get_esp().clear_low_voltage_in_cycle(); - info!("Clear low voltage in cycle"); + match battery_state { + BatteryState::Unknown => { + light_state.battery_low = false; + }, + BatteryState::Info(data) => { + if data.state_of_charge < board.board_hal.get_config().night_lamp.low_soc_cutoff { + board.board_hal.get_esp().set_low_voltage_in_cycle(); + info!("Set low voltage in cycle"); + } + if data.state_of_charge > board.board_hal.get_config().night_lamp.low_soc_restore { + board.board_hal.get_esp().clear_low_voltage_in_cycle(); + info!("Clear low voltage in cycle"); + } + } } + light_state.battery_low = board.board_hal.get_esp().low_voltage_in_cycle(); if !light_state.out_of_work_hour { @@ -583,7 +592,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { let deep_sleep_duration_minutes: u32 = // if battery soc is unknown assume battery has enough change - if state_of_charge < 10 && !matches!(battery_state, BatteryState::Unknown) { + if matches!(battery_state, BatteryState::Info(data) if data.state_of_charge < 10) { let _ = board .board_hal .get_esp() @@ -1002,7 +1011,65 @@ async fn wait_infinity( let mut pattern_step = 0; let serial_config_receive = AtomicBool::new(false); let mut suppress_further_mppt_error = false; + + // Long-press exit (for webserver config modes): hold boot button for 5 seconds. + let mut exit_hold_started: Option = None; + let exit_hold_duration = Duration::from_secs(5); + let mut exit_hold_blink = false; loop { + // While in config webserver mode, allow exiting via long-press. + if matches!(wait_type, WaitType::MissingConfig | WaitType::ConfigButton) { + let mut board = BOARD_ACCESS.get().await.lock().await; + let pressed = board.board_hal.get_esp().mode_override_pressed(); + + match (pressed, exit_hold_started) { + (true, None) => { + exit_hold_started = Some(Instant::now()); + PROGRESS_ACTIVE.store(true, Ordering::Relaxed); + } + (false, Some(_)) => { + exit_hold_started = None; + // Clear any interim hold display. + board.board_hal.clear_progress().await; + } + _ => {} + } + + if let Some(started) = exit_hold_started { + let elapsed = Instant::now() - started; + // Visible countdown: fill LEDs progressively during the hold. + // Also toggle general fault LED to match the "enter config" visibility. + exit_hold_blink = !exit_hold_blink; + + let progress = core::cmp::min(elapsed, exit_hold_duration); + let lit = ((progress.as_millis() as u64 * 8) / exit_hold_duration.as_millis() as u64) + .saturating_add(1) + .min(8) as usize; + + for i in 0..8 { + let _ = board.board_hal.fault(i, i < lit).await; + } + board.board_hal.general_fault(exit_hold_blink).await; + + if elapsed >= exit_hold_duration { + info!("Exiting config mode due to 5s button hold"); + board.board_hal.get_esp().set_restart_to_conf(false); + // ensure clean http answer / visible confirmation + Timer::after_millis(500).await; + board.board_hal + .deep_sleep(0) + .await; + } + + // Short tick while holding so the pattern updates smoothly. + drop(board); + Timer::after_millis(100).await; + continue; + } + // Release lock and continue with normal wait blinking. + drop(board); + } + { let mut board = BOARD_ACCESS.get().await.lock().await; match update_charge_indicator(&mut board).await {