From 6499b18adaa963d2a544f0eab90ea424c36bc0d4 Mon Sep 17 00:00:00 2001 From: Empire Date: Wed, 11 Jun 2025 22:08:59 +0200 Subject: [PATCH] allow startup with initial board mode and nearly no pin configs --- rust/.cargo/config.toml | 4 +- rust/src/config.rs | 22 +- rust/src/main.rs | 140 +++-- rust/src/plant_hal.rs | 873 +++++++++++++++++++------------- rust/src/plant_state.rs | 15 +- rust/src/tank.rs | 7 +- rust/src/webserver/webserver.rs | 33 +- website/themes/blowfish | 2 +- 8 files changed, 627 insertions(+), 469 deletions(-) diff --git a/rust/.cargo/config.toml b/rust/.cargo/config.toml index d87148b..c7e8e2b 100644 --- a/rust/.cargo/config.toml +++ b/rust/.cargo/config.toml @@ -5,8 +5,8 @@ target = "riscv32imac-esp-espidf" [target.riscv32imac-esp-espidf] linker = "ldproxy" #runner = "espflash flash --monitor --baud 921600 --partition-table partitions.csv -b no-reset" # Select this runner in case of usb ttl -#runner = "espflash flash --monitor --baud 921600 --flash-size 16mb --partition-table partitions.csv" -runner = "cargo runner" +runner = "espflash flash --monitor --baud 921600 --flash-size 16mb --partition-table partitions.csv" +#runner = "cargo runner" #runner = "espflash flash --monitor --partition-table partitions.csv -b no-reset" # create upgrade image file for webupload diff --git a/rust/src/config.rs b/rust/src/config.rs index f70fd25..f103e02 100644 --- a/rust/src/config.rs +++ b/rust/src/config.rs @@ -1,6 +1,5 @@ -use std::str::FromStr; - use serde::{Deserialize, Serialize}; +use std::str::FromStr; use crate::plant_state::PlantWateringMode; use crate::PLANT_COUNT; @@ -13,6 +12,7 @@ pub struct NetworkConfig { pub password: Option>, pub mqtt_url: Option>, pub base_topic: Option>, + pub max_wait: u32 } impl Default for NetworkConfig { fn default() -> Self { @@ -22,6 +22,7 @@ impl Default for NetworkConfig { password: None, mqtt_url: None, base_topic: None, + max_wait: 10000, } } } @@ -72,9 +73,26 @@ impl Default for TankConfig { } } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] +pub enum BatteryBoardVersion{ + #[default] + Disabled, + BQ34Z100G1, + WchI2cSlave +} +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] +pub enum BoardVersion{ + #[default] + INITIAL, + V3, + V4 +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] #[serde(default)] pub struct PlantControllerConfig { + pub board_hardware: BoardVersion, + pub battery_hardware: BatteryBoardVersion, pub network: NetworkConfig, pub tank: TankConfig, pub night_lamp: NightLampConfig, diff --git a/rust/src/main.rs b/rust/src/main.rs index 0d7939b..b7f72f0 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -1,4 +1,4 @@ -use crate::{config::PlantControllerConfig, webserver::webserver::httpd}; +use crate::webserver::webserver::httpd; use anyhow::bail; use chrono::{DateTime, Datelike, Timelike, Utc}; use chrono_tz::Tz; @@ -27,11 +27,11 @@ pub mod plant_hal; mod plant_state; mod tank; -use crate::plant_hal::{BatteryInteraction, BoardInteraction, HAL}; +use crate::plant_hal::{BatteryInteraction, BoardHal, BoardInteraction, HAL}; use plant_state::PlantState; use tank::*; -pub static BOARD_ACCESS: Lazy> = Lazy::new(|| PlantHal::create_v3().unwrap()); +pub static BOARD_ACCESS: Lazy> = Lazy::new(|| PlantHal::create().unwrap()); pub static STAY_ALIVE: Lazy = Lazy::new(|| AtomicBool::new(false)); mod webserver { @@ -113,7 +113,6 @@ fn safe_main() -> anyhow::Result<()> { esp_idf_sys::CONFIG_MAIN_TASK_STACK_SIZE ) } - println!("Startup Rust"); let mut to_config = false; @@ -151,20 +150,9 @@ fn safe_main() -> anyhow::Result<()> { }; log(LogMessage::PartitionState, 0, 0, "", ota_state_string); - let mut board = BOARD_ACCESS.lock().unwrap(); + let mut board = BOARD_ACCESS.lock().expect("Could not lock board no other lock should be able to exist during startup!"); board.general_fault(false); - log(LogMessage::MountingFilesystem, 0, 0, "", ""); - board.mount_file_system()?; - let free_space = board.file_system_size()?; - log( - LogMessage::FilesystemMount, - free_space.free_size as u32, - free_space.total_size as u32, - &free_space.used_size.to_string(), - "", - ); - let cur = board .get_rtc_time() .or_else(|err| { @@ -220,28 +208,22 @@ fn safe_main() -> anyhow::Result<()> { } } - let config: PlantControllerConfig = match board.get_config() { - Ok(valid) => valid, - Err(err) => { - log( - LogMessage::ConfigModeMissingConfig, - 0, - 0, - "", - &err.to_string(), - ); - //config upload will trigger reboot! - let _ = board.wifi_ap(None); + match board.board_hal { + BoardHal::Initial { .. } => { + //config upload will trigger reboot and then switch to selected board_hal + let _ = board.wifi_ap(); drop(board); let reboot_now = Arc::new(AtomicBool::new(false)); let _webserver = httpd(reboot_now.clone()); wait_infinity(WaitType::MissingConfig, reboot_now.clone()); } - }; + _ => {} + } + println!("attempting to connect wifi"); - let network_mode = if config.network.ssid.is_some() { - try_connect_wifi_sntp_mqtt(&mut board, &config) + let network_mode = if board.config.network.ssid.is_some() { + try_connect_wifi_sntp_mqtt(&mut board) } else { println!("No wifi configured"); NetworkMode::OFFLINE @@ -249,7 +231,7 @@ fn safe_main() -> anyhow::Result<()> { if matches!(network_mode, NetworkMode::OFFLINE) && to_config { println!("Could not connect to station and config mode forced, switching to ap mode!"); - match board.wifi_ap(Some(config.network.ap_ssid.clone())) { + match board.wifi_ap() { Ok(_) => { println!("Started ap, continuing") } @@ -257,7 +239,7 @@ fn safe_main() -> anyhow::Result<()> { } } - let timezone = match &config.timezone { + let timezone = match & board.config.timezone { Some(tz_str) => tz_str.parse::().unwrap_or_else(|_| { println!("Invalid timezone '{}', falling back to UTC", tz_str); UTC @@ -274,8 +256,8 @@ fn safe_main() -> anyhow::Result<()> { ); if let NetworkMode::WIFI { ref ip_address, .. } = network_mode { - publish_firmware_info(version, address, ota_state_string, &mut board, &config, &ip_address, timezone_time); - publish_battery_state(&mut board, &config); + publish_firmware_info(version, address, ota_state_string, &mut board, &ip_address, timezone_time); + publish_battery_state(&mut board); } @@ -301,10 +283,10 @@ fn safe_main() -> anyhow::Result<()> { let dry_run = false; - let tank_state = determine_tank_state(&mut board, &config); + let tank_state = determine_tank_state(&mut board); if tank_state.is_enabled() { - if let Some(err) = tank_state.got_error(&config.tank) { + if let Some(err) = tank_state.got_error(&board.config.tank) { match err { TankError::SensorDisabled => { /* unreachable */ } TankError::SensorMissing(raw_value_mv) => log( @@ -327,7 +309,7 @@ fn safe_main() -> anyhow::Result<()> { } // disabled cannot trigger this because of wrapping if is_enabled board.general_fault(true); - } else if tank_state.warn_level(&config.tank).is_ok_and(|warn| warn) { + } else if tank_state.warn_level(&board.config.tank).is_ok_and(|warn| warn) { log(LogMessage::TankWaterLevelLow, 0, 0, "", ""); board.general_fault(true); } @@ -342,15 +324,15 @@ fn safe_main() -> anyhow::Result<()> { } } - publish_tank_state(&mut board, &config, &tank_state, &water_temp); + publish_tank_state(&mut board, &tank_state, &water_temp); let plantstate: [PlantState; PLANT_COUNT] = - core::array::from_fn(|i| PlantState::read_hardware_state(i, &mut board, &config.plants[i])); - publish_plant_states(&mut board, &config, &timezone_time, &plantstate); + core::array::from_fn(|i| PlantState::read_hardware_state(i, &mut board)); + publish_plant_states(&mut board, &timezone_time, &plantstate); let pump_required = plantstate .iter() - .zip(&config.plants) + .zip(&board.config.plants) .any(|(it, conf)| it.needs_to_be_watered(conf, &timezone_time)) && !water_frozen; if pump_required { @@ -358,7 +340,7 @@ fn safe_main() -> anyhow::Result<()> { if !dry_run { board.any_pump(true)?; // enables main power output, eg for a central pump with valve setup or a main water valve for the risk affine } - for (plant_id, (state, plant_config)) in plantstate.iter().zip(&config.plants).enumerate() { + for (plant_id, (state, plant_config)) in plantstate.iter().zip(&board.config.plants.clone()).enumerate() { if state.needs_to_be_watered(plant_config, &timezone_time) { let pump_count = board.consecutive_pump_count(plant_id) + 1; board.store_consecutive_pump_count(plant_id, pump_count); @@ -385,14 +367,14 @@ fn safe_main() -> anyhow::Result<()> { board.last_pump_time(plant_id); //state.active = true; - pump_info(&mut board, &config, plant_id, true, pump_ineffective); + pump_info(&mut board, plant_id, true, pump_ineffective); if !dry_run { board.pump(plant_id, true)?; Delay::new_default().delay_ms(1000 * plant_config.pump_time_s as u32); board.pump(plant_id, false)?; } - pump_info(&mut board, &config, plant_id, false, pump_ineffective); + pump_info(&mut board, plant_id, false, pump_ineffective); } else if !state.pump_in_timeout(plant_config, &timezone_time) { // plant does not need to be watered and is not in timeout @@ -409,26 +391,26 @@ fn safe_main() -> anyhow::Result<()> { let state_of_charge = board.battery_monitor.state_charge_percent().unwrap_or(0); let mut light_state = LightState { - enabled: config.night_lamp.enabled, + enabled: board.config.night_lamp.enabled, ..Default::default() }; if light_state.enabled { light_state.is_day = is_day; light_state.out_of_work_hour = !in_time_range( &timezone_time, - config.night_lamp.night_lamp_hour_start, - config.night_lamp.night_lamp_hour_end, + board.config.night_lamp.night_lamp_hour_start, + board.config.night_lamp.night_lamp_hour_end, ); - if state_of_charge < config.night_lamp.low_soc_cutoff { + if state_of_charge < board.config.night_lamp.low_soc_cutoff { board.set_low_voltage_in_cycle(); - } else if state_of_charge > config.night_lamp.low_soc_restore { + } else if state_of_charge > board.config.night_lamp.low_soc_restore { board.clear_low_voltage_in_cycle(); } light_state.battery_low = board.low_voltage_in_cycle(); if !light_state.out_of_work_hour { - if config.night_lamp.night_lamp_only_when_dark { + if board.config.night_lamp.night_lamp_only_when_dark { if !light_state.is_day { if light_state.battery_low { board.light(false)?; @@ -453,7 +435,7 @@ fn safe_main() -> anyhow::Result<()> { match serde_json::to_string(&light_state) { Ok(state) => { - let _ = board.mqtt_publish(&config, "/light", state.as_bytes()); + let _ = board.mqtt_publish( "/light", state.as_bytes()); } Err(err) => { println!("Error publishing lightstate {}", err); @@ -461,16 +443,16 @@ fn safe_main() -> anyhow::Result<()> { }; let deep_sleep_duration_minutes: u32 = if state_of_charge < 10 { - let _ = board.mqtt_publish(&config, "/deepsleep", "low Volt 12h".as_bytes()); + let _ = board.mqtt_publish( "/deepsleep", "low Volt 12h".as_bytes()); 12 * 60 } else if is_day { - let _ = board.mqtt_publish(&config, "/deepsleep", "normal 20m".as_bytes()); + let _ = board.mqtt_publish( "/deepsleep", "normal 20m".as_bytes()); 20 } else { - let _ = board.mqtt_publish(&config, "/deepsleep", "night 1h".as_bytes()); + let _ = board.mqtt_publish( "/deepsleep", "night 1h".as_bytes()); 60 }; - let _ = board.mqtt_publish(&config, "/state", "sleep".as_bytes()); + let _ = board.mqtt_publish( "/state", "sleep".as_bytes()); mark_app_valid(); @@ -512,10 +494,10 @@ fn obtain_tank_temperature(board: &mut MutexGuard) -> anyhow::Result { water_temp } -fn publish_tank_state(board: &mut MutexGuard, config: &PlantControllerConfig, tank_state: &TankState, water_temp: &anyhow::Result) { - match serde_json::to_string(&tank_state.as_mqtt_info(&config.tank, water_temp)) { +fn publish_tank_state(board: &mut MutexGuard, tank_state: &TankState, water_temp: &anyhow::Result) { + match serde_json::to_string(&tank_state.as_mqtt_info(&board.config.tank, water_temp)) { Ok(state) => { - let _ = board.mqtt_publish(&config, "/water", state.as_bytes()); + let _ = board.mqtt_publish("/water", state.as_bytes()); } Err(err) => { println!("Error publishing tankstate {}", err); @@ -523,12 +505,12 @@ fn publish_tank_state(board: &mut MutexGuard, config: &PlantControllerConfi }; } -fn publish_plant_states(board: &mut MutexGuard, config: &PlantControllerConfig, timezone_time: &DateTime, plantstate: &[PlantState; 8]) { - for (plant_id, (plant_state, plant_conf)) in plantstate.iter().zip(&config.plants).enumerate() { +fn publish_plant_states(board: &mut MutexGuard, timezone_time: &DateTime, plantstate: &[PlantState; 8]) { + for (plant_id, (plant_state, plant_conf)) in plantstate.iter().zip(& board.config.plants.clone()).enumerate() { match serde_json::to_string(&plant_state.to_mqtt_info(plant_conf, &timezone_time)) { Ok(state) => { let plant_topic = format!("/plant{}", plant_id + 1); - let _ = board.mqtt_publish(&config, &plant_topic, state.as_bytes()); + let _ = board.mqtt_publish(&plant_topic, state.as_bytes()); //reduce speed as else messages will be dropped Delay::new_default().delay_ms(200); } @@ -539,34 +521,27 @@ fn publish_plant_states(board: &mut MutexGuard, config: &PlantControllerCon } } -fn publish_firmware_info(version: VersionInfo, address: u32, ota_state_string: &str, board: &mut MutexGuard, config: &PlantControllerConfig, ip_address: &String, timezone_time: DateTime) { - let _ = board.mqtt_publish(&config, "/firmware/address", ip_address.as_bytes()); - let _ = board.mqtt_publish(&config, "/firmware/githash", version.git_hash.as_bytes()); +fn publish_firmware_info(version: VersionInfo, address: u32, ota_state_string: &str, board: &mut MutexGuard, ip_address: &String, timezone_time: DateTime) { + let _ = board.mqtt_publish("/firmware/address", ip_address.as_bytes()); + let _ = board.mqtt_publish( "/firmware/githash", version.git_hash.as_bytes()); let _ = board.mqtt_publish( - &config, "/firmware/buildtime", version.build_time.as_bytes(), ); let _ = board.mqtt_publish( - &config, "/firmware/last_online", timezone_time.to_rfc3339().as_bytes(), ); - let _ = board.mqtt_publish(&config, "/firmware/ota_state", ota_state_string.as_bytes()); + let _ = board.mqtt_publish( "/firmware/ota_state", ota_state_string.as_bytes()); let _ = board.mqtt_publish( - &config, "/firmware/partition_address", format!("{:#06x}", address).as_bytes(), ); - let _ = board.mqtt_publish(&config, "/state", "online".as_bytes()); + let _ = board.mqtt_publish( "/state", "online".as_bytes()); } -fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard, config: &PlantControllerConfig) -> NetworkMode{ - match board.wifi( - config.network.ssid.clone().unwrap(), - config.network.password.clone(), - 10000, - ) { +fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard) -> NetworkMode{ + match board.wifi() { Ok(ip_info) => { let sntp_mode: SntpMode = match board.sntp(1000 * 10) { Ok(new_time) => { @@ -580,8 +555,8 @@ fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard, config: &PlantControl SntpMode::OFFLINE } }; - let mqtt_connected = if let Some(_) = config.network.mqtt_url { - match board.mqtt(&config) { + let mqtt_connected = if let Some(_) = board.config.network.mqtt_url { + match board.mqtt() { Ok(_) => { println!("Mqtt connection ready"); true @@ -609,7 +584,7 @@ fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard, config: &PlantControl } //TODO clean this up? better state -fn pump_info(board: &mut MutexGuard, config: &PlantControllerConfig, plant_id: usize, pump_active: bool, pump_ineffective: bool) { +fn pump_info(board: &mut MutexGuard, plant_id: usize, pump_active: bool, pump_ineffective: bool) { let pump_info = PumpInfo { enabled: pump_active, pump_ineffective @@ -617,7 +592,7 @@ fn pump_info(board: &mut MutexGuard, config: &PlantControllerConfig, plant_ let pump_topic = format!("/pump{}", plant_id + 1); match serde_json::to_string(&pump_info) { Ok(state) => { - let _ = board.mqtt_publish(config, &pump_topic, state.as_bytes()); + let _ = board.mqtt_publish(&pump_topic, state.as_bytes()); //reduce speed as else messages will be dropped Delay::new_default().delay_ms(200); } @@ -628,11 +603,10 @@ fn pump_info(board: &mut MutexGuard, config: &PlantControllerConfig, plant_ } fn publish_battery_state( - board: &mut MutexGuard<'_, HAL<'_>>, - config: &PlantControllerConfig, + board: &mut MutexGuard<'_, HAL<'_>> ) { let state = board.get_battery_state(); - let _ = board.mqtt_publish(config, "/battery", state.as_bytes()); + let _ = board.mqtt_publish( "/battery", state.as_bytes()); } fn wait_infinity(wait_type: WaitType, reboot_now: Arc) -> ! { diff --git a/rust/src/plant_hal.rs b/rust/src/plant_hal.rs index 5d3a061..b5a76c4 100644 --- a/rust/src/plant_hal.rs +++ b/rust/src/plant_hal.rs @@ -12,7 +12,7 @@ use embedded_svc::wifi::{ use esp_idf_hal::adc::oneshot::config::AdcChannelConfig; use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver}; -use esp_idf_hal::adc::{attenuation, Resolution}; +use esp_idf_hal::adc::{attenuation, Resolution, ADC1}; use esp_idf_hal::i2c::{APBTickType, I2cConfig, I2cDriver, I2cError}; use esp_idf_hal::units::FromValueType; use esp_idf_svc::eventloop::EspSystemEventLoop; @@ -49,10 +49,8 @@ use std::time::Duration; use embedded_hal::digital::OutputPin; use esp_idf_hal::delay::Delay; -use esp_idf_hal::gpio::{AnyInputPin, Gpio18, Gpio5, IOPin, InputOutput, Level, PinDriver, Pull}; -use esp_idf_hal::pcnt::{ - PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, -}; +use esp_idf_hal::gpio::{AnyInputPin, Gpio0, Gpio1, Gpio10, Gpio11, Gpio12, Gpio13, Gpio14, Gpio15, Gpio16, Gpio17, Gpio18, Gpio19, Gpio2, Gpio20, Gpio21, Gpio22, Gpio23, Gpio24, Gpio25, Gpio26, Gpio27, Gpio28, Gpio29, Gpio3, Gpio30, Gpio4, Gpio5, Gpio6, Gpio7, Gpio8, IOPin, InputOutput, Level, PinDriver, Pull}; +use esp_idf_hal::pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, PCNT0}; use esp_idf_hal::prelude::Peripherals; use esp_idf_hal::reset::ResetReason; use esp_idf_svc::sntp::{self, SyncStatus}; @@ -60,19 +58,15 @@ use esp_idf_svc::systime::EspSystemTime; use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError}; use one_wire_bus::OneWire; -use crate::config::PlantControllerConfig; +use crate::config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig}; use crate::log::log; -use crate::plant_hal::BoardHal::V3; +use crate::plant_hal::BoardHal::{Initial, V3, V4}; use crate::{plant_hal, to_string, STAY_ALIVE}; //Only support for 8 right now! pub const PLANT_COUNT: usize = 8; const REPEAT_MOIST_MEASURE: usize = 1; -const SPIFFS_PARTITION_NAME: &str = "storage"; -const CONFIG_FILE: &str = "/spiffs/config.cfg"; -const BASE_PATH: &str = "/spiffs"; - const TANK_MULTI_SAMPLE: usize = 11; const PUMP8_BIT: usize = 0; @@ -151,16 +145,194 @@ pub enum Sensor { } pub struct PlantHal {} - pub struct ESP<'a> { mqtt_client: Option>, - signal_counter: PcntDriver<'a>, wifi_driver: EspWifi<'a>, - boot_button: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, - one_wire_bus: OneWire>, + boot_button: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input> +} + +impl ESP<'_> { + const SPIFFS_PARTITION_NAME: &'static str = "storage"; + const CONFIG_FILE: &'static str = "/spiffs/config.cfg"; + const BASE_PATH: &'static str = "/spiffs"; + fn get_config(&mut self) -> Result { + let cfg = File::open(Self::CONFIG_FILE)?; + let config: PlantControllerConfig = serde_json::from_reader(cfg)?; + Ok(config) + } + pub(crate) fn set_config(&mut self, config: &PlantControllerConfig) -> Result<()> { + let mut cfg = File::create(Self::CONFIG_FILE)?; + serde_json::to_writer(&mut cfg, &config)?; + println!("Wrote config config {:?}", config); + Ok(()) + } + fn delete_config(&self) -> Result<()>{ + let config = Path::new(Self::CONFIG_FILE); + if config.exists() { + println!("Removing config"); + fs::remove_file(config)? + } + Ok(()) + } + fn mount_file_system(&mut self) -> Result<()> { + log(LogMessage::MountingFilesystem, 0, 0, "", ""); + let base_path = CString::new("/spiffs")?; + let storage = CString::new(Self::SPIFFS_PARTITION_NAME)?; + let conf = esp_idf_sys::esp_vfs_spiffs_conf_t { + base_path: base_path.as_ptr(), + partition_label: storage.as_ptr(), + max_files: 5, + format_if_mount_failed: true, + }; + + unsafe { + esp_idf_sys::esp!(esp_idf_sys::esp_vfs_spiffs_register(&conf))?; + } + + let free_space = self.file_system_size()?; + log( + LogMessage::FilesystemMount, + free_space.free_size as u32, + free_space.total_size as u32, + &free_space.used_size.to_string(), + "", + ); + Ok(()) + } + fn file_system_size(&mut self) -> Result { + let storage = CString::new(Self::SPIFFS_PARTITION_NAME)?; + let mut total_size = 0; + let mut used_size = 0; + unsafe { + esp_idf_sys::esp!(esp_spiffs_info( + storage.as_ptr(), + &mut total_size, + &mut used_size + ))?; + } + Ok(FileSystemSizeInfo { + total_size, + used_size, + free_size: total_size - used_size, + }) + } + + + pub(crate) fn list_files(&self) -> FileList { + let storage = CString::new(Self::SPIFFS_PARTITION_NAME).unwrap(); + + let mut file_system_corrupt = None; + + let mut iter_error = None; + let mut result = Vec::new(); + + let filepath = Path::new(Self::BASE_PATH); + let read_dir = fs::read_dir(filepath); + match read_dir { + OkStd(read_dir) => { + for item in read_dir { + match item { + OkStd(file) => { + let f = FileInfo { + filename: file.file_name().into_string().unwrap(), + size: file + .metadata() + .and_then(|it| Result::Ok(it.len())) + .unwrap_or_default() + as usize, + }; + result.push(f); + } + Err(err) => { + iter_error = Some(format!("{err:?}")); + break; + } + } + } + } + Err(err) => { + file_system_corrupt = Some(format!("{err:?}")); + } + } + let mut total: usize = 0; + let mut used: usize = 0; + unsafe { + esp_spiffs_info(storage.as_ptr(), &mut total, &mut used); + } + + FileList { + total, + used, + file_system_corrupt, + files: result, + iter_error, + } + } + pub(crate) fn delete_file(&self, filename: &str) -> Result<()> { + let filepath = Path::new(Self::BASE_PATH).join(Path::new(filename)); + match fs::remove_file(filepath) { + OkStd(_) => Ok(()), + Err(err) => { + bail!(format!("{err:?}")) + } + } + } + pub(crate) fn get_file_handle(&self, filename: &str, write: bool) -> Result { + let filepath = Path::new(Self::BASE_PATH).join(Path::new(filename)); + Ok(if write { + File::create(filepath)? + } else { + File::open(filepath)? + }) + } + + fn init_rtc_deepsleep_memory(&self, init_rtc_store: bool, to_config_mode: bool){ + if init_rtc_store { + unsafe { + LAST_WATERING_TIMESTAMP = [0; PLANT_COUNT]; + CONSECUTIVE_WATERING_PLANT = [0; PLANT_COUNT]; + LOW_VOLTAGE_DETECTED = false; + crate::log::init(); + RESTART_TO_CONF = to_config_mode; + }; + } else { + unsafe { + if to_config_mode { + RESTART_TO_CONF = true; + } + log( + LogMessage::RestartToConfig, + RESTART_TO_CONF as u32, + 0, + "", + "", + ); + log( + LogMessage::LowVoltage, + LOW_VOLTAGE_DETECTED as u32, + 0, + "", + "", + ); + for i in 0..PLANT_COUNT { + println!( + "LAST_WATERING_TIMESTAMP[{}] = UTC {}", + i, LAST_WATERING_TIMESTAMP[i] + ); + } + for i in 0..PLANT_COUNT { + println!( + "CONSECUTIVE_WATERING_PLANT[{}] = {}", + i, CONSECUTIVE_WATERING_PLANT[i] + ); + } + } + } + } } pub struct HAL<'a>{ + pub config: PlantControllerConfig, pub board_hal: BoardHal<'a>, pub esp: ESP<'a>, pub battery_monitor: BatteryMonitor<'a> @@ -180,7 +352,7 @@ pub enum BatteryMonitor<'a> { pub enum BoardHal<'a>{ Initial { - + general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput> }, V3 { shift_register: ShiftRegister40< @@ -196,7 +368,8 @@ pub enum BoardHal<'a>{ main_pump: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - + signal_counter: PcntDriver<'a>, + one_wire_bus: OneWire>, rtc: Ds323x>>, ds323x::ic::DS3231>, eeprom: Eeprom24x< @@ -216,8 +389,7 @@ pub enum BoardHal<'a>{ tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, wifi_driver: EspWifi<'a>, - one_wire_bus: OneWire>, - battery_driver: Bq34z100g1Driver>, Delay>, + one_wire_bus: OneWire>, rtc: Ds323x>>, ds323x::ic::DS3231>, eeprom: Eeprom24x< @@ -414,24 +586,24 @@ impl BatteryInteraction for BatteryMonitor<'_> { impl BoardInteraction for HAL<'_> { fn set_charge_indicator(&mut self, charging: bool) { match &mut self.board_hal { - BoardHal::V3 { shift_register, .. } => { + V3 { shift_register, .. } => { shift_register.decompose()[CHARGING] .set_state(charging.into()) .unwrap(); } - BoardHal::V4 { .. } => {}, - &mut plant_hal::BoardHal::Initial { } => { + V4 { .. } => {}, + Initial { .. } => { } } } fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { match &mut self.board_hal { - BoardHal::V3 { shift_register, .. } => { + V3 { shift_register, .. } => { shift_register.decompose()[AWAKE].set_low().unwrap(); } - BoardHal::V4 { .. } => {}, - &mut plant_hal::BoardHal::Initial { } => { + V4 { .. } => {}, + Initial { .. } => { } } @@ -456,7 +628,7 @@ impl BoardInteraction for HAL<'_> { let eeprom = match &mut self.board_hal { BoardHal::V3 { eeprom, .. } => {eeprom} BoardHal::V4 { eeprom, .. } => {eeprom }, - &mut plant_hal::BoardHal::Initial { } => { + &mut plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") } }; @@ -480,7 +652,7 @@ impl BoardInteraction for HAL<'_> { let eeprom = match &mut self.board_hal { BoardHal::V3 { eeprom, .. } => {eeprom} BoardHal::V4 { eeprom, .. } => {eeprom } - &mut plant_hal::BoardHal::Initial { } => { + &mut plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") } }; @@ -525,7 +697,7 @@ impl BoardInteraction for HAL<'_> { eeprom } BoardHal::V4 { eeprom, .. } => { eeprom }, - &mut plant_hal::BoardHal::Initial { } => { + &mut plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") } }; @@ -568,7 +740,7 @@ impl BoardInteraction for HAL<'_> { eeprom } BoardHal::V4 { eeprom, .. } => { eeprom } - &mut plant_hal::BoardHal::Initial { } => { + &mut plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") } }; @@ -581,7 +753,7 @@ impl BoardInteraction for HAL<'_> { let iter = (current_page % 8) as usize; if iter != lastiter { for i in 0..PLANT_COUNT { - self.fault(i, iter == i); + let _ = self.fault(i, iter == i); } lastiter = iter; } @@ -612,78 +784,11 @@ impl BoardInteraction for HAL<'_> { } } } - fn list_files(&self) -> FileList { - let storage = CString::new(SPIFFS_PARTITION_NAME).unwrap(); - - let mut file_system_corrupt = None; - - let mut iter_error = None; - let mut result = Vec::new(); - - let filepath = Path::new(BASE_PATH); - let read_dir = fs::read_dir(filepath); - match read_dir { - OkStd(read_dir) => { - for item in read_dir { - match item { - OkStd(file) => { - let f = FileInfo { - filename: file.file_name().into_string().unwrap(), - size: file - .metadata() - .and_then(|it| Result::Ok(it.len())) - .unwrap_or_default() - as usize, - }; - result.push(f); - } - Err(err) => { - iter_error = Some(format!("{err:?}")); - break; - } - } - } - } - Err(err) => { - file_system_corrupt = Some(format!("{err:?}")); - } - } - let mut total: usize = 0; - let mut used: usize = 0; - unsafe { - esp_spiffs_info(storage.as_ptr(), &mut total, &mut used); - } - - FileList { - total, - used, - file_system_corrupt, - files: result, - iter_error, - } - } - fn delete_file(&self, filename: &str) -> Result<()> { - let filepath = Path::new(BASE_PATH).join(Path::new(filename)); - match fs::remove_file(filepath) { - OkStd(_) => Ok(()), - Err(err) => { - bail!(format!("{err:?}")) - } - } - } - fn get_file_handle(&self, filename: &str, write: bool) -> Result { - let filepath = Path::new(BASE_PATH).join(Path::new(filename)); - Ok(if write { - File::create(filepath)? - } else { - File::open(filepath)? - }) - } fn is_day(& self) -> bool { match & self.board_hal { BoardHal::V3 { solar_is_day, .. } => {solar_is_day.get_level().into()} BoardHal::V4 { solar_is_day, .. } => {solar_is_day.get_level().into() } - plant_hal::BoardHal::Initial {} => { + plant_hal::BoardHal::Initial { .. } => { false } } @@ -692,10 +797,18 @@ impl BoardInteraction for HAL<'_> { fn water_temperature_c(&mut self) -> Result { let mut delay = Delay::new_default(); - self.esp.one_wire_bus + let one_wire_bus = match &mut self.board_hal { + BoardHal::V3 { one_wire_bus, .. } => one_wire_bus, + BoardHal::V4 { one_wire_bus, .. } => one_wire_bus, + &mut plant_hal::BoardHal::Initial { .. } => { + bail!("Board not configured yet") + } + }; + + one_wire_bus .reset(&mut delay) .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; - let first = self.esp.one_wire_bus.devices(false, &mut delay).next(); + let first = one_wire_bus.devices(false, &mut delay).next(); if first.is_none() { bail!("Not found any one wire Ds18b20"); } @@ -707,11 +820,11 @@ impl BoardInteraction for HAL<'_> { .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; water_temp_sensor - .start_temp_measurement(&mut self.esp.one_wire_bus, &mut delay) + .start_temp_measurement(one_wire_bus, &mut delay) .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut delay); let sensor_data = water_temp_sensor - .read_data(&mut self.esp.one_wire_bus, &mut delay) + .read_data(one_wire_bus, &mut delay) .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; if sensor_data.temperature == 85_f32 { bail!("Ds18b20 dummy temperature returned"); @@ -723,7 +836,7 @@ impl BoardInteraction for HAL<'_> { let (tank_power, tank_channel) = match &mut self.board_hal { BoardHal::V3 { tank_power, tank_channel, .. } => {(tank_power,tank_channel)} BoardHal::V4 { tank_power, tank_channel, .. } => {(tank_power,tank_channel) } - &mut plant_hal::BoardHal::Initial { } => { + &mut plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") } }; @@ -759,7 +872,7 @@ impl BoardInteraction for HAL<'_> { let light = match &mut self.board_hal { BoardHal::V3 { light, .. } => light, BoardHal::V4 { light, .. } => light, - &mut plant_hal::BoardHal::Initial { } => { + &mut plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") } }; @@ -789,7 +902,7 @@ impl BoardInteraction for HAL<'_> { BoardHal::V4 { .. } => { bail!("Not yet implemented") }, - &plant_hal::BoardHal::Initial {} => { + &plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") } } @@ -835,7 +948,7 @@ impl BoardInteraction for HAL<'_> { BoardHal::V4 { .. } => { bail!("Not yet implemented") } - &plant_hal::BoardHal::Initial {} => { + &plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") } } @@ -853,7 +966,7 @@ impl BoardInteraction for HAL<'_> { BoardHal::V4 { main_pump, .. } => { main_pump.set_state(enable.into())?; } - &mut plant_hal::BoardHal::Initial { } => { + &mut plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") } } @@ -883,11 +996,11 @@ impl BoardInteraction for HAL<'_> { } fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result { match &mut self.board_hal { - V3{ shift_register, .. } => { + V3{ signal_counter, shift_register, .. } => { let mut results = [0_f32; REPEAT_MOIST_MEASURE]; for repeat in 0..REPEAT_MOIST_MEASURE { - self.esp.signal_counter.counter_pause()?; - self.esp.signal_counter.counter_clear()?; + signal_counter.counter_pause()?; + signal_counter.counter_clear()?; //Disable all shift_register.decompose()[MS_4].set_high()?; @@ -952,13 +1065,13 @@ impl BoardInteraction for HAL<'_> { //give some time to stabilize delay.delay_ms(10); - self.esp.signal_counter.counter_resume()?; + signal_counter.counter_resume()?; delay.delay_ms(measurement); - self.esp.signal_counter.counter_pause()?; + signal_counter.counter_pause()?; shift_register.decompose()[MS_4].set_high()?; shift_register.decompose()[SENSOR_ON].set_low()?; delay.delay_ms(10); - let unscaled = self.esp.signal_counter.get_counter_value()? as i32; + let unscaled = signal_counter.get_counter_value()? as i32; let hz = unscaled as f32 * factor; log( LogMessage::RawMeasure, @@ -983,19 +1096,24 @@ impl BoardInteraction for HAL<'_> { } fn general_fault(&mut self, enable: bool) { let general_fault = match &mut self.board_hal { - BoardHal::V3 { general_fault, .. } => {general_fault} - BoardHal::V4 { general_fault, .. } => {general_fault}, - &mut plant_hal::BoardHal::Initial { } => { - return - } + V3 { general_fault, .. } => {general_fault} + V4 { general_fault, .. } => {general_fault}, + Initial { general_fault, .. } => {general_fault} }; unsafe { gpio_hold_dis(general_fault.pin()) }; general_fault.set_state(enable.into()).unwrap(); unsafe { gpio_hold_en(general_fault.pin()) }; } - fn wifi_ap(&mut self, ap_ssid: Option>) -> Result<()> { - let ssid = - ap_ssid.unwrap_or(heapless::String::from_str("PlantCtrl Emergency Mode").unwrap()); + fn wifi_ap(&mut self) -> Result<()> { + let ssid = match self.board_hal { + Initial { .. } => { + //this mode is only used if no config file is found, or it is unparseable + heapless::String::from_str("PlantCtrl Emergency Mode").unwrap() + }, + _ => { + self.config.network.ap_ssid.clone() + } + }; let apconfig = AccessPointConfiguration { ssid, auth_method: AuthMethod::None, @@ -1008,11 +1126,12 @@ impl BoardInteraction for HAL<'_> { Ok(()) } fn wifi( - &mut self, - ssid: heapless::String<32>, - password: Option>, - max_wait: u32, + &mut self ) -> Result { + let ssid = self.config.network.ssid.clone().ok_or(anyhow!("No ssid configured"))?; + let password = self.config.network.password.clone(); + let max_wait = self.config.network.max_wait; + match password { Some(pw) => { //TODO expect error due to invalid pw or similar! //call this during configuration and check if works, revert to config mode if not @@ -1050,7 +1169,7 @@ impl BoardInteraction for HAL<'_> { bail!("Did not manage wifi connection within timeout"); } } - println!("Should be connected now"); + println!("Should be connected now, waiting for link to be up"); while !self.esp.wifi_driver.is_up()? { delay.delay_ms(250); @@ -1067,51 +1186,12 @@ impl BoardInteraction for HAL<'_> { log(LogMessage::WifiInfo, 0, 0, "", &format!("{address:?}")); Ok(address) } - fn mount_file_system(&mut self) -> Result<()> { - let base_path = CString::new("/spiffs")?; - let storage = CString::new(SPIFFS_PARTITION_NAME)?; - let conf = esp_idf_sys::esp_vfs_spiffs_conf_t { - base_path: base_path.as_ptr(), - partition_label: storage.as_ptr(), - max_files: 5, - format_if_mount_failed: true, - }; - - //TODO check fielsystem esp_spiffs_check - - unsafe { - esp_idf_sys::esp!(esp_idf_sys::esp_vfs_spiffs_register(&conf))?; - Ok(()) - } - } - fn file_system_size(&mut self) -> Result { - let storage = CString::new(SPIFFS_PARTITION_NAME)?; - let mut total_size = 0; - let mut used_size = 0; - unsafe { - esp_idf_sys::esp!(esp_spiffs_info( - storage.as_ptr(), - &mut total_size, - &mut used_size - ))?; - } - Ok(FileSystemSizeInfo { - total_size, - used_size, - free_size: total_size - used_size, - }) - } fn mode_override_pressed(&mut self) -> bool { self.esp.boot_button.get_level() == Level::Low } fn factory_reset(&mut self) -> Result<()> { println!("factory resetting"); - let config = Path::new(CONFIG_FILE); - if config.exists() { - println!("Removing config"); - fs::remove_file(config)?; - } - + self.esp.delete_config()?; //destroy backup header let dummy: [u8; 0] = []; self.backup_config(&dummy)?; @@ -1120,9 +1200,9 @@ impl BoardInteraction for HAL<'_> { } fn get_rtc_time(&mut self) -> Result> { let rtc = match &mut self.board_hal { - BoardHal::V3 { rtc, .. } => {rtc} - BoardHal::V4 { rtc, .. } => {rtc}, - &mut plant_hal::BoardHal::Initial { } => { + V3 { rtc, .. } => {rtc} + V4 { rtc, .. } => {rtc}, + Initial { .. } => { bail!("Board not configured yet") } }; @@ -1135,9 +1215,9 @@ impl BoardInteraction for HAL<'_> { } fn set_rtc_time(&mut self, time: &DateTime) -> Result<()> { let rtc = match &mut self.board_hal { - BoardHal::V3 { rtc, .. } => {rtc} - BoardHal::V4 { rtc, .. } => {rtc} - &mut plant_hal::BoardHal::Initial { } => { + V3 { rtc, .. } => {rtc} + V4 { rtc, .. } => {rtc} + Initial { .. } => { bail!("Board not configured yet") } }; @@ -1149,17 +1229,6 @@ impl BoardInteraction for HAL<'_> { } } } - fn get_config(&mut self) -> Result { - let cfg = File::open(CONFIG_FILE)?; - let config: PlantControllerConfig = serde_json::from_reader(cfg)?; - Ok(config) - } - fn set_config(&mut self, config: &PlantControllerConfig) -> Result<()> { - let mut cfg = File::create(CONFIG_FILE)?; - serde_json::to_writer(&mut cfg, &config)?; - println!("Wrote config config {:?}", config); - Ok(()) - } fn wifi_scan(&mut self) -> Result> { self.esp.wifi_driver.start_scan( &ScanConfig { @@ -1220,8 +1289,8 @@ impl BoardInteraction for HAL<'_> { Delay::new_default().delay_ms(10); Ok(()) } - fn mqtt(&mut self, config: &PlantControllerConfig) -> Result<()> { - let base_topic = config + fn mqtt(&mut self) -> Result<()> { + let base_topic = self.config .network .base_topic .as_ref() @@ -1229,7 +1298,7 @@ impl BoardInteraction for HAL<'_> { if base_topic.is_empty() { bail!("Mqtt base_topic was empty") } - let mqtt_url = config + let mqtt_url = self.config .network .mqtt_url .as_ref() @@ -1378,7 +1447,6 @@ impl BoardInteraction for HAL<'_> { } fn mqtt_publish( &mut self, - config: &PlantControllerConfig, subtopic: &str, message: &[u8], ) -> Result<()> { @@ -1396,7 +1464,7 @@ impl BoardInteraction for HAL<'_> { let client = self.esp.mqtt_client.as_mut().unwrap(); let mut full_topic: heapless::String<256> = heapless::String::new(); if full_topic - .push_str(config.network.base_topic.as_ref().unwrap()) + .push_str(self.config.network.base_topic.as_ref().unwrap()) .is_err() { println!("Some error assembling full_topic 1"); @@ -1458,9 +1526,6 @@ pub trait BoardInteraction { fn get_backup_config(&mut self) -> Result>; fn backup_config(&mut self, bytes: &[u8]) -> Result<()>; fn get_battery_state(&mut self) -> String; - fn list_files(&self) -> FileList; - fn delete_file(&self, filename: &str) -> Result<()>; - fn get_file_handle(&self, filename: &str, write: bool) -> Result; fn is_day(&self) -> bool; //should be multsampled fn water_temperature_c(&mut self) -> Result; @@ -1481,28 +1546,20 @@ pub trait BoardInteraction { fn sntp(&mut self, max_wait_ms: u32) -> Result>; fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result; fn general_fault(&mut self, enable: bool); - fn wifi_ap(&mut self, ap_ssid: Option>) -> Result<()>; + fn wifi_ap(&mut self) -> Result<()>; fn wifi( &mut self, - ssid: heapless::String<32>, - password: Option>, - max_wait: u32, ) -> Result; - fn mount_file_system(&mut self) -> Result<()>; - fn file_system_size(&mut self) -> Result; fn mode_override_pressed(&mut self) -> bool; fn factory_reset(&mut self) -> Result<()>; fn get_rtc_time(&mut self) -> Result>; fn set_rtc_time(&mut self, time: &DateTime) -> Result<()>; - fn get_config(&mut self) -> Result; - fn set_config(&mut self, config: &PlantControllerConfig) -> Result<()>; fn wifi_scan(&mut self) -> Result>; fn test_pump(&mut self, plant: usize) -> Result<()>; fn test(&mut self) -> Result<()>; - fn mqtt(&mut self, config: &PlantControllerConfig) -> Result<()>; + fn mqtt(&mut self) -> Result<()>; fn mqtt_publish( &mut self, - config: &PlantControllerConfig, subtopic: &str, message: &[u8], ) -> Result<()>; @@ -1511,7 +1568,7 @@ pub trait BoardInteraction { } -fn print_battery( +fn print_battery_bq34z100( battery_driver: &mut Bq34z100g1Driver>, Delay>, ) -> Result<(), Bq34Z100Error> { println!("Try communicating with battery"); @@ -1569,6 +1626,42 @@ fn print_battery( Result::Ok(()) } + +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, + 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, + pub gpio19: Gpio19, + pub gpio20: Gpio20, + 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, +} + pub static I2C_DRIVER: Lazy>> = Lazy::new(PlantHal::create_i2c); impl PlantHal { fn create_i2c() -> Mutex> { @@ -1586,77 +1679,59 @@ impl PlantHal { Mutex::new(I2cDriver::new(i2c, sda, scl, &config).unwrap()) } - pub fn create_v3() -> Result>> { + + + pub fn create() -> Result>> { 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 clock = PinDriver::input_output(peripherals.pins.gpio15.downgrade())?; - clock.set_pull(Pull::Floating)?; - let mut latch = PinDriver::input_output(peripherals.pins.gpio3.downgrade())?; - latch.set_pull(Pull::Floating)?; - let mut data = PinDriver::input_output(peripherals.pins.gpio23.downgrade())?; - data.set_pull(Pull::Floating)?; - let shift_register = ShiftRegister40::new(clock, latch, data); - //disable all - for mut pin in shift_register.decompose() { - pin.set_low()?; - } - let awake = &mut shift_register.decompose()[AWAKE]; - awake.set_high()?; + let mut boot_button = PinDriver::input(peripherals.pins.gpio9.downgrade())?; + boot_button.set_pull(Pull::Floating)?; - let charging = &mut shift_register.decompose()[CHARGING]; - charging.set_high()?; - - let ms0 = &mut shift_register.decompose()[MS_0]; - ms0.set_low()?; - let ms1 = &mut shift_register.decompose()[MS_1]; - ms1.set_low()?; - let ms2 = &mut shift_register.decompose()[MS_2]; - ms2.set_low()?; - let ms3 = &mut shift_register.decompose()[MS_3]; - ms3.set_low()?; - - let ms4 = &mut shift_register.decompose()[MS_4]; - ms4.set_high()?; - - println!("Init battery driver"); - let mut battery_driver = Bq34z100g1Driver { - i2c: MutexDevice::new(&I2C_DRIVER), - delay: Delay::new(0), - flash_block_data: [0; 32], + 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, + gpio19: peripherals.pins.gpio19, + gpio20: peripherals.pins.gpio20, + 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, }; - println!("Init rtc driver"); - let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER)); - - println!("Init rtc eeprom driver"); - let mut eeprom = { - Eeprom24x::new_24x32( - MutexDevice::new(&I2C_DRIVER), - SlaveAddr::Alternative(true, true, true), - ) + let mut esp = ESP { + mqtt_client: None, + wifi_driver, + boot_button }; - let mut one_wire_pin = PinDriver::input_output_od(peripherals.pins.gpio18.downgrade())?; - one_wire_pin.set_pull(Pull::Floating)?; - - 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); - } - } - match eeprom.read_byte(0) { - OkStd(byte) => { - println!("Read first byte with status {}", byte); - } - Err(err) => { - println!("Eeprom could not read first byte {:?}", err); - } - } //init,reset rtc memory depending on cause let mut init_rtc_store: bool = false; @@ -1689,57 +1764,178 @@ impl PlantHal { "", &format!("{reasons:?}"), ); - if init_rtc_store { - unsafe { - LAST_WATERING_TIMESTAMP = [0; PLANT_COUNT]; - CONSECUTIVE_WATERING_PLANT = [0; PLANT_COUNT]; - LOW_VOLTAGE_DETECTED = false; - crate::log::init(); - RESTART_TO_CONF = to_config_mode; - }; - } else { - unsafe { - if to_config_mode { - RESTART_TO_CONF = true; + + esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode); + let fs_mount_error = esp.mount_file_system().is_err(); + + let config = esp.get_config(); + let hal = match config { + Result::Ok(config) => { + let board_hal : BoardHal = match config.board_hardware { + + BoardVersion::INITIAL => { + let mut general_fault = PinDriver::input_output(free_pins.gpio6.downgrade())?; + general_fault.set_pull(Pull::Floating)?; + general_fault.set_low()?; + + if fs_mount_error { + general_fault.set_high()? + } + + Initial { general_fault}}, + BoardVersion::V3 => {PlantHal::create_v3(free_pins)?}, + BoardVersion::V4 => {PlantHal::create_v4(free_pins)?} + }; + + let battery_monitor : BatteryMonitor = match config.battery_hardware { + BatteryBoardVersion::Disabled => { BatteryMonitor::Disabled {}} + 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:?})"), + ); + } + } + BatteryMonitor::BQ34Z100G1 { battery_driver } + } + BatteryBoardVersion::WchI2cSlave => { + BatteryMonitor::WchI2cSlave {} + } + }; + + + HAL{ + config, + board_hal, + esp, + battery_monitor } + } + Err(err) => { log( - LogMessage::RestartToConfig, - RESTART_TO_CONF as u32, + LogMessage::ConfigModeMissingConfig, + 0, 0, "", - "", + &err.to_string(), ); - log( - LogMessage::LowVoltage, - LOW_VOLTAGE_DETECTED as u32, - 0, - "", - "", - ); - for i in 0..PLANT_COUNT { - println!( - "LAST_WATERING_TIMESTAMP[{}] = UTC {}", - i, LAST_WATERING_TIMESTAMP[i] - ); - } - for i in 0..PLANT_COUNT { - println!( - "CONSECUTIVE_WATERING_PLANT[{}] = {}", - i, CONSECUTIVE_WATERING_PLANT[i] - ); + + let mut general_fault = PinDriver::input_output(free_pins.gpio6.downgrade())?; + general_fault.set_pull(Pull::Floating)?; + general_fault.set_low()?; + + HAL{ + config: Default::default(), + board_hal: Initial { general_fault}, + esp, + battery_monitor: BatteryMonitor::Disabled {}, } } + }; + + Ok(Mutex::new(hal)) + } + + fn create_v4(peripherals: FreePeripherals) -> Result> { + let mut general_fault = PinDriver::input_output(peripherals.gpio6.downgrade())?; + general_fault.set_pull(Pull::Floating)?; + general_fault.set_low()?; + + + //temp remove me + general_fault.set_high()?; + + bail!("not implemented"); + } + + fn create_v3(peripherals: FreePeripherals) -> Result> { + let mut clock = PinDriver::input_output(peripherals.gpio15.downgrade())?; + clock.set_pull(Pull::Floating)?; + let mut latch = PinDriver::input_output(peripherals.gpio3.downgrade())?; + latch.set_pull(Pull::Floating)?; + let mut data = PinDriver::input_output(peripherals.gpio23.downgrade())?; + data.set_pull(Pull::Floating)?; + let shift_register = ShiftRegister40::new(clock, latch, data); + //disable all + for mut pin in shift_register.decompose() { + pin.set_low()?; + } + + let awake = &mut shift_register.decompose()[AWAKE]; + awake.set_high()?; + + let charging = &mut shift_register.decompose()[CHARGING]; + charging.set_high()?; + + let ms0 = &mut shift_register.decompose()[MS_0]; + ms0.set_low()?; + let ms1 = &mut shift_register.decompose()[MS_1]; + ms1.set_low()?; + let ms2 = &mut shift_register.decompose()[MS_2]; + ms2.set_low()?; + let ms3 = &mut shift_register.decompose()[MS_3]; + ms3.set_low()?; + + let ms4 = &mut shift_register.decompose()[MS_4]; + ms4.set_high()?; + + println!("Init battery driver"); + + + println!("Init rtc driver"); + let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER)); + + println!("Init rtc eeprom driver"); + let mut eeprom = { + Eeprom24x::new_24x32( + MutexDevice::new(&I2C_DRIVER), + SlaveAddr::Alternative(true, true, true), + ) + }; + + let mut one_wire_pin = PinDriver::input_output_od(peripherals.gpio18.downgrade())?; + one_wire_pin.set_pull(Pull::Floating)?; + + 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); + } + } + match eeprom.read_byte(0) { + OkStd(byte) => { + println!("Read first byte with status {}", byte); + } + Err(err) => { + println!("Eeprom could not read first byte {:?}", err); + } } - let mut counter_unit1 = PcntDriver::new( + + let mut signal_counter = PcntDriver::new( peripherals.pcnt0, - Some(peripherals.pins.gpio22), + Some(peripherals.gpio22), Option::::None, Option::::None, Option::::None, )?; - counter_unit1.channel_config( + signal_counter.channel_config( PcntChannel::Channel0, PinIndex::Pin0, PinIndex::Pin1, @@ -1753,9 +1949,7 @@ impl PlantHal { }, )?; - let sys_loop = EspSystemEventLoop::take()?; - let nvs = EspDefaultNvsPartition::take()?; - let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?; + let adc_config = AdcChannelConfig { attenuation: attenuation::DB_11, @@ -1764,73 +1958,50 @@ impl PlantHal { }; let tank_driver = AdcDriver::new(peripherals.adc1)?; let tank_channel: AdcChannelDriver> = - AdcChannelDriver::new(tank_driver, peripherals.pins.gpio5, &adc_config)?; + AdcChannelDriver::new(tank_driver, peripherals.gpio5, &adc_config)?; - let mut solar_is_day = PinDriver::input(peripherals.pins.gpio7.downgrade())?; + let mut solar_is_day = PinDriver::input(peripherals.gpio7.downgrade())?; solar_is_day.set_pull(Pull::Floating)?; - let mut boot_button = PinDriver::input(peripherals.pins.gpio9.downgrade())?; - boot_button.set_pull(Pull::Floating)?; - let mut light = PinDriver::input_output(peripherals.pins.gpio10.downgrade())?; + + let mut light = PinDriver::input_output(peripherals.gpio10.downgrade())?; light.set_pull(Pull::Floating)?; - let mut main_pump = PinDriver::input_output(peripherals.pins.gpio2.downgrade())?; + let mut main_pump = PinDriver::input_output(peripherals.gpio2.downgrade())?; main_pump.set_pull(Pull::Floating)?; main_pump.set_low()?; - let mut tank_power = PinDriver::input_output(peripherals.pins.gpio11.downgrade())?; + let mut tank_power = PinDriver::input_output(peripherals.gpio11.downgrade())?; tank_power.set_pull(Pull::Floating)?; - let mut general_fault = PinDriver::input_output(peripherals.pins.gpio6.downgrade())?; + let mut general_fault = PinDriver::input_output(peripherals.gpio6.downgrade())?; general_fault.set_pull(Pull::Floating)?; general_fault.set_low()?; let one_wire_bus = OneWire::new(one_wire_pin) .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; - let status = print_battery(&mut battery_driver); - match status { - OkStd(_) => {} - Err(err) => { - log( - LogMessage::BatteryCommunicationError, - 0u32, - 0, - "", - &format!("{err:?})"), - ); - } - } - let mut shift_register_enable_invert = PinDriver::output(peripherals.pins.gpio21.downgrade())?; + + let mut shift_register_enable_invert = PinDriver::output(peripherals.gpio21.downgrade())?; unsafe { gpio_hold_dis(shift_register_enable_invert.pin()) }; shift_register_enable_invert.set_low()?; unsafe { gpio_hold_en(shift_register_enable_invert.pin()) }; - let rv = Mutex::new(HAL { - board_hal: BoardHal::V3 { - shift_register, - shift_register_enable_invert, - tank_channel, - solar_is_day, - light, - main_pump, - tank_power, - general_fault, - rtc, - eeprom, - }, - esp: ESP { - mqtt_client: None, - signal_counter: counter_unit1, - wifi_driver, - boot_button, - one_wire_bus, - }, - battery_monitor: BatteryMonitor::BQ34Z100G1 { - battery_driver - }, - }); - Ok(rv) + + Ok(BoardHal::V3 { + shift_register, + shift_register_enable_invert, + tank_channel, + solar_is_day, + light, + main_pump, + tank_power, + general_fault, + signal_counter, + one_wire_bus, + rtc, + eeprom, + }) } } diff --git a/rust/src/plant_state.rs b/rust/src/plant_state.rs index d531995..e0e67ef 100644 --- a/rust/src/plant_state.rs +++ b/rust/src/plant_state.rs @@ -114,15 +114,14 @@ fn map_range_moisture( impl PlantState { pub fn read_hardware_state( plant_id: usize, - board: &mut plant_hal::HAL, - config: &PlantConfig, + board: &mut plant_hal::HAL ) -> Self { - let sensor_a = if config.sensor_a { + let sensor_a = if board.config.plants[plant_id].sensor_a { match board.measure_moisture_hz(plant_id, plant_hal::Sensor::A) { Ok(raw) => match map_range_moisture( raw, - config.moisture_sensor_min_frequency, - config.moisture_sensor_max_frequency, + board.config.plants[plant_id].moisture_sensor_min_frequency, + board.config.plants[plant_id].moisture_sensor_max_frequency, ) { Ok(moisture_percent) => MoistureSensorState::MoistureValue { raw_hz: raw, @@ -138,12 +137,12 @@ impl PlantState { MoistureSensorState::Disabled }; - let sensor_b = if config.sensor_b { + let sensor_b = if board.config.plants[plant_id].sensor_b { match board.measure_moisture_hz(plant_id, plant_hal::Sensor::B) { Ok(raw) => match map_range_moisture( raw, - config.moisture_sensor_min_frequency, - config.moisture_sensor_max_frequency, + board.config.plants[plant_id].moisture_sensor_min_frequency, + board.config.plants[plant_id].moisture_sensor_max_frequency, ) { Ok(moisture_percent) => MoistureSensorState::MoistureValue { raw_hz: raw, diff --git a/rust/src/tank.rs b/rust/src/tank.rs index 8f0bd9e..9217e21 100644 --- a/rust/src/tank.rs +++ b/rust/src/tank.rs @@ -1,7 +1,7 @@ use serde::Serialize; +use crate::config::TankConfig; use crate::plant_hal::{BoardInteraction, HAL}; -use crate::config::{PlantControllerConfig, TankConfig}; const OPEN_TANK_VOLTAGE: f32 = 3.0; pub const WATER_FROZEN_THRESH: f32 = 4.0; @@ -156,10 +156,9 @@ impl TankState { } pub fn determine_tank_state( - board: &mut std::sync::MutexGuard<'_, HAL<'_>>, - config: &PlantControllerConfig, + board: &mut std::sync::MutexGuard<'_, HAL<'_>> ) -> TankState { - if config.tank.tank_sensor_enabled { + if board.config.tank.tank_sensor_enabled { match board.tank_sensor_voltage() { Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv), Err(err) => TankState::Error(TankError::BoardError(err.to_string())), diff --git a/rust/src/webserver/webserver.rs b/rust/src/webserver/webserver.rs index 5b3d483..a76f6d2 100644 --- a/rust/src/webserver/webserver.rs +++ b/rust/src/webserver/webserver.rs @@ -114,11 +114,9 @@ fn get_timezones( fn get_live_moisture( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS.lock().unwrap(); - let config = board.get_config().unwrap(); - + let mut board = BOARD_ACCESS.lock().expect("Should never fail"); let plant_state = Vec::from_iter( - (0..PLANT_COUNT).map(|i| PlantState::read_hardware_state(i, &mut board, &config.plants[i])), + (0..PLANT_COUNT).map(|i| PlantState::read_hardware_state(i, &mut board)), ); let a = Vec::from_iter( plant_state @@ -159,11 +157,8 @@ fn get_live_moisture( fn get_config( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS.lock().unwrap(); - let json = match board.get_config() { - Ok(config) => serde_json::to_string(&config)?, - Err(_) => serde_json::to_string(&PlantControllerConfig::default())?, - }; + let board = BOARD_ACCESS.lock().expect("Should never fail"); + let json = serde_json::to_string(&board.config)?; anyhow::Ok(Some(json)) } @@ -193,7 +188,7 @@ fn get_backup_config( fn backup_info( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS.lock().unwrap(); + let mut board = BOARD_ACCESS.lock().expect("Should never fail"); let header = board.get_backup_info(); let json = match header { Ok(h) => { @@ -222,7 +217,9 @@ fn set_config( let all = read_up_to_bytes_from_request(request, Some(3072))?; let config: PlantControllerConfig = serde_json::from_slice(&all)?; let mut board = BOARD_ACCESS.lock().unwrap(); - board.set_config(&config)?; + board.esp.set_config(&config)?; + + board.config = config; anyhow::Ok(Some("saved".to_owned())) } @@ -268,12 +265,11 @@ fn tank_info( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { let mut board = BOARD_ACCESS.lock().unwrap(); - let config = board.get_config()?; - let tank_info = determine_tank_state(&mut board, &config); + let tank_info = determine_tank_state(&mut board); //should be multsampled let water_temp = board.water_temperature_c(); Ok(Some(serde_json::to_string( - &tank_info.as_mqtt_info(&config.tank, &water_temp), + &tank_info.as_mqtt_info(&board.config.tank, &water_temp), )?)) } @@ -302,8 +298,8 @@ fn wifi_scan( fn list_files( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { - let board = BOARD_ACCESS.lock().unwrap(); - let result = board.list_files(); + let board = BOARD_ACCESS.lock().expect("It should be possible to lock the board for exclusive fs access"); + let result = board.esp.list_files(); let file_list_json = serde_json::to_string(&result)?; anyhow::Ok(Some(file_list_json)) } @@ -493,6 +489,7 @@ pub fn httpd(reboot_now: Arc) -> Box> { let file_handle = BOARD_ACCESS .lock() .unwrap() + .esp .get_file_handle(&filename, false); match file_handle { Ok(mut file_handle) => { @@ -529,7 +526,7 @@ pub fn httpd(reboot_now: Arc) -> Box> { .fn_handler("/file", Method::Post, move |mut request| { let filename = query_param(request.uri(), "filename").unwrap(); let lock = BOARD_ACCESS.lock().unwrap(); - let file_handle = lock.get_file_handle(&filename, true); + let file_handle = lock.esp.get_file_handle(&filename, true); match file_handle { //TODO get free filesystem size, check against during write if not to large Ok(mut file_handle) => { @@ -573,7 +570,7 @@ pub fn httpd(reboot_now: Arc) -> Box> { let filename = query_param(request.uri(), "filename").unwrap(); let copy = filename.clone(); let board = BOARD_ACCESS.lock().unwrap(); - match board.delete_file(&filename) { + match board.esp.delete_file(&filename) { Ok(_) => { let info = format!("Deleted file {copy}"); cors_response(request, 200, &info)?; diff --git a/website/themes/blowfish b/website/themes/blowfish index 26d1205..1d21656 160000 --- a/website/themes/blowfish +++ b/website/themes/blowfish @@ -1 +1 @@ -Subproject commit 26d1205439b460bee960fd4c29f3c5c20948875f +Subproject commit 1d21656d5efcf6a6b247245d057bf553f3209f39