354 lines
12 KiB
Rust
354 lines
12 KiB
Rust
pub(crate) mod battery;
|
|
mod esp;
|
|
mod initial_hal;
|
|
mod v3_hal;
|
|
mod v4_hal;
|
|
|
|
use battery::BQ34Z100G1;
|
|
use bq34z100::Bq34z100g1Driver;
|
|
|
|
use crate::log::LogMessage;
|
|
use ds323x::DateTimeAccess;
|
|
use esp_ota::mark_app_valid;
|
|
|
|
use eeprom24x::Eeprom24xTrait;
|
|
use embedded_hal_bus::i2c::MutexDevice;
|
|
|
|
use esp_idf_hal::adc::ADC1;
|
|
use esp_idf_hal::i2c::{APBTickType, I2cConfig, I2cDriver};
|
|
use esp_idf_hal::units::FromValueType;
|
|
use esp_idf_svc::eventloop::EspSystemEventLoop;
|
|
use esp_idf_svc::nvs::EspDefaultNvsPartition;
|
|
use esp_idf_svc::wifi::EspWifi;
|
|
use esp_idf_sys::esp_restart;
|
|
use esp_idf_sys::{
|
|
esp_deep_sleep, esp_sleep_enable_ext1_wakeup,
|
|
esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW,
|
|
};
|
|
use once_cell::sync::Lazy;
|
|
|
|
use anyhow::{Ok, Result};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use chrono::{DateTime, Utc};
|
|
use std::result::Result::Ok as OkStd;
|
|
use std::sync::Mutex;
|
|
use std::time::Duration;
|
|
|
|
use crate::config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig};
|
|
use crate::hal::battery::{print_battery_bq34z100, BatteryInteraction, NoBatteryMonitor};
|
|
use crate::hal::esp::ESP;
|
|
use crate::hal::initial_hal::Initial;
|
|
use crate::log::log;
|
|
use embedded_hal::digital::OutputPin;
|
|
use esp_idf_hal::delay::Delay;
|
|
use esp_idf_hal::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,
|
|
};
|
|
use esp_idf_hal::pcnt::PCNT0;
|
|
use esp_idf_hal::prelude::Peripherals;
|
|
use esp_idf_hal::reset::ResetReason;
|
|
use measurements::{Current, Voltage};
|
|
use pca9535::StandardExpanderInterface;
|
|
|
|
//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);
|
|
|
|
#[non_exhaustive]
|
|
struct V3Constants;
|
|
|
|
impl V3Constants {}
|
|
|
|
const X25: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC);
|
|
|
|
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>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
|
pub struct BackupHeader {
|
|
pub timestamp: i64,
|
|
crc16: u16,
|
|
pub size: usize,
|
|
}
|
|
|
|
impl Default for BackupHeader {
|
|
fn default() -> Self {
|
|
Self {
|
|
timestamp: Default::default(),
|
|
crc16: Default::default(),
|
|
size: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub trait BoardInteraction<'a> {
|
|
fn set_charge_indicator(&mut self, charging: bool) -> Result<()>;
|
|
fn is_day(&self) -> bool;
|
|
fn get_mptt_voltage(&mut self) -> Result<Voltage>;
|
|
fn get_mptt_current(&mut self) -> Result<Current>;
|
|
|
|
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 deep_sleep(&mut self, duration_in_ms: u64) -> !;
|
|
fn get_backup_info(&mut self) -> Result<BackupHeader>;
|
|
fn get_backup_config(&mut self) -> Result<Vec<u8>>;
|
|
fn backup_config(&mut self, bytes: &[u8]) -> Result<()>;
|
|
//should be multsampled
|
|
fn water_temperature_c(&mut self) -> Result<f32>;
|
|
/// return median tank sensor value in milli volt
|
|
fn tank_sensor_voltage(&mut self) -> Result<f32>;
|
|
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 factory_reset(&mut self) -> Result<()>;
|
|
fn get_rtc_time(&mut self) -> Result<DateTime<Utc>>;
|
|
fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> Result<()>;
|
|
fn test_pump(&mut self, plant: usize) -> Result<()>;
|
|
fn test(&mut self) -> Result<()>;
|
|
fn set_config(&mut self, config: PlantControllerConfig) -> Result<()>;
|
|
}
|
|
|
|
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();
|
|
|
|
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)?
|
|
}
|
|
BoardVersion::V4 => {
|
|
v4_hal::create_v4(free_pins, esp, config, battery_interaction)?
|
|
}
|
|
};
|
|
|
|
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))
|
|
}
|
|
}
|