- 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.
686 lines
24 KiB
Rust
686 lines
24 KiB
Rust
use esp_hal::uart::Config as UartConfig;
|
||
use lib_bms_protocol::BmsReadable;
|
||
pub(crate) mod battery;
|
||
// mod can_api; // replaced by external canapi crate
|
||
pub mod esp;
|
||
pub(crate) mod rtc;
|
||
pub(crate) mod savegame_manager;
|
||
mod shared_flash;
|
||
mod v4_hal;
|
||
mod water;
|
||
|
||
use crate::alloc::string::ToString;
|
||
use crate::hal::rtc::{BackupHeader, DS3231Module, RTCModuleInteraction};
|
||
use esp_hal::peripherals::Peripherals;
|
||
use esp_hal::peripherals::ADC1;
|
||
use esp_hal::peripherals::GPIO0;
|
||
use esp_hal::peripherals::GPIO10;
|
||
use esp_hal::peripherals::GPIO11;
|
||
use esp_hal::peripherals::GPIO12;
|
||
use esp_hal::peripherals::GPIO13;
|
||
use esp_hal::peripherals::GPIO14;
|
||
use esp_hal::peripherals::GPIO15;
|
||
use esp_hal::peripherals::GPIO16;
|
||
use esp_hal::peripherals::GPIO17;
|
||
use esp_hal::peripherals::GPIO18;
|
||
use esp_hal::peripherals::GPIO2;
|
||
use esp_hal::peripherals::GPIO21;
|
||
use esp_hal::peripherals::GPIO22;
|
||
use esp_hal::peripherals::GPIO23;
|
||
use esp_hal::peripherals::GPIO27;
|
||
use esp_hal::peripherals::GPIO3;
|
||
use esp_hal::peripherals::GPIO4;
|
||
use esp_hal::peripherals::GPIO5;
|
||
use esp_hal::peripherals::GPIO6;
|
||
use esp_hal::peripherals::GPIO7;
|
||
use esp_hal::peripherals::GPIO8;
|
||
use esp_hal::peripherals::TWAI0;
|
||
use lib_bms_protocol::ProtocolVersion;
|
||
|
||
use crate::{
|
||
bail,
|
||
config::{BatteryBoardVersion, PlantControllerConfig},
|
||
hal::{
|
||
battery::{BatteryInteraction, NoBatteryMonitor},
|
||
esp::Esp,
|
||
},
|
||
log::log,
|
||
log::LogMessage,
|
||
};
|
||
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;
|
||
use ds323x::ic::DS3231;
|
||
use ds323x::interface::I2cInterface;
|
||
use ds323x::{DateTimeAccess, Ds323x};
|
||
use eeprom24x::addr_size::TwoBytes;
|
||
use eeprom24x::page_size::B32;
|
||
use eeprom24x::unique_serial::No;
|
||
use eeprom24x::{Eeprom24x, SlaveAddr, Storage};
|
||
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||
use embassy_sync::blocking_mutex::CriticalSectionMutex;
|
||
use esp_bootloader_esp_idf::partitions::{
|
||
AppPartitionSubType, DataPartitionSubType, FlashRegion, PartitionEntry, PartitionTable,
|
||
PartitionType,
|
||
};
|
||
use esp_hal::clock::CpuClock;
|
||
use esp_hal::gpio::{Input, InputConfig, Pull};
|
||
use measurements::{Current, Voltage};
|
||
|
||
use crate::fat_error::{ContextExt, FatError, FatResult};
|
||
use crate::hal::battery::WCHI2CSlave;
|
||
use crate::hal::savegame_manager::SavegameManager;
|
||
use crate::hal::water::TankSensor;
|
||
use embassy_sync::mutex::Mutex;
|
||
use embassy_sync::once_lock::OnceLock;
|
||
use embedded_storage::nor_flash::RmwNorFlashStorage;
|
||
use embedded_storage::ReadStorage;
|
||
use esp_alloc as _;
|
||
use esp_backtrace as _;
|
||
use esp_bootloader_esp_idf::ota::{Ota, OtaImageState};
|
||
use esp_hal::delay::Delay;
|
||
use esp_hal::i2c::master::{BusTimeout, Config, FsmTimeout, I2c, SoftwareTimeout};
|
||
use esp_hal::interrupt::software::SoftwareInterruptControl;
|
||
use esp_hal::pcnt::unit::Unit;
|
||
use esp_hal::pcnt::Pcnt;
|
||
use esp_hal::rng::Rng;
|
||
use esp_hal::rtc_cntl::{Rtc, SocResetReason};
|
||
use esp_hal::system::reset_reason;
|
||
use esp_hal::time::Rate;
|
||
use esp_hal::timer::timg::{MwdtStage, TimerGroup, Wdt};
|
||
use esp_hal::uart::Uart;
|
||
use esp_hal::Blocking;
|
||
use esp_radio::{init, Controller};
|
||
use esp_storage::FlashStorage;
|
||
use log::{info, warn};
|
||
use portable_atomic::AtomicBool;
|
||
use serde::{Deserialize, Serialize};
|
||
use shared_flash::MutexFlashStorage;
|
||
|
||
//Only support for 8 right now!
|
||
pub const PLANT_COUNT: usize = 8;
|
||
|
||
pub static PROGRESS_ACTIVE: AtomicBool = AtomicBool::new(false);
|
||
|
||
pub static WATCHDOG: OnceLock<
|
||
embassy_sync::blocking_mutex::Mutex<
|
||
CriticalSectionRawMutex,
|
||
RefCell<Wdt<esp_hal::peripherals::TIMG0>>,
|
||
>,
|
||
> = OnceLock::new();
|
||
|
||
const TANK_MULTI_SAMPLE: usize = 11;
|
||
pub static I2C_DRIVER: OnceLock<
|
||
embassy_sync::blocking_mutex::Mutex<CriticalSectionRawMutex, RefCell<I2c<Blocking>>>,
|
||
> = OnceLock::new();
|
||
|
||
#[derive(Debug, PartialEq, Clone, Copy, Encode, Decode)]
|
||
pub enum Sensor {
|
||
A,
|
||
B,
|
||
}
|
||
|
||
impl From<Sensor> for SensorSlot {
|
||
fn from(val: Sensor) -> Self {
|
||
match val {
|
||
Sensor::A => SensorSlot::A,
|
||
Sensor::B => SensorSlot::B,
|
||
}
|
||
}
|
||
}
|
||
|
||
pub struct PlantHal {}
|
||
|
||
#[allow(clippy::upper_case_acronyms)]
|
||
pub struct HAL<'a> {
|
||
pub board_hal: Box<dyn BoardInteraction<'a> + Send>,
|
||
}
|
||
|
||
#[async_trait(?Send)]
|
||
pub trait BoardInteraction<'a> {
|
||
fn get_tank_sensor(&mut self) -> Result<&mut TankSensor<'a>, FatError>;
|
||
fn get_esp(&mut self) -> &mut Esp<'a>;
|
||
fn get_config(&mut self) -> &PlantControllerConfig;
|
||
fn get_battery_monitor(&mut self) -> &mut Box<dyn BatteryInteraction + Send>;
|
||
fn get_rtc_module(&mut self) -> &mut Box<dyn RTCModuleInteraction + Send>;
|
||
async fn get_time(&mut self) -> DateTime<Utc>;
|
||
async fn set_time(&mut self, time: &DateTime<FixedOffset>) -> FatResult<()>;
|
||
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<()>;
|
||
async fn pump(&mut self, plant: usize, enable: bool) -> FatResult<()>;
|
||
async fn pump_current(&mut self, plant: usize) -> FatResult<Current>;
|
||
async fn fault(&mut self, plant: usize, enable: bool) -> FatResult<()>;
|
||
async fn measure_moisture_hz(&mut self) -> Result<Moistures, FatError>;
|
||
async fn general_fault(&mut self, enable: bool);
|
||
async fn test(&mut self) -> FatResult<()>;
|
||
fn set_config(&mut self, config: PlantControllerConfig);
|
||
async fn get_mptt_voltage(&mut self) -> FatResult<Voltage>;
|
||
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");
|
||
}
|
||
|
||
async fn progress(&mut self, counter: u32) {
|
||
// Indicate progress is active to suppress default wait_infinity blinking
|
||
PROGRESS_ACTIVE.store(true, core::sync::atomic::Ordering::Relaxed);
|
||
|
||
// Feed watchdog during long-running webserver operations
|
||
PlantHal::feed_watchdog();
|
||
|
||
let current = counter % PLANT_COUNT as u32;
|
||
for led in 0..PLANT_COUNT {
|
||
if let Err(err) = self.fault(led, current == led as u32).await {
|
||
warn!("Fault on plant {led}: {err:?}");
|
||
}
|
||
}
|
||
let even = counter % 2 == 0;
|
||
let _ = self.general_fault(even).await;
|
||
}
|
||
|
||
async fn clear_progress(&mut self) {
|
||
for led in 0..PLANT_COUNT {
|
||
if let Err(err) = self.fault(led, false).await {
|
||
warn!("Fault on plant {led}: {err:?}");
|
||
}
|
||
}
|
||
let _ = self.general_fault(false).await;
|
||
|
||
// Reset progress active flag so wait_infinity can resume blinking
|
||
PROGRESS_ACTIVE.store(false, core::sync::atomic::Ordering::Relaxed);
|
||
}
|
||
}
|
||
|
||
#[allow(dead_code)]
|
||
pub struct FreePeripherals<'a> {
|
||
pub gpio0: GPIO0<'a>,
|
||
pub gpio2: GPIO2<'a>,
|
||
pub gpio3: GPIO3<'a>,
|
||
pub gpio4: GPIO4<'a>,
|
||
pub gpio5: GPIO5<'a>,
|
||
pub gpio6: GPIO6<'a>,
|
||
pub gpio7: GPIO7<'a>,
|
||
pub gpio8: GPIO8<'a>,
|
||
// //config button here
|
||
pub gpio10: GPIO10<'a>,
|
||
pub gpio11: GPIO11<'a>,
|
||
pub gpio12: GPIO12<'a>,
|
||
pub gpio13: GPIO13<'a>,
|
||
pub gpio14: GPIO14<'a>,
|
||
pub gpio15: GPIO15<'a>,
|
||
pub gpio16: GPIO16<'a>,
|
||
pub gpio17: GPIO17<'a>,
|
||
pub gpio18: GPIO18<'a>,
|
||
// //i2c here
|
||
pub gpio21: GPIO21<'a>,
|
||
pub gpio22: GPIO22<'a>,
|
||
pub gpio23: GPIO23<'a>,
|
||
pub gpio27: GPIO27<'a>,
|
||
pub twai: TWAI0<'a>,
|
||
pub pcnt0: Unit<'a, 0>,
|
||
pub pcnt1: Unit<'a, 1>,
|
||
pub adc1: ADC1<'a>,
|
||
}
|
||
|
||
macro_rules! mk_static {
|
||
($t:ty,$val:expr) => {{
|
||
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
|
||
#[deny(unused_attributes)]
|
||
let x = STATIC_CELL.uninit().write($val);
|
||
x
|
||
}};
|
||
}
|
||
|
||
impl PlantHal {
|
||
pub async fn create() -> Result<Mutex<CriticalSectionRawMutex, HAL<'static>>, FatError> {
|
||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||
let peripherals: Peripherals = esp_hal::init(config);
|
||
|
||
esp_alloc::heap_allocator!(size: 64 * 1024);
|
||
esp_alloc::heap_allocator!(#[link_section = ".dram2_uninit"] size: 64000);
|
||
|
||
let rtc_peripheral: Rtc = Rtc::new(peripherals.LPWR);
|
||
|
||
let timg0 = TimerGroup::new(peripherals.TIMG0);
|
||
let sw_int = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
|
||
esp_rtos::start(timg0.timer0, sw_int.software_interrupt0);
|
||
|
||
// Initialize and enable the watchdog with 30 second timeout
|
||
let mut wdt = timg0.wdt;
|
||
wdt.set_timeout(MwdtStage::Stage0, esp_hal::time::Duration::from_secs(30));
|
||
wdt.enable();
|
||
WATCHDOG
|
||
.init(embassy_sync::blocking_mutex::Mutex::new(RefCell::new(wdt)))
|
||
.map_err(|_| FatError::String {
|
||
error: "Watchdog already initialized".to_string(),
|
||
})?;
|
||
|
||
let boot_button = Input::new(
|
||
peripherals.GPIO9,
|
||
InputConfig::default().with_pull(Pull::None),
|
||
);
|
||
|
||
// Reserve GPIO1 for deep sleep wake (configured just before entering sleep)
|
||
let wake_gpio1 = peripherals.GPIO1;
|
||
|
||
let rng = Rng::new();
|
||
let esp_wifi_ctrl = &*mk_static!(
|
||
Controller<'static>,
|
||
init().map_err(|e| FatError::String {
|
||
error: format!("Could not init wifi controller: {:?}", e)
|
||
})?
|
||
);
|
||
|
||
let (controller, interfaces) =
|
||
esp_radio::wifi::new(esp_wifi_ctrl, peripherals.WIFI, Default::default()).map_err(
|
||
|e| FatError::String {
|
||
error: format!("Could not init wifi: {:?}", e),
|
||
},
|
||
)?;
|
||
|
||
let pcnt_module = Pcnt::new(peripherals.PCNT);
|
||
|
||
let free_pins = FreePeripherals {
|
||
gpio0: peripherals.GPIO0,
|
||
gpio2: peripherals.GPIO2,
|
||
gpio3: peripherals.GPIO3,
|
||
gpio4: peripherals.GPIO4,
|
||
gpio5: peripherals.GPIO5,
|
||
gpio6: peripherals.GPIO6,
|
||
gpio7: peripherals.GPIO7,
|
||
gpio8: peripherals.GPIO8,
|
||
gpio10: peripherals.GPIO10,
|
||
gpio11: peripherals.GPIO11,
|
||
gpio12: peripherals.GPIO12,
|
||
gpio13: peripherals.GPIO13,
|
||
gpio14: peripherals.GPIO14,
|
||
gpio15: peripherals.GPIO15,
|
||
gpio16: peripherals.GPIO16,
|
||
gpio17: peripherals.GPIO17,
|
||
gpio18: peripherals.GPIO18,
|
||
gpio21: peripherals.GPIO21,
|
||
gpio22: peripherals.GPIO22,
|
||
gpio23: peripherals.GPIO23,
|
||
gpio27: peripherals.GPIO27,
|
||
twai: peripherals.TWAI0,
|
||
pcnt0: pcnt_module.unit0,
|
||
pcnt1: pcnt_module.unit1,
|
||
adc1: peripherals.ADC1,
|
||
};
|
||
|
||
let tablebuffer = mk_static!(
|
||
[u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN],
|
||
[0u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN]
|
||
);
|
||
|
||
let bullshit = MutexFlashStorage {
|
||
inner: Arc::new(CriticalSectionMutex::new(RefCell::new(FlashStorage::new(
|
||
peripherals.FLASH,
|
||
)))),
|
||
};
|
||
let flash_storage = mk_static!(MutexFlashStorage, bullshit.clone());
|
||
let flash_storage_2 = mk_static!(MutexFlashStorage, bullshit.clone());
|
||
let flash_storage_3 = mk_static!(MutexFlashStorage, bullshit.clone());
|
||
|
||
let pt =
|
||
esp_bootloader_esp_idf::partitions::read_partition_table(flash_storage, tablebuffer)?;
|
||
|
||
let ota_data = mk_static!(
|
||
PartitionEntry,
|
||
pt.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
|
||
DataPartitionSubType::Ota,
|
||
))?
|
||
.context("No OTA data partition found")?
|
||
);
|
||
|
||
let ota_data = mk_static!(
|
||
FlashRegion<RmwNorFlashStorage<&mut MutexFlashStorage>>,
|
||
ota_data.as_embedded_storage(mk_static!(
|
||
RmwNorFlashStorage<&mut MutexFlashStorage>,
|
||
RmwNorFlashStorage::new(flash_storage_2, mk_static!([u8; 4096], [0_u8; 4096]))
|
||
))
|
||
);
|
||
|
||
let state_0 = ota_state(AppPartitionSubType::Ota0, ota_data);
|
||
let state_1 = ota_state(AppPartitionSubType::Ota1, ota_data);
|
||
let mut ota = Ota::new(ota_data, 2)?;
|
||
let running = get_current_slot(&pt, &mut ota)?;
|
||
let target = next_partition(running)?;
|
||
|
||
info!("Currently running OTA slot: {running:?}");
|
||
info!("Updates will be stored in OTA slot: {target:?}");
|
||
info!("Slot0 state: {state_0:?}");
|
||
info!("Slot1 state: {state_1:?}");
|
||
|
||
//get current_state and next_state here!
|
||
let ota_target = match target {
|
||
AppPartitionSubType::Ota0 => pt
|
||
.find_partition(PartitionType::App(AppPartitionSubType::Ota0))?
|
||
.context("Partition table invalid no ota0")?,
|
||
AppPartitionSubType::Ota1 => pt
|
||
.find_partition(PartitionType::App(AppPartitionSubType::Ota1))?
|
||
.context("Partition table invalid no ota1")?,
|
||
_ => {
|
||
bail!("Invalid target partition");
|
||
}
|
||
};
|
||
|
||
let ota_target = mk_static!(PartitionEntry, ota_target);
|
||
let ota_target = mk_static!(
|
||
FlashRegion<MutexFlashStorage>,
|
||
ota_target.as_embedded_storage(flash_storage)
|
||
);
|
||
|
||
let data_partition = pt
|
||
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
|
||
DataPartitionSubType::LittleFs,
|
||
))?
|
||
.context("Storage data partition not found")?;
|
||
let data_partition = mk_static!(PartitionEntry, data_partition);
|
||
|
||
let data = mk_static!(
|
||
FlashRegion<'static, MutexFlashStorage>,
|
||
data_partition.as_embedded_storage(flash_storage_3)
|
||
);
|
||
|
||
let savegame = SavegameManager::new(data);
|
||
info!(
|
||
"Savegame storage initialized ({} slots × {} KB)",
|
||
savegame_manager::SAVEGAME_SLOT_COUNT,
|
||
savegame_manager::SAVEGAME_SLOT_SIZE / 1024
|
||
);
|
||
|
||
let uart0 =
|
||
Uart::new(peripherals.UART0, UartConfig::default()).map_err(|_| FatError::String {
|
||
error: "Uart creation failed".to_string(),
|
||
})?;
|
||
|
||
let ap = interfaces.ap;
|
||
let sta = interfaces.sta;
|
||
let mut esp = Esp {
|
||
savegame,
|
||
rng,
|
||
controller: Arc::new(Mutex::new(controller)),
|
||
interface_sta: Some(sta),
|
||
interface_ap: Some(ap),
|
||
boot_button,
|
||
wake_gpio1,
|
||
ota,
|
||
ota_target,
|
||
current: running,
|
||
slot0_state: state_0,
|
||
slot1_state: state_1,
|
||
uart0,
|
||
rtc: rtc_peripheral,
|
||
};
|
||
|
||
//init,reset rtc memory depending on cause
|
||
let mut init_rtc_store: bool = false;
|
||
let mut to_config_mode: bool = false;
|
||
let reasons = match reset_reason() {
|
||
None => "unknown",
|
||
Some(reason) => match reason {
|
||
SocResetReason::ChipPowerOn => "power on",
|
||
SocResetReason::CoreSDIO => "sdio reset",
|
||
SocResetReason::CoreMwdt0 => "Watchdog Main",
|
||
SocResetReason::CoreMwdt1 => "Watchdog 1",
|
||
SocResetReason::CoreRtcWdt => "Watchdog RTC",
|
||
SocResetReason::Cpu0Mwdt0 => "Watchdog MCpu0",
|
||
SocResetReason::Cpu0Sw => "software reset cpu0",
|
||
SocResetReason::SysRtcWdt => "Watchdog Sys rtc",
|
||
SocResetReason::Cpu0Mwdt1 => "cpu0 mwdt1",
|
||
SocResetReason::SysSuperWdt => "Watchdog Super",
|
||
SocResetReason::Cpu0RtcWdt => {
|
||
init_rtc_store = true;
|
||
"Watchdog RTC cpu0"
|
||
}
|
||
SocResetReason::CoreSw => "software reset",
|
||
SocResetReason::CoreDeepSleep => "deep sleep",
|
||
SocResetReason::SysBrownOut => "sys brown out",
|
||
SocResetReason::CoreEfuseCrc => "core efuse crc",
|
||
SocResetReason::CoreUsbUart => {
|
||
//TODO still required? or via button ignore? to_config_mode = true;
|
||
to_config_mode = true;
|
||
"core usb uart"
|
||
}
|
||
SocResetReason::CoreUsbJtag => "core usb jtag",
|
||
SocResetReason::Cpu0JtagCpu => "cpu0 jtag cpu",
|
||
},
|
||
};
|
||
log(
|
||
LogMessage::ResetReason,
|
||
init_rtc_store as u32,
|
||
to_config_mode as u32,
|
||
"",
|
||
&format!("{reasons:?}"),
|
||
)
|
||
.await;
|
||
|
||
esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode)
|
||
.await;
|
||
|
||
let config = esp.load_config().await;
|
||
|
||
info!("Init rtc driver");
|
||
|
||
let sda = peripherals.GPIO20;
|
||
let scl = peripherals.GPIO19;
|
||
|
||
// Configure I2C with 1-second timeout
|
||
// At 100 Hz I2C clock, one bus cycle = 10ms
|
||
// For 1 second timeout: 100 bus cycles
|
||
let i2c = I2c::new(
|
||
peripherals.I2C0,
|
||
Config::default()
|
||
//.with_frequency(Rate::from_hz(100))
|
||
//1s at 100khz
|
||
.with_timeout(BusTimeout::BusCycles(100_000))
|
||
.with_scl_main_st_timeout(FsmTimeout::new(21)?),
|
||
)?
|
||
.with_scl(scl)
|
||
.with_sda(sda);
|
||
let i2c_bus: embassy_sync::blocking_mutex::Mutex<
|
||
CriticalSectionRawMutex,
|
||
RefCell<I2c<Blocking>>,
|
||
> = CriticalSectionMutex::new(RefCell::new(i2c));
|
||
|
||
I2C_DRIVER.init(i2c_bus).map_err(|_| FatError::String {
|
||
error: "Could not init i2c driver".to_string(),
|
||
})?;
|
||
|
||
let i2c_bus = I2C_DRIVER.get().await;
|
||
let rtc_device = I2cDevice::new(i2c_bus);
|
||
let mut bms_device = I2cDevice::new(i2c_bus);
|
||
let eeprom_device = I2cDevice::new(i2c_bus);
|
||
|
||
let mut rtc: Ds323x<
|
||
I2cInterface<I2cDevice<CriticalSectionRawMutex, I2c<Blocking>>>,
|
||
DS3231,
|
||
> = Ds323x::new_ds3231(rtc_device);
|
||
|
||
info!("Init rtc eeprom driver");
|
||
let eeprom = Eeprom24x::new_24x32(eeprom_device, SlaveAddr::Alternative(true, true, true));
|
||
let rtc_time = rtc.datetime();
|
||
match rtc_time {
|
||
Ok(tt) => {
|
||
info!("Rtc Module reports time at UTC {tt}");
|
||
}
|
||
Err(err) => {
|
||
info!("Rtc Module could not be read {err:?}");
|
||
}
|
||
}
|
||
|
||
let storage: Storage<
|
||
I2cDevice<'static, CriticalSectionRawMutex, I2c<Blocking>>,
|
||
B32,
|
||
TwoBytes,
|
||
No,
|
||
Delay,
|
||
> = Storage::new(eeprom, Delay::new());
|
||
let rtc_module: Box<dyn RTCModuleInteraction + Send> =
|
||
Box::new(DS3231Module { rtc, storage }) as Box<dyn RTCModuleInteraction + Send>;
|
||
|
||
let hal = match config {
|
||
Ok(config) => {
|
||
let battery_interaction: Box<dyn BatteryInteraction + Send> =
|
||
match config.hardware.battery {
|
||
BatteryBoardVersion::Disabled => Box::new(NoBatteryMonitor {}),
|
||
BatteryBoardVersion::WchI2cSlave => {
|
||
let version = ProtocolVersion::read_from_i2c(&mut bms_device);
|
||
let version_val = match version {
|
||
Ok(v) => unsafe { core::mem::transmute::<ProtocolVersion, u32>(v) },
|
||
Err(_) => 0,
|
||
};
|
||
if version_val == 1 {
|
||
Box::new(WCHI2CSlave { i2c: bms_device })
|
||
} else {
|
||
//todo should be an error variant instead?
|
||
Box::new(NoBatteryMonitor {})
|
||
}
|
||
}
|
||
};
|
||
|
||
let board_hal: Box<dyn BoardInteraction + Send> = //match config.hardware.board {
|
||
//BoardVersion::Initial => {
|
||
// initial_hal::create_initial_board(free_pins, config, esp)?
|
||
//}
|
||
//BoardVersion::V4 => {
|
||
v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)
|
||
.await?;
|
||
//}
|
||
//};
|
||
|
||
HAL { board_hal }
|
||
}
|
||
Err(err) => {
|
||
log(
|
||
LogMessage::ConfigModeMissingConfig,
|
||
0,
|
||
0,
|
||
"",
|
||
&err.to_string(),
|
||
)
|
||
.await;
|
||
HAL {
|
||
board_hal: v4_hal::create_v4(
|
||
free_pins,
|
||
esp,
|
||
PlantControllerConfig::default(),
|
||
Box::new(NoBatteryMonitor {}),
|
||
rtc_module,
|
||
)
|
||
.await?,
|
||
}
|
||
}
|
||
};
|
||
|
||
Ok(Mutex::new(hal))
|
||
}
|
||
|
||
/// Feed the watchdog timer to prevent system reset
|
||
pub fn feed_watchdog() {
|
||
if let Some(wdt_mutex) = WATCHDOG.try_get() {
|
||
wdt_mutex.lock(|cell| {
|
||
cell.borrow_mut().feed();
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
fn ota_state(
|
||
slot: AppPartitionSubType,
|
||
ota_data: &mut FlashRegion<RmwNorFlashStorage<&mut MutexFlashStorage>>,
|
||
) -> OtaImageState {
|
||
// Read and log OTA states for both slots before constructing Ota
|
||
// Each OTA select entry is 32 bytes: [seq:4][label:20][state:4][crc:4]
|
||
// Offsets within the OTA data partition: slot0 @ 0x0000, slot1 @ 0x1000
|
||
let mut slot_buf = [0u8; 32];
|
||
if slot == AppPartitionSubType::Ota0 {
|
||
let _ = ReadStorage::read(ota_data, 0x0000, &mut slot_buf);
|
||
} else {
|
||
let _ = ReadStorage::read(ota_data, 0x1000, &mut slot_buf);
|
||
}
|
||
let raw_state = u32::from_le_bytes(slot_buf[24..28].try_into().unwrap_or([0xff; 4]));
|
||
|
||
OtaImageState::try_from(raw_state).unwrap_or(OtaImageState::Undefined)
|
||
}
|
||
|
||
fn get_current_slot(
|
||
pt: &PartitionTable,
|
||
ota: &mut Ota<RmwNorFlashStorage<&mut MutexFlashStorage>>,
|
||
) -> Result<AppPartitionSubType, FatError> {
|
||
let booted = pt.booted_partition()?.ok_or(FatError::OTAError)?;
|
||
let booted_type = booted.partition_type();
|
||
let booted_ota_type = match booted_type {
|
||
PartitionType::App(subtype) => subtype,
|
||
_ => {
|
||
bail!("Booted partition is not an app partition");
|
||
}
|
||
};
|
||
|
||
let expected_partition = ota.current_app_partition()?;
|
||
if expected_partition == booted_ota_type {
|
||
info!("Booted partition matches expected partition");
|
||
} else {
|
||
info!("Booted partition does not match expected partition, fixing ota entry");
|
||
ota.set_current_app_partition(booted_ota_type)?;
|
||
}
|
||
|
||
let fixed = ota.current_app_partition()?;
|
||
let state = ota.current_ota_state();
|
||
info!("Expected partition: {expected_partition:?}, current partition: {booted_ota_type:?}, state: {state:?}");
|
||
|
||
if fixed != booted_ota_type {
|
||
bail!(
|
||
"Could not fix ota entry, booted partition is still not correct: {:?} != {:?}",
|
||
booted_ota_type,
|
||
fixed
|
||
);
|
||
}
|
||
|
||
Ok(booted_ota_type)
|
||
}
|
||
|
||
pub fn next_partition(current: AppPartitionSubType) -> FatResult<AppPartitionSubType> {
|
||
let next = match current {
|
||
AppPartitionSubType::Ota0 => AppPartitionSubType::Ota1,
|
||
AppPartitionSubType::Ota1 => AppPartitionSubType::Ota0,
|
||
_ => {
|
||
bail!("Current slot is not ota0 or ota1");
|
||
}
|
||
};
|
||
Ok(next)
|
||
}
|
||
|
||
#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize)]
|
||
pub struct Moistures {
|
||
pub sensor_a_hz: [Option<f32>; PLANT_COUNT],
|
||
pub sensor_b_hz: [Option<f32>; PLANT_COUNT],
|
||
}
|
||
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||
pub struct Detection {
|
||
plant: [DetectionSensorResult; PLANT_COUNT],
|
||
}
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||
pub struct DetectionSensorResult {
|
||
sensor_a: bool,
|
||
sensor_b: bool,
|
||
}
|