356 lines
12 KiB
Rust
356 lines
12 KiB
Rust
pub(crate) mod battery;
|
|
mod esp;
|
|
mod initial_hal;
|
|
mod rtc;
|
|
mod v3_hal;
|
|
mod v4_hal;
|
|
mod water;
|
|
|
|
use crate::hal::rtc::{DS3231Module, RTCModuleInteraction};
|
|
use crate::hal::water::TankSensor;
|
|
use crate::{
|
|
config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig},
|
|
hal::{
|
|
battery::{print_battery_bq34z100, BatteryInteraction, NoBatteryMonitor},
|
|
esp::Esp,
|
|
},
|
|
log::{log, LogMessage},
|
|
};
|
|
use anyhow::{Ok, Result};
|
|
use battery::BQ34Z100G1;
|
|
use bq34z100::Bq34z100g1Driver;
|
|
use ds323x::{DateTimeAccess, Ds323x};
|
|
use eeprom24x::{Eeprom24x, SlaveAddr, Storage};
|
|
use embedded_hal_bus::i2c::MutexDevice;
|
|
use esp_idf_hal::{
|
|
adc::ADC1,
|
|
delay::Delay,
|
|
gpio::{
|
|
Gpio0, Gpio1, Gpio10, Gpio11, Gpio12, Gpio13, Gpio14, Gpio15, Gpio16, Gpio17, Gpio18,
|
|
Gpio2, Gpio21, Gpio22, Gpio23, Gpio24, Gpio25, Gpio26, Gpio27, Gpio28, Gpio29, Gpio3,
|
|
Gpio30, Gpio4, Gpio5, Gpio6, Gpio7, Gpio8, IOPin, PinDriver, Pull,
|
|
},
|
|
i2c::{APBTickType, I2cConfig, I2cDriver},
|
|
pcnt::PCNT0,
|
|
prelude::Peripherals,
|
|
reset::ResetReason,
|
|
units::FromValueType,
|
|
};
|
|
use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition, wifi::EspWifi};
|
|
use esp_idf_sys::{
|
|
esp_deep_sleep, esp_restart, esp_sleep_enable_ext1_wakeup,
|
|
esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW,
|
|
};
|
|
use esp_ota::mark_app_valid;
|
|
use measurements::{Current, Voltage};
|
|
use once_cell::sync::Lazy;
|
|
use std::result::Result::Ok as OkStd;
|
|
use std::sync::Mutex;
|
|
use std::time::Duration;
|
|
|
|
//Only support for 8 right now!
|
|
pub const PLANT_COUNT: usize = 8;
|
|
const REPEAT_MOIST_MEASURE: usize = 1;
|
|
|
|
const TANK_MULTI_SAMPLE: usize = 11;
|
|
|
|
pub static I2C_DRIVER: Lazy<Mutex<I2cDriver<'static>>> = Lazy::new(PlantHal::create_i2c);
|
|
|
|
fn deep_sleep(duration_in_ms: u64) -> ! {
|
|
unsafe {
|
|
//if we don't do this here, we might just revert newly flashed firmware
|
|
mark_app_valid();
|
|
//allow early wakeup by pressing the boot button
|
|
if duration_in_ms == 0 {
|
|
esp_restart();
|
|
} else {
|
|
//configure gpio 1 to wakeup on low, reused boot button for this
|
|
esp_sleep_enable_ext1_wakeup(
|
|
0b10u64,
|
|
esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW,
|
|
);
|
|
esp_deep_sleep(duration_in_ms);
|
|
}
|
|
};
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum Sensor {
|
|
A,
|
|
B,
|
|
}
|
|
|
|
pub struct PlantHal {}
|
|
|
|
pub struct HAL<'a> {
|
|
pub board_hal: Box<dyn BoardInteraction<'a> + Send>,
|
|
}
|
|
|
|
pub trait BoardInteraction<'a> {
|
|
fn get_tank_sensor(&mut self) -> Option<&mut TankSensor<'a>>;
|
|
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<()>;
|
|
fn deep_sleep(&mut self, duration_in_ms: u64) -> !;
|
|
|
|
fn is_day(&self) -> bool;
|
|
//should be multsampled
|
|
fn light(&mut self, enable: bool) -> Result<()>;
|
|
fn pump(&mut self, plant: usize, enable: bool) -> Result<()>;
|
|
fn fault(&mut self, plant: usize, enable: bool) -> Result<()>;
|
|
fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32>;
|
|
fn general_fault(&mut self, enable: bool);
|
|
|
|
fn test_pump(&mut self, plant: usize) -> Result<()>;
|
|
fn test(&mut self) -> Result<()>;
|
|
fn set_config(&mut self, config: PlantControllerConfig) -> Result<()>;
|
|
fn get_mptt_voltage(&mut self) -> anyhow::Result<Voltage>;
|
|
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
|
|
fn progress(&mut self, counter: u32) {
|
|
let even = counter % 2 == 0;
|
|
let current = counter / (PLANT_COUNT as u32);
|
|
for led in 0..PLANT_COUNT {
|
|
self.fault(led, current == led as u32).unwrap();
|
|
}
|
|
let _ = self.general_fault(even.into());
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub struct FreePeripherals {
|
|
pub gpio0: Gpio0,
|
|
pub gpio1: Gpio1,
|
|
pub gpio2: Gpio2,
|
|
pub gpio3: Gpio3,
|
|
pub gpio4: Gpio4,
|
|
pub gpio5: Gpio5,
|
|
pub gpio6: Gpio6,
|
|
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,
|
|
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 adc1: ADC1,
|
|
}
|
|
|
|
impl PlantHal {
|
|
fn create_i2c() -> Mutex<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 fn create() -> Result<Mutex<HAL<'static>>> {
|
|
let peripherals = Peripherals::take()?;
|
|
let sys_loop = EspSystemEventLoop::take()?;
|
|
let nvs = EspDefaultNvsPartition::take()?;
|
|
let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?;
|
|
|
|
let mut boot_button = PinDriver::input(peripherals.pins.gpio9.downgrade())?;
|
|
boot_button.set_pull(Pull::Floating)?;
|
|
|
|
let free_pins = FreePeripherals {
|
|
adc1: peripherals.adc1,
|
|
pcnt0: peripherals.pcnt0,
|
|
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.pins.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.pins.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 mut esp = Esp {
|
|
mqtt_client: None,
|
|
wifi_driver,
|
|
boot_button,
|
|
delay: Delay::new(1000),
|
|
};
|
|
|
|
//init,reset rtc memory depending on cause
|
|
let mut init_rtc_store: bool = false;
|
|
let mut to_config_mode: bool = false;
|
|
let reasons = ResetReason::get();
|
|
match reasons {
|
|
ResetReason::Software => {}
|
|
ResetReason::ExternalPin => {}
|
|
ResetReason::Watchdog => {
|
|
init_rtc_store = true;
|
|
}
|
|
ResetReason::Sdio => init_rtc_store = true,
|
|
ResetReason::Panic => init_rtc_store = true,
|
|
ResetReason::InterruptWatchdog => init_rtc_store = true,
|
|
ResetReason::PowerOn => init_rtc_store = true,
|
|
ResetReason::Unknown => init_rtc_store = true,
|
|
ResetReason::Brownout => init_rtc_store = true,
|
|
ResetReason::TaskWatchdog => init_rtc_store = true,
|
|
ResetReason::DeepSleep => {}
|
|
ResetReason::USBPeripheral => {
|
|
init_rtc_store = true;
|
|
to_config_mode = true;
|
|
}
|
|
ResetReason::JTAG => init_rtc_store = true,
|
|
};
|
|
log(
|
|
LogMessage::ResetReason,
|
|
init_rtc_store as u32,
|
|
to_config_mode as u32,
|
|
"",
|
|
&format!("{reasons:?}"),
|
|
);
|
|
|
|
esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode);
|
|
let fs_mount_error = esp.mount_file_system().is_err();
|
|
|
|
let config = esp.load_config();
|
|
|
|
println!("Init rtc driver");
|
|
let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER));
|
|
|
|
println!("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) => {
|
|
println!("Rtc Module reports time at UTC {}", tt);
|
|
}
|
|
Err(err) => {
|
|
println!("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 {
|
|
OkStd(_) => {}
|
|
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 {})
|
|
}
|
|
};
|
|
|
|
let board_hal: Box<dyn BoardInteraction + Send> = match config.hardware.board {
|
|
BoardVersion::INITIAL => {
|
|
initial_hal::create_initial_board(free_pins, fs_mount_error, 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)?
|
|
}
|
|
};
|
|
|
|
HAL { board_hal }
|
|
}
|
|
Err(err) => {
|
|
log(
|
|
LogMessage::ConfigModeMissingConfig,
|
|
0,
|
|
0,
|
|
"",
|
|
&err.to_string(),
|
|
);
|
|
HAL {
|
|
board_hal: initial_hal::create_initial_board(
|
|
free_pins,
|
|
fs_mount_error,
|
|
PlantControllerConfig::default(),
|
|
esp,
|
|
)?,
|
|
}
|
|
}
|
|
};
|
|
|
|
Ok(Mutex::new(hal))
|
|
}
|
|
}
|