Introduce watchdog and serialization improvements

- Added watchdog timer for improved system stability and responsiveness.
- Switched save data serialization to Bincode for better efficiency.
- Enhanced compatibility by supporting fallback to older JSON format.
- Improved logging during flash operations for easier debugging.
- Simplified SavegameManager by managing storage directly.
This commit is contained in:
2026-04-12 20:38:52 +02:00
parent 95f7488fa3
commit b26206eb96
7 changed files with 152 additions and 68 deletions

View File

@@ -140,7 +140,8 @@ macro_rules! mk_static {
impl Esp<'_> {
pub fn get_time(&self) -> DateTime<Utc> {
DateTime::from_timestamp_micros(self.rtc.current_time_us() as i64).unwrap_or(DateTime::UNIX_EPOCH)
DateTime::from_timestamp_micros(self.rtc.current_time_us() as i64)
.unwrap_or(DateTime::UNIX_EPOCH)
}
pub fn set_time(&mut self, time: DateTime<Utc>) {
@@ -517,10 +518,7 @@ impl Esp<'_> {
Ok(*stack)
}
pub fn deep_sleep(
&mut self,
duration_in_ms: u64,
) -> ! {
pub fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
// 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 {
@@ -556,23 +554,24 @@ impl Esp<'_> {
}
/// Load a config from a specific save slot.
pub(crate) async fn load_config_slot(
&mut self,
idx: usize,
) -> FatResult<String> {
pub(crate) async fn load_config_slot(&mut self, idx: usize) -> FatResult<String> {
match self.savegame.load_slot(idx)? {
None => bail!("Slot {idx} is empty or invalid"),
Some(data) => {
Ok(String::from_utf8_lossy(&data).to_string())
}
Some(data) => Ok(String::from_utf8_lossy(&data).to_string()),
}
}
/// Persist a JSON config blob to the next wear-leveling slot.
pub(crate) async fn save_config(&mut self, mut config: Vec<u8>) -> FatResult<()> {
/// Retries once on flash error.
pub(crate) async fn save_config(&mut self, config: Vec<u8>) -> FatResult<()> {
let timestamp = self.get_time().to_rfc3339();
self.savegame.save(config.as_mut_slice(), &timestamp)?;
Ok(())
match self.savegame.save(config.as_slice(), &timestamp) {
Ok(()) => Ok(()),
Err(e) => {
warn!("First save attempt failed: {e:?}. Retrying...");
self.savegame.save(config.as_slice(), &timestamp)
}
}
}
/// Delete a specific save slot by erasing it on flash.
@@ -608,7 +607,14 @@ impl Esp<'_> {
if to_config_mode {
RESTART_TO_CONF = 1;
}
log(LogMessage::RestartToConfig, RESTART_TO_CONF as u32, 0, "", "").await;
log(
LogMessage::RestartToConfig,
RESTART_TO_CONF as u32,
0,
"",
"",
)
.await;
log(
LogMessage::LowVoltage,
LOW_VOLTAGE_DETECTED as u32,
@@ -702,6 +708,7 @@ impl Esp<'_> {
let mqtt_timeout = 15000;
let res = async {
while !MQTT_CONNECTED_EVENT_RECEIVED.load(Ordering::Relaxed) {
crate::hal::PlantHal::feed_watchdog();
Timer::after(Duration::from_millis(100)).await;
}
Ok::<(), FatError>(())
@@ -720,6 +727,7 @@ impl Esp<'_> {
let res = async {
while !MQTT_ROUND_TRIP_RECEIVED.load(Ordering::Relaxed) {
crate::hal::PlantHal::feed_watchdog();
Timer::after(Duration::from_millis(100)).await;
}
Ok::<(), FatError>(())