fix ota abort/invalid switching

This commit is contained in:
2025-10-06 02:43:37 +02:00
parent 894be7c373
commit a3cdd92af8
11 changed files with 224 additions and 192 deletions

12
rust/all.sh Executable file
View 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

View File

@@ -7,5 +7,5 @@ cp index.html.gz ../src/webserver/index.html.gz
cp bundle.js.gz ../src/webserver/bundle.js.gz cp bundle.js.gz ../src/webserver/bundle.js.gz
cd ../ 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 espflash flash --monitor --bootloader bootloader.bin --chip esp32c6 --baud 921600 --partition-table partitions.csv target/riscv32imac-unknown-none-elf/release/plant-ctrl2

View File

@@ -65,6 +65,9 @@ pub enum FatError {
CanBusError { CanBusError {
error: EspTwaiError, error: EspTwaiError,
}, },
SNTPError {
error: sntpc::Error,
},
} }
pub type FatResult<T> = Result<T, FatError>; pub type FatResult<T> = Result<T, FatError>;
@@ -96,6 +99,7 @@ impl fmt::Display for FatError {
FatError::CanBusError { error } => { FatError::CanBusError { error } => {
write!(f, "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 { fn from(value: NorFlashErrorKind) -> Self {
FatError::String { 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 }
}
}

View File

@@ -1,6 +1,6 @@
use crate::bail; use crate::bail;
use crate::config::{NetworkConfig, PlantControllerConfig}; 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 crate::log::{LogMessage, LOG_ACCESS};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::Serialize; 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::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::{Mutex, MutexGuard}; use embassy_sync::mutex::{Mutex, MutexGuard};
use embassy_sync::once_lock::OnceLock; 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 embedded_storage::nor_flash::{check_erase, NorFlash, ReadNorFlash};
use esp_bootloader_esp_idf::ota::OtaImageState::Valid; 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_bootloader_esp_idf::partitions::FlashRegion;
use esp_hal::gpio::{Input, RtcPinWithResistors}; use esp_hal::gpio::{Input, RtcPinWithResistors};
use esp_hal::rng::Rng; use esp_hal::rng::Rng;
@@ -128,7 +128,10 @@ pub struct Esp<'a> {
pub wake_gpio1: esp_hal::peripherals::GPIO1<'static>, pub wake_gpio1: esp_hal::peripherals::GPIO1<'static>,
pub ota: Ota<'static, FlashStorage>, 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 // 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)) 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> { pub(crate) async fn write_ota(&mut self, offset: u32, buf: &[u8]) -> Result<(), FatError> {
if self.ota.current_ota_state() == Ok(OtaImageState::Invalid) { let _ = check_erase(self.ota_target, offset, offset + 4096);
bail!("Invalid OTA state, refusing ota write") self.ota_target.erase(offset, offset + 4096)?;
}
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 mut temp = vec![0; buf.len()]; let mut temp = vec![0; buf.len()];
let read_back = temp.as_mut_slice(); let read_back = temp.as_mut_slice();
//change to nor flash, align writes! //change to nor flash, align writes!
self.ota_next.write(offset, buf)?; self.ota_target.write(offset, buf)?;
self.ota_next.read(offset, read_back)?; self.ota_target.read(offset, read_back)?;
if buf != read_back { if buf != read_back {
info!("Expected {:?} but got {:?}", buf, read_back); info!("Expected {:?} but got {:?}", buf, read_back);
bail!( bail!(
@@ -261,23 +236,16 @@ impl Esp<'_> {
} }
pub(crate) async fn finalize_ota(&mut self) -> Result<(), FatError> { pub(crate) async fn finalize_ota(&mut self) -> Result<(), FatError> {
if self.ota.current_ota_state() == Ok(OtaImageState::Invalid) { let current = self.ota.current_slot()?;
bail!("Invalid OTA state, refusing ota write") if self.ota.current_ota_state()? != OtaImageState::Valid {
} info!(
if self.ota.current_ota_state() == Ok(OtaImageState::Undefined) { "Validating current slot {:?} as it was able to ota",
bail!("Invalid OTA state, refusing ota write") current
} );
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");
self.ota.set_current_ota_state(Valid)?; self.ota.set_current_ota_state(Valid)?;
} }
self.ota.set_current_slot(next_slot)?; self.ota.set_current_slot(current.next())?;
info!("switched slot"); info!("switched slot");
self.ota.set_current_ota_state(OtaImageState::New)?; self.ota.set_current_ota_state(OtaImageState::New)?;
info!("switched state for new partition"); info!("switched state for new partition");
@@ -327,16 +295,19 @@ impl Esp<'_> {
let mut counter = 0; let mut counter = 0;
loop { loop {
let addr: IpAddr = ntp_addrs[0].into(); 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 { match timeout {
Ok(time) => { Ok(result) => {
let time = result?;
info!("Time: {:?}", time); info!("Time: {:?}", time);
return DateTime::from_timestamp(time.seconds as i64, 0) return DateTime::from_timestamp(time.seconds as i64, 0)
.context("Could not convert Sntp result"); .context("Could not convert Sntp result");
} }
Err(e) => { Err(err) => {
warn!("Error: {:?}", e); warn!("sntp timeout, retry: {:?}", err);
counter += 1; counter += 1;
if counter > 10 { if counter > 10 {
bail!("Failed to get time from NTP server"); bail!("Failed to get time from NTP server");

View File

@@ -9,7 +9,6 @@ mod v3_shift_register;
mod v4_hal; mod v4_hal;
mod v4_sensor; mod v4_sensor;
mod water; mod water;
use crate::alloc::string::ToString; use crate::alloc::string::ToString;
use crate::hal::rtc::{DS3231Module, RTCModuleInteraction}; use crate::hal::rtc::{DS3231Module, RTCModuleInteraction};
use esp_hal::peripherals::Peripherals; use esp_hal::peripherals::Peripherals;
@@ -44,6 +43,7 @@ use esp_hal::peripherals::GPIO8;
use esp_hal::peripherals::TWAI0; use esp_hal::peripherals::TWAI0;
use crate::{ use crate::{
bail,
config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig}, config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig},
hal::{ hal::{
battery::{BatteryInteraction, NoBatteryMonitor}, battery::{BatteryInteraction, NoBatteryMonitor},
@@ -83,9 +83,11 @@ use crate::hal::water::TankSensor;
use crate::log::LOG_ACCESS; use crate::log::LOG_ACCESS;
use embassy_sync::mutex::Mutex; use embassy_sync::mutex::Mutex;
use embassy_sync::once_lock::OnceLock; use embassy_sync::once_lock::OnceLock;
use embedded_storage::nor_flash::ReadNorFlash;
use esp_alloc as _; use esp_alloc as _;
use esp_backtrace 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::delay::Delay;
use esp_hal::i2c::master::{BusTimeout, Config, I2c}; use esp_hal::i2c::master::{BusTimeout, Config, I2c};
use esp_hal::pcnt::unit::Unit; use esp_hal::pcnt::unit::Unit;
@@ -101,12 +103,15 @@ use esp_wifi::{init, EspWifiController};
use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem}; use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem};
use littlefs2::object_safe::DynStorage; use littlefs2::object_safe::DynStorage;
use log::{error, info, warn}; use log::{error, info, warn};
use portable_atomic::AtomicBool;
pub static TIME_ACCESS: OnceLock<Mutex<CriticalSectionRawMutex, Rtc>> = OnceLock::new(); pub static TIME_ACCESS: OnceLock<Mutex<CriticalSectionRawMutex, Rtc>> = OnceLock::new();
//Only support for 8 right now! //Only support for 8 right now!
pub const PLANT_COUNT: usize = 8; pub const PLANT_COUNT: usize = 8;
pub static PROGRESS_ACTIVE: AtomicBool = AtomicBool::new(false);
const TANK_MULTI_SAMPLE: usize = 11; const TANK_MULTI_SAMPLE: usize = 11;
pub static I2C_DRIVER: OnceLock< pub static I2C_DRIVER: OnceLock<
embassy_sync::blocking_mutex::Mutex<CriticalSectionRawMutex, RefCell<I2c<Blocking>>>, 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 get_mptt_current(&mut self) -> Result<Current, FatError>;
async fn progress(&mut self, counter: u32) { 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; let current = counter % PLANT_COUNT as u32;
for led in 0..PLANT_COUNT { for led in 0..PLANT_COUNT {
if let Err(err) = self.fault(led, current == led as u32).await { 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; 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 = let pt =
esp_bootloader_esp_idf::partitions::read_partition_table(storage_ota, tablebuffer)?; 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!( let ota_data = mk_static!(
PartitionEntry, PartitionEntry,
pt.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data( pt.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
@@ -320,13 +327,18 @@ impl PlantHal {
ota_data.as_embedded_storage(storage_ota) 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 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!("Currently running OTA slot: {:?}", running);
info!("Current OTA state: {:?}", state); info!("Slot0 state: {:?}", state_0);
let next_slot = get_next_slot(&mut ota)?; info!("Slot1 state: {:?}", state_1);
info!("Next OTA slot: {:?}", next_slot);
let ota_next = match next_slot { //obtain current_state and next_state here!
let ota_target = match target {
Slot::None => { Slot::None => {
panic!("No OTA slot active?"); panic!("No OTA slot active?");
} }
@@ -342,11 +354,11 @@ impl PlantHal {
.context("Partition table invalid no ota1")?, .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 storage_ota = mk_static!(FlashStorage, FlashStorage::new());
let ota_next = mk_static!( let ota_target = mk_static!(
FlashRegion<FlashStorage>, FlashRegion<FlashStorage>,
ota_next.as_embedded_storage(storage_ota) ota_target.as_embedded_storage(storage_ota)
); );
let data_partition = pt let data_partition = pt
@@ -391,7 +403,10 @@ impl PlantHal {
boot_button, boot_button,
wake_gpio1, wake_gpio1,
ota, ota,
ota_next, ota_target,
current: running,
slot0_state: state_0,
slot1_state: state_1,
}; };
//init,reset rtc memory depending on cause //init,reset rtc memory depending on cause
@@ -573,29 +588,75 @@ impl PlantHal {
} }
} }
fn get_next_slot(ota: &mut Ota<FlashStorage>) -> Result<Slot, FatError> { fn ota_state(slot: ota_slot, ota_data: &mut FlashRegion<FlashStorage>) -> OtaImageState {
let next_slot = { // Read and log OTA states for both slots before constructing Ota
let state = ota.current_ota_state().unwrap_or_default(); // Each OTA select entry is 32 bytes: [seq:4][label:20][state:4][crc:4]
let current = ota.current_slot()?; // Offsets within the OTA data partition: slot0 @ 0x0000, slot1 @ 0x1000
match state { if slot == ota_slot::None {
OtaImageState::New => current.next(), return OtaImageState::Undefined;
OtaImageState::PendingVerify => current.next(), }
OtaImageState::Valid => current.next(), 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 => { OtaImageState::Invalid => {
//we actually booted other slot, than partition table assumes bail!(
current "cannot recover slot, as both slots in invalid state {:?} {:?} {:?}",
current,
state0,
state1
);
} }
OtaImageState::Aborted => { OtaImageState::Aborted => {
//we actually booted other slot, than partition table assumes bail!(
current "cannot recover slot, as both slots in invalid state {:?} {:?} {:?}",
} current,
OtaImageState::Undefined => { state0,
//missing bootloader? state1
current.next() );
} }
_ => {}
} }
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> { pub async fn esp_time() -> DateTime<Utc> {

View File

@@ -17,6 +17,7 @@ use crate::config::{NetworkConfig, PlantConfig};
use crate::fat_error::FatResult; use crate::fat_error::FatResult;
use crate::hal::esp::MQTT_STAY_ALIVE; use crate::hal::esp::MQTT_STAY_ALIVE;
use crate::hal::{esp_time, TIME_ACCESS}; use crate::hal::{esp_time, TIME_ACCESS};
use crate::hal::PROGRESS_ACTIVE;
use crate::log::{log, LOG_ACCESS}; use crate::log::{log, LOG_ACCESS};
use crate::tank::{determine_tank_state, TankError, TankState, WATER_FROZEN_THRESH}; use crate::tank::{determine_tank_state, TankError, TankState, WATER_FROZEN_THRESH};
use crate::webserver::http_server; use crate::webserver::http_server;
@@ -822,18 +823,9 @@ async fn publish_firmware_info(
let esp = board.board_hal.get_esp(); let esp = board.board_hal.get_esp();
let _ = esp.mqtt_publish("/firmware/address", ip_address).await; let _ = esp.mqtt_publish("/firmware/address", ip_address).await;
let _ = esp let _ = esp
.mqtt_publish("/firmware/githash", &version.git_hash) .mqtt_publish("/firmware/state", format!("{:?}", &version).as_str())
.await;
let _ = esp
.mqtt_publish("/firmware/buildtime", &version.build_time)
.await; .await;
let _ = esp.mqtt_publish("/firmware/last_online", timezone_time); 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; let _ = esp.mqtt_publish("/state", "online").await;
} }
macro_rules! mk_static { macro_rules! mk_static {
@@ -995,41 +987,48 @@ async fn wait_infinity(
let mut board = BOARD_ACCESS.get().await.lock().await; let mut board = BOARD_ACCESS.get().await.lock().await;
update_charge_indicator(&mut board).await; update_charge_indicator(&mut board).await;
match wait_type { // Skip default blink code when a progress display is active
WaitType::MissingConfig => { if !PROGRESS_ACTIVE.load(Ordering::Relaxed) {
// Keep existing behavior: circular filling pattern match wait_type {
led_count %= 8; WaitType::MissingConfig => {
led_count += 1; // Keep existing behavior: circular filling pattern
for i in 0..8 { led_count %= 8;
let _ = board.board_hal.fault(i, i < led_count).await; 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; WaitType::ConfigButton => {
for i in 0..8 { // Alternating pattern: 1010 1010 -> 0101 0101
let _ = board.board_hal.fault(i, (i + pattern_step) % 2 == 0).await; 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; WaitType::MqttConfig => {
for i in 0..8 { // Moving dot pattern
let _ = board.board_hal.fault(i, i == pattern_step).await; 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; Timer::after_millis(delay).await;
{ {
let mut board = BOARD_ACCESS.get().await.lock().await; let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.general_fault(false).await;
// Clear all LEDs // Skip clearing LEDs when progress is active to avoid interrupting the progress display
for i in 0..8 { if !PROGRESS_ACTIVE.load(Ordering::Relaxed) {
let _ = board.board_hal.fault(i, false).await; 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 hash = &env!("VERGEN_GIT_SHA")[0..8];
let board = board.board_hal.get_esp(); let board = board.board_hal.get_esp();
let ota_slot = board.get_current_ota_slot();
let ota_state = board.get_ota_state();
VersionInfo { VersionInfo {
git_hash: branch + "@" + hash, git_hash: branch + "@" + hash,
build_time: env!("VERGEN_BUILD_TIMESTAMP").to_owned(), build_time: env!("VERGEN_BUILD_TIMESTAMP").to_owned(),
partition: ota_slot, current: format!("{:?}", board.current),
ota_state, slot0_state: format!("{:?}", board.slot0_state),
slot1_state: format!("{:?}", board.slot1_state),
} }
} }
@@ -1111,6 +1108,7 @@ async fn get_version(
struct VersionInfo { struct VersionInfo {
git_hash: String, git_hash: String,
build_time: String, build_time: String,
partition: String, current: String,
ota_state: String, slot0_state: String,
slot1_state: String,
} }

View File

@@ -240,34 +240,6 @@ pub async fn http_server(reboot_now: Arc<AtomicBool>, stack: Stack<'static>) {
info!("Webserver started and waiting for connections"); info!("Webserver started and waiting for connections");
//TODO https if mbed_esp lands //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>( async fn handle_json<'a, T, const N: usize>(

View File

@@ -30,9 +30,6 @@ where
Method::Post => { Method::Post => {
let mut offset = 0_usize; let mut offset = 0_usize;
let mut chunk = 0; 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 { loop {
let buf = read_up_to_bytes_from_request(conn, Some(4096)).await?; let buf = read_up_to_bytes_from_request(conn, Some(4096)).await?;
if buf.len() == 0 { if buf.len() == 0 {

View File

@@ -157,9 +157,9 @@ export interface Moistures {
export interface VersionInfo { export interface VersionInfo {
git_hash: string, git_hash: string,
build_time: string, build_time: string,
partition: string, current: string,
ota_state: string slot0_state: string,
slot1_state: string,
} }
export interface BatteryState { export interface BatteryState {

View File

@@ -1,18 +1,21 @@
<style> <style>
.otakey{ .otakey {
min-width: 100px; min-width: 100px;
} }
.otavalue{
flex-grow: 1; .otavalue {
} flex-grow: 1;
.otaform { }
min-width: 100px;
flex-grow: 1; .otaform {
} min-width: 100px;
.otachooser { flex-grow: 1;
min-width: 100px; }
width: 100%;
} .otachooser {
min-width: 100px;
width: 100%;
}
</style> </style>
<div class="flexcontainer"> <div class="flexcontainer">
<div class="subtitle"> <div class="subtitle">
@@ -28,12 +31,16 @@
<span class="otavalue" id="firmware_githash"></span> <span class="otavalue" id="firmware_githash"></span>
</div> </div>
<div class="flexcontainer"> <div class="flexcontainer">
<span class="otakey">Partition:</span> <span class="otakey">Partition:</span>
<span class="otavalue" id="firmware_partition"></span> <span class="otavalue" id="firmware_partition"></span>
</div> </div>
<div class="flexcontainer"> <div class="flexcontainer">
<span class="otakey">Status:</span> <span class="otakey">State0:</span>
<span class="otavalue" id="firmware_state"></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>
<div class="flexcontainer"> <div class="flexcontainer">

View File

@@ -1,4 +1,4 @@
import { Controller } from "./main"; import {Controller} from "./main";
import {VersionInfo} from "./api"; import {VersionInfo} from "./api";
export class OTAView { export class OTAView {
@@ -6,19 +6,22 @@ export class OTAView {
readonly firmware_buildtime: HTMLDivElement; readonly firmware_buildtime: HTMLDivElement;
readonly firmware_githash: HTMLDivElement; readonly firmware_githash: HTMLDivElement;
readonly firmware_partition: HTMLDivElement; readonly firmware_partition: HTMLDivElement;
readonly firmware_state: HTMLDivElement; readonly firmware_state0: HTMLDivElement;
readonly firmware_state1: HTMLDivElement;
constructor(controller: Controller) { constructor(controller: Controller) {
(document.getElementById("firmwareview") as HTMLElement).innerHTML = require("./ota.html") (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_buildtime = document.getElementById("firmware_buildtime") as HTMLDivElement;
this.firmware_githash = document.getElementById("firmware_githash") as HTMLDivElement; this.firmware_githash = document.getElementById("firmware_githash") as HTMLDivElement;
this.firmware_partition = document.getElementById("firmware_partition") 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; const file = document.getElementById("firmware_file") as HTMLInputElement;
this.file1Upload = file this.file1Upload = file
this.file1Upload.onchange = () => { this.file1Upload.onchange = () => {
@@ -38,7 +41,8 @@ export class OTAView {
setVersion(versionInfo: VersionInfo) { setVersion(versionInfo: VersionInfo) {
this.firmware_buildtime.innerText = versionInfo.build_time; this.firmware_buildtime.innerText = versionInfo.build_time;
this.firmware_githash.innerText = versionInfo.git_hash; this.firmware_githash.innerText = versionInfo.git_hash;
this.firmware_partition.innerText = versionInfo.partition; this.firmware_partition.innerText = versionInfo.current;
this.firmware_state.innerText = versionInfo.ota_state; this.firmware_state0.innerText = versionInfo.slot0_state;
this.firmware_state1.innerText = versionInfo.slot1_state;
} }
} }