diff --git a/Software/MainBoard/rust/.idea/vcs.xml b/Software/MainBoard/rust/.idea/vcs.xml
index c2365ab..298f634 100644
--- a/Software/MainBoard/rust/.idea/vcs.xml
+++ b/Software/MainBoard/rust/.idea/vcs.xml
@@ -2,5 +2,6 @@
+
\ No newline at end of file
diff --git a/Software/MainBoard/rust/Cargo.toml b/Software/MainBoard/rust/Cargo.toml
index 34e4b80..12ecede 100644
--- a/Software/MainBoard/rust/Cargo.toml
+++ b/Software/MainBoard/rust/Cargo.toml
@@ -51,6 +51,8 @@ esp-storage = { version = "0.8.1", features = ["esp32c6"] }
esp-radio = { version = "0.17.0", features = ["esp32c6", "log-04", "smoltcp", "wifi", "unstable"] }
esp-alloc = { version = "0.9.0", features = ["esp32c6", "internal-heap-stats"] }
+esp-hal-ota = { version = "0.4.6", features = ["esp32c6"] }
+
# Async runtime (Embassy core)
embassy-executor = { version = "0.9.1", features = ["log", "nightly"] }
embassy-time = { version = "0.5.0", features = ["log"], default-features = false }
@@ -95,7 +97,7 @@ embedded-can = "0.4.1"
nb = "1.1.0"
# Concrete hardware drivers and sensors/IO expanders
-lib-bms-protocol = {git = "https://gitea.wlandt.de/judge/ch32-bms.git" , default-features = false }
+lib-bms-protocol = { git = "https://gitea.wlandt.de/judge/ch32-bms.git", default-features = false }
onewire = "0.4.0"
ds323x = "0.7.0"
eeprom24x = "0.7.2"
diff --git a/Software/MainBoard/rust/src/fat_error.rs b/Software/MainBoard/rust/src/fat_error.rs
index 9bbb6cb..b6a70cf 100644
--- a/Software/MainBoard/rust/src/fat_error.rs
+++ b/Software/MainBoard/rust/src/fat_error.rs
@@ -11,6 +11,7 @@ use embedded_storage::nor_flash::NorFlashErrorKind;
use esp_hal::i2c::master::ConfigError;
use esp_hal::pcnt::unit::{InvalidHighLimit, InvalidLowLimit};
use esp_hal::twai::EspTwaiError;
+use esp_hal_ota::OtaError;
use esp_radio::wifi::WifiError;
use ina219::errors::{BusVoltageReadError, ShuntVoltageReadError};
use lib_bms_protocol::BmsProtocolError;
@@ -73,6 +74,9 @@ pub enum FatError {
SNTPError {
error: sntpc::Error,
},
+ OtaError {
+ error: OtaError,
+ },
}
pub type FatResult = Result;
@@ -106,6 +110,7 @@ impl fmt::Display for FatError {
}
FatError::SNTPError { error } => write!(f, "SNTPError {error:?}"),
FatError::BMSError { error } => write!(f, "BMSError, {error}"),
+ FatError::OtaError { error } => write!(f, "OtaError {error:?}"),
}
}
}
@@ -332,3 +337,9 @@ impl From for FatError {
}
}
}
+
+impl From for FatError {
+ fn from(value: OtaError) -> Self {
+ FatError::OtaError { error: value }
+ }
+}
diff --git a/Software/MainBoard/rust/src/hal/esp.rs b/Software/MainBoard/rust/src/hal/esp.rs
index 4c89ac9..6f576dd 100644
--- a/Software/MainBoard/rust/src/hal/esp.rs
+++ b/Software/MainBoard/rust/src/hal/esp.rs
@@ -10,7 +10,7 @@ use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem;
use crate::hal::shared_flash::MutexFlashStorage;
use alloc::string::ToString;
use alloc::sync::Arc;
-use alloc::{format, string::String, vec, vec::Vec};
+use alloc::{format, string::String, vec::Vec};
use core::net::{IpAddr, Ipv4Addr, SocketAddr};
use core::str::FromStr;
use core::sync::atomic::Ordering;
@@ -21,10 +21,6 @@ use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::{Mutex, MutexGuard};
use embassy_sync::once_lock::OnceLock;
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::partitions::{AppPartitionSubType, FlashRegion};
use esp_hal::gpio::{Input, RtcPinWithResistors};
use esp_hal::rng::Rng;
use esp_hal::rtc_cntl::{
@@ -34,6 +30,7 @@ use esp_hal::rtc_cntl::{
use esp_hal::system::software_reset;
use esp_hal::uart::Uart;
use esp_hal::Blocking;
+use esp_hal_ota::Ota;
use esp_println::println;
use esp_radio::wifi::{
AccessPointConfig, AccessPointInfo, AuthMethod, ClientConfig, ModeConfig, ScanConfig,
@@ -129,12 +126,7 @@ pub struct Esp<'a> {
// RTC-capable GPIO used as external wake source (store the raw peripheral)
pub wake_gpio1: esp_hal::peripherals::GPIO1<'static>,
pub uart0: Uart<'a, Blocking>,
-
- pub ota: Ota<'static, MutexFlashStorage>,
- pub ota_target: &'static mut FlashRegion<'static, MutexFlashStorage>,
- pub current: AppPartitionSubType,
- pub slot0_state: OtaImageState,
- pub slot1_state: OtaImageState,
+ pub ota: &'static mut Ota<&'static mut MutexFlashStorage>,
}
// SAFETY: On this target we never move Esp across OS threads; the firmware runs single-core
@@ -249,50 +241,6 @@ impl Esp<'_> {
Ok((buf, read))
}
- pub(crate) async fn write_ota(&mut self, offset: u32, buf: &[u8]) -> Result<(), FatError> {
- 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_target.write(offset, buf)?;
- self.ota_target.read(offset, read_back)?;
- if buf != read_back {
- info!("Expected {buf:?} but got {read_back:?}");
- bail!(
- "Flash error, read back does not match write buffer at offset {:x}",
- offset
- )
- }
- Ok(())
- }
-
- pub(crate) async fn finalize_ota(&mut self) -> Result<(), FatError> {
- let current = self.ota.current_app_partition()?;
- if self.ota.current_ota_state()? != Valid {
- info!("Validating current slot {current:?} as it was able to ota");
- self.ota.set_current_ota_state(Valid)?;
- }
- let next = match current {
- AppPartitionSubType::Ota0 => AppPartitionSubType::Ota1,
- AppPartitionSubType::Ota1 => AppPartitionSubType::Ota0,
- _ => {
- bail!("Invalid current slot {current:?} for ota");
- }
- };
- self.ota.set_current_app_partition(next)?;
- info!("switched slot");
- self.ota.set_current_ota_state(OtaImageState::New)?;
- info!("switched state for new partition");
- let state_new = self.ota.current_ota_state()?;
- info!("state on new partition now {state_new:?}");
- //determine nextslot crc
-
- self.set_restart_to_conf(true);
- Ok(())
- }
-
pub(crate) fn mode_override_pressed(&mut self) -> bool {
self.boot_button.is_low()
}
@@ -593,14 +541,7 @@ impl Esp<'_> {
duration_in_ms: u64,
mut rtc: MutexGuard,
) -> ! {
- // 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(Valid)
- .expect("Could not set image to valid");
- }
- }
+ //TODO HERE Mark the current OTA image as valid if we reached here while in pending verify.
if duration_in_ms == 0 {
software_reset();
diff --git a/Software/MainBoard/rust/src/hal/mod.rs b/Software/MainBoard/rust/src/hal/mod.rs
index 2107d42..5eb76d7 100644
--- a/Software/MainBoard/rust/src/hal/mod.rs
+++ b/Software/MainBoard/rust/src/hal/mod.rs
@@ -65,24 +65,20 @@ use eeprom24x::{Eeprom24x, SlaveAddr, Storage};
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::blocking_mutex::CriticalSectionMutex;
-use esp_bootloader_esp_idf::partitions::{
- AppPartitionSubType, DataPartitionSubType, FlashRegion, PartitionEntry,
-};
+use esp_bootloader_esp_idf::partitions::{DataPartitionSubType, FlashRegion, PartitionEntry};
use esp_hal::clock::CpuClock;
use esp_hal::gpio::{Input, InputConfig, Pull};
use measurements::{Current, Voltage};
-use crate::fat_error::{ContextExt, FatError, FatResult};
+use crate::fat_error::{FatError, FatResult};
use crate::hal::battery::WCHI2CSlave;
use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem;
use crate::hal::water::TankSensor;
use crate::log::LOG_ACCESS;
use embassy_sync::mutex::Mutex;
use embassy_sync::once_lock::OnceLock;
-use embedded_storage::ReadStorage;
use esp_alloc as _;
use esp_backtrace as _;
-use esp_bootloader_esp_idf::ota::{Ota, OtaImageState};
use esp_hal::delay::Delay;
use esp_hal::i2c::master::{BusTimeout, Config, I2c};
use esp_hal::interrupt::software::SoftwareInterruptControl;
@@ -95,6 +91,7 @@ use esp_hal::time::Rate;
use esp_hal::timer::timg::TimerGroup;
use esp_hal::uart::Uart;
use esp_hal::Blocking;
+use esp_hal_ota::Ota;
use esp_radio::{init, Controller};
use esp_storage::FlashStorage;
use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem};
@@ -138,11 +135,6 @@ pub struct HAL<'a> {
pub board_hal: Box + Send>,
}
-pub struct DetectionRequest {
- pub sensorsa: [Sensor; PLANT_COUNT],
- pub sensorsb: [Sensor; PLANT_COUNT],
-}
-
#[async_trait(?Send)]
pub trait BoardInteraction<'a> {
fn get_tank_sensor(&mut self) -> Result<&mut TankSensor<'a>, FatError>;
@@ -168,7 +160,9 @@ pub trait BoardInteraction<'a> {
async fn can_power(&mut self, state: bool) -> FatResult<()>;
// Return JSON string with autodetected sensors per plant. Default: not supported.
- async fn detect_sensors(&mut self, request: Detection) -> FatResult;
+ async fn detect_sensors(&mut self, _request: Detection) -> FatResult {
+ bail!("Autodetection is only available on v4 HAL with CAN bus");
+ }
async fn progress(&mut self, counter: u32) {
// Indicate progress is active to suppress default wait_infinity blinking
@@ -321,51 +315,15 @@ impl PlantHal {
let pt =
esp_bootloader_esp_idf::partitions::read_partition_table(flash_storage, tablebuffer)?;
- let ota_data = mk_static!(
- PartitionEntry,
- pt.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
- DataPartitionSubType::Ota,
- ))?
- .expect("No OTA data partition found")
- );
-
- let ota_data = mk_static!(
- FlashRegion,
- ota_data.as_embedded_storage(flash_storage_2)
- );
-
- let state_0 = ota_state(AppPartitionSubType::Ota0, ota_data);
- let state_1 = ota_state(AppPartitionSubType::Ota1, ota_data);
- let mut ota = Ota::new(ota_data, 2)?;
- let running = get_current_slot_and_fix_ota_data(&mut ota, state_0, state_1)?;
- let target = next_partition(running)?;
-
- info!("Currently running OTA slot: {running:?}");
- info!("Slot0 state: {state_0:?}");
- info!("Slot1 state: {state_1:?}");
-
- //get current_state and next_state here!
- let ota_target = match target {
- AppPartitionSubType::Ota0 => pt
- .find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App(
- AppPartitionSubType::Ota0,
- ))?
- .context("Partition table invalid no ota0")?,
- AppPartitionSubType::Ota1 => pt
- .find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App(
- AppPartitionSubType::Ota1,
- ))?
- .context("Partition table invalid no ota1")?,
- _ => {
- bail!("Invalid target partition");
- }
- };
-
- let ota_target = mk_static!(PartitionEntry, ota_target);
- let ota_target = mk_static!(
- FlashRegion,
- ota_target.as_embedded_storage(flash_storage)
+ let ota = mk_static!(
+ Ota<&mut MutexFlashStorage>,
+ Ota::new(flash_storage_2).unwrap()
);
+ info!("Ota initialized");
+ let selected = ota.get_currently_booted_partition();
+ let ota_state = ota.get_ota_image_state();
+ info!("Currently running partition slot: {selected:?}");
+ info!("Slot state: {ota_state:?}");
let data_partition = pt
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
@@ -414,10 +372,6 @@ impl PlantHal {
boot_button,
wake_gpio1,
ota,
- ota_target,
- current: running,
- slot0_state: state_0,
- slot1_state: state_1,
uart0,
};
@@ -584,87 +538,6 @@ impl PlantHal {
}
}
-fn ota_state(
- slot: AppPartitionSubType,
- ota_data: &mut FlashRegion,
-) -> 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
- let mut slot_buf = [0u8; 32];
- if slot == AppPartitionSubType::Ota0 {
- let _ = ReadStorage::read(ota_data, 0x0000, &mut slot_buf);
- } else {
- let _ = ReadStorage::read(ota_data, 0x1000, &mut slot_buf);
- }
- let raw_state = u32::from_le_bytes(slot_buf[24..28].try_into().unwrap_or([0xff; 4]));
-
- OtaImageState::try_from(raw_state).unwrap_or(OtaImageState::Undefined)
-}
-
-fn get_current_slot_and_fix_ota_data(
- ota: &mut Ota,
- state0: OtaImageState,
- state1: OtaImageState,
-) -> Result {
- 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_app_partition()?;
- if swap {
- let other = match current {
- AppPartitionSubType::Ota0 => state1,
- AppPartitionSubType::Ota1 => state0,
- _ => OtaImageState::Invalid,
- };
-
- match other {
- OtaImageState::Invalid => {
- bail!(
- "cannot recover slot, as both slots in invalid state {:?} {:?} {:?}",
- current,
- state0,
- state1
- );
- }
- OtaImageState::Aborted => {
- bail!(
- "cannot recover slot, as both slots in invalid state {:?} {:?} {:?}",
- current,
- state0,
- state1
- );
- }
- _ => {}
- }
- info!("Current slot has state {state:?} other state has {other:?} swapping");
- let next = next_partition(current)?;
- ota.set_current_app_partition(next)?;
-
- //we actually booted other slot, than partition table assumes
- return Ok(ota.current_app_partition()?);
- };
- Ok(current)
-}
-
-pub fn next_partition(current: AppPartitionSubType) -> FatResult {
- let next = match current {
- AppPartitionSubType::Ota0 => AppPartitionSubType::Ota1,
- AppPartitionSubType::Ota1 => AppPartitionSubType::Ota0,
- _ => {
- bail!("Current slot is not ota0 or ota1");
- }
- };
- Ok(next)
-}
-
pub async fn esp_time() -> DateTime {
let guard = TIME_ACCESS.get().await.lock().await;
DateTime::from_timestamp_micros(guard.current_time_us() as i64).unwrap()
diff --git a/Software/MainBoard/rust/src/hal/shared_flash.rs b/Software/MainBoard/rust/src/hal/shared_flash.rs
index b206ea5..cef76b3 100644
--- a/Software/MainBoard/rust/src/hal/shared_flash.rs
+++ b/Software/MainBoard/rust/src/hal/shared_flash.rs
@@ -3,7 +3,7 @@ use core::cell::RefCell;
use core::ops::{Deref, DerefMut};
use embassy_sync::blocking_mutex::CriticalSectionMutex;
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
-use embedded_storage::ReadStorage;
+use embedded_storage::{ReadStorage, Storage};
use esp_storage::{FlashStorage, FlashStorageError};
#[derive(Clone)]
@@ -11,6 +11,19 @@ pub struct MutexFlashStorage {
pub(crate) inner: Arc>>>,
}
+impl Storage for MutexFlashStorage {
+ fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
+ self.inner
+ .lock(|f| NorFlash::write(f.borrow_mut().deref_mut(), offset, bytes))
+ }
+}
+
+impl Storage for &mut MutexFlashStorage {
+ fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
+ Storage::write(&mut (**self), offset, bytes)
+ }
+}
+
impl ReadStorage for MutexFlashStorage {
type Error = FlashStorageError;
@@ -25,9 +38,29 @@ impl ReadStorage for MutexFlashStorage {
}
}
-impl embedded_storage::Storage for MutexFlashStorage {
- fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
- NorFlash::write(self, offset, bytes)
+impl ReadStorage for &mut MutexFlashStorage {
+ type Error = FlashStorageError;
+
+ fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), FlashStorageError> {
+ ReadStorage::read(&mut (**self), offset, bytes)
+ }
+
+ fn capacity(&self) -> usize {
+ ReadStorage::capacity(&(**self))
+ }
+}
+
+impl ReadStorage for &MutexFlashStorage {
+ type Error = FlashStorageError;
+
+ fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), FlashStorageError> {
+ self.inner
+ .lock(|f| ReadStorage::read(f.borrow_mut().deref_mut(), offset, bytes))
+ }
+
+ fn capacity(&self) -> usize {
+ self.inner
+ .lock(|f| ReadStorage::capacity(f.borrow().deref()))
}
}
@@ -35,6 +68,10 @@ impl ErrorType for MutexFlashStorage {
type Error = FlashStorageError;
}
+impl ErrorType for &MutexFlashStorage {
+ type Error = FlashStorageError;
+}
+
impl ReadNorFlash for MutexFlashStorage {
const READ_SIZE: usize = 1;
diff --git a/Software/MainBoard/rust/src/hal/v4_hal.rs b/Software/MainBoard/rust/src/hal/v4_hal.rs
index ff8d87c..0d20d4f 100644
--- a/Software/MainBoard/rust/src/hal/v4_hal.rs
+++ b/Software/MainBoard/rust/src/hal/v4_hal.rs
@@ -23,7 +23,6 @@ use esp_hal::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig, Pull}
use esp_hal::i2c::master::I2c;
use esp_hal::twai::{EspTwaiError, EspTwaiFrame, StandardId, Twai, TwaiConfiguration, TwaiMode};
use esp_hal::{twai, Async, Blocking};
-use esp_println::println;
use ina219::address::{Address, Pin};
use ina219::calibration::UnCalibrated;
use ina219::configuration::{Configuration, OperatingMode, Resolution};
diff --git a/Software/MainBoard/rust/src/main.rs b/Software/MainBoard/rust/src/main.rs
index af2942f..96ff5e3 100644
--- a/Software/MainBoard/rust/src/main.rs
+++ b/Software/MainBoard/rust/src/main.rs
@@ -42,6 +42,7 @@ use embassy_sync::once_lock::OnceLock;
use embassy_time::{Duration, Instant, Timer, WithTimeout};
use esp_hal::rom::ets_delay_us;
use esp_hal::system::software_reset;
+use esp_hal_ota::OtaImgState;
use esp_println::{logger, println};
use hal::battery::BatteryState;
use log::LogMessage;
@@ -1242,13 +1243,35 @@ async fn get_version(
let branch = env!("VERGEN_GIT_BRANCH").to_owned();
let hash = &env!("VERGEN_GIT_SHA")[0..8];
- let board = board.board_hal.get_esp();
+ let esp = board.board_hal.get_esp();
+ let current_partition: String = match esp.ota.get_currently_booted_partition() {
+ Some(partition) => match partition {
+ 0 => "OTA0".to_string(),
+ 1 => "OTA1".to_string(),
+ _ => format!("Pentry: {:?}", partition),
+ },
+ None => "Missing otainfo".to_string(),
+ };
+ let state = esp.ota.get_ota_image_state();
+ let current_state: String = match state {
+ Ok(state) => match state {
+ OtaImgState::EspOtaImgNew => "New".to_string(),
+ OtaImgState::EspOtaImgPendingVerify => "PendingVerify".to_string(),
+ OtaImgState::EspOtaImgValid => "Valid".to_string(),
+ OtaImgState::EspOtaImgInvalid => "Invalid".to_string(),
+ OtaImgState::EspOtaImgAborted => "Aborted".to_string(),
+ OtaImgState::EspOtaImgUndefined => "Undefined".to_string(),
+ },
+ Err(err) => {
+ format!("Error: {:?}", err)
+ }
+ };
+
VersionInfo {
git_hash: branch + "@" + hash,
build_time: env!("VERGEN_BUILD_TIMESTAMP").to_owned(),
- current: format!("{:?}", board.current),
- slot0_state: format!("{:?}", board.slot0_state),
- slot1_state: format!("{:?}", board.slot1_state),
+ current: format!("{:?}", current_partition),
+ state: format!("{:?}", current_state),
}
}
@@ -1257,6 +1280,5 @@ struct VersionInfo {
git_hash: String,
build_time: String,
current: String,
- slot0_state: String,
- slot1_state: String,
+ state: String,
}
diff --git a/Software/MainBoard/rust/src/webserver/get_json.rs b/Software/MainBoard/rust/src/webserver/get_json.rs
index d34edcc..107ee90 100644
--- a/Software/MainBoard/rust/src/webserver/get_json.rs
+++ b/Software/MainBoard/rust/src/webserver/get_json.rs
@@ -5,7 +5,7 @@ use crate::plant_state::{MoistureSensorState, PlantState};
use crate::tank::determine_tank_state;
use crate::{get_version, BOARD_ACCESS};
use alloc::format;
-use alloc::string::{String, ToString};
+use alloc::string::String;
use alloc::vec::Vec;
use chrono_tz::Tz;
use core::str::FromStr;
diff --git a/Software/MainBoard/rust/src/webserver/ota.rs b/Software/MainBoard/rust/src/webserver/ota.rs
index c8195df..0a22122 100644
--- a/Software/MainBoard/rust/src/webserver/ota.rs
+++ b/Software/MainBoard/rust/src/webserver/ota.rs
@@ -4,7 +4,8 @@ use crate::BOARD_ACCESS;
use edge_http::io::server::Connection;
use edge_http::Method;
use edge_nal::io::{Read, Write};
-use log::info;
+use esp_hal_ota::OtaError;
+use log::{error, info};
pub(crate) async fn ota_operations(
conn: &mut Connection<'_, T, { N }>,
@@ -28,27 +29,45 @@ where
Some(200)
}
Method::Post => {
- let mut offset = 0_usize;
+ let size = read_up_to_bytes_from_request(conn, Some(4)).await?;
+ let flash_size = u32::from_le_bytes(size[..4].try_into().unwrap());
+ info!("flash size: {flash_size}");
+ let crc32 = read_up_to_bytes_from_request(conn, Some(4)).await?;
+ let target_crc = u32::from_le_bytes(crc32[..4].try_into().unwrap());
+ info!("crc32: {target_crc}");
+
+ BOARD_ACCESS
+ .get()
+ .await
+ .lock()
+ .await
+ .board_hal
+ .get_esp()
+ .ota
+ .ota_begin(flash_size, target_crc)?;
+
let mut chunk = 0;
loop {
let buf = read_up_to_bytes_from_request(conn, Some(4096)).await?;
if buf.is_empty() {
- info!("file request for ota finished");
- let mut board = BOARD_ACCESS.get().await.lock().await;
- board.board_hal.get_esp().finalize_ota().await?;
- break;
+ error!("Upload finished, but no enough data received.");
+ return Err(OtaError::WrongCRC)?;
} else {
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.progress(chunk as u32).await;
// Erase next block if we are at a 4K boundary (including the first block at offset 0)
- info!("erasing and writing block 0x{offset:x}");
- board
- .board_hal
- .get_esp()
- .write_ota(offset as u32, &buf)
- .await?;
+ let finsihed = board.board_hal.get_esp().ota.ota_write_chunk(&buf)?;
+ if finsihed {
+ board.board_hal.get_esp().ota.ota_flush(true, true)?;
+
+ board.board_hal.get_esp().set_restart_to_conf(true);
+ break;
+ }
+ info!(
+ "Progress: {}%",
+ (board.board_hal.get_esp().ota.get_ota_progress() * 100.0) as u8
+ );
}
- offset += buf.len();
chunk += 1;
}
BOARD_ACCESS
diff --git a/Software/MainBoard/rust/src/webserver/post_json.rs b/Software/MainBoard/rust/src/webserver/post_json.rs
index d994eb8..c92fa75 100644
--- a/Software/MainBoard/rust/src/webserver/post_json.rs
+++ b/Software/MainBoard/rust/src/webserver/post_json.rs
@@ -1,6 +1,6 @@
use crate::config::PlantControllerConfig;
use crate::fat_error::FatResult;
-use crate::hal::{esp_set_time, Detection, DetectionRequest};
+use crate::hal::{esp_set_time, Detection};
use crate::webserver::read_up_to_bytes_from_request;
use crate::{do_secure_pump, BOARD_ACCESS};
use alloc::string::{String, ToString};
diff --git a/Software/MainBoard/rust/src_webpack/src/api.ts b/Software/MainBoard/rust/src_webpack/src/api.ts
index 876390b..80aeea9 100644
--- a/Software/MainBoard/rust/src_webpack/src/api.ts
+++ b/Software/MainBoard/rust/src_webpack/src/api.ts
@@ -163,8 +163,7 @@ export interface VersionInfo {
git_hash: string,
build_time: string,
current: string,
- slot0_state: string,
- slot1_state: string,
+ state: string
}
export interface BatteryState {
diff --git a/Software/MainBoard/rust/src_webpack/src/main.ts b/Software/MainBoard/rust/src_webpack/src/main.ts
index f3dcf9f..a9dc729 100644
--- a/Software/MainBoard/rust/src_webpack/src/main.ts
+++ b/Software/MainBoard/rust/src_webpack/src/main.ts
@@ -191,36 +191,70 @@ export class Controller {
}
uploadNewFirmware(file: File) {
- let current = 0;
- let max = 100;
- controller.progressview.addProgress("ota_upload", (current / max) * 100, "Uploading firmeware (" + current + "/" + max + ")")
- const ajax = new XMLHttpRequest();
- ajax.upload.addEventListener("progress", event => {
- current = event.loaded / 1000;
- max = event.total / 1000;
+ const reader = new FileReader();
+ reader.onload = () => {
+ const arrayBuffer = reader.result as ArrayBuffer;
+ const data = new Uint8Array(arrayBuffer);
+ const crc = this.crc32(data);
+ const size = data.length;
+
+ const payload = new Uint8Array(size + 8);
+ const view = new DataView(payload.buffer);
+ view.setUint32(0, size, true);
+ view.setUint32(4, crc, true);
+ payload.set(data, 8);
+
+ let current = 0;
+ let max = 100;
controller.progressview.addProgress("ota_upload", (current / max) * 100, "Uploading firmeware (" + current + "/" + max + ")")
- }, false);
- ajax.addEventListener("load", () => {
- controller.progressview.removeProgress("ota_upload")
- const status = ajax.status;
- if (status >= 200 && status < 300) {
- controller.reboot();
- } else {
- const statusText = ajax.statusText || "";
- const body = ajax.responseText || "";
- toast.error(`OTA update error (${status}${statusText ? ' ' + statusText : ''}): ${body}`);
+ const ajax = new XMLHttpRequest();
+ ajax.upload.addEventListener("progress", event => {
+ current = event.loaded / 1000;
+ max = event.total / 1000;
+ controller.progressview.addProgress("ota_upload", (current / max) * 100, "Uploading firmeware (" + current + "/" + max + ")")
+ }, false);
+ ajax.addEventListener("load", () => {
+ controller.progressview.removeProgress("ota_upload")
+ const status = ajax.status;
+ if (status >= 200 && status < 300) {
+ controller.reboot();
+ } else {
+ const statusText = ajax.statusText || "";
+ const body = ajax.responseText || "";
+ toast.error(`OTA update error (${status}${statusText ? ' ' + statusText : ''}): ${body}`);
+ }
+ }, false);
+ ajax.addEventListener("error", () => {
+ controller.progressview.removeProgress("ota_upload")
+ toast.error("OTA upload failed due to a network error.");
+ }, false);
+ ajax.addEventListener("abort", () => {
+ controller.progressview.removeProgress("ota_upload")
+ toast.error("OTA upload was aborted.");
+ }, false);
+ ajax.open("POST", PUBLIC_URL + "/ota");
+ ajax.send(payload);
+ };
+ reader.onerror = () => {
+ toast.error("Error reading firmware file.");
+ };
+ reader.readAsArrayBuffer(file);
+ }
+
+ private crc32(data: Uint8Array): number {
+ let crc = 0xFFFFFFFF;
+ for (let i = 0; i < data.length; i++) {
+ let byte = data[i];
+ crc ^= byte;
+ for (let j = 0; j < 8; j++) {
+ if (crc & 1) {
+ crc = (crc >>> 1) ^ 0xEDB88320;
+ } else {
+ crc = crc >>> 1;
+ }
}
- }, false);
- ajax.addEventListener("error", () => {
- controller.progressview.removeProgress("ota_upload")
- toast.error("OTA upload failed due to a network error.");
- }, false);
- ajax.addEventListener("abort", () => {
- controller.progressview.removeProgress("ota_upload")
- toast.error("OTA upload was aborted.");
- }, false);
- ajax.open("POST", PUBLIC_URL + "/ota");
- ajax.send(file);
+ }
+ return (crc ^ 0xFFFFFFFF) >>> 0;
}
async version(): Promise {
@@ -264,12 +298,14 @@ export class Controller {
method: "POST",
body: json,
})
- .then(response => response.text())
- .then(text => statusCallback(text))
- .then( _ => {
- controller.progressview.removeProgress("set_config");
- setTimeout(() => { controller.downloadConfig() }, 250)
- })
+ .then(response => response.text())
+ .then(text => statusCallback(text))
+ .then(_ => {
+ controller.progressview.removeProgress("set_config");
+ setTimeout(() => {
+ controller.downloadConfig()
+ }, 250)
+ })
}
async backupConfig(json: string): Promise {
@@ -290,7 +326,6 @@ export class Controller {
method: "POST",
body: pretty
}).then(
-
_ => controller.progressview.removeProgress("write_rtc")
)
}
@@ -378,9 +413,9 @@ export class Controller {
var pretty = JSON.stringify(detection, undefined, 1);
- fetch(PUBLIC_URL + "/detect_sensors", { method: "POST", body: pretty })
+ fetch(PUBLIC_URL + "/detect_sensors", {method: "POST", body: pretty})
.then(response => response.json())
- .then (json => json as Detection)
+ .then(json => json as Detection)
.then(json => {
clearTimeout(timerId);
controller.progressview.removeProgress("detect_sensors");
@@ -531,7 +566,7 @@ export class Controller {
private setCanPower(checked: boolean) {
var body: CanPower = {
- state : checked
+ state: checked
}
var pretty = JSON.stringify(body, undefined, 1);
diff --git a/Software/MainBoard/rust/src_webpack/src/ota.ts b/Software/MainBoard/rust/src_webpack/src/ota.ts
index c407932..dd791ed 100644
--- a/Software/MainBoard/rust/src_webpack/src/ota.ts
+++ b/Software/MainBoard/rust/src_webpack/src/ota.ts
@@ -6,8 +6,8 @@ export class OTAView {
readonly firmware_buildtime: HTMLDivElement;
readonly firmware_githash: HTMLDivElement;
readonly firmware_partition: HTMLDivElement;
- readonly firmware_state0: HTMLDivElement;
- readonly firmware_state1: HTMLDivElement;
+ readonly firmware_state: HTMLDivElement;
+
constructor(controller: Controller) {
(document.getElementById("firmwareview") as HTMLElement).innerHTML = require("./ota.html")
@@ -18,8 +18,7 @@ export class OTAView {
this.firmware_githash = document.getElementById("firmware_githash") as HTMLDivElement;
this.firmware_partition = document.getElementById("firmware_partition") as HTMLDivElement;
- this.firmware_state0 = document.getElementById("firmware_state0") as HTMLDivElement;
- this.firmware_state1 = document.getElementById("firmware_state1") as HTMLDivElement;
+ this.firmware_state = document.getElementById("firmware_state") as HTMLDivElement;
const file = document.getElementById("firmware_file") as HTMLInputElement;
@@ -42,7 +41,6 @@ export class OTAView {
this.firmware_buildtime.innerText = versionInfo.build_time;
this.firmware_githash.innerText = versionInfo.git_hash;
this.firmware_partition.innerText = versionInfo.current;
- this.firmware_state0.innerText = versionInfo.slot0_state;
- this.firmware_state1.innerText = versionInfo.slot1_state;
+ this.firmware_state.innerText = versionInfo.state;
}
}
\ No newline at end of file