wifi config file handling added

This commit is contained in:
Empire 2023-12-12 03:46:53 +01:00
parent e43538ec8a
commit 5d6871250e
4 changed files with 316 additions and 176 deletions

View File

@ -74,6 +74,7 @@ one-wire-bus = "0.1.1"
anyhow = { version = "1.0.75", features = ["std", "backtrace"] } anyhow = { version = "1.0.75", features = ["std", "backtrace"] }
schemars = "0.8.16" schemars = "0.8.16"
heapless = { version = "0.8.0", features = ["serde"] } heapless = { version = "0.8.0", features = ["serde"] }
serde_json = "1.0.108"
#?bq34z100 required #?bq34z100 required
[build-dependencies] [build-dependencies]

View File

@ -1,10 +1,12 @@
use std::fmt;
use serde::{Serialize, Deserialize};
use crate::PLANT_COUNT; use crate::PLANT_COUNT;
pub struct Config {
ssid: heapless::String<32>,
password: Option<heapless::String<64>>,
#[derive(Serialize, Deserialize)]
pub struct Config {
tank_sensor_enabled: bool, tank_sensor_enabled: bool,
tank_full_ml: u32, tank_full_ml: u32,
tank_warn_percent: u8, tank_warn_percent: u8,
@ -19,6 +21,16 @@ pub struct Config {
night_lamp_hour_start: u8, night_lamp_hour_start: u8,
night_lamp_hour_end: u8, night_lamp_hour_end: u8,
night_lamp_only_when_dark: u8 night_lamp_only_when_dark: u8
}
#[derive(Serialize, Deserialize)]
#[derive(Debug)]
pub struct WifiConfig {
pub ssid: heapless::String<32>,
pub password: Option<heapless::String<64>>,
}
impl fmt::Display for WifiConfig {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, ****)", self.ssid)
}
} }

View File

