From 9f249af4309a8abed228143400ef9d10bcf9fa3a Mon Sep 17 00:00:00 2001 From: Empire Phoenix Date: Wed, 20 Nov 2024 01:14:39 +0100 Subject: [PATCH] initial file browser work --- rust/Cargo.toml | 2 +- rust/src/plant_hal.rs | 32 +++++++- rust/src/webserver/webserver.rs | 127 ++++++++++++++++++++++++++++---- 3 files changed, 145 insertions(+), 16 deletions(-) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 766163d..dba420d 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -87,7 +87,7 @@ eeprom24x = "0.7.2" #esp-idf-sys = { git = "https://github.com/esp-rs/esp-idf-sys.git" } #esp-idf-svc = { git = "https://github.com/esp-rs/esp-idf-svc.git" } ds323x = { git = "https://github.com/empirephoenix/ds323x-rs.git" } -bq34z100 = { path = "../../bq34z100_rust" } +#bq34z100 = { path = "../../bq34z100_rust" } [build-dependencies] embuild = "0.32.0" diff --git a/rust/src/plant_hal.rs b/rust/src/plant_hal.rs index 450964e..3847fbe 100644 --- a/rust/src/plant_hal.rs +++ b/rust/src/plant_hal.rs @@ -26,8 +26,10 @@ use plant_ctrl2::sipo::ShiftRegister40; use anyhow::{anyhow, Context}; use anyhow::{bail, Ok, Result}; +use serde::Serialize; use std::ffi::CString; -use std::fs::File; +use std::fs::{self, DirEntry, File}; +use std::io::{Read, Write}; use std::path::Path; use chrono::{DateTime, Utc}; @@ -60,6 +62,7 @@ const REPEAT_MOIST_MEASURE: usize = 1; const SPIFFS_PARTITION_NAME: &str = "storage"; const CONFIG_FILE: &str = "/spiffs/config.cfg"; +const BASE_PATH: &str = "/spiffs"; const TANK_MULTI_SAMPLE: usize = 11; @@ -165,7 +168,34 @@ pub struct PlantCtrlBoard<'a> { >, } +#[derive(Serialize, Debug)] +pub struct FileInfo{ + filename:String, + size:usize +} + 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 + } + }); + return Ok(list.collect()); + } + + pub fn get_file_handle(&self, filename:String, 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 { self.solar_is_day.get_level().into() } diff --git a/rust/src/webserver/webserver.rs b/rust/src/webserver/webserver.rs index 782b573..ffa6019 100644 --- a/rust/src/webserver/webserver.rs +++ b/rust/src/webserver/webserver.rs @@ -2,17 +2,17 @@ use std::{ collections::VecDeque, - io::{BufRead, Read, Write}, + io::{BufRead, Read, Seek, Write}, str::from_utf8, sync::{atomic::AtomicBool, Arc}, }; -use crate::{espota::OtaUpdate, map_range_moisture, plant_hal::PLANT_COUNT, BOARD_ACCESS}; +use crate::{espota::OtaUpdate, map_range_moisture, plant_hal::{FileInfo, PLANT_COUNT}, BOARD_ACCESS}; use chrono::DateTime; 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 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 heapless::String; use serde::{Deserialize, Serialize}; @@ -23,6 +23,11 @@ struct SSIDList<'a> { ssids: Vec<&'a String<32>>, } +#[derive(Serialize, Debug)] +struct FileList { + file: Vec, +} + #[derive(Serialize, Debug)] struct VersionInfo<'a> { git_hash: &'a str, @@ -170,6 +175,15 @@ fn wifi_scan( anyhow::Ok(Some(ssid_json)) } +fn list_files( + _request: &mut Request<&mut EspHttpConnection>, +) -> Result, anyhow::Error> { + let board = BOARD_ACCESS.lock().unwrap(); + let result = board.list_files()?; + let file_list_json = serde_json::to_string(&FileList { file: result })?; + return anyhow::Ok(Some(file_list_json)); +} + fn ota( request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { @@ -252,10 +266,96 @@ pub fn httpd(_reboot_now: Arc) -> Box> { .unwrap(); server - .fn_handler("/set_config", Method::Post, move |mut request| { + .fn_handler("/set_config", Method::Post, move |request| { handle_error_to500(request, set_config) }) .unwrap(); + server + .fn_handler("/list_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); + match file_handle { + Ok(file_handle) => { + let mut response = request.into_ok_response()?; + const BUFFER_SIZE: usize = 512; + let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; + let mut total_read: usize = 0; + loop { + let read = file_handle.read(&mut buffer)?; + total_read += read; + 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}"); + if read == 0 { + break; + } + } + response.flush()?; + + }, + Err(err) => { + //todo set headers here for filename to be error + let error_text = err.to_string(); + println!("error handling get file {}", error_text); + request + .into_status_response(500)? + .write(error_text.as_bytes())?; + } + } + anyhow::Ok(()) + }) + .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); + match file_handle { + Ok(file_handle) => { + const BUFFER_SIZE: usize = 512; + let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; + let mut total_read: usize = 0; + loop { + let read = request.read(&mut buffer)?; + total_read += read; + println!("sending {read} bytes of {total_read} for upload {filename}"); + let to_write = &buffer[0..read]; + file_handle.write(to_write)?; + println!("wrote {read} bytes of {total_read} for upload {filename}"); + if read == 0 { + break; + } + } + request.into_ok_response().unwrap().write(format!("saved {total_read} bytes").as_bytes()); + }, + Err(err) => { + //todo set headers here for filename to be error + let error_text = err.to_string(); + println!("error handling get file {}", error_text); + request + .into_status_response(500)? + .write(error_text.as_bytes())?; + } + } + 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("/flashbattery", Method::Post, move |mut request| { @@ -327,14 +427,12 @@ 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!("config.html"))?; - anyhow::Ok(()) - }) - .unwrap(); - server - .fn_handler("/bundle.js", Method::Get, |request| { - 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(()) }) @@ -345,6 +443,7 @@ pub fn httpd(_reboot_now: Arc) -> Box> { .into_ok_response()? .write(include_bytes!("favicon.ico"))?; anyhow::Ok(()) + }) .unwrap(); server