store backup now in binary, and let backend serialize/deserialize

This commit is contained in:
2026-04-05 13:30:11 +02:00
parent 1fa765a5d8
commit 4d4fcbe33b
9 changed files with 121 additions and 204 deletions

View File

@@ -749,10 +749,13 @@ impl Esp<'_> {
let mut builder: McutieBuilder<'_, String, PublishDisplay<String, &str>, 0> =
McutieBuilder::new(stack, "plant ctrl", mqtt_url);
if network_config.mqtt_user.is_some() && network_config.mqtt_password.is_some() {
if let (Some(mqtt_user), Some(mqtt_password)) = (
network_config.mqtt_user.as_ref(),
network_config.mqtt_password.as_ref(),
) {
builder = builder.with_authentication(
network_config.mqtt_user.as_ref().unwrap().as_str(),
network_config.mqtt_password.as_ref().unwrap().as_str(),
mqtt_user,
mqtt_password,
);
info!("With authentification");
}

View File

@@ -10,7 +10,7 @@ mod v4_hal;
mod water;
use crate::alloc::string::ToString;
use crate::hal::rtc::{DS3231Module, RTCModuleInteraction};
use crate::hal::rtc::{BackupHeader, DS3231Module, RTCModuleInteraction};
use esp_hal::peripherals::Peripherals;
use esp_hal::peripherals::ADC1;
use esp_hal::peripherals::GPIO0;
@@ -150,6 +150,7 @@ pub trait BoardInteraction<'a> {
async fn set_charge_indicator(&mut self, charging: bool) -> Result<(), FatError>;
async fn deep_sleep(&mut self, duration_in_ms: u64) -> !;
fn is_day(&self) -> bool;
//should be multsampled
async fn light(&mut self, enable: bool) -> FatResult<()>;
@@ -164,6 +165,10 @@ pub trait BoardInteraction<'a> {
async fn get_mptt_current(&mut self) -> FatResult<Current>;
async fn can_power(&mut self, state: bool) -> FatResult<()>;
async fn backup_config(&mut self, config: &PlantControllerConfig) -> FatResult<()>;
async fn read_backup(&mut self) -> FatResult<PlantControllerConfig>;
async fn backup_info(&mut self) -> FatResult<BackupHeader>;
// Return JSON string with autodetected sensors per plant. Default: not supported.
async fn detect_sensors(&mut self, _request: Detection) -> FatResult<Detection> {
bail!("Autodetection is only available on v4 HAL with CAN bus");

View File

@@ -1,8 +1,7 @@
use crate::fat_error::FatResult;
use crate::hal::Box;
use crate::fat_error::FatResult;
use async_trait::async_trait;
use bincode::config::Configuration;
use bincode::{config, Decode, Encode};
use bincode::{Decode, Encode};
use chrono::{DateTime, Utc};
use ds323x::ic::DS3231;
use ds323x::interface::I2cInterface;
@@ -19,24 +18,21 @@ use esp_hal::Blocking;
use serde::{Deserialize, Serialize};
pub const X25: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC);
const CONFIG: Configuration = config::standard();
pub const EEPROM_PAGE: usize = 32;
//
#[async_trait(?Send)]
pub trait RTCModuleInteraction {
async fn get_backup_info(&mut self) -> FatResult<BackupHeader>;
async fn get_backup_config(&mut self, chunk: usize) -> FatResult<([u8; 32], usize, u16)>;
async fn backup_config(&mut self, offset: usize, bytes: &[u8]) -> FatResult<()>;
async fn backup_config_finalize(&mut self, crc: u16, length: usize) -> FatResult<()>;
async fn get_rtc_time(&mut self) -> FatResult<DateTime<Utc>>;
async fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> FatResult<()>;
fn write(&mut self, offset: u32, data: &[u8]) -> FatResult<()>;
fn read(&mut self, offset:u32, data: &mut [u8]) -> FatResult<()>;
}
//
const BACKUP_HEADER_MAX_SIZE: usize = 64;
#[derive(Serialize, Deserialize, PartialEq, Debug, Default, Encode, Decode)]
pub struct BackupHeader {
pub timestamp: i64,
crc16: u16,
pub(crate) crc16: u16,
pub size: u16,
}
//
@@ -46,7 +42,7 @@ pub struct DS3231Module {
DS3231,
>,
pub(crate) storage: eeprom24x::Storage<
pub storage: eeprom24x::Storage<
I2cDevice<'static, CriticalSectionRawMutex, I2c<'static, Blocking>>,
B32,
TwoBytes,
@@ -57,67 +53,6 @@ pub struct DS3231Module {
#[async_trait(?Send)]
impl RTCModuleInteraction for DS3231Module {
async fn get_backup_info(&mut self) -> FatResult<BackupHeader> {
let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
self.storage.read(0, &mut header_page_buffer)?;
let (header, len): (BackupHeader, usize) =
bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?;
log::info!("Raw header is {header_page_buffer:?} with size {len}");
Ok(header)
}
async fn get_backup_config(&mut self, chunk: usize) -> FatResult<([u8; 32], usize, u16)> {
let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
self.storage.read(0, &mut header_page_buffer)?;
let (header, _header_size): (BackupHeader, usize) =
bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?;
let mut buf = [0_u8; 32];
let offset = chunk * buf.len() + BACKUP_HEADER_MAX_SIZE;
let end: usize = header.size as usize + BACKUP_HEADER_MAX_SIZE;
let current_end = offset + buf.len();
let chunk_size = if current_end > end {
end - offset
} else {
buf.len()
};
if chunk_size == 0 {
Ok((buf, 0, header.crc16))
} else {
self.storage.read(offset as u32, &mut buf)?;
//&buf[..chunk_size];
Ok((buf, chunk_size, header.crc16))
}
}
async fn backup_config(&mut self, offset: usize, bytes: &[u8]) -> FatResult<()> {
//skip header and write after
self.storage
.write((BACKUP_HEADER_MAX_SIZE + offset) as u32, bytes)?;
Ok(())
}
async fn backup_config_finalize(&mut self, crc: u16, length: usize) -> FatResult<()> {
let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
let time = self.get_rtc_time().await?.timestamp_millis();
let header = BackupHeader {
crc16: crc,
timestamp: time,
size: length as u16,
};
let config = config::standard();
let encoded = bincode::encode_into_slice(&header, &mut header_page_buffer, config)?;
log::info!("Raw header is {header_page_buffer:?} with size {encoded}");
self.storage.write(0, &header_page_buffer)?;
Ok(())
}
async fn get_rtc_time(&mut self) -> FatResult<DateTime<Utc>> {
Ok(self.rtc.datetime()?.and_utc())
}
@@ -126,4 +61,14 @@ impl RTCModuleInteraction for DS3231Module {
let naive_time = time.naive_utc();
Ok(self.rtc.set_datetime(&naive_time)?)
}
fn write(&mut self, offset: u32, data: &[u8]) -> FatResult<()> {
self.storage.write(offset, data)?;
Ok(())
}
fn read(&mut self, offset:u32, data: &mut [u8]) -> FatResult<()> {
self.storage.read(offset, data)?;
Ok(())
}
}

View File

@@ -3,7 +3,7 @@ use crate::config::PlantControllerConfig;
use crate::fat_error::{ContextExt, FatError, FatResult};
use crate::hal::battery::BatteryInteraction;
use crate::hal::esp::{hold_disable, hold_enable, Esp};
use crate::hal::rtc::RTCModuleInteraction;
use crate::hal::rtc::{BackupHeader, RTCModuleInteraction, EEPROM_PAGE, X25};
use crate::hal::water::TankSensor;
use crate::hal::{
BoardInteraction, Detection, FreePeripherals, Moistures, Sensor, I2C_DRIVER, PLANT_COUNT,
@@ -13,6 +13,7 @@ use crate::log::{LogMessage, LOG_ACCESS};
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 embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
@@ -32,6 +33,9 @@ use measurements::Resistance;
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 {
baud_rate_prescaler: 200, // 40MHz / 200 * 2 = 100 on C6, 100 * 20 = 2000 divisor, 40MHz / 2000 = 20kHz
@@ -538,6 +542,59 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
}
Ok(())
}
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 mut checksum = X25.digest();
checksum.update(&buffer[..length]);
let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
let time = self.rtc_module.get_rtc_time().await?.timestamp_millis();
let header = BackupHeader {
crc16: checksum.finalize(),
timestamp: time,
size: length as u16,
};
bincode::encode_into_slice(&header, &mut header_page_buffer, CONFIG)?;
self.get_rtc_module().write(0, &header_page_buffer)?;
let mut to_write = length;
let mut chunk: usize = 0;
while to_write > 0 {
self.progress(chunk as u32).await;
let start = BACKUP_HEADER_MAX_SIZE + chunk* EEPROM_PAGE;
let end = start + crate::hal::rtc::EEPROM_PAGE;
let part = &buffer[start..end];
to_write -= part.len();
chunk += 1;
self.get_rtc_module().write((1 + chunk) as u32, part)?;
}
self.clear_progress().await;
Ok(())
}
async fn read_backup(&mut self) -> FatResult<PlantControllerConfig> {
let info = self.backup_info().await?;
let mut store = alloc::vec![0_u8; info.size as usize];
self.rtc_module.read(BACKUP_HEADER_MAX_SIZE as u32, store.as_mut_slice())?;
let mut checksum = X25.digest();
checksum.update(&store[..]);
let crc = checksum.finalize();
if crc != info.crc16 {
bail!("CRC mismatch in backup data")
}
let (decoded, _) = bincode::decode_from_slice(&store[..], CONFIG)?;
Ok(decoded)
}
async fn backup_info(&mut self) -> FatResult<BackupHeader> {
let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
self.get_rtc_module().read(0, &mut header_page_buffer)?;
let info: Result<(BackupHeader, usize), bincode::error::DecodeError> =
bincode::decode_from_slice(&header_page_buffer[..], CONFIG);
info.map(|(header, _)| header).map_err(|e| FatError::String {error:"Could not read backup header: ".to_string() + &e.to_string()})
}
}
async fn wait_for_can_measurements(
@@ -597,6 +654,8 @@ async fn wait_for_can_measurements(
}
}
}
}
impl From<Moistures> for Detection {