538 lines
19 KiB
Rust
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);
|
|
}
|