From 5d6871250e96753726177f2a7f4a277d46ded939 Mon Sep 17 00:00:00 2001 From: Empire Date: Tue, 12 Dec 2023 03:46:53 +0100 Subject: [PATCH] wifi config file handling added --- rust/Cargo.toml | 1 + rust/src/config.rs | 22 ++- rust/src/main.rs | 370 +++++++++++++++++++++++------------------- rust/src/plant_hal.rs | 99 ++++++++++- 4 files changed, 316 insertions(+), 176 deletions(-) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 0d1d835..17aae5d 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -74,6 +74,7 @@ one-wire-bus = "0.1.1" anyhow = { version = "1.0.75", features = ["std", "backtrace"] } schemars = "0.8.16" heapless = { version = "0.8.0", features = ["serde"] } +serde_json = "1.0.108" #?bq34z100 required [build-dependencies] diff --git a/rust/src/config.rs b/rust/src/config.rs index 55cb978..653707f 100644 --- a/rust/src/config.rs +++ b/rust/src/config.rs @@ -1,10 +1,12 @@ +use std::fmt; +use serde::{Serialize, Deserialize}; use crate::PLANT_COUNT; -pub struct Config { - ssid: heapless::String<32>, - password: Option>, + +#[derive(Serialize, Deserialize)] +pub struct Config { tank_sensor_enabled: bool, tank_full_ml: u32, tank_warn_percent: u8, @@ -19,6 +21,16 @@ pub struct Config { night_lamp_hour_start: u8, night_lamp_hour_end: u8, night_lamp_only_when_dark: u8 - - +} +#[derive(Serialize, Deserialize)] +#[derive(Debug)] +pub struct WifiConfig { + pub ssid: heapless::String<32>, + pub password: Option>, +} + +impl fmt::Display for WifiConfig { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "({}, ****)", self.ssid) + } } \ No newline at end of file diff --git a/rust/src/main.rs b/rust/src/main.rs index 3a0756a..d0771cf 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -2,6 +2,7 @@ use std::{ ffi::CString, fs::File, io::{Read, Write}, + mem, str::from_utf8, }; @@ -11,14 +12,39 @@ use anyhow::{Context, Result}; use chrono_tz::Europe::Berlin; use esp_idf_hal::delay::Delay; use esp_idf_svc::http::server::EspHttpServer; -use plant_hal::{CreatePlantHal, PlantCtrlBoardInteraction, PlantHal, PLANT_COUNT}; +use esp_idf_sys::{esp_restart, vTaskDelay, TickType_t}; +use heapless::String; +use plant_hal::{CreatePlantHal, PlantCtrlBoard, PlantCtrlBoardInteraction, PlantHal, PLANT_COUNT}; use webserver::webserver::httpd; + +use crate::config::{Config, WifiConfig}; mod config; pub mod plant_hal; mod webserver { pub mod webserver; } +#[derive(PartialEq)] +enum OnlineMode { + Offline, + Wifi, + SnTp, + Mqtt, + MqttRoundtrip +} + +fn wait_infinity(board: &mut PlantCtrlBoard<'_>) { + loop { + unsafe { + //do not trigger watchdog + board.general_fault(true); + vTaskDelay(500_u32); + board.general_fault(false); + vTaskDelay(500_u32); + } + } +} + fn main() -> Result<()> { // It is necessary to call this function once. Otherwise some patches to the runtime // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71 @@ -32,184 +58,202 @@ fn main() -> Result<()> { let git_hash = env!("VERGEN_GIT_DESCRIBE"); println!("Version useing git has {}", git_hash); - - let mut board = PlantHal::create()?; + println!("Board hal init"); + let mut board = PlantHal::create()?; + println!("Mounting filesystem"); + board.mountFileSystem()?; + let free_space = board.fileSystemSize()?; + println!( + "Mounted, total space {} used {} free {}", + free_space.total_size, free_space.used_size, free_space.free_size + ); - println!("Board hal init"); + let time = board.time(); + let mut cur = match time { + Ok(cur) => cur, + Err(err) => { + log::error!("time error {}", err); + NaiveDateTime::from_timestamp_millis(0).unwrap().and_utc() + } + }; + //check if we know the time current > 2020 + if cur.year() < 2020 { + if board.is_day() { + //assume TZ safe times ;) + cur = *cur.with_hour(15).get_or_insert(cur); + } else { + cur = *cur.with_hour(3).get_or_insert(cur); + } + } - let time = board.time(); - let mut cur = match time { - Ok(cur) => cur, - Err(err) => { - log::error!("time error {}", err); - NaiveDateTime::from_timestamp_millis(0).unwrap().and_utc() - } - }; - //check if we know the time current > 2020 - if cur.year() < 2020 { - if board.is_day() { - //assume TZ safe times ;) - cur = *cur.with_hour(15).get_or_insert(cur); - } else { - cur = *cur.with_hour(3).get_or_insert(cur); + println!("cur is {}", cur); + + if board.is_config_reset() { + println!("Reset config is pressed, waiting 5s"); + Delay::new_default().delay_ms(5000); + if board.is_config_reset() { + println!("Reset config is still pressed, deleting configs and reboot"); + match board.remove_configs() { + Ok(_) => { + println!("Removed config files, restarting"); + unsafe { + esp_restart(); + } + } + Err(err) => { + println!("Could not remove config files, system borked {}", err); + //terminate main app and freeze + wait_infinity(&mut board); + } } } + } - println!("cur is {}", cur); + let mut online_mode = OnlineMode::Offline; + let wifi_conf = board.get_wifi(); + let wifi: WifiConfig; + match wifi_conf{ + Ok(conf) => { + wifi = conf; + }, + Err(err) => { + println!("Missing wifi config, entering initial config mode {}", err); + //config upload will trigger reboot! + let _webserver = httpd(true); + wait_infinity(&mut board); + //how to do this better? + let ssid: String<32> = String::try_from("").unwrap(); + wifi = WifiConfig { ssid : ssid, password : None}; + }, + }; - - - //continous/interrupt? - //check if boot button is pressed, if longer than 5s delete config and reboot into config mode - - - let config = board.get_config(); - match config { - Ok(conf) => { - - }, - Err(err) => { - - }, - } - // let proceed = config.unwrap(); - //check if we have a config file - // if not found or parsing error -> error very fast blink general fault - //if this happens after a firmeware upgrade (check image state), mark as invalid - //blink general fault error_reading_config_after_upgrade, reboot after - // open accesspoint with webserver for wlan mqtt setup - //blink general fault error_no_config_after_upgrade - //once config is set store it and reboot + //check if we have a config file + // if not found or parsing error -> error very fast blink general fault + //if this happens after a firmeware upgrade (check image state), mark as invalid + //blink general fault error_reading_config_after_upgrade, reboot after + // open accesspoint with webserver for wlan mqtt setup + //blink general fault error_no_config_after_upgrade + //once config is set store it and reboot - //if proceed.tank_sensor_enabled() { + //if proceed.tank_sensor_enabled() { - //} - //is tank sensor enabled in config? - //measure tank level (without wifi due to interference) - //TODO this should be a result// detect invalid measurement value - let tank_value = board.tank_sensor_mv(); - match tank_value { - Ok(tank_raw) => { - println!("Tank sensor returned {}", tank_raw); - }, - Err(_) => { - //if not possible value, blink general fault error_tank_sensor_fault - board.general_fault(true); - //set general fault persistent - //set tank sensor state to fault - }, + //} + //is tank sensor enabled in config? + //measure tank level (without wifi due to interference) + //TODO this should be a result// detect invalid measurement value + let tank_value = board.tank_sensor_mv(); + match tank_value { + Ok(tank_raw) => { + println!("Tank sensor returned {}", tank_raw); + } + Err(_) => { + //if not possible value, blink general fault error_tank_sensor_fault + board.general_fault(true); + //set general fault persistent + //set tank sensor state to fault + } + } + + //measure each plant moisture + let mut initial_measurements_a: [i32; PLANT_COUNT] = [0; PLANT_COUNT]; + let mut initial_measurements_b: [i32; PLANT_COUNT] = [0; PLANT_COUNT]; + let mut initial_measurements_p: [i32; PLANT_COUNT] = [0; PLANT_COUNT]; + for plant in 0..PLANT_COUNT { + initial_measurements_a[plant] = board.measure_moisture_hz(plant, plant_hal::Sensor::A)?; + initial_measurements_b[plant] = board.measure_moisture_hz(plant, plant_hal::Sensor::B)?; + initial_measurements_p[plant] = + board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)?; + } + + println!("attempting to connect wifi"); + match board.wifi(&wifi.ssid, wifi.password.as_deref(), 10000) { + Ok(_) => { + online_mode = OnlineMode::Wifi; + } + Err(_) => { + println!("Offline mode"); + board.general_fault(true); + } + } + + if online_mode == OnlineMode::Wifi { + match board.sntp(1000 * 120) { + Ok(new_time) => { + cur = new_time; + online_mode = OnlineMode::SnTp; + }, + Err(err) => { + println!("sntp error: {}", err); + board.general_fault(true); } - - - //measure each plant moisture - let mut initial_measurements_a: [i32;PLANT_COUNT] = [0;PLANT_COUNT]; - let mut initial_measurements_b: [i32;PLANT_COUNT] = [0;PLANT_COUNT]; - let mut initial_measurements_p: [i32;PLANT_COUNT] = [0;PLANT_COUNT]; - for plant in 0..PLANT_COUNT { - initial_measurements_a[plant] = board.measure_moisture_hz(plant, plant_hal::Sensor::A)?; - initial_measurements_b[plant] = board.measure_moisture_hz(plant, plant_hal::Sensor::B)?; - initial_measurements_p[plant] = board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)?; } - - //match board.wifi("C3MA", Some("chaosimquadrat"), 10000) { - // Ok(_) => println!("online mode"), - // Err(_) => { - - // println!("Offline mode"); - //}, - //} - //try connect wifi and do mqtt roundtrip - // if no wifi, set general fault persistent - //if no mqtt, set general fault persistent - - let mut total_size = 0; - let mut used_size = 0; - unsafe { - let base_path = CString::new("/spiffs")?; - let storage = CString::new("storage")?; - - 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, - }; - esp_idf_sys::esp!(esp_idf_sys::esp_vfs_spiffs_register(&conf))?; - - esp_idf_sys::esp!(esp_idf_sys::esp_spiffs_info(storage.as_ptr(),&mut total_size,&mut used_size))?; + println!("Running logic at utc {}", cur); + let europe_time = cur.with_timezone(&Berlin); + println!("Running logic at europe/berlin {}", europe_time); } - println!("Total spiffs size is {}, used size is {}", total_size, used_size); - println!("writing"); - let mut config_file = File::create("/spiffs/config.cfg")?; - config_file.write_all("test stuff".as_bytes())?; - config_file.flush()?; - println!("Reading"); - let mut cfg = File::open("/spiffs/config.cfg")?; - let mut data: [u8; 512] = [0; 512]; - let read = cfg.read(&mut data)?; - println!("Read file {}", from_utf8(&data[0..read])?); - /* - match board.sntp(1000 * 120) { - Ok(new_time) => cur = new_time, - Err(err) => { - println!("sntp error: {}", err); + if(online_mode == OnlineMode::SnTp){ + //mqtt here + } + if(online_mode == OnlineMode::Mqtt){ + //mqtt roundtrip here + } + //TODO configmode webserver logic here + + /* + + + //if config battery mode + //read battery level + //if not possible set general fault persistent, but do continue + //else + //assume 12v and max capacity + + //if tank sensor is enabled + //if tank sensor fault abort if config require is set + //check if water is > minimum allowed || fault + //if not, set all plants requiring water to persistent fault + + //for each plant + //check if moisture is < target + //state += dry + //check if in cooldown + //state += cooldown + //check if consecutive pumps > limit + //state += notworking + //set plant fault persistent + + //pump one cycle + // set last pump time to now + //during pump state += active + //after pump check if Pump moisture value is increased by config delta x + // state -= active + // state += cooldown + // if not set plant error persistent fault + // state += notworking + //set consecutive pumps+=1 + + //check if during light time + //lightstate += out of worktime + //check battery level + //lightstate += battery empty + //check solar level if config requires + //lightstate += stillday + //if no preventing lightstate, enable light + //lightstate = active + + //keep webserver in scope + let webserver = httpd(true); + let delay = Delay::new_default(); + loop { + //let freertos do shit + delay.delay_ms(1001); } - } - println!("Running logic at utc {}", cur); - let europe_time = cur.with_timezone(&Berlin); - println!("Running logic at europe/berlin {}", europe_time); - - //if config battery mode - //read battery level - //if not possible set general fault persistent, but do continue - //else - //assume 12v and max capacity - - //if tank sensor is enabled - //if tank sensor fault abort if config require is set - //check if water is > minimum allowed || fault - //if not, set all plants requiring water to persistent fault - - //for each plant - //check if moisture is < target - //state += dry - //check if in cooldown - //state += cooldown - //check if consecutive pumps > limit - //state += notworking - //set plant fault persistent - - //pump one cycle - // set last pump time to now - //during pump state += active - //after pump check if Pump moisture value is increased by config delta x - // state -= active - // state += cooldown - // if not set plant error persistent fault - // state += notworking - //set consecutive pumps+=1 - - //check if during light time - //lightstate += out of worktime - //check battery level - //lightstate += battery empty - //check solar level if config requires - //lightstate += stillday - //if no preventing lightstate, enable light - //lightstate = active - - //keep webserver in scope - let webserver = httpd(true); - let delay = Delay::new_default(); - loop { - //let freertos do shit - delay.delay_ms(1001); - } -*/ + */ + //deepsleep here? return Ok(()); } diff --git a/rust/src/plant_hal.rs b/rust/src/plant_hal.rs index 017195e..1ff3d38 100644 --- a/rust/src/plant_hal.rs +++ b/rust/src/plant_hal.rs @@ -1,12 +1,15 @@ //mod config; -use embedded_svc::wifi::{Configuration, ClientConfiguration, AuthMethod}; +use embedded_svc::wifi::{Configuration, ClientConfiguration, AuthMethod, Wifi}; use esp_idf_svc::eventloop::EspSystemEventLoop; use esp_idf_svc::nvs::EspDefaultNvsPartition; use esp_idf_svc::wifi::EspWifi; +use std::ffi::CString; use std::fs::File; -use std::io::Read; +use std::io::{Read, BufReader}; +use std::path::Path; +use std::str::from_utf8; use std::sync::Mutex; use anyhow::{Context, Result, bail, Ok}; use anyhow::anyhow; @@ -25,10 +28,11 @@ use esp_idf_svc::systime::EspSystemTime; use esp_idf_sys::EspError; use one_wire_bus::OneWire; use shift_register_driver::sipo::ShiftRegister40; -use esp_idf_hal::gpio::{PinDriver, Gpio39, Gpio4, AnyInputPin}; +use esp_idf_hal::gpio::{PinDriver, Gpio39, Gpio4, AnyInputPin, Level}; use esp_idf_hal::prelude::Peripherals; +use serde::{Deserialize, Serialize}; -use crate::config; +use crate::config::{self, WifiConfig}; pub const PLANT_COUNT:usize = 8; const PINS_PER_PLANT:usize = 5; @@ -38,6 +42,9 @@ const PLANT_MOIST_PUMP_OFFSET:usize = 2; const PLANT_MOIST_B_OFFSET:usize = 3; const PLANT_MOIST_A_OFFSET:usize = 4; +const SPIFFS_PARTITION_NAME: &str = "storage"; +const WIFI_CONFIG_FILE: &str = "/spiffs/wifi.cfg"; +const CONFIG_FILE: &str = "/spiffs/config.cfg"; #[link_section = ".rtc.data"] @@ -64,6 +71,12 @@ pub struct BatteryState { state_health_percent: u8 } +pub struct FileSystemSizeInfo{ + pub total_size: usize, + pub used_size: usize, + pub free_size: usize +} + #[derive(Debug)] pub enum Sensor{ A, @@ -74,6 +87,8 @@ pub trait PlantCtrlBoardInteraction{ fn time(&mut self) -> Result>; fn wifi(&mut self, ssid:&str, password:Option<&str>, max_wait:u32) -> Result<()>; fn sntp(&mut self, max_wait:u32) -> Result>; + fn mountFileSystem(&mut self) -> Result<()>; + fn fileSystemSize(&mut self) -> Result; fn battery_state(&mut self) -> Result; @@ -101,7 +116,12 @@ pub trait PlantCtrlBoardInteraction{ //keep state during deepsleep fn fault(&self,plant:usize, enable:bool); + //config + fn is_config_reset(&mut self) -> bool; + fn remove_configs(&mut self) -> Result<()>; fn get_config(&mut self) -> Result; + fn get_wifi(&mut self) -> Result; + fn set_wifi(&mut self, wifi: &WifiConfig) -> Result<()>; } pub trait CreatePlantHal<'a> { @@ -199,6 +219,7 @@ impl CreatePlantHal<'_> for PlantHal{ let tank_driver = AdcDriver::new(peripherals.adc1, &Config::new())?; let tank_channel: AdcChannelDriver<'_, {attenuation::DB_11}, Gpio39> = AdcChannelDriver::new(peripherals.pins.gpio39)?; let solar_is_day = PinDriver::input(peripherals.pins.gpio25)?; + let boot_button = PinDriver::input(peripherals.pins.gpio0)?; let light = PinDriver::output(peripherals.pins.gpio26)?; let main_pump = PinDriver::output(peripherals.pins.gpio23)?; let tank_power = PinDriver::output(peripherals.pins.gpio27)?; @@ -215,6 +236,7 @@ impl CreatePlantHal<'_> for PlantHal{ tank_driver : tank_driver, tank_channel: tank_channel, solar_is_day : solar_is_day, + boot_button : boot_button, light: light, main_pump: main_pump, tank_power: tank_power, @@ -235,6 +257,7 @@ pub struct PlantCtrlBoard<'a>{ tank_driver: AdcDriver<'a, esp_idf_hal::adc::ADC1>, tank_channel: esp_idf_hal::adc::AdcChannelDriver<'a, { attenuation::DB_11 }, Gpio39 >, solar_is_day: PinDriver<'a, esp_idf_hal::gpio::Gpio25, esp_idf_hal::gpio::Input>, + boot_button: PinDriver<'a, esp_idf_hal::gpio::Gpio0, esp_idf_hal::gpio::Input>, signal_counter: PcntDriver<'a>, light: PinDriver<'a, esp_idf_hal::gpio::Gpio26, esp_idf_hal::gpio::Output>, main_pump: PinDriver<'a, esp_idf_hal::gpio::Gpio23, esp_idf_hal::gpio::Output>, @@ -445,12 +468,72 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> { return Ok(()); } - fn get_config(&mut self) -> Result { - let config_file = File::open("config.cfg")?; - //serde_json::from_str(&data); - bail!("Not implemented"); + fn mountFileSystem(&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: 2, + format_if_mount_failed: true, + }; + unsafe { + esp_idf_sys::esp!(esp_idf_sys::esp_vfs_spiffs_register(&conf))?; + Ok(()) + } } + fn fileSystemSize(&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_idf_sys::esp_spiffs_info(storage.as_ptr(),&mut total_size,&mut used_size))?; + } + return Ok(FileSystemSizeInfo{total_size, used_size, free_size : total_size - used_size}); + } + + fn is_config_reset(&mut self) -> bool { + return self.boot_button.get_level() == Level::Low; + } + + fn remove_configs(&mut self) -> Result<()> { + let wifi_config = Path::new(WIFI_CONFIG_FILE); + if wifi_config.exists() { + println!("Removing wifi config"); + std::fs::remove_file(wifi_config)?; + } + + let config = Path::new(CONFIG_FILE); + if config.exists() { + println!("Removing config"); + std::fs::remove_file(config)?; + } + Ok(()) + } + + fn get_wifi(&mut self) -> Result { + let cfg = File::open(WIFI_CONFIG_FILE)?; + let config: WifiConfig = serde_json::from_reader(cfg)?; + return Ok(config); + } + + fn set_wifi(&mut self, wifi: &WifiConfig ) -> Result<()> { + let mut cfg = File::create(WIFI_CONFIG_FILE)?; + serde_json::to_writer(&mut cfg, &wifi)?; + println!("Wrote wifi config {}", wifi); + return Ok(()); + } + + fn get_config(&mut self) -> Result { + let mut cfg = File::open(CONFIG_FILE)?; + let mut data: [u8; 512] = [0; 512]; + let read = cfg.read(&mut data)?; + println!("Read file {}", from_utf8(&data[0..read])?); + + + bail!("todo") + } } \ No newline at end of file