diff --git a/rust/Cargo.toml b/rust/Cargo.toml index dba420d..ee8e1ba 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -8,7 +8,7 @@ rust-version = "1.71" [profile.dev] # Explicitly disable LTO which the Xtensa codegen backend has issues -lto = false +lto = true strip = false debug = true overflow-checks = true @@ -78,6 +78,7 @@ serde_json = "1.0.108" chrono = { version = "0.4.23", default-features = false , features = ["iana-time-zone" , "alloc"] } chrono-tz = {version="0.8.0", default-features = false , features = [ "filter-by-regex" ]} eeprom24x = "0.7.2" +url = "2.5.3" [patch.crates-io] diff --git a/rust/src/plant_hal.rs b/rust/src/plant_hal.rs index 3847fbe..6b39562 100644 --- a/rust/src/plant_hal.rs +++ b/rust/src/plant_hal.rs @@ -2,6 +2,7 @@ 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::{ @@ -13,6 +14,7 @@ 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; @@ -50,7 +52,7 @@ 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::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError}; +use esp_idf_sys::{esp, esp_spiffs_check, f_opendir, gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError}; use one_wire_bus::OneWire; use crate::config::{self, Config}; @@ -174,21 +176,82 @@ pub struct FileInfo{ size:usize } +#[derive(Serialize, Debug)] +pub struct FileList{ + files: Vec, + file_system_corrupt: Option, + iter_error: Option, +} + impl PlantCtrlBoard<'_> { - pub fn list_files(&self) -> Result> { - let spiffs = Path::new(BASE_PATH); - let list = fs::read_dir(spiffs).unwrap().map(|dir| { - let file = dir.unwrap(); - FileInfo{ - filename: file.file_name().into_string().unwrap(), - size: file.metadata().unwrap().len() as usize + pub fn list_files(&self, filename:&str) -> FileList { + let storage = CString::new(SPIFFS_PARTITION_NAME).unwrap(); + let error = unsafe { + esp!{ + esp_spiffs_check(storage.as_ptr()) } - }); - return Ok(list.collect()); + }; + + let mut file_system_corrupt = match error { + OkStd(_) => { + None + }, + Err(err) => { + println!("Corrupt spiffs {err:?}"); + Some(format!("{err:?}")) + }, + }; + + let mut iter_error = None; + let mut result = Vec::new(); + + println!("Filename {filename}"); + let filepath = Path::new(BASE_PATH); + let read_dir = fs::read_dir(filepath); + match read_dir { + OkStd(read_dir) => { + for item in read_dir { + println!("start loop"); + match item { + OkStd(file) => { + 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 + }; + println!("fileinfo {f:?}"); + result.push(f); + }, + Err(err) => { + iter_error = Some (format!("{err:?}")); + break; + }, + } + } + }, + Err(err) => { + file_system_corrupt = Some(format!("{err:?}")); + }, + } + + return FileList{ + file_system_corrupt, + files: result, + iter_error + }; } - pub fn get_file_handle(&self, filename:String, write:bool) -> Result { - let filepath = Path::new(BASE_PATH).join(Path::new(&filename)); + pub 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:?}")) + }, + } + } + + 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 { @@ -417,7 +480,7 @@ impl PlantCtrlBoard<'_> { .unwrap(); let delay = Delay::new_default(); - let measurement = 5000; + let measurement = 100; let factor = 1000 as f32 / measurement as f32; //give some time to stabilize @@ -532,9 +595,11 @@ impl PlantCtrlBoard<'_> { let conf = esp_idf_sys::esp_vfs_spiffs_conf_t { base_path: base_path.as_ptr(), partition_label: storage.as_ptr(), - max_files: 2, + 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))?; diff --git a/rust/src/webserver/webserver.rs b/rust/src/webserver/webserver.rs index ffa6019..9e840a1 100644 --- a/rust/src/webserver/webserver.rs +++ b/rust/src/webserver/webserver.rs @@ -1,18 +1,16 @@ //offer ota and config mode use std::{ - collections::VecDeque, - io::{BufRead, Read, Seek, Write}, - str::from_utf8, - sync::{atomic::AtomicBool, Arc}, + collections::VecDeque, fs, io::{BufRead, Read, Write}, str::from_utf8, sync::{atomic::AtomicBool, Arc} }; -use crate::{espota::OtaUpdate, map_range_moisture, plant_hal::{FileInfo, PLANT_COUNT}, BOARD_ACCESS}; +use crate::{espota::OtaUpdate, map_range_moisture, plant_hal::FileInfo, BOARD_ACCESS}; use chrono::DateTime; +use url::Url; use core::result::Result::Ok; -use embedded_svc::http::{Method, Query}; -use esp_idf_hal::{delay::Delay, io::Write}; -use esp_idf_svc::{fs, http::server::{Configuration, EspHttpConnection, EspHttpServer, Request}}; +use embedded_svc::http::Method; +use esp_idf_hal::delay::Delay; +use esp_idf_svc::{http::server::{Configuration, EspHttpConnection, EspHttpServer, Request}}; use heapless::String; use serde::{Deserialize, Serialize}; @@ -81,7 +79,7 @@ fn get_data( let mut a: Vec = Vec::new(); let mut b: Vec = Vec::new(); - for plant in 0..2 { + for plant in 0..8 { let a_hz = board.measure_moisture_hz(plant, crate::plant_hal::Sensor::A)?; let b_hz = board.measure_moisture_hz(plant, crate::plant_hal::Sensor::B)?; let a_pct = map_range_moisture(a_hz as f32); @@ -176,11 +174,12 @@ fn wifi_scan( } fn list_files( - _request: &mut Request<&mut EspHttpConnection>, + request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { + let filename = query_param(request.uri(),"filename").unwrap_or_default(); let board = BOARD_ACCESS.lock().unwrap(); - let result = board.list_files()?; - let file_list_json = serde_json::to_string(&FileList { file: result })?; + let result = board.list_files(&filename); + let file_list_json = serde_json::to_string(&result)?; return anyhow::Ok(Some(file_list_json)); } @@ -216,6 +215,20 @@ fn ota( finalizer.restart(); } +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 + }, + } +} + pub fn httpd(_reboot_now: Arc) -> Box> { let server_config = Configuration { stack_size: 32768, @@ -255,7 +268,7 @@ pub fn httpd(_reboot_now: Arc) -> Box> { }) .unwrap(); server - .fn_handler("/ota", Method::Post, |mut request| { + .fn_handler("/ota", Method::Post, |request| { handle_error_to500(request, ota) }) .unwrap(); @@ -271,16 +284,16 @@ pub fn httpd(_reboot_now: Arc) -> Box> { }) .unwrap(); server - .fn_handler("/list_files", Method::Get, move |request| { + .fn_handler("/files", Method::Get, move |request| { handle_error_to500(request, list_files) }) .unwrap(); server - .fn_handler("/get_file", Method::Get, move |request| { - let filename:String = request.uri().magic_here().get("filename"); - let file_handle = BOARD_ACCESS.lock().unwrap().get_file_handle(filename); + .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); match file_handle { - Ok(file_handle) => { + Ok(mut file_handle) => { let mut response = request.into_ok_response()?; const BUFFER_SIZE: usize = 512; let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; @@ -288,7 +301,7 @@ 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}"); @@ -296,6 +309,7 @@ pub fn httpd(_reboot_now: Arc) -> Box> { break; } } + drop(file_handle); response.flush()?; }, @@ -312,11 +326,11 @@ pub fn httpd(_reboot_now: Arc) -> Box> { }) .unwrap(); server - .fn_handler("/put_file", Method::Post, move |request| { - let filename:String = request.uri().magic_here().get("filename"); - let file_handle = BOARD_ACCESS.lock().unwrap().get_file_handle(filename); + .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); match file_handle { - Ok(file_handle) => { + Ok(mut file_handle) => { const BUFFER_SIZE: usize = 512; let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; let mut total_read: usize = 0; @@ -331,7 +345,7 @@ pub fn httpd(_reboot_now: Arc) -> Box> { break; } } - request.into_ok_response().unwrap().write(format!("saved {total_read} bytes").as_bytes()); + request.into_ok_response().unwrap().write(format!("saved {total_read} bytes").as_bytes()).unwrap(); }, Err(err) => { //todo set headers here for filename to be error @@ -345,17 +359,26 @@ pub fn httpd(_reboot_now: Arc) -> Box> { anyhow::Ok(()) }) .unwrap(); - server - .fn_handler("/get_file", Method::Post, move |request| { - handle_error_to500(request, set_config) - }) - .unwrap(); - server - .fn_handler("/put_file", Method::Post, move |request| { - handle_error_to500(request, set_config) - }) - .unwrap(); + server + .fn_handler("/file", Method::Delete, move |request| { + 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}"); + request.into_ok_response().unwrap().write(info.as_bytes()).unwrap(); + }, + Err(err) => { + let info = format!("Could not delete file {copy} {err:?}"); + request.into_status_response(400).unwrap().write(info.as_bytes()).unwrap(); + }, + } + anyhow::Ok(()) + }) + .unwrap(); + server .fn_handler("/flashbattery", Method::Post, move |mut request| { @@ -428,12 +451,6 @@ pub fn httpd(_reboot_now: Arc) -> Box> { .fn_handler("/", Method::Get, move |request| { let mut response = request.into_ok_response()?; response.write(include_bytes!("config.html"))?;let mut buf = [0_u8; 3072]; - 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)?; - - .write(include_bytes!("bundle.js"))?; anyhow::Ok(()) }) .unwrap(); @@ -447,6 +464,15 @@ pub fn httpd(_reboot_now: Arc) -> Box> { }) .unwrap(); server + .fn_handler("/bundle.js", Method::Get, |request| { + request + .into_ok_response()? + .write(include_bytes!("bundle.js"))?; + anyhow::Ok(()) + + }) + .unwrap(); + server }