@ -2,6 +2,7 @@ use std::{
ffi::CString, ffi::CString,
fs::File, fs::File,
io::{Read, Write}, io::{Read, Write},
mem,
str::from_utf8, str::from_utf8,
}; };
@ -11,14 +12,39 @@ use anyhow::{Context, Result};
use chrono_tz::Europe::Berlin; use chrono_tz::Europe::Berlin;
use esp_idf_hal::delay::Delay; use esp_idf_hal::delay::Delay;
use esp_idf_svc::http::server::EspHttpServer; 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 webserver::webserver::httpd;
use crate::config::{Config, WifiConfig};
mod config; mod config;
pub mod plant_hal; pub mod plant_hal;
mod webserver { mod webserver {
pub 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<()> { fn main() -> Result<()> {
// It is necessary to call this function once. Otherwise some patches to the runtime // 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 // 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"); let git_hash = env!("VERGEN_GIT_DESCRIBE");
println!("Version useing git has {}", git_hash); println!("Version useing git has {}", git_hash);
println!("Board hal init");
let mut board = PlantHal::create()?; 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(); println!("cur is {}", cur);
let mut cur = match time {
Ok(cur) => cur, if board.is_config_reset() {
Err(err) => { println!("Reset config is pressed, waiting 5s");
log::error!("time error {}", err); Delay::new_default().delay_ms(5000);
NaiveDateTime::from_timestamp_millis(0).unwrap().and_utc() if board.is_config_reset() {
} println!("Reset config is still pressed, deleting configs and reboot");
}; match board.remove_configs() {
//check if we know the time current > 2020 Ok(_) => {
if cur.year() < 2020 { println!("Removed config files, restarting");
if board.is_day() { unsafe {
//assume TZ safe times ;) esp_restart();
cur = *cur.with_hour(15).get_or_insert(cur); }
} else { }
cur = *cur.with_hour(3).get_or_insert(cur); 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(); // let proceed = config.unwrap();
//check if we have a config file //check if we have a config file
// if not found or parsing error -> error very fast blink general fault // 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 //if this happens after a firmeware upgrade (check image state), mark as invalid
//blink general fault error_reading_config_after_upgrade, reboot after //blink general fault error_reading_config_after_upgrade, reboot after
// open accesspoint with webserver for wlan mqtt setup // open accesspoint with webserver for wlan mqtt setup
//blink general fault error_no_config_after_upgrade //blink general fault error_no_config_after_upgrade
//once config is set store it and reboot //once config is set store it and reboot
//if proceed.tank_sensor_enabled() { //if proceed.tank_sensor_enabled() {
//} //}
//is tank sensor enabled in config? //is tank sensor enabled in config?
//measure tank level (without wifi due to interference) //measure tank level (without wifi due to interference)
//TODO this should be a result// detect invalid measurement value //TODO this should be a result// detect invalid measurement value
let tank_value = board.tank_sensor_mv(); let tank_value = board.tank_sensor_mv();
match tank_value { match tank_value {
Ok(tank_raw) => { Ok(tank_raw) => {
println!("Tank sensor returned {}", tank_raw); println!("Tank sensor returned {}", tank_raw);
}, }
Err(_) => { Err(_) => {
//if not possible value, blink general fault error_tank_sensor_fault //if not possible value, blink general fault error_tank_sensor_fault
board.general_fault(true); board.general_fault(true);
//set general fault persistent //set general fault persistent
//set tank sensor state to fault //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)?;
} }
println!("Running logic at utc {}", cur);
//match board.wifi("C3MA", Some("chaosimquadrat"), 10000) { let europe_time = cur.with_timezone(&Berlin);
// Ok(_) => println!("online mode"), println!("Running logic at europe/berlin {}", europe_time);
// 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!("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])?);
/* if(online_mode == OnlineMode::SnTp){
match board.sntp(1000 * 120) { //mqtt here
Ok(new_time) => cur = new_time, }
Err(err) => { if(online_mode == OnlineMode::Mqtt){
println!("sntp error: {}", err); //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); //deepsleep here?
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);
}
*/
return Ok(()); return Ok(());
} }

View File

@ -1,12 +1,15 @@
//mod config; //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::eventloop::EspSystemEventLoop;
use esp_idf_svc::nvs::EspDefaultNvsPartition; use esp_idf_svc::nvs::EspDefaultNvsPartition;
use esp_idf_svc::wifi::EspWifi; use esp_idf_svc::wifi::EspWifi;
use std::ffi::CString;
use std::fs::File; 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 std::sync::Mutex;
use anyhow::{Context, Result, bail, Ok}; use anyhow::{Context, Result, bail, Ok};
use anyhow::anyhow; use anyhow::anyhow;
@ -25,10 +28,11 @@ use esp_idf_svc::systime::EspSystemTime;
use esp_idf_sys::EspError; use esp_idf_sys::EspError;
use one_wire_bus::OneWire; use one_wire_bus::OneWire;
use shift_register_driver::sipo::ShiftRegister40; 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 esp_idf_hal::prelude::Peripherals;
use serde::{Deserialize, Serialize};
use crate::config; use crate::config::{self, WifiConfig};
pub const PLANT_COUNT:usize = 8; pub const PLANT_COUNT:usize = 8;
const PINS_PER_PLANT:usize = 5; 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_B_OFFSET:usize = 3;
const PLANT_MOIST_A_OFFSET:usize = 4; 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"] #[link_section = ".rtc.data"]
@ -64,6 +71,12 @@ pub struct BatteryState {
state_health_percent: u8 state_health_percent: u8
} }
pub struct FileSystemSizeInfo{
pub total_size: usize,
pub used_size: usize,
pub free_size: usize
}
#[derive(Debug)] #[derive(Debug)]
pub enum Sensor{ pub enum Sensor{
A, A,
@ -74,6 +87,8 @@ pub trait PlantCtrlBoardInteraction{
fn time(&mut self) -> Result<chrono::DateTime<Utc>>; fn time(&mut self) -> Result<chrono::DateTime<Utc>>;
fn wifi(&mut self, ssid:&str, password:Option<&str>, max_wait:u32) -> Result<()>; fn wifi(&mut self, ssid:&str, password:Option<&str>, max_wait:u32) -> Result<()>;
fn sntp(&mut self, max_wait:u32) -> Result<chrono::DateTime<Utc>>; fn sntp(&mut self, max_wait:u32) -> Result<chrono::DateTime<Utc>>;
fn mountFileSystem(&mut self) -> Result<()>;
fn fileSystemSize(&mut self) -> Result<FileSystemSizeInfo>;
fn battery_state(&mut self) -> Result<BatteryState>; fn battery_state(&mut self) -> Result<BatteryState>;
@ -101,7 +116,12 @@ pub trait PlantCtrlBoardInteraction{
//keep state during deepsleep //keep state during deepsleep
fn fault(&self,plant:usize, enable:bool); 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<config::Config>; fn get_config(&mut self) -> Result<config::Config>;
fn get_wifi(&mut self) -> Result<config::WifiConfig>;
fn set_wifi(&mut self, wifi: &WifiConfig) -> Result<()>;
} }
pub trait CreatePlantHal<'a> { pub trait CreatePlantHal<'a> {
@ -199,6 +219,7 @@ impl CreatePlantHal<'_> for PlantHal{
let tank_driver = AdcDriver::new(peripherals.adc1, &Config::new())?; let tank_driver = AdcDriver::new(peripherals.adc1, &Config::new())?;
let tank_channel: AdcChannelDriver<'_, {attenuation::DB_11}, Gpio39> = AdcChannelDriver::new(peripherals.pins.gpio39)?; let tank_channel: AdcChannelDriver<'_, {attenuation::DB_11}, Gpio39> = AdcChannelDriver::new(peripherals.pins.gpio39)?;
let solar_is_day = PinDriver::input(peripherals.pins.gpio25)?; 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 light = PinDriver::output(peripherals.pins.gpio26)?;
let main_pump = PinDriver::output(peripherals.pins.gpio23)?; let main_pump = PinDriver::output(peripherals.pins.gpio23)?;
let tank_power = PinDriver::output(peripherals.pins.gpio27)?; let tank_power = PinDriver::output(peripherals.pins.gpio27)?;
@ -215,6 +236,7 @@ impl CreatePlantHal<'_> for PlantHal{
tank_driver : tank_driver, tank_driver : tank_driver,
tank_channel: tank_channel, tank_channel: tank_channel,
solar_is_day : solar_is_day, solar_is_day : solar_is_day,
boot_button : boot_button,
light: light, light: light,
main_pump: main_pump, main_pump: main_pump,
tank_power: tank_power, tank_power: tank_power,
@ -235,6 +257,7 @@ pub struct PlantCtrlBoard<'a>{
tank_driver: AdcDriver<'a, esp_idf_hal::adc::ADC1>, tank_driver: AdcDriver<'a, esp_idf_hal::adc::ADC1>,
tank_channel: esp_idf_hal::adc::AdcChannelDriver<'a, { attenuation::DB_11 }, Gpio39 >, 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>, 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>, signal_counter: PcntDriver<'a>,
light: PinDriver<'a, esp_idf_hal::gpio::Gpio26, esp_idf_hal::gpio::Output>, 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>, main_pump: PinDriver<'a, esp_idf_hal::gpio::Gpio23, esp_idf_hal::gpio::Output>,
@ -445,12 +468,72 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
return Ok(()); return Ok(());
} }
fn get_config(&mut self) -> Result<config::Config> { fn mountFileSystem(&mut self) -> Result<()> {
let config_file = File::open("config.cfg")?; let base_path = CString::new("/spiffs")?;
//serde_json::from_str(&data); let storage = CString::new(SPIFFS_PARTITION_NAME)?;
bail!("Not implemented"); 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<FileSystemSizeInfo> {
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<config::WifiConfig> {
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<config::Config> {
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")
}
} }