688 lines
24 KiB
Rust
688 lines
24 KiB
Rust
pub(crate) mod battery;
|
|
mod can_api;
|
|
pub mod esp;
|
|
mod initial_hal;
|
|
mod little_fs2storage_adapter;
|
|
pub(crate) mod rtc;
|
|
mod v3_hal;
|
|
mod v3_shift_register;
|
|
mod v4_hal;
|
|
mod v4_sensor;
|
|
mod water;
|
|
use crate::alloc::string::ToString;
|
|
use crate::hal::rtc::{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::GPIO24;
|
|
use esp_hal::peripherals::GPIO25;
|
|
use esp_hal::peripherals::GPIO26;
|
|
use esp_hal::peripherals::GPIO27;
|
|
use esp_hal::peripherals::GPIO28;
|
|
use esp_hal::peripherals::GPIO29;
|
|
use esp_hal::peripherals::GPIO3;
|
|
use esp_hal::peripherals::GPIO30;
|
|
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 crate::{
|
|
bail,
|
|
config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig},
|
|
hal::{
|
|
battery::{BatteryInteraction, NoBatteryMonitor},
|
|
esp::Esp,
|
|
},
|
|
log::LogMessage,
|
|
BOARD_ACCESS,
|
|
};
|
|
use alloc::boxed::Box;
|
|
use alloc::format;
|
|
use alloc::sync::Arc;
|
|
use async_trait::async_trait;
|
|
use bincode::{Decode, Encode};
|
|
use bq34z100::Bq34z100g1Driver;
|
|
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,
|
|
};
|
|
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::{print_battery_bq34z100, BQ34Z100G1};
|
|
use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem;
|
|
use crate::hal::water::TankSensor;
|
|
use crate::log::LOG_ACCESS;
|
|
use embassy_sync::mutex::Mutex;
|
|
use embassy_sync::once_lock::OnceLock;
|
|
use embedded_storage::nor_flash::ReadNorFlash;
|
|
use esp_alloc as _;
|
|
use esp_backtrace as _;
|
|
use esp_bootloader_esp_idf::ota::{Ota, OtaImageState};
|
|
use esp_bootloader_esp_idf::ota::{Slot as ota_slot, Slot};
|
|
use esp_hal::delay::Delay;
|
|
use esp_hal::i2c::master::{BusTimeout, Config, I2c};
|
|
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::TimerGroup;
|
|
use esp_hal::Blocking;
|
|
use esp_storage::FlashStorage;
|
|
use esp_wifi::{init, EspWifiController};
|
|
use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem};
|
|
use littlefs2::object_safe::DynStorage;
|
|
use log::{error, info, warn};
|
|
use portable_atomic::AtomicBool;
|
|
|
|
pub static TIME_ACCESS: OnceLock<Mutex<CriticalSectionRawMutex, Rtc>> = OnceLock::new();
|
|
|
|
//Only support for 8 right now!
|
|
pub const PLANT_COUNT: usize = 8;
|
|
|
|
pub static PROGRESS_ACTIVE: AtomicBool = AtomicBool::new(false);
|
|
|
|
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,
|
|
}
|
|
|
|
pub struct PlantHal {}
|
|
|
|
pub struct HAL<'a> {
|
|
pub board_hal: Box<dyn BoardInteraction<'a> + Send>,
|
|
}
|
|
|
|
#[async_trait]
|
|
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 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) -> Result<(), FatError>;
|
|
async fn pump(&mut self, plant: usize, enable: bool) -> Result<(), FatError>;
|
|
async fn pump_current(&mut self, plant: usize) -> Result<Current, FatError>;
|
|
async fn fault(&mut self, plant: usize, enable: bool) -> Result<(), FatError>;
|
|
async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32, FatError>;
|
|
async fn general_fault(&mut self, enable: bool);
|
|
async fn test(&mut self) -> Result<(), FatError>;
|
|
fn set_config(&mut self, config: PlantControllerConfig);
|
|
async fn get_mptt_voltage(&mut self) -> Result<Voltage, FatError>;
|
|
async fn get_mptt_current(&mut self) -> Result<Current, FatError>;
|
|
|
|
// Return JSON string with autodetected sensors per plant. Default: not supported.
|
|
async fn detect_sensors(&mut self) -> Result<alloc::string::String, FatError> {
|
|
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
|
|
crate::hal::PROGRESS_ACTIVE.store(true, core::sync::atomic::Ordering::Relaxed);
|
|
|
|
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.into()).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
|
|
crate::hal::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 gpio24: GPIO24<'a>,
|
|
pub gpio25: GPIO25<'a>,
|
|
pub gpio26: GPIO26<'a>,
|
|
pub gpio27: GPIO27<'a>,
|
|
pub gpio28: GPIO28<'a>,
|
|
pub gpio29: GPIO29<'a>,
|
|
pub gpio30: GPIO30<'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: Rtc = Rtc::new(peripherals.LPWR);
|
|
TIME_ACCESS
|
|
.init(Mutex::new(rtc))
|
|
.map_err(|_| FatError::String {
|
|
error: "Init error rct".to_string(),
|
|
})?;
|
|
|
|
let systimer = SystemTimer::new(peripherals.SYSTIMER);
|
|
|
|
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(peripherals.RNG);
|
|
let timg0 = TimerGroup::new(peripherals.TIMG0);
|
|
let esp_wifi_ctrl = &*mk_static!(
|
|
EspWifiController<'static>,
|
|
init(timg0.timer0, rng.clone()).expect("Could not init wifi controller")
|
|
);
|
|
|
|
let (controller, interfaces) =
|
|
esp_wifi::wifi::new(&esp_wifi_ctrl, peripherals.WIFI).expect("Could not init wifi");
|
|
|
|
use esp_hal::timer::systimer::SystemTimer;
|
|
esp_hal_embassy::init(systimer.alarm0);
|
|
|
|
//let mut adc1 = Adc::new(peripherals.ADC1, adc1_config);
|
|
//
|
|
|
|
let pcnt_module = Pcnt::new(peripherals.PCNT);
|
|
|
|
let free_pins = FreePeripherals {
|
|
// can: peripherals.can,
|
|
// adc1: peripherals.adc1,
|
|
// pcnt0: peripherals.pcnt0,
|
|
// pcnt1: peripherals.pcnt1,
|
|
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,
|
|
gpio24: peripherals.GPIO24,
|
|
gpio25: peripherals.GPIO25,
|
|
gpio26: peripherals.GPIO26,
|
|
gpio27: peripherals.GPIO27,
|
|
gpio28: peripherals.GPIO28,
|
|
gpio29: peripherals.GPIO29,
|
|
gpio30: peripherals.GPIO30,
|
|
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 storage_ota = mk_static!(FlashStorage, FlashStorage::new());
|
|
let pt =
|
|
esp_bootloader_esp_idf::partitions::read_partition_table(storage_ota, tablebuffer)?;
|
|
|
|
let ota_data = mk_static!(
|
|
PartitionEntry,
|
|
pt.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
|
|
DataPartitionSubType::Ota,
|
|
))?
|
|
.expect("No OTA data partition found")
|
|
);
|
|
|
|
let ota_data = mk_static!(
|
|
FlashRegion<FlashStorage>,
|
|
ota_data.as_embedded_storage(storage_ota)
|
|
);
|
|
|
|
let state_0 = ota_state(ota_slot::Slot0, ota_data);
|
|
let state_1 = ota_state(ota_slot::Slot1, ota_data);
|
|
let mut ota = Ota::new(ota_data)?;
|
|
let running = get_current_slot_and_fix_ota_data(&mut ota, state_0, state_1)?;
|
|
let target = running.next();
|
|
|
|
info!("Currently running OTA slot: {:?}", running);
|
|
info!("Slot0 state: {:?}", state_0);
|
|
info!("Slot1 state: {:?}", state_1);
|
|
|
|
//obtain current_state and next_state here!
|
|
let ota_target = match target {
|
|
Slot::None => {
|
|
panic!("No OTA slot active?");
|
|
}
|
|
Slot::Slot0 => pt
|
|
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App(
|
|
AppPartitionSubType::Ota0,
|
|
))?
|
|
.context("Partition table invalid no ota0")?,
|
|
Slot::Slot1 => pt
|
|
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App(
|
|
AppPartitionSubType::Ota1,
|
|
))?
|
|
.context("Partition table invalid no ota1")?,
|
|
};
|
|
|
|
let ota_target = mk_static!(PartitionEntry, ota_target);
|
|
let storage_ota = mk_static!(FlashStorage, FlashStorage::new());
|
|
let ota_target = mk_static!(
|
|
FlashRegion<FlashStorage>,
|
|
ota_target.as_embedded_storage(storage_ota)
|
|
);
|
|
|
|
let data_partition = pt
|
|
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
|
|
DataPartitionSubType::LittleFs,
|
|
))?
|
|
.expect("Data partition with littlefs not found");
|
|
let data_partition = mk_static!(PartitionEntry, data_partition);
|
|
|
|
let storage_data = mk_static!(FlashStorage, FlashStorage::new());
|
|
let data = mk_static!(
|
|
FlashRegion<FlashStorage>,
|
|
data_partition.as_embedded_storage(storage_data)
|
|
);
|
|
let lfs2filesystem = mk_static!(LittleFs2Filesystem, LittleFs2Filesystem { storage: data });
|
|
let alloc = mk_static!(Allocation<LittleFs2Filesystem>, lfs2Filesystem::allocate());
|
|
if lfs2filesystem.is_mountable() {
|
|
log::info!("Littlefs2 filesystem is mountable");
|
|
} else {
|
|
match lfs2filesystem.format() {
|
|
Result::Ok(..) => {
|
|
log::info!("Littlefs2 filesystem is formatted");
|
|
}
|
|
Err(err) => {
|
|
error!("Littlefs2 filesystem could not be formatted: {:?}", err);
|
|
}
|
|
}
|
|
}
|
|
|
|
let fs = Arc::new(Mutex::new(
|
|
lfs2Filesystem::mount(alloc, lfs2filesystem).expect("Could not mount lfs2 filesystem"),
|
|
));
|
|
|
|
let ap = interfaces.ap;
|
|
let sta = interfaces.sta;
|
|
let mut esp = Esp {
|
|
fs,
|
|
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,
|
|
};
|
|
|
|
//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_ACCESS
|
|
.lock()
|
|
.await
|
|
.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;
|
|
|
|
log::info!("Init rtc driver");
|
|
|
|
let sda = peripherals.GPIO20;
|
|
let scl = peripherals.GPIO19;
|
|
|
|
let i2c = I2c::new(
|
|
peripherals.I2C0,
|
|
Config::default()
|
|
.with_frequency(Rate::from_hz(100))
|
|
.with_timeout(BusTimeout::Maximum),
|
|
)?
|
|
.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).expect("Could not init i2c driver");
|
|
|
|
let i2c_bus = I2C_DRIVER.get().await;
|
|
let rtc_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) => {
|
|
log::info!("Rtc Module reports time at UTC {}", tt);
|
|
}
|
|
Err(err) => {
|
|
log::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 {
|
|
Result::Ok(config) => {
|
|
let battery_interaction: Box<dyn BatteryInteraction + Send> =
|
|
match config.hardware.battery {
|
|
BatteryBoardVersion::Disabled => Box::new(NoBatteryMonitor {}),
|
|
BatteryBoardVersion::BQ34Z100G1 => {
|
|
let battery_device = I2cDevice::new(I2C_DRIVER.get().await);
|
|
let mut battery_driver = Bq34z100g1Driver {
|
|
i2c: battery_device,
|
|
delay: Delay::new(),
|
|
flash_block_data: [0; 32],
|
|
};
|
|
let status = print_battery_bq34z100(&mut battery_driver);
|
|
match status {
|
|
Ok(_) => {}
|
|
Err(err) => {
|
|
LOG_ACCESS
|
|
.lock()
|
|
.await
|
|
.log(
|
|
LogMessage::BatteryCommunicationError,
|
|
0u32,
|
|
0,
|
|
"",
|
|
&format!("{err:?})"),
|
|
)
|
|
.await;
|
|
}
|
|
}
|
|
Box::new(BQ34Z100G1 { battery_driver })
|
|
}
|
|
BatteryBoardVersion::WchI2cSlave => {
|
|
// TODO use correct implementation once availible
|
|
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::V3 => {
|
|
v3_hal::create_v3(free_pins, esp, config, battery_interaction, rtc_module)?
|
|
}
|
|
BoardVersion::V4 => {
|
|
v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)
|
|
.await?
|
|
}
|
|
};
|
|
|
|
HAL { board_hal }
|
|
}
|
|
Err(err) => {
|
|
LOG_ACCESS
|
|
.lock()
|
|
.await
|
|
.log(
|
|
LogMessage::ConfigModeMissingConfig,
|
|
0,
|
|
0,
|
|
"",
|
|
&err.to_string(),
|
|
)
|
|
.await;
|
|
HAL {
|
|
board_hal: initial_hal::create_initial_board(
|
|
free_pins,
|
|
PlantControllerConfig::default(),
|
|
esp,
|
|
)?,
|
|
}
|
|
}
|
|
};
|
|
|
|
Ok(Mutex::new(hal))
|
|
}
|
|
}
|
|
|
|
fn ota_state(slot: ota_slot, ota_data: &mut FlashRegion<FlashStorage>) -> 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
|
|
if slot == ota_slot::None {
|
|
return OtaImageState::Undefined;
|
|
}
|
|
let mut slot_buf = [0u8; 32];
|
|
if slot == ota_slot::Slot0 {
|
|
let _ = ota_data.read(0x0000, &mut slot_buf);
|
|
} else {
|
|
let _ = ota_data.read(0x1000, &mut slot_buf);
|
|
}
|
|
let raw_state = u32::from_le_bytes(slot_buf[24..28].try_into().unwrap_or([0xff; 4]));
|
|
let state0 = OtaImageState::try_from(raw_state).unwrap_or(OtaImageState::Undefined);
|
|
state0
|
|
}
|
|
|
|
fn get_current_slot_and_fix_ota_data(
|
|
ota: &mut Ota<FlashStorage>,
|
|
state0: OtaImageState,
|
|
state1: OtaImageState,
|
|
) -> Result<ota_slot, FatError> {
|
|
let state = ota.current_ota_state().unwrap_or_default();
|
|
let swap = match state {
|
|
OtaImageState::Invalid => true,
|
|
OtaImageState::Aborted => true,
|
|
OtaImageState::Undefined => {
|
|
info!("Undefined image in current slot, bootloader wrong?");
|
|
false
|
|
}
|
|
_ => false,
|
|
};
|
|
let current = ota.current_slot()?;
|
|
if swap {
|
|
let other = match current {
|
|
ota_slot::Slot0 => state1,
|
|
ota_slot::Slot1 => state0,
|
|
_ => OtaImageState::Invalid,
|
|
};
|
|
|
|
match other {
|
|
OtaImageState::Invalid => {
|
|
bail!(
|
|
"cannot recover slot, as both slots in invalid state {:?} {:?} {:?}",
|
|
current,
|
|
state0,
|
|
state1
|
|
);
|
|
}
|
|
OtaImageState::Aborted => {
|
|
bail!(
|
|
"cannot recover slot, as both slots in invalid state {:?} {:?} {:?}",
|
|
current,
|
|
state0,
|
|
state1
|
|
);
|
|
}
|
|
_ => {}
|
|
}
|
|
info!(
|
|
"Current slot has state {:?} other state has {:?} swapping",
|
|
state, other
|
|
);
|
|
ota.set_current_slot(current.next())?;
|
|
//we actually booted other slot, than partition table assumes
|
|
return Ok(ota.current_slot()?);
|
|
};
|
|
Ok(current)
|
|
}
|
|
|
|
pub async fn esp_time() -> DateTime<Utc> {
|
|
let guard = TIME_ACCESS.get().await.lock().await;
|
|
DateTime::from_timestamp_micros(guard.current_time_us() as i64).unwrap()
|
|
}
|
|
|
|
pub async fn esp_set_time(time: DateTime<FixedOffset>) -> FatResult<()> {
|
|
{
|
|
let guard = TIME_ACCESS.get().await.lock().await;
|
|
guard.set_current_time_us(time.timestamp_micros() as u64);
|
|
}
|
|
BOARD_ACCESS
|
|
.get()
|
|
.await
|
|
.lock()
|
|
.await
|
|
.board_hal
|
|
.get_rtc_module()
|
|
.set_rtc_time(&time.to_utc())
|
|
.await
|
|
}
|