fix ota abort/invalid switching
This commit is contained in:
		
							
								
								
									
										12
									
								
								rust/all.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										12
									
								
								rust/all.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| rm ./src/webserver/index.html.gz | ||||
| rm ./src/webserver/bundle.js.gz | ||||
| set -e | ||||
| cd ./src_webpack/ | ||||
| npx webpack build | ||||
| cp index.html.gz ../src/webserver/index.html.gz | ||||
| cp bundle.js.gz ../src/webserver/bundle.js.gz | ||||
| cd ../ | ||||
|  | ||||
| cargo build --release | ||||
| espflash save-image --bootloader bootloader.bin --partition-table partitions.csv --chip esp32c6 target/riscv32imac-unknown-none-elf/release/plant-ctrl2 image.bin | ||||
| espflash flash --monitor --bootloader bootloader.bin --chip esp32c6 --baud 921600 --partition-table partitions.csv  target/riscv32imac-unknown-none-elf/release/plant-ctrl2 | ||||
| @@ -7,5 +7,5 @@ cp index.html.gz ../src/webserver/index.html.gz | ||||
| cp bundle.js.gz ../src/webserver/bundle.js.gz | ||||
| cd ../ | ||||
|  | ||||
| cargo build | ||||
| cargo build --release | ||||
| espflash flash --monitor --bootloader bootloader.bin --chip esp32c6 --baud 921600 --partition-table partitions.csv  target/riscv32imac-unknown-none-elf/release/plant-ctrl2 | ||||
|   | ||||
| @@ -65,6 +65,9 @@ pub enum FatError { | ||||
|     CanBusError { | ||||
|         error: EspTwaiError, | ||||
|     }, | ||||
|     SNTPError { | ||||
|         error: sntpc::Error, | ||||
|     }, | ||||
| } | ||||
|  | ||||
| pub type FatResult<T> = Result<T, FatError>; | ||||
| @@ -96,6 +99,7 @@ impl fmt::Display for FatError { | ||||
|             FatError::CanBusError { error } => { | ||||
|                 write!(f, "CanBusError {:?}", error) | ||||
|             } | ||||
|             FatError::SNTPError { error } => write!(f, "SNTPError {:?}", error), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -299,10 +303,16 @@ impl From<nb::Error<EspTwaiError>> for FatError { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<NorFlashErrorKind> for FatError{ | ||||
| impl From<NorFlashErrorKind> for FatError { | ||||
|     fn from(value: NorFlashErrorKind) -> Self { | ||||
|         FatError::String { | ||||
|             error: value.to_string() | ||||
|             error: value.to_string(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<sntpc::Error> for FatError { | ||||
|     fn from(value: sntpc::Error) -> Self { | ||||
|         FatError::SNTPError { error: value } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| use crate::bail; | ||||
| use crate::config::{NetworkConfig, PlantControllerConfig}; | ||||
| use crate::hal::{get_next_slot, PLANT_COUNT, TIME_ACCESS}; | ||||
| use crate::hal::{get_current_slot_and_fix_ota_data, PLANT_COUNT, TIME_ACCESS}; | ||||
| use crate::log::{LogMessage, LOG_ACCESS}; | ||||
| use chrono::{DateTime, Utc}; | ||||
| use serde::Serialize; | ||||
| @@ -19,10 +19,10 @@ use embassy_net::{DhcpConfig, Ipv4Cidr, Runner, Stack, StackResources, StaticCon | ||||
| use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; | ||||
| use embassy_sync::mutex::{Mutex, MutexGuard}; | ||||
| use embassy_sync::once_lock::OnceLock; | ||||
| use embassy_time::{Duration, Timer}; | ||||
| use embassy_time::{Duration, Timer, WithTimeout}; | ||||
| use embedded_storage::nor_flash::{check_erase, NorFlash, ReadNorFlash}; | ||||
| use esp_bootloader_esp_idf::ota::OtaImageState::Valid; | ||||
| use esp_bootloader_esp_idf::ota::{Ota, OtaImageState}; | ||||
| use esp_bootloader_esp_idf::ota::{Ota, OtaImageState, Slot}; | ||||
| use esp_bootloader_esp_idf::partitions::FlashRegion; | ||||
| use esp_hal::gpio::{Input, RtcPinWithResistors}; | ||||
| use esp_hal::rng::Rng; | ||||
| @@ -128,7 +128,10 @@ pub struct Esp<'a> { | ||||
|     pub wake_gpio1: esp_hal::peripherals::GPIO1<'static>, | ||||
|  | ||||
|     pub ota: Ota<'static, FlashStorage>, | ||||
|     pub ota_next: &'static mut FlashRegion<'static, FlashStorage>, | ||||
|     pub ota_target: &'static mut FlashRegion<'static, FlashStorage>, | ||||
|     pub current: Slot, | ||||
|     pub slot0_state: OtaImageState, | ||||
|     pub slot1_state: OtaImageState, | ||||
| } | ||||
|  | ||||
| // SAFETY: On this target we never move Esp across OS threads; the firmware runs single-core | ||||
| @@ -213,43 +216,15 @@ impl Esp<'_> { | ||||
|         Ok((buf, read)) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn get_current_ota_slot(&mut self) -> String { | ||||
|         match get_next_slot(&mut self.ota) { | ||||
|             Ok(slot) => { | ||||
|                 format!("{:?}", slot.next()) | ||||
|             } | ||||
|             Err(err) => { | ||||
|                 format!("{:?}", err) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn get_ota_state(&mut self) -> String { | ||||
|         match self.ota.current_ota_state() { | ||||
|             Ok(state) => { | ||||
|                 format!("{:?}", state) | ||||
|             } | ||||
|             Err(err) => { | ||||
|                 format!("{:?}", err) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) async fn write_ota(&mut self, offset: u32, buf: &[u8]) -> Result<(), FatError> { | ||||
|         if self.ota.current_ota_state() == Ok(OtaImageState::Invalid) { | ||||
|             bail!("Invalid OTA state, refusing ota write") | ||||
|         } | ||||
|         if self.ota.current_ota_state() == Ok(OtaImageState::Undefined) { | ||||
|             bail!("Invalid OTA state, refusing ota write") | ||||
|         } | ||||
|         let _ = check_erase(self.ota_next, offset, offset + 4096); | ||||
|         self.ota_next.erase(offset, offset + 4096)?; | ||||
|         let _ = check_erase(self.ota_target, offset, offset + 4096); | ||||
|         self.ota_target.erase(offset, offset + 4096)?; | ||||
|  | ||||
|         let mut temp = vec![0; buf.len()]; | ||||
|         let read_back = temp.as_mut_slice(); | ||||
|         //change to nor flash, align writes! | ||||
|         self.ota_next.write(offset, buf)?; | ||||
|         self.ota_next.read(offset, read_back)?; | ||||
|         self.ota_target.write(offset, buf)?; | ||||
|         self.ota_target.read(offset, read_back)?; | ||||
|         if buf != read_back { | ||||
|             info!("Expected {:?} but got {:?}", buf, read_back); | ||||
|             bail!( | ||||
| @@ -261,23 +236,16 @@ impl Esp<'_> { | ||||
|     } | ||||
|  | ||||
|     pub(crate) async fn finalize_ota(&mut self) -> Result<(), FatError> { | ||||
|         if self.ota.current_ota_state() == Ok(OtaImageState::Invalid) { | ||||
|             bail!("Invalid OTA state, refusing ota write") | ||||
|         } | ||||
|         if self.ota.current_ota_state() == Ok(OtaImageState::Undefined) { | ||||
|             bail!("Invalid OTA state, refusing ota write") | ||||
|         } | ||||
|  | ||||
|         let current_state = self.ota.current_ota_state()?; | ||||
|         info!("current state {:?}", current_state); | ||||
|         let next_slot = get_next_slot(&mut self.ota)?; | ||||
|         info!("current slot {:?}", next_slot.next()); | ||||
|         if current_state == OtaImageState::PendingVerify { | ||||
|             info!("verifying ota image from pending"); | ||||
|         let current = self.ota.current_slot()?; | ||||
|         if self.ota.current_ota_state()? != OtaImageState::Valid { | ||||
|             info!( | ||||
|                 "Validating current slot {:?} as it was able to ota", | ||||
|                 current | ||||
|             ); | ||||
|             self.ota.set_current_ota_state(Valid)?; | ||||
|         } | ||||
|  | ||||
|         self.ota.set_current_slot(next_slot)?; | ||||
|         self.ota.set_current_slot(current.next())?; | ||||
|         info!("switched slot"); | ||||
|         self.ota.set_current_ota_state(OtaImageState::New)?; | ||||
|         info!("switched state for new partition"); | ||||
| @@ -327,16 +295,19 @@ impl Esp<'_> { | ||||
|         let mut counter = 0; | ||||
|         loop { | ||||
|             let addr: IpAddr = ntp_addrs[0].into(); | ||||
|             let result = get_time(SocketAddr::from((addr, 123)), &socket, context).await; | ||||
|             let timeout = get_time(SocketAddr::from((addr, 123)), &socket, context) | ||||
|                 .with_timeout(Duration::from_millis((_max_wait_ms / 10) as u64)) | ||||
|                 .await; | ||||
|  | ||||
|             match result { | ||||
|                 Ok(time) => { | ||||
|             match timeout { | ||||
|                 Ok(result) => { | ||||
|                     let time = result?; | ||||
|                     info!("Time: {:?}", time); | ||||
|                     return DateTime::from_timestamp(time.seconds as i64, 0) | ||||
|                         .context("Could not convert Sntp result"); | ||||
|                 } | ||||
|                 Err(e) => { | ||||
|                     warn!("Error: {:?}", e); | ||||
|                 Err(err) => { | ||||
|                     warn!("sntp timeout, retry: {:?}", err); | ||||
|                     counter += 1; | ||||
|                     if counter > 10 { | ||||
|                         bail!("Failed to get time from NTP server"); | ||||
|   | ||||
| @@ -9,7 +9,6 @@ mod v3_shift_register; | ||||
| mod v4_hal; | ||||
| mod v4_sensor; | ||||
| mod water; | ||||
|  | ||||
| use crate::alloc::string::ToString; | ||||
| use crate::hal::rtc::{DS3231Module, RTCModuleInteraction}; | ||||
| use esp_hal::peripherals::Peripherals; | ||||
| @@ -44,6 +43,7 @@ use esp_hal::peripherals::GPIO8; | ||||
| use esp_hal::peripherals::TWAI0; | ||||
|  | ||||
| use crate::{ | ||||
|     bail, | ||||
|     config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig}, | ||||
|     hal::{ | ||||
|         battery::{BatteryInteraction, NoBatteryMonitor}, | ||||
| @@ -83,9 +83,11 @@ use crate::hal::water::TankSensor; | ||||
| use crate::log::LOG_ACCESS; | ||||
| use embassy_sync::mutex::Mutex; | ||||
| use embassy_sync::once_lock::OnceLock; | ||||
| use embedded_storage::nor_flash::ReadNorFlash; | ||||
| use esp_alloc as _; | ||||
| use esp_backtrace as _; | ||||
| use esp_bootloader_esp_idf::ota::{Ota, OtaImageState, Slot}; | ||||
| use esp_bootloader_esp_idf::ota::{Ota, OtaImageState}; | ||||
| use esp_bootloader_esp_idf::ota::{Slot as ota_slot, Slot}; | ||||
| use esp_hal::delay::Delay; | ||||
| use esp_hal::i2c::master::{BusTimeout, Config, I2c}; | ||||
| use esp_hal::pcnt::unit::Unit; | ||||
| @@ -101,12 +103,15 @@ use esp_wifi::{init, EspWifiController}; | ||||
| use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem}; | ||||
| use littlefs2::object_safe::DynStorage; | ||||
| use log::{error, info, warn}; | ||||
| use portable_atomic::AtomicBool; | ||||
|  | ||||
| pub static TIME_ACCESS: OnceLock<Mutex<CriticalSectionRawMutex, Rtc>> = OnceLock::new(); | ||||
|  | ||||
| //Only support for 8 right now! | ||||
| pub const PLANT_COUNT: usize = 8; | ||||
|  | ||||
| pub static PROGRESS_ACTIVE: AtomicBool = AtomicBool::new(false); | ||||
|  | ||||
| const TANK_MULTI_SAMPLE: usize = 11; | ||||
| pub static I2C_DRIVER: OnceLock< | ||||
|     embassy_sync::blocking_mutex::Mutex<CriticalSectionRawMutex, RefCell<I2c<Blocking>>>, | ||||
| @@ -148,6 +153,9 @@ pub trait BoardInteraction<'a> { | ||||
|     async fn get_mptt_current(&mut self) -> Result<Current, FatError>; | ||||
|  | ||||
|     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); | ||||
|  | ||||
|         let current = counter % PLANT_COUNT as u32; | ||||
|         for led in 0..PLANT_COUNT { | ||||
|             if let Err(err) = self.fault(led, current == led as u32).await { | ||||
| @@ -165,6 +173,9 @@ 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); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -303,10 +314,6 @@ impl PlantHal { | ||||
|         let pt = | ||||
|             esp_bootloader_esp_idf::partitions::read_partition_table(storage_ota, tablebuffer)?; | ||||
|  | ||||
|         // List all partitions - this is just FYI | ||||
|         for i in 0..pt.len() { | ||||
|             info!("{:?}", pt.get_partition(i)); | ||||
|         } | ||||
|         let ota_data = mk_static!( | ||||
|             PartitionEntry, | ||||
|             pt.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data( | ||||
| @@ -320,13 +327,18 @@ impl PlantHal { | ||||
|             ota_data.as_embedded_storage(storage_ota) | ||||
|         ); | ||||
|  | ||||
|         let state_0 = ota_state(ota_slot::Slot0, ota_data); | ||||
|         let state_1 = ota_state(ota_slot::Slot1, ota_data); | ||||
|         let mut ota = Ota::new(ota_data)?; | ||||
|         let running = get_current_slot_and_fix_ota_data(&mut ota, state_0, state_1)?; | ||||
|         let target = running.next(); | ||||
|  | ||||
|         let state = ota.current_ota_state().unwrap_or_default(); | ||||
|         info!("Current OTA state: {:?}", state); | ||||
|         let next_slot = get_next_slot(&mut ota)?; | ||||
|         info!("Next OTA slot: {:?}", next_slot); | ||||
|         let ota_next = match next_slot { | ||||
|         info!("Currently running OTA slot: {:?}", running); | ||||
|         info!("Slot0 state: {:?}", state_0); | ||||
|         info!("Slot1 state: {:?}", state_1); | ||||
|  | ||||
|         //obtain current_state and next_state here! | ||||
|         let ota_target = match target { | ||||
|             Slot::None => { | ||||
|                 panic!("No OTA slot active?"); | ||||
|             } | ||||
| @@ -342,11 +354,11 @@ impl PlantHal { | ||||
|                 .context("Partition table invalid no ota1")?, | ||||
|         }; | ||||
|  | ||||
|         let ota_next = mk_static!(PartitionEntry, ota_next); | ||||
|         let ota_target = mk_static!(PartitionEntry, ota_target); | ||||
|         let storage_ota = mk_static!(FlashStorage, FlashStorage::new()); | ||||
|         let ota_next = mk_static!( | ||||
|         let ota_target = mk_static!( | ||||
|             FlashRegion<FlashStorage>, | ||||
|             ota_next.as_embedded_storage(storage_ota) | ||||
|             ota_target.as_embedded_storage(storage_ota) | ||||
|         ); | ||||
|  | ||||
|         let data_partition = pt | ||||
| @@ -391,7 +403,10 @@ impl PlantHal { | ||||
|             boot_button, | ||||
|             wake_gpio1, | ||||
|             ota, | ||||
|             ota_next, | ||||
|             ota_target, | ||||
|             current: running, | ||||
|             slot0_state: state_0, | ||||
|             slot1_state: state_1, | ||||
|         }; | ||||
|  | ||||
|         //init,reset rtc memory depending on cause | ||||
| @@ -573,29 +588,75 @@ impl PlantHal { | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn get_next_slot(ota: &mut Ota<FlashStorage>) -> Result<Slot, FatError> { | ||||
|     let next_slot = { | ||||
|         let state = ota.current_ota_state().unwrap_or_default(); | ||||
|         let current = ota.current_slot()?; | ||||
|         match state { | ||||
|             OtaImageState::New => current.next(), | ||||
|             OtaImageState::PendingVerify => current.next(), | ||||
|             OtaImageState::Valid => current.next(), | ||||
| fn ota_state(slot: ota_slot, ota_data: &mut FlashRegion<FlashStorage>) -> OtaImageState { | ||||
|     // Read and log OTA states for both slots before constructing Ota | ||||
|     // Each OTA select entry is 32 bytes: [seq:4][label:20][state:4][crc:4] | ||||
|     // Offsets within the OTA data partition: slot0 @ 0x0000, slot1 @ 0x1000 | ||||
|     if slot == ota_slot::None { | ||||
|         return OtaImageState::Undefined; | ||||
|     } | ||||
|     let mut slot_buf = [0u8; 32]; | ||||
|     if slot == ota_slot::Slot0 { | ||||
|         let _ = ota_data.read(0x0000, &mut slot_buf); | ||||
|     } else { | ||||
|         let _ = ota_data.read(0x1000, &mut slot_buf); | ||||
|     } | ||||
|     let raw_state = u32::from_le_bytes(slot_buf[24..28].try_into().unwrap_or([0xff; 4])); | ||||
|     let state0 = OtaImageState::try_from(raw_state).unwrap_or(OtaImageState::Undefined); | ||||
|     state0 | ||||
| } | ||||
|  | ||||
| fn get_current_slot_and_fix_ota_data( | ||||
|     ota: &mut Ota<FlashStorage>, | ||||
|     state0: OtaImageState, | ||||
|     state1: OtaImageState, | ||||
| ) -> Result<ota_slot, FatError> { | ||||
|     let state = ota.current_ota_state().unwrap_or_default(); | ||||
|     let swap = match state { | ||||
|         OtaImageState::Invalid => true, | ||||
|         OtaImageState::Aborted => true, | ||||
|         OtaImageState::Undefined => { | ||||
|             info!("Undefined image in current slot, bootloader wrong?"); | ||||
|             false | ||||
|         } | ||||
|         _ => false, | ||||
|     }; | ||||
|     let current = ota.current_slot()?; | ||||
|     if swap { | ||||
|         let other = match current { | ||||
|             ota_slot::Slot0 => state1, | ||||
|             ota_slot::Slot1 => state0, | ||||
|             _ => OtaImageState::Invalid, | ||||
|         }; | ||||
|  | ||||
|         match other { | ||||
|             OtaImageState::Invalid => { | ||||
|                 //we actually booted other slot, than partition table assumes | ||||
|                 current | ||||
|                 bail!( | ||||
|                     "cannot recover slot, as both slots in invalid state {:?} {:?} {:?}", | ||||
|                     current, | ||||
|                     state0, | ||||
|                     state1 | ||||
|                 ); | ||||
|             } | ||||
|             OtaImageState::Aborted => { | ||||
|                 //we actually booted other slot, than partition table assumes | ||||
|                 current | ||||
|             } | ||||
|             OtaImageState::Undefined => { | ||||
|                 //missing bootloader? | ||||
|                 current.next() | ||||
|                 bail!( | ||||
|                     "cannot recover slot, as both slots in invalid state {:?} {:?} {:?}", | ||||
|                     current, | ||||
|                     state0, | ||||
|                     state1 | ||||
|                 ); | ||||
|             } | ||||
|             _ => {} | ||||
|         } | ||||
|         info!( | ||||
|             "Current slot has state {:?} other state has {:?} swapping", | ||||
|             state, other | ||||
|         ); | ||||
|         ota.set_current_slot(current.next())?; | ||||
|         //we actually booted other slot, than partition table assumes | ||||
|         return Ok(ota.current_slot()?); | ||||
|     }; | ||||
|     Ok(next_slot) | ||||
|     Ok(current) | ||||
| } | ||||
|  | ||||
| pub async fn esp_time() -> DateTime<Utc> { | ||||
|   | ||||
| @@ -17,6 +17,7 @@ use crate::config::{NetworkConfig, PlantConfig}; | ||||
| use crate::fat_error::FatResult; | ||||
| use crate::hal::esp::MQTT_STAY_ALIVE; | ||||
| use crate::hal::{esp_time, TIME_ACCESS}; | ||||
| use crate::hal::PROGRESS_ACTIVE; | ||||
| use crate::log::{log, LOG_ACCESS}; | ||||
| use crate::tank::{determine_tank_state, TankError, TankState, WATER_FROZEN_THRESH}; | ||||
| use crate::webserver::http_server; | ||||
| @@ -822,18 +823,9 @@ async fn publish_firmware_info( | ||||
|     let esp = board.board_hal.get_esp(); | ||||
|     let _ = esp.mqtt_publish("/firmware/address", ip_address).await; | ||||
|     let _ = esp | ||||
|         .mqtt_publish("/firmware/githash", &version.git_hash) | ||||
|         .await; | ||||
|     let _ = esp | ||||
|         .mqtt_publish("/firmware/buildtime", &version.build_time) | ||||
|         .mqtt_publish("/firmware/state", format!("{:?}", &version).as_str()) | ||||
|         .await; | ||||
|     let _ = esp.mqtt_publish("/firmware/last_online", timezone_time); | ||||
|     let state = esp.get_ota_state(); | ||||
|     let _ = esp.mqtt_publish("/firmware/ota_state", &state).await; | ||||
|     let slot = esp.get_current_ota_slot(); | ||||
|     let _ = esp | ||||
|         .mqtt_publish("/firmware/ota_slot", &format!("slot{slot}")) | ||||
|         .await; | ||||
|     let _ = esp.mqtt_publish("/state", "online").await; | ||||
| } | ||||
| macro_rules! mk_static { | ||||
| @@ -995,41 +987,48 @@ async fn wait_infinity( | ||||
|             let mut board = BOARD_ACCESS.get().await.lock().await; | ||||
|             update_charge_indicator(&mut board).await; | ||||
|  | ||||
|             match wait_type { | ||||
|                 WaitType::MissingConfig => { | ||||
|                     // Keep existing behavior: circular filling pattern | ||||
|                     led_count %= 8; | ||||
|                     led_count += 1; | ||||
|                     for i in 0..8 { | ||||
|                         let _ = board.board_hal.fault(i, i < led_count).await; | ||||
|                     } | ||||
|                 } | ||||
|                 WaitType::ConfigButton => { | ||||
|                     // Alternating pattern: 1010 1010 -> 0101 0101 | ||||
|                     pattern_step = (pattern_step + 1) % 2; | ||||
|                     for i in 0..8 { | ||||
|                         let _ = board.board_hal.fault(i, (i + pattern_step) % 2 == 0).await; | ||||
|                     } | ||||
|                 } | ||||
|                 WaitType::MqttConfig => { | ||||
|                     // Moving dot pattern | ||||
|                     pattern_step = (pattern_step + 1) % 8; | ||||
|                     for i in 0..8 { | ||||
|                         let _ = board.board_hal.fault(i, i == pattern_step).await; | ||||
|             // Skip default blink code when a progress display is active | ||||
|             if !PROGRESS_ACTIVE.load(Ordering::Relaxed) { | ||||
|                 match wait_type { | ||||
|                     WaitType::MissingConfig => { | ||||
|                         // Keep existing behavior: circular filling pattern | ||||
|                         led_count %= 8; | ||||
|                         led_count += 1; | ||||
|                         for i in 0..8 { | ||||
|                             let _ = board.board_hal.fault(i, i < led_count).await; | ||||
|                         } | ||||
|                     } | ||||
|                     WaitType::ConfigButton => { | ||||
|                         // Alternating pattern: 1010 1010 -> 0101 0101 | ||||
|                         pattern_step = (pattern_step + 1) % 2; | ||||
|                         for i in 0..8 { | ||||
|                             let _ = board.board_hal.fault(i, (i + pattern_step) % 2 == 0).await; | ||||
|                         } | ||||
|                     } | ||||
|                     WaitType::MqttConfig => { | ||||
|                         // Moving dot pattern | ||||
|                         pattern_step = (pattern_step + 1) % 8; | ||||
|                         for i in 0..8 { | ||||
|                             let _ = board.board_hal.fault(i, i == pattern_step).await; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 board.board_hal.general_fault(true).await; | ||||
|             } | ||||
|             board.board_hal.general_fault(true).await; | ||||
|         } | ||||
|  | ||||
|         Timer::after_millis(delay).await; | ||||
|         { | ||||
|             let mut board = BOARD_ACCESS.get().await.lock().await; | ||||
|             board.board_hal.general_fault(false).await; | ||||
|  | ||||
|             // Clear all LEDs | ||||
|             for i in 0..8 { | ||||
|                 let _ = board.board_hal.fault(i, false).await; | ||||
|             // Skip clearing LEDs when progress is active to avoid interrupting the progress display | ||||
|             if !PROGRESS_ACTIVE.load(Ordering::Relaxed) { | ||||
|                 board.board_hal.general_fault(false).await; | ||||
|  | ||||
|                 // Clear all LEDs | ||||
|                 for i in 0..8 { | ||||
|                     let _ = board.board_hal.fault(i, false).await; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -1096,14 +1095,12 @@ async fn get_version( | ||||
|     let hash = &env!("VERGEN_GIT_SHA")[0..8]; | ||||
|  | ||||
|     let board = board.board_hal.get_esp(); | ||||
|  | ||||
|     let ota_slot = board.get_current_ota_slot(); | ||||
|     let ota_state = board.get_ota_state(); | ||||
|     VersionInfo { | ||||
|         git_hash: branch + "@" + hash, | ||||
|         build_time: env!("VERGEN_BUILD_TIMESTAMP").to_owned(), | ||||
|         partition: ota_slot, | ||||
|         ota_state, | ||||
|         current: format!("{:?}", board.current), | ||||
|         slot0_state: format!("{:?}", board.slot0_state), | ||||
|         slot1_state: format!("{:?}", board.slot1_state), | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -1111,6 +1108,7 @@ async fn get_version( | ||||
| struct VersionInfo { | ||||
|     git_hash: String, | ||||
|     build_time: String, | ||||
|     partition: String, | ||||
|     ota_state: String, | ||||
|     current: String, | ||||
|     slot0_state: String, | ||||
|     slot1_state: String, | ||||
| } | ||||
|   | ||||
| @@ -240,34 +240,6 @@ pub async fn http_server(reboot_now: Arc<AtomicBool>, stack: Stack<'static>) { | ||||
|     info!("Webserver started and waiting for connections"); | ||||
|  | ||||
|     //TODO https if mbed_esp lands | ||||
|  | ||||
|     // server | ||||
|     //     .fn_handler("/ota", Method::Post, |request| { | ||||
|     //         handle_error_to500(request, ota) | ||||
|     //     }) | ||||
|     //     .unwrap(); | ||||
|     // server | ||||
|     //     .fn_handler("/ota", Method::Options, |request| { | ||||
|     //         cors_response(request, 200, "") | ||||
|     //     }) | ||||
|     //     .unwrap(); | ||||
|     // let reboot_now_for_reboot = reboot_now.clone(); | ||||
|     // server | ||||
|     //     .fn_handler("/reboot", Method::Post, move |_| { | ||||
|     //         BOARD_ACCESS | ||||
|     //             .lock() | ||||
|     //             .unwrap() | ||||
|     //             .board_hal | ||||
|     //             .get_esp() | ||||
|     //             .set_restart_to_conf(true); | ||||
|     //         reboot_now_for_reboot.store(true, std::sync::atomic::Ordering::Relaxed); | ||||
|     //         anyhow::Ok(()) | ||||
|     //     }) | ||||
|     //     .unwrap(); | ||||
|     // | ||||
|     // unsafe { vTaskDelay(1) }; | ||||
|     // | ||||
|     // server | ||||
| } | ||||
|  | ||||
| async fn handle_json<'a, T, const N: usize>( | ||||
|   | ||||
| @@ -30,9 +30,6 @@ where | ||||
|         Method::Post => { | ||||
|             let mut offset = 0_usize; | ||||
|             let mut chunk = 0; | ||||
|  | ||||
|             // Erase only a single 4K block right before writing into it. | ||||
|             // The first block will be erased when offset == 0 below. | ||||
|             loop { | ||||
|                 let buf = read_up_to_bytes_from_request(conn, Some(4096)).await?; | ||||
|                 if buf.len() == 0 { | ||||
|   | ||||
| @@ -157,9 +157,9 @@ export interface Moistures { | ||||
| export interface VersionInfo { | ||||
|     git_hash: string, | ||||
|     build_time: string, | ||||
|     partition: string, | ||||
|     ota_state: string | ||||
|  | ||||
|     current: string, | ||||
|     slot0_state: string, | ||||
|     slot1_state: string, | ||||
| } | ||||
|  | ||||
| export interface BatteryState { | ||||
|   | ||||
| @@ -1,18 +1,21 @@ | ||||
| <style> | ||||
|   .otakey{ | ||||
|     min-width: 100px; | ||||
|   } | ||||
|   .otavalue{ | ||||
|     flex-grow: 1; | ||||
|   } | ||||
|   .otaform { | ||||
|     min-width: 100px; | ||||
|     flex-grow: 1; | ||||
|   } | ||||
|   .otachooser { | ||||
|     min-width: 100px; | ||||
|     width: 100%; | ||||
|   } | ||||
|     .otakey { | ||||
|         min-width: 100px; | ||||
|     } | ||||
|  | ||||
|     .otavalue { | ||||
|         flex-grow: 1; | ||||
|     } | ||||
|  | ||||
|     .otaform { | ||||
|         min-width: 100px; | ||||
|         flex-grow: 1; | ||||
|     } | ||||
|  | ||||
|     .otachooser { | ||||
|         min-width: 100px; | ||||
|         width: 100%; | ||||
|     } | ||||
| </style> | ||||
| <div class="flexcontainer"> | ||||
|     <div class="subtitle"> | ||||
| @@ -28,12 +31,16 @@ | ||||
|     <span class="otavalue" id="firmware_githash"></span> | ||||
| </div> | ||||
| <div class="flexcontainer"> | ||||
|   <span class="otakey">Partition:</span> | ||||
|   <span class="otavalue" id="firmware_partition"></span> | ||||
|     <span class="otakey">Partition:</span> | ||||
|     <span class="otavalue" id="firmware_partition"></span> | ||||
| </div> | ||||
| <div class="flexcontainer"> | ||||
|     <span class="otakey">Status:</span> | ||||
|     <span class="otavalue" id="firmware_state"></span> | ||||
|     <span class="otakey">State0:</span> | ||||
|     <span class="otavalue" id="firmware_state0"></span> | ||||
| </div> | ||||
| <div class="flexcontainer"> | ||||
|     <span class="otakey">State1:</span> | ||||
|     <span class="otavalue" id="firmware_state1"></span> | ||||
| </div> | ||||
|  | ||||
| <div class="flexcontainer"> | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { Controller } from "./main"; | ||||
| import {Controller} from "./main"; | ||||
| import {VersionInfo} from "./api"; | ||||
|  | ||||
| export class OTAView { | ||||
| @@ -6,19 +6,22 @@ export class OTAView { | ||||
|     readonly firmware_buildtime: HTMLDivElement; | ||||
|     readonly firmware_githash: HTMLDivElement; | ||||
|     readonly firmware_partition: HTMLDivElement; | ||||
|     readonly firmware_state: HTMLDivElement; | ||||
|     readonly firmware_state0: HTMLDivElement; | ||||
|     readonly firmware_state1: HTMLDivElement; | ||||
|  | ||||
|     constructor(controller: Controller) { | ||||
|         (document.getElementById("firmwareview") as HTMLElement).innerHTML = require("./ota.html") | ||||
|  | ||||
|         let test  = document.getElementById("test") as HTMLButtonElement; | ||||
|         let test = document.getElementById("test") as HTMLButtonElement; | ||||
|  | ||||
|         this.firmware_buildtime = document.getElementById("firmware_buildtime") as HTMLDivElement; | ||||
|         this.firmware_githash = document.getElementById("firmware_githash") as HTMLDivElement; | ||||
|         this.firmware_partition = document.getElementById("firmware_partition") as HTMLDivElement; | ||||
|         this.firmware_state = document.getElementById("firmware_state") as HTMLDivElement; | ||||
|  | ||||
|          | ||||
|         this.firmware_state0 = document.getElementById("firmware_state0") as HTMLDivElement; | ||||
|         this.firmware_state1 = document.getElementById("firmware_state1") as HTMLDivElement; | ||||
|  | ||||
|  | ||||
|         const file = document.getElementById("firmware_file") as HTMLInputElement; | ||||
|         this.file1Upload = file | ||||
|         this.file1Upload.onchange = () => { | ||||
| @@ -38,7 +41,8 @@ export class OTAView { | ||||
|     setVersion(versionInfo: VersionInfo) { | ||||
|         this.firmware_buildtime.innerText = versionInfo.build_time; | ||||
|         this.firmware_githash.innerText = versionInfo.git_hash; | ||||
|         this.firmware_partition.innerText = versionInfo.partition; | ||||
|         this.firmware_state.innerText = versionInfo.ota_state; | ||||
|         this.firmware_partition.innerText = versionInfo.current; | ||||
|         this.firmware_state0.innerText = versionInfo.slot0_state; | ||||
|         this.firmware_state1.innerText = versionInfo.slot1_state; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user