diff --git a/rust/src/config.rs b/rust/src/config.rs index 682f8f1..6ba371a 100644 --- a/rust/src/config.rs +++ b/rust/src/config.rs @@ -1,68 +1,77 @@ -use std::{array::from_fn, str::FromStr}; +use std::str::FromStr; use serde::{Deserialize, Serialize}; use crate::PLANT_COUNT; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct Config { +pub struct NetworkConfig { pub ap_ssid: heapless::String<32>, - pub ssid: Option>, pub password: Option>, - pub mqtt_url: Option>, pub base_topic: Option>, - pub max_consecutive_pump_count: u8, +} +impl Default for NetworkConfig { + fn default() -> Self { + Self { + ap_ssid: heapless::String::from_str("PlantCtrl Init").unwrap(), + ssid: None, + password: None, + mqtt_url: None, + base_topic: None, + } + } +} - pub tank_allow_pumping_if_sensor_error: bool, +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct NightLampConfig { + pub night_lamp_hour_start: u8, + pub night_lamp_hour_end: u8, + pub night_lamp_only_when_dark: bool, +} +impl Default for NightLampConfig { + fn default() -> Self { + Self { + night_lamp_hour_start: 19, + night_lamp_hour_end: 2, + night_lamp_only_when_dark: true, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct TankConfig { pub tank_sensor_enabled: bool, + pub tank_allow_pumping_if_sensor_error: bool, pub tank_useable_ml: u32, pub tank_warn_percent: u8, pub tank_empty_percent: u8, pub tank_full_percent: u8, - - pub night_lamp_hour_start: u8, - pub night_lamp_hour_end: u8, - pub night_lamp_only_when_dark: bool, - - pub plants: [Plant; PLANT_COUNT], } - -impl Default for Config { +impl Default for TankConfig { fn default() -> Self { Self { - ap_ssid: heapless::String::from_str("Plantctrl").unwrap(), - ssid: None, - password: None, - - base_topic: Some(heapless::String::from_str("plant/one").unwrap()), - mqtt_url: Some(heapless::String::from_str("mqtt://192.168.1.1:1883").unwrap()), - + tank_sensor_enabled: false, tank_allow_pumping_if_sensor_error: true, - tank_sensor_enabled: true, - tank_warn_percent: 50, - night_lamp_hour_start: 21, - night_lamp_hour_end: 2, - night_lamp_only_when_dark: true, - plants: from_fn(|_i| Plant::default()), - max_consecutive_pump_count: 15, - tank_useable_ml: 5000, - tank_empty_percent: 0_u8, - tank_full_percent: 100_u8, + tank_useable_ml: 50000, + tank_warn_percent: 40, + tank_empty_percent: 5, + tank_full_percent: 95, } } } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub enum Mode { - OFF, - TargetMoisture, - TimerOnly, - TimerAndDeadzone, + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] +pub struct PlantControllerConfig { + pub network: NetworkConfig, + pub tank: TankConfig, + pub night_lamp: NightLampConfig, + pub plants: [PlantConfig; PLANT_COUNT], } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct Plant { +pub struct PlantConfig { pub mode: Mode, pub target_moisture: u8, pub pump_time_s: u16, @@ -70,17 +79,27 @@ pub struct Plant { pub pump_hour_start: u8, pub pump_hour_end: u8, pub sensor_b: bool, + pub max_consecutive_pump_count: u8, } -impl Default for Plant { +impl Default for PlantConfig { fn default() -> Self { Self { - target_moisture: 40, - pump_time_s: 60, - pump_cooldown_min: 60, - pump_hour_start: 8, - pump_hour_end: 20, mode: Mode::OFF, + target_moisture: 40, + pump_time_s: 30, + pump_cooldown_min: 60, + pump_hour_start: 9, + pump_hour_end: 20, sensor_b: false, + max_consecutive_pump_count: 10, } } } + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub enum Mode { + OFF, + TargetMoisture, + TimerOnly, + TimerAndDeadzone, +} diff --git a/rust/src/main.rs b/rust/src/main.rs index 850da95..27a752f 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -22,7 +22,7 @@ use plant_hal::{PlantCtrlBoard, PlantHal, PLANT_COUNT}; use serde::{Deserialize, Serialize}; use crate::{ - config::Config, + config::PlantControllerConfig, espota::{mark_app_valid, rollback_and_reboot}, webserver::webserver::httpd, }; @@ -250,7 +250,7 @@ fn safe_main() -> anyhow::Result<()> { } } - let config: Config; + let config: PlantControllerConfig; match board.get_config() { Ok(valid) => { config = valid; @@ -271,8 +271,12 @@ fn safe_main() -> anyhow::Result<()> { let mut sntp = false; println!("attempting to connect wifi"); let mut ip_address: Option = None; - if config.ssid.is_some() { - match board.wifi(config.ssid.clone().unwrap(), config.password.clone(), 10000) { + if config.network.ssid.is_some() { + match board.wifi( + config.network.ssid.clone().unwrap(), + config.network.password.clone(), + 10000, + ) { Ok(ip_info) => { ip_address = Some(ip_info.ip.to_string()); wifi = true; @@ -412,16 +416,16 @@ fn safe_main() -> anyhow::Result<()> { let mut did_pump = false; match plant_to_pump { Some(plant) => { + let plant_config = &config.plants[plant]; + let state = &mut plantstate[plant]; state.consecutive_pump_count = board.consecutive_pump_count(plant) + 1; board.store_consecutive_pump_count(plant, state.consecutive_pump_count); - if state.consecutive_pump_count > config.max_consecutive_pump_count.into() { + if state.consecutive_pump_count > plant_config.max_consecutive_pump_count.into() { state.not_effective = true; board.fault(plant, true); } - let plant_config = &config.plants[plant]; - println!( "Trying to pump for {}s with pump {} now", plant_config.pump_time_s, plant @@ -454,8 +458,8 @@ fn safe_main() -> anyhow::Result<()> { light_state.is_day = is_day; light_state.out_of_work_hour = !in_time_range( &timezone_time, - config.night_lamp_hour_start, - config.night_lamp_hour_end, + config.night_lamp.night_lamp_hour_start, + config.night_lamp.night_lamp_hour_end, ); let state_of_charge = board.state_charge_percent().unwrap_or(0); @@ -467,7 +471,7 @@ fn safe_main() -> anyhow::Result<()> { light_state.battery_low = board.low_voltage_in_cycle(); if !light_state.out_of_work_hour { - if config.night_lamp_only_when_dark { + if config.night_lamp.night_lamp_only_when_dark { if !light_state.is_day { if light_state.battery_low { board.light(false).unwrap(); @@ -537,7 +541,7 @@ fn safe_main() -> anyhow::Result<()> { fn publish_battery_state( board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, - config: &Config, + config: &PlantControllerConfig, ) { let bat = BatteryState { voltage_milli_volt: &to_string(&board.voltage_milli_volt()), @@ -560,9 +564,9 @@ fn publish_battery_state( fn determine_tank_state( board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, - config: &Config, + config: &PlantControllerConfig, ) -> TankState { - if config.tank_sensor_enabled { + if config.tank.tank_sensor_enabled { let mut rv: TankState = TankState { ..Default::default() }; @@ -572,30 +576,30 @@ fn determine_tank_state( rv.raw = raw; return map_range( ( - config.tank_empty_percent as f32, - config.tank_full_percent as f32, + config.tank.tank_empty_percent as f32, + config.tank.tank_full_percent as f32, ), raw as f32, ); }) .and_then(|percent| { - rv.left_ml = ((percent * config.tank_useable_ml as f32) / 100_f32) as u32; + rv.left_ml = ((percent * config.tank.tank_useable_ml as f32) / 100_f32) as u32; println!( "Tank sensor returned mv {} as {}% leaving {} ml useable", rv.raw, percent as u8, rv.left_ml ); - if config.tank_warn_percent > percent as u8 { + if config.tank.tank_warn_percent > percent as u8 { board.general_fault(true); println!( "Low water, current percent is {}, minimum warn level is {}", - percent as u8, config.tank_warn_percent + percent as u8, config.tank.tank_warn_percent ); rv.warn_level = true; } - if config.tank_empty_percent < percent as u8 { + if config.tank.tank_empty_percent < percent as u8 { println!( "Enough water, current percent is {}, minimum empty level is {}", - percent as u8, config.tank_empty_percent + percent as u8, config.tank.tank_empty_percent ); rv.enough_water = true; } @@ -624,7 +628,7 @@ fn determine_state_target_moisture_for_plant( board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, plant: usize, state: &mut PlantState, - config: &Config, + config: &PlantControllerConfig, tank_state: &TankState, cur: DateTime, ) { @@ -671,7 +675,7 @@ fn determine_state_target_moisture_for_plant( if a_low || b_low { state.dry = true; - if tank_state.sensor_error && !config.tank_allow_pumping_if_sensor_error { + if tank_state.sensor_error && !config.tank.tank_allow_pumping_if_sensor_error { //ignore is ok } else if !tank_state.enough_water { state.no_water = true; @@ -714,7 +718,7 @@ fn determine_state_timer_only_for_plant( board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, plant: usize, state: &mut PlantState, - config: &Config, + config: &PlantControllerConfig, tank_state: &TankState, cur: DateTime, ) { @@ -730,7 +734,7 @@ fn determine_state_timer_only_for_plant( state.next_pump = Some(europe_time); state.cooldown = true; } else { - if tank_state.sensor_error && !config.tank_allow_pumping_if_sensor_error { + if tank_state.sensor_error && !config.tank.tank_allow_pumping_if_sensor_error { state.do_water = true; } else if !tank_state.enough_water { state.no_water = true; @@ -752,7 +756,7 @@ fn determine_state_timer_and_deadzone_for_plant( board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, plant: usize, state: &mut PlantState, - config: &Config, + config: &PlantControllerConfig, tank_state: &TankState, cur: DateTime, ) { @@ -776,7 +780,7 @@ fn determine_state_timer_and_deadzone_for_plant( state.out_of_work_hour = true; } if !state.cooldown && !state.out_of_work_hour { - if tank_state.sensor_error && !config.tank_allow_pumping_if_sensor_error { + if tank_state.sensor_error && !config.tank.tank_allow_pumping_if_sensor_error { state.do_water = true; } else if !tank_state.enough_water { state.no_water = true; @@ -799,7 +803,7 @@ fn determine_next_plant( cur: DateTime, tank_state: &TankState, water_frozen: bool, - config: &Config, + config: &PlantControllerConfig, board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, ) -> Option { for plant in 0..PLANT_COUNT { @@ -850,7 +854,7 @@ fn determine_next_plant( fn update_plant_state( plantstate: &mut [PlantState; PLANT_COUNT], board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, - config: &Config, + config: &PlantControllerConfig, ) { for plant in 0..PLANT_COUNT { let state = &plantstate[plant]; diff --git a/rust/src/plant_hal.rs b/rust/src/plant_hal.rs index 6b39562..a0cb12c 100644 --- a/rust/src/plant_hal.rs +++ b/rust/src/plant_hal.rs @@ -2,7 +2,6 @@ use bq34z100::{Bq34Z100Error, Bq34z100g1, Bq34z100g1Driver}; use ds323x::{DateTimeAccess, Ds323x}; -use eeprom24x::page_size::No; use eeprom24x::{Eeprom24x, SlaveAddr}; use embedded_hal_bus::i2c::MutexDevice; use embedded_svc::wifi::{ @@ -14,7 +13,6 @@ use esp_idf_hal::adc::{attenuation, Resolution}; use esp_idf_hal::i2c::{APBTickType, I2cConfig, I2cDriver, I2cError}; use esp_idf_hal::units::FromValueType; use esp_idf_svc::eventloop::EspSystemEventLoop; -use esp_idf_svc::io::vfs; use esp_idf_svc::ipv4::IpInfo; use esp_idf_svc::mqtt::client::QoS::AtLeastOnce; use esp_idf_svc::mqtt::client::QoS::ExactlyOnce; @@ -30,8 +28,7 @@ use anyhow::{anyhow, Context}; use anyhow::{bail, Ok, Result}; use serde::Serialize; use std::ffi::CString; -use std::fs::{self, DirEntry, File}; -use std::io::{Read, Write}; +use std::fs::{self, File}; use std::path::Path; use chrono::{DateTime, Utc}; @@ -52,10 +49,10 @@ use esp_idf_hal::prelude::Peripherals; use esp_idf_hal::reset::ResetReason; use esp_idf_svc::sntp::{self, SyncStatus}; use esp_idf_svc::systime::EspSystemTime; -use esp_idf_sys::{esp, esp_spiffs_check, f_opendir, gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError}; +use esp_idf_sys::{esp, esp_spiffs_check, gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError}; use one_wire_bus::OneWire; -use crate::config::{self, Config}; +use crate::config::{self, PlantControllerConfig}; use crate::{plant_hal, STAY_ALIVE}; //Only support for 8 right now! @@ -171,37 +168,35 @@ pub struct PlantCtrlBoard<'a> { } #[derive(Serialize, Debug)] -pub struct FileInfo{ - filename:String, - size:usize +pub struct FileInfo { + filename: String, + size: usize, } #[derive(Serialize, Debug)] -pub struct FileList{ +pub struct FileList { files: Vec, file_system_corrupt: Option, iter_error: Option, } impl PlantCtrlBoard<'_> { - pub fn list_files(&self, filename:&str) -> FileList { + pub fn list_files(&self, filename: &str) -> FileList { let storage = CString::new(SPIFFS_PARTITION_NAME).unwrap(); let error = unsafe { - esp!{ + esp! { esp_spiffs_check(storage.as_ptr()) } }; - + let mut file_system_corrupt = match error { - OkStd(_) => { - None - }, + OkStd(_) => None, Err(err) => { println!("Corrupt spiffs {err:?}"); Some(format!("{err:?}")) - }, + } }; - + let mut iter_error = None; let mut result = Vec::new(); @@ -214,49 +209,53 @@ impl PlantCtrlBoard<'_> { println!("start loop"); match item { OkStd(file) => { - let f = FileInfo{ + let f = FileInfo { filename: file.file_name().into_string().unwrap(), - size: file.metadata().and_then(|it| core::result::Result::Ok(it.len())).unwrap_or_default() as usize + size: file + .metadata() + .and_then(|it| core::result::Result::Ok(it.len())) + .unwrap_or_default() + as usize, }; println!("fileinfo {f:?}"); result.push(f); - }, + } Err(err) => { - iter_error = Some (format!("{err:?}")); + iter_error = Some(format!("{err:?}")); break; - }, - } + } + } } - }, + } Err(err) => { file_system_corrupt = Some(format!("{err:?}")); - }, + } } - return FileList{ + return FileList { file_system_corrupt, files: result, - iter_error + iter_error, }; } - pub fn delete_file(&self, filename:&str) -> Result<()>{ + pub fn delete_file(&self, filename: &str) -> Result<()> { let filepath = Path::new(BASE_PATH).join(Path::new(filename)); - match (fs::remove_file(filepath)){ + match fs::remove_file(filepath) { OkStd(_) => Ok(()), Err(err) => { bail!(format!("{err:?}")) - }, + } } } - pub fn get_file_handle(&self, filename:&str, write:bool) -> Result { + pub fn get_file_handle(&self, filename: &str, write: bool) -> Result { let filepath = Path::new(BASE_PATH).join(Path::new(filename)); return Ok(if write { File::create(filepath)? } else { File::open(filepath)? - }) + }); } pub fn is_day(&self) -> bool { @@ -495,7 +494,10 @@ impl PlantCtrlBoard<'_> { delay.delay_ms(10); let unscaled = self.signal_counter.get_counter_value()? as i32; let hz = (unscaled as f32 * factor) as i32; - println!("raw measure unscaled {} hz {}, plant {} sensor {:?}",unscaled, hz, plant, sensor); + println!( + "raw measure unscaled {} hz {}, plant {} sensor {:?}", + unscaled, hz, plant, sensor + ); results[repeat] = hz; //println!("Measuring {:?} @ {} with {}", sensor, plant, hz); } @@ -598,7 +600,7 @@ impl PlantCtrlBoard<'_> { max_files: 5, format_if_mount_failed: true, }; - + //TODO check fielsystem esp_spiffs_check unsafe { @@ -661,13 +663,13 @@ impl PlantCtrlBoard<'_> { } } - pub fn get_config(&mut self) -> Result { + pub fn get_config(&mut self) -> Result { let cfg = File::open(CONFIG_FILE)?; - let config: Config = serde_json::from_reader(cfg)?; + let config: PlantControllerConfig = serde_json::from_reader(cfg)?; Ok(config) } - pub fn set_config(&mut self, config: &Config) -> Result<()> { + pub 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); @@ -735,9 +737,17 @@ impl PlantCtrlBoard<'_> { config.exists() } - pub fn mqtt(&mut self, config: &Config) -> Result<()> { - let base_topic = config.base_topic.as_ref().context("missing base topic")?; - let mqtt_url = config.mqtt_url.as_ref().context("missing mqtt url")?; + pub fn mqtt(&mut self, config: &PlantControllerConfig) -> Result<()> { + let base_topic = config + .network + .base_topic + .as_ref() + .context("missing base topic")?; + let mqtt_url = config + .network + .mqtt_url + .as_ref() + .context("missing mqtt url")?; let last_will_topic = format!("{}/state", base_topic); let mqtt_client_config = MqttClientConfiguration { @@ -865,7 +875,12 @@ impl PlantCtrlBoard<'_> { bail!("Mqtt did not fire connection callback in time"); } - pub fn mqtt_publish(&mut self, config: &Config, subtopic: &str, message: &[u8]) -> Result<()> { + pub fn mqtt_publish( + &mut self, + config: &PlantControllerConfig, + subtopic: &str, + message: &[u8], + ) -> Result<()> { if self.mqtt_client.is_none() { return Ok(()); } @@ -880,7 +895,7 @@ impl PlantCtrlBoard<'_> { let client = self.mqtt_client.as_mut().unwrap(); let mut full_topic: heapless::String<256> = heapless::String::new(); if full_topic - .push_str(&config.base_topic.as_ref().unwrap()) + .push_str(&config.network.base_topic.as_ref().unwrap()) .is_err() { println!("Some error assembling full_topic 1"); diff --git a/rust/src/webserver/webserver.rs b/rust/src/webserver/webserver.rs index 270c567..2cd9cd6 100644 --- a/rust/src/webserver/webserver.rs +++ b/rust/src/webserver/webserver.rs @@ -1,20 +1,23 @@ //offer ota and config mode use std::{ - collections::VecDeque, fs, io::{BufRead, Read, Write}, str::from_utf8, sync::{atomic::AtomicBool, Arc} + io::{BufRead, BufReader, Read, Write}, + str::from_utf8, + sync::{atomic::AtomicBool, Arc}, }; use crate::{espota::OtaUpdate, map_range_moisture, plant_hal::FileInfo, BOARD_ACCESS}; +use anyhow::bail; use chrono::DateTime; -use url::Url; use core::result::Result::Ok; use embedded_svc::http::Method; use esp_idf_hal::delay::Delay; -use esp_idf_svc::{http::server::{Configuration, EspHttpConnection, EspHttpServer, Request}}; +use esp_idf_svc::http::server::{Configuration, EspHttpConnection, EspHttpServer, Request}; use heapless::String; use serde::{Deserialize, Serialize}; +use url::Url; -use crate::config::Config; +use crate::config::PlantControllerConfig; #[derive(Serialize, Debug)] struct SSIDList<'a> { @@ -87,8 +90,8 @@ fn get_data( match a_pct { Ok(result) => { a.push(result); - }, - Err(err) => { + } + Err(_) => { a.push(200); } } @@ -97,19 +100,18 @@ fn get_data( match b_pct { Ok(result) => { b.push(result); - }, - Err(err) => { + } + Err(_) => { b.push(200); } } } - let data = LoadData { rtc: rtc.as_str(), native: native.as_str(), moisture_a: a, - moisture_b: b + moisture_b: b, }; let json = serde_json::to_string(&data)?; @@ -122,7 +124,7 @@ fn get_config( 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(&Config::default())?, + Err(_) => serde_json::to_string(&PlantControllerConfig::default())?, }; anyhow::Ok(Some(json)) } @@ -134,7 +136,7 @@ fn set_config( let read = request.read(&mut buf)?; let actual_data = &buf[0..read]; println!("Raw data {}", from_utf8(actual_data).unwrap()); - let config: Config = serde_json::from_slice(actual_data)?; + let config: PlantControllerConfig = serde_json::from_slice(actual_data)?; let mut board = BOARD_ACCESS.lock().unwrap(); board.set_config(&config)?; anyhow::Ok(Some("saved".to_owned())) @@ -176,7 +178,7 @@ fn wifi_scan( fn list_files( request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { - let filename = query_param(request.uri(),"filename").unwrap_or_default(); + let filename = query_param(request.uri(), "filename").unwrap_or_default(); let board = BOARD_ACCESS.lock().unwrap(); let result = board.list_files(&filename); let file_list_json = serde_json::to_string(&result)?; @@ -215,17 +217,54 @@ fn ota( finalizer.restart(); } -fn query_param(uri:&str, param_name:&str) -> Option{ +fn flash_bq(filename: &str, dryrun: bool) -> anyhow::Result<()> { + let mut board = BOARD_ACCESS.lock().unwrap(); + + let mut toggle = true; + let delay = Delay::new(1); + + let file_handle = board.get_file_handle(filename, false)?; + + let mut reader = BufReader::with_capacity(512, file_handle).lines(); + let mut line = 0; + loop { + board.general_fault(toggle); + toggle = !toggle; + + delay.delay_us(2); + line += 1; + match reader.next() { + Some(next) => { + let input = next?; + println!("flashing bq34z100 dryrun:{dryrun} line {line} payload: {input}"); + match board.flash_bq34_z100(&input, dryrun) { + Ok(_) => { + println!("ok") + } + Err(err) => { + bail!( + "Error flashing bq34z100 in dryrun: {dryrun} line: {line} error: {err}" + ) + } + } + } + None => break, + } + } + println!("Finished flashing file {line} lines processed"); + board.general_fault(false); + return anyhow::Ok(()); +} + +fn query_param(uri: &str, param_name: &str) -> Option { println!("{uri} get {param_name}"); let parsed = Url::parse(&format!("http://127.0.0.1/{uri}")).unwrap(); let value = parsed.query_pairs().filter(|it| it.0 == param_name).next(); match value { Some(found) => { return Some(found.1.into_owned()); - }, - None => { - return None - }, + } + None => return None, } } @@ -284,18 +323,20 @@ pub fn httpd(_reboot_now: Arc) -> Box> { }) .unwrap(); server - .fn_handler("/files", Method::Get, move |request| { - handle_error_to500(request, list_files) - }) - .unwrap(); + .fn_handler("/files", Method::Get, move |request| { + handle_error_to500(request, list_files) + }) + .unwrap(); server .fn_handler("/file", Method::Get, move |request| { - let filename = query_param(request.uri(),"filename").unwrap(); - let file_handle = BOARD_ACCESS.lock().unwrap().get_file_handle(&filename, false); + let filename = query_param(request.uri(), "filename").unwrap(); + let file_handle = BOARD_ACCESS + .lock() + .unwrap() + .get_file_handle(&filename, false); match file_handle { Ok(mut file_handle) => { - - let headers = [("Access-Control-Allow-Origin","*")]; + let headers = [("Access-Control-Allow-Origin", "*")]; let mut response = request.into_response(200, None, &headers)?; const BUFFER_SIZE: usize = 512; let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; @@ -303,7 +344,10 @@ pub fn httpd(_reboot_now: Arc) -> Box> { loop { let read = file_handle.read(&mut buffer)?; total_read += read; - println!("sending {read} bytes of {total_read} for file {}", &filename); + println!( + "sending {read} bytes of {total_read} for file {}", + &filename + ); let to_write = &buffer[0..read]; response.write(to_write)?; println!("wrote {read} bytes of {total_read} for file {filename}"); @@ -313,8 +357,7 @@ pub fn httpd(_reboot_now: Arc) -> Box> { } drop(file_handle); response.flush()?; - - }, + } Err(err) => { //todo set headers here for filename to be error let error_text = err.to_string(); @@ -327,8 +370,11 @@ pub fn httpd(_reboot_now: Arc) -> Box> { .unwrap(); server .fn_handler("/file", Method::Post, move |mut request| { - let filename = query_param(request.uri(),"filename").unwrap(); - let file_handle = BOARD_ACCESS.lock().unwrap().get_file_handle(&filename,true); + let filename = query_param(request.uri(), "filename").unwrap(); + let file_handle = BOARD_ACCESS + .lock() + .unwrap() + .get_file_handle(&filename, true); match file_handle { Ok(mut file_handle) => { const BUFFER_SIZE: usize = 512; @@ -346,7 +392,7 @@ pub fn httpd(_reboot_now: Arc) -> Box> { } } cors_response(request, 200, &format!("saved {total_read} bytes"))?; - }, + } Err(err) => { //todo set headers here for filename to be error let error_text = err.to_string(); @@ -357,90 +403,51 @@ pub fn httpd(_reboot_now: Arc) -> Box> { anyhow::Ok(()) }) .unwrap(); - - server + + server .fn_handler("/file", Method::Delete, move |request| { - let filename = query_param(request.uri(),"filename").unwrap(); + let filename = query_param(request.uri(), "filename").unwrap(); let copy = filename.clone(); let board = BOARD_ACCESS.lock().unwrap(); match board.delete_file(&filename) { Ok(_) => { let info = format!("Deleted file {copy}"); cors_response(request, 200, &info)?; - }, + } Err(err) => { let info = format!("Could not delete file {copy} {err:?}"); cors_response(request, 400, &info)?; - }, + } } anyhow::Ok(()) }) .unwrap(); server - .fn_handler("/flashbattery", Method::Post, move |mut request| { - - let mut board = BOARD_ACCESS.lock().unwrap(); - let mut buffer: [u8; 128] = [0; 128]; - let mut line_buffer: VecDeque = VecDeque::new(); - - let is_dry_run = !request.uri().ends_with("?flash=true"); - let mut total_read: usize = 0; - - let mut toggle = true; - let delay = Delay::new(1); - todo!("Write to storage before attempting to flash!"); - loop { - delay.delay_us(2); - let read = request.read(&mut buffer).unwrap(); - total_read += read; - if read == 0 { - if line_buffer.len() > 0 { - println!("No further body but no endline"); - let mut line = std::string::String::new(); - line_buffer.read_to_string(&mut line).unwrap(); - let msg = format!("Finished reading, but there is still some leftover in buffer and no full line {line}
"); - println!("{}", msg); - let mut response = request.into_status_response(400_u16).unwrap(); - response.write(msg.as_bytes()).unwrap(); - response.flush().unwrap(); - return anyhow::Ok(()) + .fn_handler("/flashbattery", Method::Post, move |request| { + let filename = query_param(request.uri(),"filename").unwrap(); + let dryrun = true; + match flash_bq(&filename, false) { + Ok(_) => { + if !dryrun { + match flash_bq(&filename, true) { + Ok(_) => { + cors_response(request, 200, "Sucessfully flashed bq34z100")?; + }, + Err(err) => { + let info = format!("Could not flash bq34z100, could be bricked now! {filename} {err:?}"); + cors_response(request, 500, &info)?; + }, + } + } else { + cors_response(request, 200, "Sucessfully processed bq34z100")?; } - break; - } - let to_write = &buffer[0..read]; - - line_buffer.write_all(to_write).unwrap(); - board.general_fault(toggle); - toggle = !toggle; - loop { - let has_line = line_buffer.contains(&b'\n'); - if !has_line { - break; - } - let mut line = std::string::String::new(); - line_buffer.read_line(&mut line)?; - let line2 = &line[0..line.len()-1]; - println!("Processing dry:{} line {}", is_dry_run, line2); - let validate = board.flash_bq34_z100(&line2, is_dry_run); - delay.delay_us(2); - if validate.is_err() { - let mut response = request.into_status_response(400_u16).unwrap(); - let err = validate.unwrap_err(); - let err_str = err.to_string(); - let err_msg = err_str.as_bytes(); - println!("Error writing {}", err_str); - response - .write(err_msg) - .unwrap(); - return anyhow::Ok(()) - } - } - } - let mut response = request.into_status_response(200_u16).unwrap(); - let msg = format!("Finished writing {total_read} bytes
"); - response.write(msg.as_bytes()).unwrap(); - board.general_fault(false); + }, + Err(err) => { + let info = format!("Could not process firmware file for, bq34z100, refusing to flash! {filename} {err:?}"); + cors_response(request, 500, &info)?; + }, + }; anyhow::Ok(()) }) .unwrap(); @@ -448,7 +455,7 @@ pub fn httpd(_reboot_now: Arc) -> Box> { server .fn_handler("/", Method::Get, move |request| { let mut response = request.into_ok_response()?; - response.write(include_bytes!("index.html"))?;let mut buf = [0_u8; 3072]; + response.write(include_bytes!("index.html"))?; anyhow::Ok(()) }) .unwrap(); @@ -458,7 +465,6 @@ pub fn httpd(_reboot_now: Arc) -> Box> { .into_ok_response()? .write(include_bytes!("favicon.ico"))?; anyhow::Ok(()) - }) .unwrap(); server @@ -467,14 +473,17 @@ pub fn httpd(_reboot_now: Arc) -> Box> { .into_ok_response()? .write(include_bytes!("bundle.js"))?; anyhow::Ok(()) - }) .unwrap(); server } -fn cors_response(request: Request<&mut EspHttpConnection>, status: u16, body: &str) -> Result<(), anyhow::Error>{ - let headers = [("Access-Control-Allow-Origin","*")]; +fn cors_response( + request: Request<&mut EspHttpConnection>, + status: u16, + body: &str, +) -> Result<(), anyhow::Error> { + let headers = [("Access-Control-Allow-Origin", "*")]; let mut response = request.into_response(status, None, &headers)?; response.write(body.as_bytes())?; response.flush()?; @@ -504,4 +513,4 @@ fn handle_error_to500( } } return anyhow::Ok(()); -} \ No newline at end of file +}