538 lines
19 KiB
Rust

pub(crate) mod battery;
pub mod esp;
mod initial_hal;
mod little_fs2storage_adapter;
mod rtc;
//mod water;
use crate::alloc::string::ToString;
use crate::hal::rtc::RTCModuleInteraction;
use esp_hal::peripherals::Peripherals;
use esp_hal::peripherals::GPIO23;
use esp_hal::peripherals::GPIO6;
//use crate::hal::water::TankSensor;
use crate::{
config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig},
hal::{
battery::{BatteryInteraction, NoBatteryMonitor},
esp::Esp,
},
log::{LogMessage},
};
use alloc::boxed::Box;
use alloc::format;
use alloc::sync::Arc;
use core::cell::OnceCell;
use anyhow::{bail, Ok, Result};
use async_trait::async_trait;
use chrono::{DateTime, FixedOffset, Utc};
use embassy_executor::Spawner;
//use battery::BQ34Z100G1;
//use bq34z100::Bq34z100g1Driver;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
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::hal::little_fs2storage_adapter::LittleFs2Filesystem;
use embassy_sync::mutex::Mutex;
use embassy_sync::once_lock::OnceLock;
use esp_alloc as _;
use esp_backtrace as _;
use esp_bootloader_esp_idf::ota::Slot;
use esp_hal::rng::Rng;
use esp_hal::rtc_cntl::{Rtc, SocResetReason};
use esp_hal::system::reset_reason;
use esp_hal::timer::timg::TimerGroup;
use esp_storage::FlashStorage;
use esp_wifi::{init, EspWifiController};
use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem};
use littlefs2::object_safe::DynStorage;
use log::{info, warn};
use crate::log::{LogArray, LOG_ACCESS};
pub static TIME_ACCESS: OnceLock<Rtc> = OnceLock::new();
//Only support for 8 right now!
pub const PLANT_COUNT: usize = 8;
const TANK_MULTI_SAMPLE: usize = 11;
//pub static I2C_DRIVER: LazyLock<Mutex<CriticalSectionRawMutex,I2cDriver<'static>>> = LazyLock::new(PlantHal::create_i2c);
#[derive(Debug, PartialEq)]
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) -> Option<&mut TankSensor>;
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>;
fn set_charge_indicator(&mut self, charging: bool) -> Result<()>;
async fn deep_sleep(&mut self, duration_in_ms: u64) -> !;
fn is_day(&self) -> bool;
//should be multsampled
fn light(&mut self, enable: bool) -> Result<()>;
async fn pump(&mut self, plant: usize, enable: bool) -> Result<()>;
async fn pump_current(&mut self, plant: usize) -> Result<Current>;
async fn fault(&mut self, plant: usize, enable: bool) -> Result<()>;
async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32>;
async fn general_fault(&mut self, enable: bool);
async fn test(&mut self) -> Result<()>;
fn set_config(&mut self, config: PlantControllerConfig);
async fn get_mptt_voltage(&mut self) -> anyhow::Result<Voltage>;
async fn get_mptt_current(&mut self) -> anyhow::Result<Current>;
}
impl dyn BoardInteraction<'_> {
//the counter is just some arbitrary number that increases whenever some progress was made, try to keep the updates < 10 per second for ux reasons
async fn _progress(&mut self, counter: u32) {
let even = counter % 2 == 0;
let current = counter / (PLANT_COUNT as u32);
for led in 0..PLANT_COUNT {
match self.fault(led, current == led as u32).await {
Result::Ok(_) => {}
Err(err) => {
warn!("Fault on plant {}: {:?}", led, err);
}
}
}
let _ = self.general_fault(even.into());
}
}
#[allow(dead_code)]
pub struct FreePeripherals<'a> {
// pub gpio0: Gpio0,
// pub gpio1: Gpio1,
// pub gpio2: Gpio2,
// pub gpio3: Gpio3,
// pub gpio4: Gpio4,
// pub gpio5: Gpio5,
pub gpio6: GPIO6<'a>,
// pub gpio7: Gpio7,
// pub gpio8: Gpio8,
// //config button here
// pub gpio10: Gpio10,
// pub gpio11: Gpio11,
// pub gpio12: Gpio12,
// pub gpio13: Gpio13,
// pub gpio14: Gpio14,
// pub gpio15: Gpio15,
// pub gpio16: Gpio16,
// pub gpio17: Gpio17,
// pub gpio18: Gpio18,
// //i2c here
// pub gpio21: Gpio21,
// pub gpio22: Gpio22,
pub gpio23: GPIO23<'a>,
// pub gpio24: Gpio24,
// pub gpio25: Gpio25,
// pub gpio26: Gpio26,
// pub gpio27: Gpio27,
// pub gpio28: Gpio28,
// pub gpio29: Gpio29,
// pub gpio30: Gpio30,
// pub pcnt0: PCNT0,
// pub pcnt1: PCNT1,
// pub adc1: ADC1,
// pub can: CAN,
}
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
}};
}
const GW_IP_ADDR_ENV: Option<&'static str> = option_env!("GATEWAY_IP");
impl PlantHal {
// fn create_i2c() -> Mutex<CriticalSectionRawMutex, I2cDriver<'static>> {
// let peripherals = unsafe { Peripherals::new() };
//
// let config = I2cConfig::new()
// .scl_enable_pullup(true)
// .sda_enable_pullup(true)
// .baudrate(100_u32.kHz().into())
// .timeout(APBTickType::from(Duration::from_millis(100)));
//
// let i2c = peripherals.i2c0;
// let scl = peripherals.pins.gpio19.downgrade();
// let sda = peripherals.pins.gpio20.downgrade();
//
// Mutex::new(I2cDriver::new(i2c, sda, scl, &config).unwrap())
// }
pub async fn create(spawner: Spawner) -> Result<Mutex<CriticalSectionRawMutex, HAL<'static>>> {
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);
match(TIME_ACCESS.init(rtc)){
Result::Ok(_) => {}
Err(_) => {}
}
let systimer = SystemTimer::new(peripherals.SYSTIMER);
let boot_button = Input::new(
peripherals.GPIO9,
InputConfig::default().with_pull(Pull::None),
);
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 free_pins = FreePeripherals {
// can: peripherals.can,
// adc1: peripherals.adc1,
// pcnt0: peripherals.pcnt0,
// pcnt1: peripherals.pcnt1,
// gpio0: peripherals.pins.gpio0,
// gpio1: peripherals.pins.gpio1,
// gpio2: peripherals.pins.gpio2,
// gpio3: peripherals.pins.gpio3,
// gpio4: peripherals.pins.gpio4,
// gpio5: peripherals.pins.gpio5,
gpio6: peripherals.GPIO6,
// gpio7: peripherals.pins.gpio7,
// gpio8: peripherals.pins.gpio8,
// gpio10: peripherals.pins.gpio10,
// gpio11: peripherals.pins.gpio11,
// gpio12: peripherals.pins.gpio12,
// gpio13: peripherals.pins.gpio13,
// gpio14: peripherals.pins.gpio14,
// gpio15: peripherals.pins.gpio15,
// gpio16: peripherals.pins.gpio16,
// gpio17: peripherals.pins.gpio17,
// gpio18: peripherals.pins.gpio18,
// gpio21: peripherals.pins.gpio21,
// gpio22: peripherals.pins.gpio22,
gpio23: peripherals.GPIO23,
// gpio24: peripherals.pins.gpio24,
// gpio25: peripherals.pins.gpio25,
// gpio26: peripherals.pins.gpio26,
// gpio27: peripherals.pins.gpio27,
// gpio28: peripherals.pins.gpio28,
// gpio29: peripherals.pins.gpio29,
// gpio30: peripherals.pins.gpio30,
};
//
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)?;
// List all partitions - this is just FYI
for i in 0..pt.len() {
info!("{:?}", pt.get_partition(i));
}
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 mut ota = esp_bootloader_esp_idf::ota::Ota::new(ota_data)?;
let ota_partition = match ota.current_slot()? {
Slot::None => {
panic!("No OTA slot active?");
}
Slot::Slot0 => pt
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App(
AppPartitionSubType::Ota0,
))?
.expect("No OTA slot0 found"),
Slot::Slot1 => pt
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App(
AppPartitionSubType::Ota1,
))?
.expect("No OTA slot1 found"),
};
let ota_next = mk_static!(PartitionEntry, ota_partition);
let storage_ota = mk_static!(FlashStorage, FlashStorage::new());
let ota_next = mk_static!(
FlashRegion<FlashStorage>,
ota_next.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) => {
bail!("Littlefs2 filesystem could not be formatted: {:?}", err);
}
}
}
let fs = Arc::new(Mutex::new(
lfs2Filesystem::mount(alloc, lfs2filesystem).expect("Could not mount lfs2 filesystem"),
));
let mut esp = Esp {
fs,
rng,
controller: Arc::new(Mutex::new(controller)),
interfaces: Some(interfaces),
boot_button,
mqtt_client: None,
ota,
ota_next,
wall_clock_offset: 0,
};
//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::CoreSw => {
"software reset"
}
SocResetReason::CoreDeepSleep => {
"deep sleep"
}
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::Cpu0RtcWdt => {
init_rtc_store = true;
"Watchdog RTC cpu0"
}
SocResetReason::SysBrownOut => {
"sys brown out"
}
SocResetReason::SysRtcWdt => {
"Watchdog Sys rtc"
}
SocResetReason::Cpu0Mwdt1 => {
"cpu0 mwdt1"
}
SocResetReason::SysSuperWdt => {
"Watchdog Super"
}
SocResetReason::CoreEfuseCrc => {
"core efuse crc"
}
SocResetReason::CoreUsbUart => {
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 mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER));
//
// log::info!("Init rtc eeprom driver");
// let eeprom = {
// Eeprom24x::new_24x32(
// MutexDevice::new(&I2C_DRIVER),
// SlaveAddr::Alternative(true, true, true),
// )
// };
// let rtc_time = rtc.datetime();
// match rtc_time {
// OkStd(tt) => {
// log::info!("Rtc Module reports time at UTC {}", tt);
// }
// Err(err) => {
// log::info!("Rtc Module could not be read {:?}", err);
// }
// }
// let storage = Storage::new(eeprom, Delay::new(1000));
//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 mut battery_driver = Bq34z100g1Driver {
// i2c: MutexDevice::new(&I2C_DRIVER),
// delay: Delay::new(0),
// flash_block_data: [0; 32],
// };
// let status = print_battery_bq34z100(&mut battery_driver);
// match status {
// Ok(_) => {}
// Err(err) => {
// log(
// LogMessage::BatteryCommunicationError,
// 0u32,
// 0,
// "",
// &format!("{err:?})"),
// );
// }
// }
// Box::new(BQ34Z100G1 { battery_driver })
// }
BatteryBoardVersion::WchI2cSlave => {
// TODO use correct implementation once availible
Box::new(NoBatteryMonitor {})
}
_ => {
todo!()
}
};
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)?
//}
//_ => {
// todo!()
//}
//};
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))
}
}
pub async fn esp_time() -> DateTime<Utc> {
DateTime::from_timestamp_micros(TIME_ACCESS.get().await.current_time_us() as i64).unwrap()
}
pub async fn esp_set_time(time: DateTime<FixedOffset>) {
TIME_ACCESS.get().await.set_current_time_us(time.timestamp_micros() as u64);
}