store backup now in binary, and let backend serialize/deserialize
This commit is contained in:
@@ -115,7 +115,7 @@ littlefs2-core = "0.1.2"
|
|||||||
# Serialization / codecs
|
# Serialization / codecs
|
||||||
serde = { version = "1.0.228", features = ["derive", "alloc"], default-features = false }
|
serde = { version = "1.0.228", features = ["derive", "alloc"], default-features = false }
|
||||||
serde_json = { version = "1.0.145", default-features = false, features = ["alloc"] }
|
serde_json = { version = "1.0.145", default-features = false, features = ["alloc"] }
|
||||||
bincode = { version = "2.0.1", default-features = false, features = ["derive"] }
|
bincode = { version = "2.0.1", default-features = false, features = ["derive", "alloc"] }
|
||||||
|
|
||||||
# Time and time zones
|
# Time and time zones
|
||||||
chrono = { version = "0.4.42", default-features = false, features = ["iana-time-zone", "alloc", "serde"] }
|
chrono = { version = "0.4.42", default-features = false, features = ["iana-time-zone", "alloc", "serde"] }
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
use crate::hal::PLANT_COUNT;
|
use crate::hal::PLANT_COUNT;
|
||||||
use crate::plant_state::PlantWateringMode;
|
use crate::plant_state::PlantWateringMode;
|
||||||
use alloc::string::String;
|
use alloc::string::{String, ToString};
|
||||||
use core::str::FromStr;
|
use bincode::{Decode, Encode};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Encode, Decode)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct NetworkConfig {
|
pub struct NetworkConfig {
|
||||||
pub ap_ssid: heapless::String<32>,
|
pub ap_ssid: String,
|
||||||
pub ssid: Option<heapless::String<32>>,
|
pub ssid: Option<String>,
|
||||||
pub password: Option<heapless::String<64>>,
|
pub password: Option<String>,
|
||||||
pub mqtt_url: Option<String>,
|
pub mqtt_url: Option<String>,
|
||||||
pub base_topic: Option<heapless::String<64>>,
|
pub base_topic: Option<String>,
|
||||||
pub mqtt_user: Option<String>,
|
pub mqtt_user: Option<String>,
|
||||||
pub mqtt_password: Option<String>,
|
pub mqtt_password: Option<String>,
|
||||||
pub max_wait: u32,
|
pub max_wait: u32,
|
||||||
@@ -19,7 +19,7 @@ pub struct NetworkConfig {
|
|||||||
impl Default for NetworkConfig {
|
impl Default for NetworkConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
ap_ssid: heapless::String::from_str("PlantCtrl Init").unwrap(),
|
ap_ssid: "PlantCtrl Init".to_string(),
|
||||||
ssid: None,
|
ssid: None,
|
||||||
password: None,
|
password: None,
|
||||||
mqtt_url: None,
|
mqtt_url: None,
|
||||||
@@ -31,7 +31,7 @@ impl Default for NetworkConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Encode, Decode)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct NightLampConfig {
|
pub struct NightLampConfig {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
@@ -54,7 +54,7 @@ impl Default for NightLampConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Encode, Decode)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct TankConfig {
|
pub struct TankConfig {
|
||||||
pub tank_sensor_enabled: bool,
|
pub tank_sensor_enabled: bool,
|
||||||
@@ -79,26 +79,26 @@ impl Default for TankConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Encode, Decode)]
|
||||||
pub enum BatteryBoardVersion {
|
pub enum BatteryBoardVersion {
|
||||||
#[default]
|
#[default]
|
||||||
Disabled,
|
Disabled,
|
||||||
WchI2cSlave,
|
WchI2cSlave,
|
||||||
}
|
}
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Encode, Decode)]
|
||||||
pub enum BoardVersion {
|
pub enum BoardVersion {
|
||||||
Initial,
|
Initial,
|
||||||
#[default]
|
#[default]
|
||||||
V4,
|
V4,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Encode, Decode)]
|
||||||
pub struct BoardHardware {
|
pub struct BoardHardware {
|
||||||
pub board: BoardVersion,
|
pub board: BoardVersion,
|
||||||
pub battery: BatteryBoardVersion,
|
pub battery: BatteryBoardVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Encode, Decode)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct PlantControllerConfig {
|
pub struct PlantControllerConfig {
|
||||||
pub hardware: BoardHardware,
|
pub hardware: BoardHardware,
|
||||||
@@ -109,7 +109,7 @@ pub struct PlantControllerConfig {
|
|||||||
pub timezone: Option<String>,
|
pub timezone: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Encode, Decode)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct PlantConfig {
|
pub struct PlantConfig {
|
||||||
pub mode: PlantWateringMode,
|
pub mode: PlantWateringMode,
|
||||||
|
|||||||
@@ -749,10 +749,13 @@ impl Esp<'_> {
|
|||||||
|
|
||||||
let mut builder: McutieBuilder<'_, String, PublishDisplay<String, &str>, 0> =
|
let mut builder: McutieBuilder<'_, String, PublishDisplay<String, &str>, 0> =
|
||||||
McutieBuilder::new(stack, "plant ctrl", mqtt_url);
|
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(
|
builder = builder.with_authentication(
|
||||||
network_config.mqtt_user.as_ref().unwrap().as_str(),
|
mqtt_user,
|
||||||
network_config.mqtt_password.as_ref().unwrap().as_str(),
|
mqtt_password,
|
||||||
);
|
);
|
||||||
info!("With authentification");
|
info!("With authentification");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ mod v4_hal;
|
|||||||
mod water;
|
mod water;
|
||||||
|
|
||||||
use crate::alloc::string::ToString;
|
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::Peripherals;
|
||||||
use esp_hal::peripherals::ADC1;
|
use esp_hal::peripherals::ADC1;
|
||||||
use esp_hal::peripherals::GPIO0;
|
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 set_charge_indicator(&mut self, charging: bool) -> Result<(), FatError>;
|
||||||
async fn deep_sleep(&mut self, duration_in_ms: u64) -> !;
|
async fn deep_sleep(&mut self, duration_in_ms: u64) -> !;
|
||||||
|
|
||||||
|
|
||||||
fn is_day(&self) -> bool;
|
fn is_day(&self) -> bool;
|
||||||
//should be multsampled
|
//should be multsampled
|
||||||
async fn light(&mut self, enable: bool) -> FatResult<()>;
|
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 get_mptt_current(&mut self) -> FatResult<Current>;
|
||||||
async fn can_power(&mut self, state: bool) -> FatResult<()>;
|
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.
|
// Return JSON string with autodetected sensors per plant. Default: not supported.
|
||||||
async fn detect_sensors(&mut self, _request: Detection) -> FatResult<Detection> {
|
async fn detect_sensors(&mut self, _request: Detection) -> FatResult<Detection> {
|
||||||
bail!("Autodetection is only available on v4 HAL with CAN bus");
|
bail!("Autodetection is only available on v4 HAL with CAN bus");
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use crate::fat_error::FatResult;
|
|
||||||
use crate::hal::Box;
|
use crate::hal::Box;
|
||||||
|
use crate::fat_error::FatResult;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use bincode::config::Configuration;
|
use bincode::{Decode, Encode};
|
||||||
use bincode::{config, Decode, Encode};
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use ds323x::ic::DS3231;
|
use ds323x::ic::DS3231;
|
||||||
use ds323x::interface::I2cInterface;
|
use ds323x::interface::I2cInterface;
|
||||||
@@ -19,24 +18,21 @@ use esp_hal::Blocking;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub const X25: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC);
|
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)]
|
#[async_trait(?Send)]
|
||||||
pub trait RTCModuleInteraction {
|
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 get_rtc_time(&mut self) -> FatResult<DateTime<Utc>>;
|
||||||
async fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> FatResult<()>;
|
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)]
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Default, Encode, Decode)]
|
||||||
pub struct BackupHeader {
|
pub struct BackupHeader {
|
||||||
pub timestamp: i64,
|
pub timestamp: i64,
|
||||||
crc16: u16,
|
pub(crate) crc16: u16,
|
||||||
pub size: u16,
|
pub size: u16,
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
@@ -46,7 +42,7 @@ pub struct DS3231Module {
|
|||||||
DS3231,
|
DS3231,
|
||||||
>,
|
>,
|
||||||
|
|
||||||
pub(crate) storage: eeprom24x::Storage<
|
pub storage: eeprom24x::Storage<
|
||||||
I2cDevice<'static, CriticalSectionRawMutex, I2c<'static, Blocking>>,
|
I2cDevice<'static, CriticalSectionRawMutex, I2c<'static, Blocking>>,
|
||||||
B32,
|
B32,
|
||||||
TwoBytes,
|
TwoBytes,
|
||||||
@@ -57,67 +53,6 @@ pub struct DS3231Module {
|
|||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
impl RTCModuleInteraction for DS3231Module {
|
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>> {
|
async fn get_rtc_time(&mut self) -> FatResult<DateTime<Utc>> {
|
||||||
Ok(self.rtc.datetime()?.and_utc())
|
Ok(self.rtc.datetime()?.and_utc())
|
||||||
}
|
}
|
||||||
@@ -126,4 +61,14 @@ impl RTCModuleInteraction for DS3231Module {
|
|||||||
let naive_time = time.naive_utc();
|
let naive_time = time.naive_utc();
|
||||||
Ok(self.rtc.set_datetime(&naive_time)?)
|
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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::config::PlantControllerConfig;
|
|||||||
use crate::fat_error::{ContextExt, FatError, FatResult};
|
use crate::fat_error::{ContextExt, FatError, FatResult};
|
||||||
use crate::hal::battery::BatteryInteraction;
|
use crate::hal::battery::BatteryInteraction;
|
||||||
use crate::hal::esp::{hold_disable, hold_enable, Esp};
|
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::water::TankSensor;
|
||||||
use crate::hal::{
|
use crate::hal::{
|
||||||
BoardInteraction, Detection, FreePeripherals, Moistures, Sensor, I2C_DRIVER, PLANT_COUNT,
|
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::boxed::Box;
|
||||||
use alloc::string::ToString;
|
use alloc::string::ToString;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use bincode::config;
|
||||||
use canapi::id::{classify, plant_id, MessageKind, IDENTIFY_CMD_OFFSET};
|
use canapi::id::{classify, plant_id, MessageKind, IDENTIFY_CMD_OFFSET};
|
||||||
use canapi::SensorSlot;
|
use canapi::SensorSlot;
|
||||||
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
||||||
@@ -32,6 +33,9 @@ use measurements::Resistance;
|
|||||||
use measurements::{Current, Voltage};
|
use measurements::{Current, Voltage};
|
||||||
use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
|
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 MPPT_CURRENT_SHUNT_OHMS: f64 = 0.05_f64;
|
||||||
const TWAI_BAUDRATE: twai::BaudRate = twai::BaudRate::Custom(twai::TimingConfig {
|
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
|
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(())
|
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(
|
async fn wait_for_can_measurements(
|
||||||
@@ -597,6 +654,8 @@ async fn wait_for_can_measurements(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Moistures> for Detection {
|
impl From<Moistures> for Detection {
|
||||||
|
|||||||
@@ -1049,8 +1049,8 @@ async fn wait_infinity(
|
|||||||
exit_hold_blink = !exit_hold_blink;
|
exit_hold_blink = !exit_hold_blink;
|
||||||
|
|
||||||
let progress = core::cmp::min(elapsed, exit_hold_duration);
|
let progress = core::cmp::min(elapsed, exit_hold_duration);
|
||||||
let lit = ((progress.as_millis() as u64 * 8)
|
let lit = ((progress.as_millis() * 8)
|
||||||
/ exit_hold_duration.as_millis() as u64)
|
/ exit_hold_duration.as_millis())
|
||||||
.saturating_add(1)
|
.saturating_add(1)
|
||||||
.min(8) as usize;
|
.min(8) as usize;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use bincode::{Decode, Encode};
|
||||||
use crate::hal::Moistures;
|
use crate::hal::Moistures;
|
||||||
use crate::{config::PlantConfig, hal::HAL, in_time_range};
|
use crate::{config::PlantConfig, hal::HAL, in_time_range};
|
||||||
use chrono::{DateTime, TimeDelta, Utc};
|
use chrono::{DateTime, TimeDelta, Utc};
|
||||||
@@ -70,7 +71,7 @@ impl PumpState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Encode, Decode)]
|
||||||
pub enum PlantWateringMode {
|
pub enum PlantWateringMode {
|
||||||
Off,
|
Off,
|
||||||
TargetMoisture,
|
TargetMoisture,
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
use crate::fat_error::{FatError, FatResult};
|
use crate::fat_error::{FatError, FatResult};
|
||||||
use crate::hal::rtc::X25;
|
use crate::webserver::read_up_to_bytes_from_request;
|
||||||
use crate::BOARD_ACCESS;
|
use crate::BOARD_ACCESS;
|
||||||
use alloc::borrow::ToOwned;
|
use alloc::borrow::ToOwned;
|
||||||
use alloc::format;
|
|
||||||
use alloc::string::{String, ToString};
|
use alloc::string::{String, ToString};
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
use edge_http::io::server::Connection;
|
use edge_http::io::server::Connection;
|
||||||
use edge_nal::io::{Read, Write};
|
use edge_nal::io::{Read, Write};
|
||||||
use log::info;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
@@ -21,48 +19,9 @@ pub(crate) async fn get_backup_config<T, const N: usize>(
|
|||||||
where
|
where
|
||||||
T: Read + Write,
|
T: Read + Write,
|
||||||
{
|
{
|
||||||
// First pass: verify checksum without sending data
|
|
||||||
let mut checksum = X25.digest();
|
|
||||||
let mut chunk = 0_usize;
|
|
||||||
loop {
|
|
||||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
board.board_hal.progress(chunk as u32).await;
|
let backup = board.board_hal.read_backup().await?;
|
||||||
let (buf, len, expected_crc) = board
|
|
||||||
.board_hal
|
|
||||||
.get_rtc_module()
|
|
||||||
.get_backup_config(chunk)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Update checksum with the actual data bytes of this chunk
|
|
||||||
checksum.update(&buf[..len]);
|
|
||||||
|
|
||||||
let is_last = len == 0 || len < buf.len();
|
|
||||||
if is_last {
|
|
||||||
let actual_crc = checksum.finalize();
|
|
||||||
if actual_crc != expected_crc {
|
|
||||||
BOARD_ACCESS
|
|
||||||
.get()
|
|
||||||
.await
|
|
||||||
.lock()
|
|
||||||
.await
|
|
||||||
.board_hal
|
|
||||||
.clear_progress()
|
|
||||||
.await;
|
|
||||||
conn.initiate_response(
|
|
||||||
409,
|
|
||||||
Some(
|
|
||||||
format!("Checksum mismatch expected {expected_crc} got {actual_crc}")
|
|
||||||
.as_str(),
|
|
||||||
),
|
|
||||||
&[],
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
return Ok(Some(409));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
chunk += 1;
|
|
||||||
}
|
|
||||||
// Second pass: stream data
|
// Second pass: stream data
|
||||||
conn.initiate_response(
|
conn.initiate_response(
|
||||||
200,
|
200,
|
||||||
@@ -75,35 +34,7 @@ where
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut chunk = 0_usize;
|
conn.write_all(serde_json::to_string(&backup)?.as_bytes()).await?;
|
||||||
loop {
|
|
||||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
|
||||||
board.board_hal.progress(chunk as u32).await;
|
|
||||||
let (buf, len, _expected_crc) = board
|
|
||||||
.board_hal
|
|
||||||
.get_rtc_module()
|
|
||||||
.get_backup_config(chunk)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if len == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.write_all(&buf[..len]).await?;
|
|
||||||
|
|
||||||
if len < buf.len() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
chunk += 1;
|
|
||||||
}
|
|
||||||
BOARD_ACCESS
|
|
||||||
.get()
|
|
||||||
.await
|
|
||||||
.lock()
|
|
||||||
.await
|
|
||||||
.board_hal
|
|
||||||
.clear_progress()
|
|
||||||
.await;
|
|
||||||
Ok(Some(200))
|
Ok(Some(200))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,39 +44,10 @@ pub(crate) async fn backup_config<T, const N: usize>(
|
|||||||
where
|
where
|
||||||
T: Read + Write,
|
T: Read + Write,
|
||||||
{
|
{
|
||||||
let mut offset = 0_usize;
|
let input = read_up_to_bytes_from_request(conn, Option::None).await?;
|
||||||
let mut buf = [0_u8; 32];
|
|
||||||
|
|
||||||
let mut checksum = X25.digest();
|
|
||||||
|
|
||||||
let mut counter = 0;
|
|
||||||
loop {
|
|
||||||
let to_write = conn.read(&mut buf).await?;
|
|
||||||
if to_write == 0 {
|
|
||||||
info!("backup finished");
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
board.board_hal.progress(counter).await;
|
let config_to_backup = serde_json::from_slice(&input)?;
|
||||||
|
board.board_hal.backup_config(&config_to_backup).await?;
|
||||||
counter += 1;
|
|
||||||
board
|
|
||||||
.board_hal
|
|
||||||
.get_rtc_module()
|
|
||||||
.backup_config(offset, &buf[0..to_write])
|
|
||||||
.await?;
|
|
||||||
checksum.update(&buf[0..to_write]);
|
|
||||||
}
|
|
||||||
offset += to_write;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
|
||||||
board
|
|
||||||
.board_hal
|
|
||||||
.get_rtc_module()
|
|
||||||
.backup_config_finalize(checksum.finalize(), offset)
|
|
||||||
.await?;
|
|
||||||
board.board_hal.clear_progress().await;
|
|
||||||
conn.initiate_response(
|
conn.initiate_response(
|
||||||
200,
|
200,
|
||||||
Some("OK"),
|
Some("OK"),
|
||||||
@@ -166,8 +68,10 @@ where
|
|||||||
T: Read + Write,
|
T: Read + Write,
|
||||||
{
|
{
|
||||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
let header = board.board_hal.get_rtc_module().get_backup_info().await;
|
let info = board.board_hal.backup_info().await;
|
||||||
let json = match header {
|
|
||||||
|
|
||||||
|
let json = match info {
|
||||||
Ok(h) => {
|
Ok(h) => {
|
||||||
let timestamp = DateTime::from_timestamp_millis(h.timestamp).unwrap();
|
let timestamp = DateTime::from_timestamp_millis(h.timestamp).unwrap();
|
||||||
let wbh = WebBackupHeader {
|
let wbh = WebBackupHeader {
|
||||||
|
|||||||
Reference in New Issue
Block a user