From f1c85d1d74b63f3b99059c2c65a9d19e4b9ed4df Mon Sep 17 00:00:00 2001 From: Empire Phoenix Date: Sun, 26 Apr 2026 20:46:52 +0200 Subject: [PATCH] Migrate serialization from Bincode to Postcard - Replaced Bincode with Postcard for serialization/deserialization across configs and save operations. - Simplified struct derives by removing `bincode`-specific traits. - Updated `Cargo.toml` and `Cargo.lock` to include `postcard` and dependencies. - Added padding stripping for deserialization and improved error handling. - Adjusted serialization logic in `savegame_manager.rs` and related modules. --- Software/MainBoard/rust/Cargo.lock | 50 ++++++++++++++++++- Software/MainBoard/rust/Cargo.toml | 2 +- Software/MainBoard/rust/src/config.rs | 17 +++---- Software/MainBoard/rust/src/fat_error.rs | 12 +---- Software/MainBoard/rust/src/hal/esp.rs | 10 +++- Software/MainBoard/rust/src/hal/mod.rs | 3 +- Software/MainBoard/rust/src/hal/rtc.rs | 3 +- .../rust/src/hal/savegame_manager.rs | 18 ++++++- Software/MainBoard/rust/src/hal/v4_hal.rs | 11 ++-- Software/MainBoard/rust/src/plant_state.rs | 3 +- 10 files changed, 91 insertions(+), 38 deletions(-) diff --git a/Software/MainBoard/rust/Cargo.lock b/Software/MainBoard/rust/Cargo.lock index adb3691..4da74d5 100644 --- a/Software/MainBoard/rust/Cargo.lock +++ b/Software/MainBoard/rust/Cargo.lock @@ -83,7 +83,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" dependencies = [ "bincode_derive", - "serde", "unty", ] @@ -225,6 +224,15 @@ dependencies = [ "regex", ] +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + [[package]] name = "const-default" version = "1.0.0" @@ -746,6 +754,12 @@ dependencies = [ "embedded-hal 1.0.0", ] +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + [[package]] name = "embedded-io" version = "0.6.1" @@ -1884,7 +1898,6 @@ name = "plant-ctrl2" version = "0.1.0" dependencies = [ "async-trait", - "bincode", "bytemuck", "canapi", "chrono", @@ -1926,6 +1939,7 @@ dependencies = [ "option-lock", "pca9535", "portable-atomic", + "postcard", "serde", "serde_json", "sntpc", @@ -1971,6 +1985,18 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2404,6 +2430,26 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "thread_local" version = "1.1.9" diff --git a/Software/MainBoard/rust/Cargo.toml b/Software/MainBoard/rust/Cargo.toml index ffa0418..bd3cbeb 100644 --- a/Software/MainBoard/rust/Cargo.toml +++ b/Software/MainBoard/rust/Cargo.toml @@ -91,7 +91,7 @@ embedded-savegame = { version = "0.3.0" } # Serialization / codecs serde = { version = "1.0.228", features = ["derive", "alloc"], default-features = false } serde_json = { version = "1.0.145", default-features = false, features = ["alloc"] } -bincode = { version = "2.0.1", default-features = false, features = ["derive", "alloc"] } +postcard = { version = "1.1.3", default-features = false, features = ["alloc"] } # Time and time zones chrono = { version = "0.4.42", default-features = false, features = ["iana-time-zone", "alloc", "serde"] } diff --git a/Software/MainBoard/rust/src/config.rs b/Software/MainBoard/rust/src/config.rs index 2f1b66c..041cbb3 100644 --- a/Software/MainBoard/rust/src/config.rs +++ b/Software/MainBoard/rust/src/config.rs @@ -1,10 +1,9 @@ use crate::hal::PLANT_COUNT; use crate::plant_state::PlantWateringMode; use alloc::string::{String, ToString}; -use bincode::{Decode, Encode}; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Encode, Decode)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(default)] pub struct NetworkConfig { pub ap_ssid: String, @@ -31,7 +30,7 @@ impl Default for NetworkConfig { } } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Encode, Decode)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(default)] pub struct NightLampConfig { pub enabled: bool, @@ -54,7 +53,7 @@ impl Default for NightLampConfig { } } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Encode, Decode)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(default)] pub struct TankConfig { pub tank_sensor_enabled: bool, @@ -79,20 +78,20 @@ impl Default for TankConfig { } } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Encode, Decode)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] pub enum BatteryBoardVersion { #[default] Disabled, WchI2cSlave, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Encode, Decode)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] pub enum BoardVersion { Initial, #[default] V4, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Encode, Decode)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] pub struct BoardHardware { pub board: BoardVersion, pub battery: BatteryBoardVersion, @@ -100,7 +99,7 @@ pub struct BoardHardware { pub pump_corrosion_protection: bool, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Encode, Decode)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] #[serde(default)] pub struct PlantControllerConfig { pub hardware: BoardHardware, @@ -111,7 +110,7 @@ pub struct PlantControllerConfig { pub timezone: Option, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Encode, Decode)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(default)] pub struct PlantConfig { pub mode: PlantWateringMode, diff --git a/Software/MainBoard/rust/src/fat_error.rs b/Software/MainBoard/rust/src/fat_error.rs index 9588f0e..0032b1e 100644 --- a/Software/MainBoard/rust/src/fat_error.rs +++ b/Software/MainBoard/rust/src/fat_error.rs @@ -230,16 +230,8 @@ impl From>> for FatError { } } -impl From for FatError { - fn from(value: bincode::error::DecodeError) -> Self { - FatError::Eeprom24x { - error: format!("{value:?}"), - } - } -} - -impl From for FatError { - fn from(value: bincode::error::EncodeError) -> Self { +impl From for FatError { + fn from(value: postcard::Error) -> Self { FatError::Eeprom24x { error: format!("{value:?}"), } diff --git a/Software/MainBoard/rust/src/hal/esp.rs b/Software/MainBoard/rust/src/hal/esp.rs index 7a2bdbf..503cd52 100644 --- a/Software/MainBoard/rust/src/hal/esp.rs +++ b/Software/MainBoard/rust/src/hal/esp.rs @@ -582,7 +582,15 @@ impl Esp<'_> { /// Retries once on flash error. pub(crate) async fn save_config(&mut self, config: Vec) -> FatResult<()> { let timestamp = self.get_time().to_rfc3339(); - self.savegame.save(config.as_slice(), ×tamp) + self.savegame.save(config.as_slice(), ×tamp)?; + + match self.savegame.load_latest()? { + None => bail!("Config save verification failed: no latest save found"), + Some(data) => { + let _: PlantControllerConfig = serde_json::from_slice(&data)?; + Ok(()) + } + } } /// Delete a specific save slot by erasing it on flash. diff --git a/Software/MainBoard/rust/src/hal/mod.rs b/Software/MainBoard/rust/src/hal/mod.rs index 1150916..75641c2 100644 --- a/Software/MainBoard/rust/src/hal/mod.rs +++ b/Software/MainBoard/rust/src/hal/mod.rs @@ -51,7 +51,6 @@ use alloc::boxed::Box; use alloc::format; use alloc::sync::Arc; use async_trait::async_trait; -use bincode::{Decode, Encode}; use canapi::SensorSlot; use chrono::{DateTime, FixedOffset, Utc}; use core::cell::RefCell; @@ -118,7 +117,7 @@ pub static I2C_DRIVER: OnceLock< embassy_sync::blocking_mutex::Mutex>>, > = OnceLock::new(); -#[derive(Debug, PartialEq, Clone, Copy, Encode, Decode)] +#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)] pub enum Sensor { A, B, diff --git a/Software/MainBoard/rust/src/hal/rtc.rs b/Software/MainBoard/rust/src/hal/rtc.rs index ef7eea9..5efd271 100644 --- a/Software/MainBoard/rust/src/hal/rtc.rs +++ b/Software/MainBoard/rust/src/hal/rtc.rs @@ -1,7 +1,6 @@ use crate::fat_error::FatResult; use crate::hal::Box; use async_trait::async_trait; -use bincode::{Decode, Encode}; use chrono::{DateTime, Utc}; use ds323x::ic::DS3231; use ds323x::interface::I2cInterface; @@ -29,7 +28,7 @@ pub trait RTCModuleInteraction { fn read(&mut self, offset: u32, data: &mut [u8]) -> FatResult<()>; } -#[derive(Serialize, Deserialize, PartialEq, Debug, Default, Encode, Decode)] +#[derive(Serialize, Deserialize, PartialEq, Debug, Default)] pub struct BackupHeader { pub timestamp: i64, pub(crate) crc16: u16, diff --git a/Software/MainBoard/rust/src/hal/savegame_manager.rs b/Software/MainBoard/rust/src/hal/savegame_manager.rs index 04aeb15..4d1c5b8 100644 --- a/Software/MainBoard/rust/src/hal/savegame_manager.rs +++ b/Software/MainBoard/rust/src/hal/savegame_manager.rs @@ -1,3 +1,4 @@ +use alloc::string::ToString; use alloc::vec::Vec; use embedded_savegame::storage::{Flash, Storage}; use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; @@ -32,6 +33,19 @@ struct ParsedSave<'a> { data: &'a [u8], } +fn strip_padding(data: &[u8]) -> &[u8] { + let mut end = data.len(); + while end > 0 { + let b = data[end - 1]; + if b == 0x00 || b == 0xFF { + end -= 1; + } else { + break; + } + } + &data[..end] +} + fn parse_save(data: &[u8]) -> FatResult> { if data.len() < SAVE_HEADER_LEN { return Err(FatError::String { @@ -60,7 +74,7 @@ fn parse_save(data: &[u8]) -> FatResult> { Ok(ParsedSave { created_at, - data: &data[timestamp_end..], + data: strip_padding(&data[timestamp_end..]), }) } @@ -240,7 +254,7 @@ impl SavegameManager { saves.push(SaveInfo { idx, len: 0, - created_at: None, + created_at: Some(err.to_string()), }); } } diff --git a/Software/MainBoard/rust/src/hal/v4_hal.rs b/Software/MainBoard/rust/src/hal/v4_hal.rs index 228fe78..5a9bc3e 100644 --- a/Software/MainBoard/rust/src/hal/v4_hal.rs +++ b/Software/MainBoard/rust/src/hal/v4_hal.rs @@ -12,7 +12,6 @@ use crate::log::{log, LogMessage}; use alloc::boxed::Box; use alloc::string::ToString; use async_trait::async_trait; -use bincode::config; use canapi::id::{classify, plant_id, MessageKind, IDENTIFY_CMD_OFFSET}; use canapi::SensorSlot; use chrono::{DateTime, FixedOffset, Utc}; @@ -36,7 +35,6 @@ use measurements::{Current, Voltage}; use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; pub const BACKUP_HEADER_MAX_SIZE: usize = 64; -const CONFIG: config::Configuration = config::standard(); const MPPT_CURRENT_SHUNT_OHMS: f64 = 0.05_f64; const TWAI_BAUDRATE: twai::BaudRate = twai::BaudRate::Custom(twai::TimingConfig { @@ -543,7 +541,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> { } async fn backup_config(&mut self, controller_config: &PlantControllerConfig) -> FatResult<()> { let mut buffer: [u8; 4096 - BACKUP_HEADER_MAX_SIZE] = [0; 4096 - BACKUP_HEADER_MAX_SIZE]; - let length = bincode::encode_into_slice(controller_config, &mut buffer, CONFIG)?; + let length = postcard::to_slice(controller_config, &mut buffer)?.len(); info!("Writing backup config of size {}", length); let mut checksum = X25.digest(); checksum.update(&buffer[..length]); @@ -556,7 +554,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> { size: length as u16, }; info!("Header is {:?}", header); - bincode::encode_into_slice(&header, &mut header_page_buffer, CONFIG)?; + postcard::to_slice(&header, &mut header_page_buffer)?; info!("Header is serialized"); self.get_rtc_module().write(0, &header_page_buffer)?; info!("Header written"); @@ -600,7 +598,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> { bail!("CRC mismatch in backup data") } info!("CRC is correct"); - let (decoded, _) = bincode::decode_from_slice(&store[..], CONFIG)?; + let decoded = postcard::from_bytes(&store[..])?; info!("Backup data decoded"); Ok(decoded) } @@ -609,8 +607,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> { let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE]; self.get_rtc_module().read(0, &mut header_page_buffer)?; info!("Read header page"); - let info: Result<(BackupHeader, usize), bincode::error::DecodeError> = - bincode::decode_from_slice(&header_page_buffer[..], CONFIG); + let info = postcard::take_from_bytes::(&header_page_buffer[..]); info!("decoding header: {:?}", info); let (header, _) = info.context("Could not read backup header")?; Ok(header) diff --git a/Software/MainBoard/rust/src/plant_state.rs b/Software/MainBoard/rust/src/plant_state.rs index 4d9356e..d179106 100644 --- a/Software/MainBoard/rust/src/plant_state.rs +++ b/Software/MainBoard/rust/src/plant_state.rs @@ -1,6 +1,5 @@ use crate::hal::Moistures; use crate::{config::PlantConfig, hal::HAL, in_time_range}; -use bincode::{Decode, Encode}; use chrono::{DateTime, TimeDelta, Utc}; use chrono_tz::Tz; use serde::{Deserialize, Serialize}; @@ -73,7 +72,7 @@ impl PumpState { } } -#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Encode, Decode)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] pub enum PlantWateringMode { Off, TargetMoisture,