diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 8afedbf..d1ac493 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -159,7 +159,7 @@ edge-nal-embassy = "0.6.0" static_cell = "2.1.1" cfg-if = "1.0.3" edge-http = { version = "0.6.1", features = ["log"] } -littlefs2 = { version = "0.6.1", features = ["c-stubs", "alloc", "serde"] } +littlefs2 = { version = "0.6.1", features = ["c-stubs", "alloc"] } littlefs2-core = "0.1.1" diff --git a/rust/src/hal/esp.rs b/rust/src/hal/esp.rs index a375d68..2549865 100644 --- a/rust/src/hal/esp.rs +++ b/rust/src/hal/esp.rs @@ -6,7 +6,7 @@ use anyhow::{anyhow, bail, Context}; use chrono::{DateTime, Utc}; use serde::Serialize; -use crate::hal::LittleFS2StorageAdapter::LittleFs2Filesystem; +use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem; use alloc::string::ToString; use alloc::sync::Arc; use alloc::{format, string::String, vec::Vec}; @@ -34,7 +34,7 @@ use esp_wifi::wifi::{ ScanTypeConfig, WifiController, WifiDevice, WifiEvent, WifiState, }; use littlefs2::fs::Filesystem; -use littlefs2_core::{FileType, PathBuf}; +use littlefs2_core::{DynFile, FileType, OpenSeekFrom, Path, PathBuf, SeekFrom}; use log::{info, warn}; #[link_section = ".rtc.data"] @@ -113,6 +113,79 @@ macro_rules! mk_static { } impl Esp<'_> { + pub(crate) async fn delete_file(&self, filename: String) -> anyhow::Result<()> { + let file = PathBuf::try_from(filename.as_str()).unwrap(); + let access = self.fs.lock().await; + access + .remove(&*file) + .map_err(|err| anyhow!("Could not delete file: {:?}", err))?; + Ok(()) + } + pub(crate) async fn write_file( + &mut self, + filename: String, + offset: u32, + buf: &[u8], + ) -> anyhow::Result<()> { + let file = PathBuf::try_from(filename.as_str()).unwrap(); + let access = self.fs.lock().await; + info!("write file {} at offset {}", filename, offset); + match access.open_file_with_options_and_then( + |options| options.read(true).write(true).create(true), + &*file, + |file| { + file.seek(SeekFrom::Start(offset))?; + file.write(buf)?; + Ok(()) + }, + ) { + Ok(_) => Ok(()), + Err(err) => { + bail!(format!("{err:?}")) + } + } + } + pub(crate) async fn get_file( + &mut self, + filename: String, + chunk: u32, + ) -> anyhow::Result<([u8; 128], usize)> { + use littlefs2::io::Error as lfs2Error; + + let file = PathBuf::try_from(filename.as_str()).unwrap(); + let access = self.fs.lock().await; + let mut buf = [0_u8; 128]; + let mut read = 0; + let offset = chunk * 128; + info!("read file {} at offset {}", filename, offset); + match access.open_file_with_options_and_then( + |options| options.read(true), + &*file, + |file| { + let length = file.len()? as u32; + info!("file length {}", length); + if length == 0 { + Err(lfs2Error::IO) + } else if length > offset { + file.seek(SeekFrom::Start(offset))?; + info!("seek to {}", offset); + read = file.read(&mut buf)?; + info!("read {} bytes", read); + Ok(()) + } else { + //exactly at end, do nothing + Ok(()) + } + }, + ) { + Ok(_) => {} + Err(err) => { + bail!(format!("{err:?}")) + } + } + Ok((buf, read)) + } + pub(crate) fn get_ota_slot(&mut self) -> String { match self.ota.current_slot() { Ok(slot) => { @@ -435,11 +508,12 @@ impl Esp<'_> { match self.fs.lock().await.read_dir_and_then(&path, |dir| { for entry in dir { let e = entry?; - - result.files.push(FileInfo { - filename: e.path().to_string(), - size: e.metadata().len(), - }); + if e.file_type() == FileType::File { + result.files.push(FileInfo { + filename: e.path().to_string(), + size: e.metadata().len(), + }); + } } Result::Ok(()) }) { @@ -450,16 +524,7 @@ impl Esp<'_> { } Ok(result) } - pub(crate) async fn delete_file(&self, _filename: &str) -> anyhow::Result<()> { - bail!("todo"); - // let filepath = Path::new(Self::BASE_PATH).join(Path::new(filename)); - // match fs::remove_file(filepath) { - // OkStd(_) => anyhow::Ok(()), - // Err(err) => { - // bail!(format!("{err:?}")) - // } - // } - } + // pub(crate) async fn get_file_handle( // &self, // filename: &str, @@ -473,13 +538,17 @@ impl Esp<'_> { // }) // } - pub(crate) fn init_rtc_deepsleep_memory(&self, init_rtc_store: bool, to_config_mode: bool) { + pub(crate) async 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(); + crate::log::init().await; RESTART_TO_CONF = to_config_mode; }; } else { @@ -493,14 +562,16 @@ impl Esp<'_> { 0, "", "", - ); + ) + .await; log( LogMessage::LowVoltage, LOW_VOLTAGE_DETECTED as u32, 0, "", "", - ); + ) + .await; for i in 0..PLANT_COUNT { log::info!( "LAST_WATERING_TIMESTAMP[{}] = UTC {}", diff --git a/rust/src/hal/LittleFS2StorageAdapter.rs b/rust/src/hal/little_fs2storage_adapter.rs similarity index 82% rename from rust/src/hal/LittleFS2StorageAdapter.rs rename to rust/src/hal/little_fs2storage_adapter.rs index 92a31f6..59f197b 100644 --- a/rust/src/hal/LittleFS2StorageAdapter.rs +++ b/rust/src/hal/little_fs2storage_adapter.rs @@ -1,8 +1,8 @@ use embedded_storage::{ReadStorage, Storage}; use esp_bootloader_esp_idf::partitions::FlashRegion; use esp_storage::FlashStorage; -use littlefs2::consts::U1 as lfs2Array1; -use littlefs2::consts::U512 as lfs2Array512; +use littlefs2::consts::U512 as lfsCache; +use littlefs2::consts::U512 as lfsLookahead; use littlefs2::driver::Storage as lfs2Storage; use littlefs2::fs::Filesystem as lfs2Filesystem; use littlefs2::io::Error as lfs2Error; @@ -14,20 +14,15 @@ pub struct LittleFs2Filesystem { } impl lfs2Storage for LittleFs2Filesystem { - const READ_SIZE: usize = 512; + const READ_SIZE: usize = 256; const WRITE_SIZE: usize = 512; - const BLOCK_SIZE: usize = 1024; //usually optimal for flash access - const BLOCK_COUNT: usize = 8 * 1024 * 1024 / 1024; //8mb in 32kb blocks + const BLOCK_SIZE: usize = 512; //usually optimal for flash access + const BLOCK_COUNT: usize = 8 * 1024 * 1024 / 512; //8mb in 32kb blocks const BLOCK_CYCLES: isize = 100; - type CACHE_SIZE = lfs2Array512; - type LOOKAHEAD_SIZE = lfs2Array1; + type CACHE_SIZE = lfsCache; + type LOOKAHEAD_SIZE = lfsLookahead; fn read(&mut self, off: usize, buf: &mut [u8]) -> lfs2Result { - info!( - "Littlefs2Filesystem read at offset {} with len {}", - off, - buf.len() - ); let read_size: usize = Self::READ_SIZE; assert_eq!(off % read_size, 0); assert_eq!(buf.len() % read_size, 0); diff --git a/rust/src/hal/mod.rs b/rust/src/hal/mod.rs index 0dc7d31..7059545 100644 --- a/rust/src/hal/mod.rs +++ b/rust/src/hal/mod.rs @@ -1,7 +1,7 @@ -mod LittleFS2StorageAdapter; pub(crate) mod battery; pub mod esp; mod initial_hal; +mod little_fs2storage_adapter; mod rtc; //mod water; @@ -34,12 +34,10 @@ use esp_bootloader_esp_idf::partitions::{ }; use esp_hal::clock::CpuClock; use esp_hal::gpio::{Input, InputConfig, Pull}; -use esp_println::println; use measurements::{Current, Voltage}; -use crate::hal::LittleFS2StorageAdapter::LittleFs2Filesystem; +use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem; use embassy_sync::mutex::Mutex; -use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; use esp_alloc as _; use esp_backtrace as _; use esp_bootloader_esp_idf::ota::Slot; @@ -182,6 +180,7 @@ impl PlantHal { let peripherals: Peripherals = esp_hal::init(config); esp_alloc::heap_allocator!(size: 64 * 1024); + esp_alloc::heap_allocator!(#[link_section = ".dram2_uninit"] size: 64000); let systimer = SystemTimer::new(peripherals.SYSTIMER); let boot_button = Input::new( @@ -364,9 +363,11 @@ impl PlantHal { to_config_mode as u32, "", &format!("{reasons:?}"), - ); + ) + .await; - esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode); + esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode) + .await; let config = esp.load_config().await; @@ -453,7 +454,8 @@ impl PlantHal { 0, "", &err.to_string(), - ); + ) + .await; HAL { board_hal: initial_hal::create_initial_board( free_pins, diff --git a/rust/src/main.rs b/rust/src/main.rs index a4074bc..39f5fef 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -31,8 +31,6 @@ use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::mutex::{Mutex, MutexGuard}; use embassy_sync::once_lock::OnceLock; use embassy_time::Timer; -use esp_alloc::heap_allocator; -use esp_bootloader_esp_idf::ota::OtaImageState; use esp_hal::rom::ets_delay_us; use esp_hal::system::software_reset; use esp_println::{logger, println}; @@ -317,8 +315,7 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> { info!("executing config mode override"); //config upload will trigger reboot! let reboot_now = Arc::new(AtomicBool::new(false)); - //TODO - //let _webserver = httpd(reboot_now.clone()); + //spawner.spawn(httpd(reboot_now.clone(), stack))?; let board = BOARD_ACCESS.get().await.lock().await; wait_infinity(board, WaitType::ConfigButton, reboot_now.clone()).await; } else { @@ -667,7 +664,8 @@ pub async fn do_secure_pump( current_ma as u32, plant_config.max_pump_current_ma.to_string().as_str(), step.to_string().as_str(), - ); + ) + .await; board.board_hal.general_fault(true).await; board.board_hal.fault(plant_id, true).await?; if !plant_config.ignore_current_error { @@ -686,7 +684,8 @@ pub async fn do_secure_pump( current_ma as u32, plant_config.min_pump_current_ma.to_string().as_str(), step.to_string().as_str(), - ); + ) + .await; board.board_hal.general_fault(true).await; board.board_hal.fault(plant_id, true).await?; if !plant_config.ignore_current_error { @@ -706,7 +705,8 @@ pub async fn do_secure_pump( 0, "", "", - ); + ) + .await; error = true; break; } else { @@ -840,7 +840,7 @@ async fn update_charge_indicator(board: &mut MutexGuard<'_, CriticalSectionRawMu // } async fn publish_firmware_info( - mut board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>, + board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>, version: VersionInfo, ip_address: &String, timezone_time: &String, @@ -867,17 +867,6 @@ async fn publish_firmware_info( let _ = esp.mqtt_publish("/state", "online".as_bytes()).await; } -fn state_to_string(state: OtaImageState) -> &'static str { - match state { - OtaImageState::New => "New", - OtaImageState::PendingVerify => "PendingVerify", - OtaImageState::Valid => "Valid", - OtaImageState::Invalid => "Invalid", - OtaImageState::Aborted => "Aborted", - OtaImageState::Undefined => "Undefined", - } -} - async fn try_connect_wifi_sntp_mqtt() -> NetworkMode { let board = &mut BOARD_ACCESS.get().await.lock().await; let nw_conf = &board.board_hal.get_config().network.clone(); diff --git a/rust/src/webserver/mod.rs b/rust/src/webserver/mod.rs index 7f568b3..4b501e9 100644 --- a/rust/src/webserver/mod.rs +++ b/rust/src/webserver/mod.rs @@ -3,6 +3,8 @@ use crate::config::PlantControllerConfig; use crate::{get_version, log::LogMessage, BOARD_ACCESS}; use alloc::borrow::ToOwned; +use alloc::fmt::format; +use alloc::format; use alloc::string::{String, ToString}; use alloc::sync::Arc; use alloc::vec::Vec; @@ -21,6 +23,7 @@ use embassy_net::Stack; use embassy_time::Instant; use embedded_io_async::{Read, Write}; use esp_println::println; +use littlefs2_core::Path; use log::info; use serde::{Deserialize, Serialize}; @@ -294,15 +297,6 @@ pub struct NightLampCommand { // anyhow::Ok(None) // } // -// fn query_param(uri: &str, param_name: &str) -> Option { -// log::info!("{uri} get {param_name}"); -// let parsed = Url::parse(&format!("http://127.0.0.1/{uri}")).unwrap(); -// let value = parsed.query_pairs().find(|it| it.0 == param_name); -// match value { -// Some(found) => Some(found.1.into_owned()), -// None => None, -// } -// } struct HttpHandler { reboot_now: Arc, @@ -324,43 +318,147 @@ impl Handler for HttpHandler { let method = headers.method; let path = headers.path; - let status = match method { - Method::Get => match path { - "/favicon.ico" => { - conn.initiate_response(200, Some("OK"), &[("Content-Type", "image/x-icon")]) + let prefix = "/file?filename="; + let status = if path.starts_with(prefix) { + let filename = &path[prefix.len()..]; + let mut board = BOARD_ACCESS.get().await.lock().await; + info!("file request for {} with method {}", filename, method); + match method { + Method::Delete => { + board + .board_hal + .get_esp() + .delete_file(filename.to_owned()) + .await + .unwrap(); + Some(200) + } + Method::Get => { + let disp = format!("attachment; filename=\"{filename}\""); + conn.initiate_response( + 200, + Some("OK"), + &[ + ("Content-Type", "application/octet-stream"), + ("Content-Disposition", disp.as_str()), + ], + ) + .await?; + let mut chunk = 0; + loop { + let read_chunk = board + .board_hal + .get_esp() + .get_file(filename.to_owned(), chunk) + .await + .unwrap(); + let length = read_chunk.1; + info!("read {} bytes for file request for {}", length, filename); + if length == 0 { + info!("file request for {} finished", filename); + break; + } + let data = &read_chunk.0[0..length]; + conn.write_all(data).await?; + if length < 128 { + info!("file request for {} finished", filename); + break; + } + chunk = chunk + 1; + } + Some(200) + } + Method::Post => { + //ensure file is deleted, otherwise we would need to truncate the file which will not work with streaming + let _ = board + .board_hal + .get_esp() + .delete_file(filename.to_owned()) + .await; + let mut offset = 0_usize; + loop { + let mut buf = [0_u8; 1024]; + let to_write = conn.read(&mut buf).await?; + if to_write == 0 { + info!("file request for {} finished", filename); + break; + } else { + info!( + "writing {} bytes for file request for {}", + to_write, filename + ); + board + .board_hal + .get_esp() + .write_file(filename.to_owned(), offset as u32, &buf[0..to_write]) + .await + .unwrap(); + } + offset = offset + to_write + } + + Some(200) + } + _ => None, + } + } else { + match method { + Method::Get => match path { + "/favicon.ico" => { + conn.initiate_response( + 200, + Some("OK"), + &[("Content-Type", "image/x-icon")], + ) .await?; - conn.write_all(include_bytes!("favicon.ico")).await?; - Some(200) - } - "/" => { - conn.initiate_response(200, Some("OK"), &[("Content-Type", "text/html")]) + conn.write_all(include_bytes!("favicon.ico")).await?; + Some(200) + } + "/" => { + conn.initiate_response(200, Some("OK"), &[("Content-Type", "text/html")]) + .await?; + conn.write_all(include_bytes!("index.html")).await?; + Some(200) + } + "/bundle.js" => { + conn.initiate_response( + 200, + Some("OK"), + &[("Content-Type", "text/javascript")], + ) .await?; - conn.write_all(include_bytes!("index.html")).await?; - Some(200) - } - "/bundle.js" => { - conn.initiate_response(200, Some("OK"), &[("Content-Type", "text/javascript")]) - .await?; - conn.write_all(include_bytes!("bundle.js")).await?; - Some(200) - } - "/reboot" => { - let mut board = BOARD_ACCESS.get().await.lock().await; - board.board_hal.get_esp().set_restart_to_conf(true); - self.reboot_now.store(true, Ordering::Relaxed); - Some(200) - } - &_ => { + conn.write_all(include_bytes!("bundle.js")).await?; + Some(200) + } + "/reboot" => { + let mut board = BOARD_ACCESS.get().await.lock().await; + board.board_hal.get_esp().set_restart_to_conf(true); + self.reboot_now.store(true, Ordering::Relaxed); + Some(200) + } + &_ => { + let json = match path { + "/version" => Some(get_version_web(conn).await), + "/time" => Some(get_time(conn).await), + "/battery" => Some(get_battery_state(conn).await), + "/solar" => Some(get_solar_state(conn).await), + "/get_config" => Some(get_config(conn).await), + "/files" => Some(list_files(conn).await), + "/log_localization" => Some(get_log_localization_config(conn).await), + "/log" => Some(get_log(conn).await), + "/wifiscan" => Some(wifi_scan(conn).await), + _ => None, + }; + match json { + None => None, + Some(json) => Some(handle_json(conn, json).await?), + } + } + }, + Method::Post => { let json = match path { - "/version" => Some(get_version_web(conn).await), - "/time" => Some(get_time(conn).await), - "/battery" => Some(get_battery_state(conn).await), - "/solar" => Some(get_solar_state(conn).await), - "/get_config" => Some(get_config(conn).await), - "/files" => Some(list_files(conn).await), - "/log_localization" => Some(get_log_localization_config(conn).await), - "/log" => Some(get_log(conn).await), "/wifiscan" => Some(wifi_scan(conn).await), + "/set_config" => Some(set_config(conn).await), _ => None, }; match json { @@ -368,20 +466,9 @@ impl Handler for HttpHandler { Some(json) => Some(handle_json(conn, json).await?), } } - }, - Method::Post => { - let json = match path { - "/wifiscan" => Some(wifi_scan(conn).await), - "/set_config" => Some(set_config(conn).await), - _ => None, - }; - match json { - None => None, - Some(json) => Some(handle_json(conn, json).await?), - } + Method::Options | Method::Delete | Method::Head | Method::Put => None, + _ => None, } - Method::Options | Method::Delete | Method::Head | Method::Put => None, - _ => None, }; let code = match status { None => { @@ -390,6 +477,7 @@ impl Handler for HttpHandler { } Some(code) => code, }; + conn.complete().await?; let response_time = Instant::now().duration_since(start).as_millis(); @@ -398,16 +486,87 @@ impl Handler for HttpHandler { } } -// fn set_config( -// request: &mut Request<&mut EspHttpConnection>, -// ) -> Result, anyhow::Error> { -// let all = read_up_to_bytes_from_request(request, Some(4096))?; -// let config: PlantControllerConfig = serde_json::from_slice(&all)?; +// .fn_handler("/file", Method::Get, move |request| { +// let filename = query_param(request.uri(), "filename").unwrap(); +// let file_handle = BOARD_ACCESS +// .lock() +// .unwrap() +// .board_hal +// .get_esp() +// .get_file_handle(&filename, false); +// match file_handle { +// Ok(mut file_handle) => { +// 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]; +// let mut total_read: usize = 0; +// loop { +// unsafe { vTaskDelay(1) }; +// let read = std::io::Read::read(&mut file_handle, &mut buffer)?; +// total_read += read; +// let to_write = &buffer[0..read]; +// response.write(to_write)?; +// if read == 0 { +// break; +// } +// } +// log::info!("wrote {total_read} for file {filename}"); +// drop(file_handle); +// response.flush()?; +// } +// Err(err) => { +// //todo set headers here for filename to be error +// let error_text = err.to_string(); +// log::info!("error handling get file {}", error_text); +// cors_response(request, 500, &error_text)?; +// } +// } +// anyhow::Ok(()) +// }) +// .unwrap(); +// server +// .fn_handler("/file", Method::Post, move |mut request| { +// let filename = query_param(request.uri(), "filename").unwrap(); +// let mut board = BOARD_ACCESS.lock().unwrap(); +// let file_handle = board.board_hal.get_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) => { +// const BUFFER_SIZE: usize = 512; +// let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; +// let mut total_read: usize = 0; +// let mut lastiter = 0; +// loop { +// let iter = (total_read / 1024) % 8; +// if iter != lastiter { +// for i in 0..PLANT_COUNT { +// let _ = board.board_hal.fault(i, iter == i); +// } +// lastiter = iter; +// } // -// let mut board = BOARD_ACCESS.lock().expect("board access"); -// board.board_hal.set_config(config)?; -// anyhow::Ok(Some("saved".to_owned())) -// } +// let read = request.read(&mut buffer)?; +// total_read += read; +// let to_write = &buffer[0..read]; +// std::io::Write::write(&mut file_handle, to_write)?; +// if read == 0 { +// break; +// } +// } +// 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(); +// log::info!("error handling get file {}", error_text); +// cors_response(request, 500, &error_text)?; +// } +// } +// drop(board); +// anyhow::Ok(()) +// }) +// .unwrap(); async fn set_config( request: &mut Connection<'_, T, N>, @@ -703,87 +862,7 @@ pub async fn httpd(reboot_now: Arc, stack: Stack<'static>) { // }) // .unwrap(); // server - // .fn_handler("/file", Method::Get, move |request| { - // let filename = query_param(request.uri(), "filename").unwrap(); - // let file_handle = BOARD_ACCESS - // .lock() - // .unwrap() - // .board_hal - // .get_esp() - // .get_file_handle(&filename, false); - // match file_handle { - // Ok(mut file_handle) => { - // 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]; - // let mut total_read: usize = 0; - // loop { - // unsafe { vTaskDelay(1) }; - // let read = std::io::Read::read(&mut file_handle, &mut buffer)?; - // total_read += read; - // let to_write = &buffer[0..read]; - // response.write(to_write)?; - // if read == 0 { - // break; - // } - // } - // log::info!("wrote {total_read} for file {filename}"); - // drop(file_handle); - // response.flush()?; - // } - // Err(err) => { - // //todo set headers here for filename to be error - // let error_text = err.to_string(); - // log::info!("error handling get file {}", error_text); - // cors_response(request, 500, &error_text)?; - // } - // } - // anyhow::Ok(()) - // }) - // .unwrap(); - // server - // .fn_handler("/file", Method::Post, move |mut request| { - // let filename = query_param(request.uri(), "filename").unwrap(); - // let mut board = BOARD_ACCESS.lock().unwrap(); - // let file_handle = board.board_hal.get_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) => { - // const BUFFER_SIZE: usize = 512; - // let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; - // let mut total_read: usize = 0; - // let mut lastiter = 0; - // loop { - // let iter = (total_read / 1024) % 8; - // if iter != lastiter { - // for i in 0..PLANT_COUNT { - // let _ = board.board_hal.fault(i, iter == i); - // } - // lastiter = iter; - // } - // - // let read = request.read(&mut buffer)?; - // total_read += read; - // let to_write = &buffer[0..read]; - // std::io::Write::write(&mut file_handle, to_write)?; - // if read == 0 { - // break; - // } - // } - // 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(); - // log::info!("error handling get file {}", error_text); - // cors_response(request, 500, &error_text)?; - // } - // } - // drop(board); - // anyhow::Ok(()) - // }) - // .unwrap(); + // // server // .fn_handler("/file", Method::Delete, move |request| {