allow list get put delete of files

This commit is contained in:
Empire 2024-11-23 00:39:36 +01:00
parent 3d17ba67ff
commit 4c19d757c6
3 changed files with 147 additions and 55 deletions

View File

@ -8,7 +8,7 @@ rust-version = "1.71"
[profile.dev] [profile.dev]
# Explicitly disable LTO which the Xtensa codegen backend has issues # Explicitly disable LTO which the Xtensa codegen backend has issues
lto = false lto = true
strip = false strip = false
debug = true debug = true
overflow-checks = 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 = { 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" ]} chrono-tz = {version="0.8.0", default-features = false , features = [ "filter-by-regex" ]}
eeprom24x = "0.7.2" eeprom24x = "0.7.2"
url = "2.5.3"
[patch.crates-io] [patch.crates-io]

View File

@ -2,6 +2,7 @@ use bq34z100::{Bq34Z100Error, Bq34z100g1, Bq34z100g1Driver};
use ds323x::{DateTimeAccess, Ds323x}; use ds323x::{DateTimeAccess, Ds323x};
use eeprom24x::page_size::No;
use eeprom24x::{Eeprom24x, SlaveAddr}; use eeprom24x::{Eeprom24x, SlaveAddr};
use embedded_hal_bus::i2c::MutexDevice; use embedded_hal_bus::i2c::MutexDevice;
use embedded_svc::wifi::{ 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::i2c::{APBTickType, I2cConfig, I2cDriver, I2cError};
use esp_idf_hal::units::FromValueType; use esp_idf_hal::units::FromValueType;
use esp_idf_svc::eventloop::EspSystemEventLoop; use esp_idf_svc::eventloop::EspSystemEventLoop;
use esp_idf_svc::io::vfs;
use esp_idf_svc::ipv4::IpInfo; use esp_idf_svc::ipv4::IpInfo;
use esp_idf_svc::mqtt::client::QoS::AtLeastOnce; use esp_idf_svc::mqtt::client::QoS::AtLeastOnce;
use esp_idf_svc::mqtt::client::QoS::ExactlyOnce; 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_hal::reset::ResetReason;
use esp_idf_svc::sntp::{self, SyncStatus}; use esp_idf_svc::sntp::{self, SyncStatus};
use esp_idf_svc::systime::EspSystemTime; 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 one_wire_bus::OneWire;
use crate::config::{self, Config}; use crate::config::{self, Config};
@ -174,21 +176,82 @@ pub struct FileInfo{
size:usize size:usize
} }
#[derive(Serialize, Debug)]
pub struct FileList{
files: Vec<FileInfo>,
file_system_corrupt: Option<String>,
iter_error: Option<String>,
}
impl PlantCtrlBoard<'_> { impl PlantCtrlBoard<'_> {
pub fn list_files(&self) -> Result<Vec<FileInfo>> { pub fn list_files(&self, filename:&str) -> FileList {
let spiffs = Path::new(BASE_PATH); let storage = CString::new(SPIFFS_PARTITION_NAME).unwrap();
let list = fs::read_dir(spiffs).unwrap().map(|dir| { let error = unsafe {
let file = dir.unwrap(); esp!{
FileInfo{ esp_spiffs_check(storage.as_ptr())
filename: file.file_name().into_string().unwrap(),
size: file.metadata().unwrap().len() as usize
} }
}); };
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<File> { pub fn delete_file(&self, filename:&str) -> Result<()>{
let filepath = Path::new(BASE_PATH).join(Path::new(&filename)); 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<File> {
let filepath = Path::new(BASE_PATH).join(Path::new(filename));
return Ok(if write { return Ok(if write {
File::create(filepath)? File::create(filepath)?
} else { } else {
@ -417,7 +480,7 @@ impl PlantCtrlBoard<'_> {
.unwrap(); .unwrap();
let delay = Delay::new_default(); let delay = Delay::new_default();
let measurement = 5000; let measurement = 100;
let factor = 1000 as f32 / measurement as f32; let factor = 1000 as f32 / measurement as f32;
//give some time to stabilize //give some time to stabilize
@ -532,9 +595,11 @@ impl PlantCtrlBoard<'_> {
let conf = esp_idf_sys::esp_vfs_spiffs_conf_t { let conf = esp_idf_sys::esp_vfs_spiffs_conf_t {
base_path: base_path.as_ptr(), base_path: base_path.as_ptr(),
partition_label: storage.as_ptr(), partition_label: storage.as_ptr(),
max_files: 2, max_files: 5,
format_if_mount_failed: true, format_if_mount_failed: true,
}; };
//TODO check fielsystem esp_spiffs_check
unsafe { unsafe {
esp_idf_sys::esp!(esp_idf_sys::esp_vfs_spiffs_register(&conf))?; esp_idf_sys::esp!(esp_idf_sys::esp_vfs_spiffs_register(&conf))?;

View File

@ -1,18 +1,16 @@
//offer ota and config mode //offer ota and config mode
use std::{ use std::{
collections::VecDeque, collections::VecDeque, fs, io::{BufRead, Read, Write}, str::from_utf8, sync::{atomic::AtomicBool, Arc}
io::{BufRead, Read, Seek, 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 chrono::DateTime;
use url::Url;
use core::result::Result::Ok; use core::result::Result::Ok;
use embedded_svc::http::{Method, Query}; use embedded_svc::http::Method;
use esp_idf_hal::{delay::Delay, io::Write}; use esp_idf_hal::delay::Delay;
use esp_idf_svc::{fs, http::server::{Configuration, EspHttpConnection, EspHttpServer, Request}}; use esp_idf_svc::{http::server::{Configuration, EspHttpConnection, EspHttpServer, Request}};
use heapless::String; use heapless::String;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -81,7 +79,7 @@ fn get_data(
let mut a: Vec<u8> = Vec::new(); let mut a: Vec<u8> = Vec::new();
let mut b: Vec<u8> = Vec::new(); let mut b: Vec<u8> = 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 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 b_hz = board.measure_moisture_hz(plant, crate::plant_hal::Sensor::B)?;
let a_pct = map_range_moisture(a_hz as f32); let a_pct = map_range_moisture(a_hz as f32);
@ -176,11 +174,12 @@ fn wifi_scan(
} }
fn list_files( fn list_files(
_request: &mut Request<&mut EspHttpConnection>, request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> { ) -> Result<Option<std::string::String>, anyhow::Error> {
let filename = query_param(request.uri(),"filename").unwrap_or_default();
let board = BOARD_ACCESS.lock().unwrap(); let board = BOARD_ACCESS.lock().unwrap();
let result = board.list_files()?; let result = board.list_files(&filename);
let file_list_json = serde_json::to_string(&FileList { file: result })?; let file_list_json = serde_json::to_string(&result)?;
return anyhow::Ok(Some(file_list_json)); return anyhow::Ok(Some(file_list_json));
} }
@ -216,6 +215,20 @@ fn ota(
finalizer.restart(); finalizer.restart();
} }
fn query_param(uri:&str, param_name:&str) -> Option<std::string::String>{
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<AtomicBool>) -> Box<EspHttpServer<'static>> { pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
let server_config = Configuration { let server_config = Configuration {
stack_size: 32768, stack_size: 32768,
@ -255,7 +268,7 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
}) })
.unwrap(); .unwrap();
server server
.fn_handler("/ota", Method::Post, |mut request| { .fn_handler("/ota", Method::Post, |request| {
handle_error_to500(request, ota) handle_error_to500(request, ota)
}) })
.unwrap(); .unwrap();
@ -271,16 +284,16 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
}) })
.unwrap(); .unwrap();
server server
.fn_handler("/list_files", Method::Get, move |request| { .fn_handler("/files", Method::Get, move |request| {
handle_error_to500(request, list_files) handle_error_to500(request, list_files)
}) })
.unwrap(); .unwrap();
server server
.fn_handler("/get_file", Method::Get, move |request| { .fn_handler("/file", Method::Get, move |request| {
let filename:String = request.uri().magic_here().get("filename"); let filename = query_param(request.uri(),"filename").unwrap();
let file_handle = BOARD_ACCESS.lock().unwrap().get_file_handle(filename); let file_handle = BOARD_ACCESS.lock().unwrap().get_file_handle(&filename, false);
match file_handle { match file_handle {
Ok(file_handle) => { Ok(mut file_handle) => {
let mut response = request.into_ok_response()?; let mut response = request.into_ok_response()?;
const BUFFER_SIZE: usize = 512; const BUFFER_SIZE: usize = 512;
let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
@ -288,7 +301,7 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
loop { loop {
let read = file_handle.read(&mut buffer)?; let read = file_handle.read(&mut buffer)?;
total_read += read; 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]; let to_write = &buffer[0..read];
response.write(to_write)?; response.write(to_write)?;
println!("wrote {read} bytes of {total_read} for file {filename}"); println!("wrote {read} bytes of {total_read} for file {filename}");
@ -296,6 +309,7 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
break; break;
} }
} }
drop(file_handle);
response.flush()?; response.flush()?;
}, },
@ -312,11 +326,11 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
}) })
.unwrap(); .unwrap();
server server
.fn_handler("/put_file", Method::Post, move |request| { .fn_handler("/file", Method::Post, move |mut request| {
let filename:String = request.uri().magic_here().get("filename"); let filename = query_param(request.uri(),"filename").unwrap();
let file_handle = BOARD_ACCESS.lock().unwrap().get_file_handle(filename); let file_handle = BOARD_ACCESS.lock().unwrap().get_file_handle(&filename,true);
match file_handle { match file_handle {
Ok(file_handle) => { Ok(mut file_handle) => {
const BUFFER_SIZE: usize = 512; const BUFFER_SIZE: usize = 512;
let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
let mut total_read: usize = 0; let mut total_read: usize = 0;
@ -331,7 +345,7 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
break; 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) => { Err(err) => {
//todo set headers here for filename to be error //todo set headers here for filename to be error
@ -345,17 +359,26 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
anyhow::Ok(()) anyhow::Ok(())
}) })
.unwrap(); .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 server
.fn_handler("/flashbattery", Method::Post, move |mut request| { .fn_handler("/flashbattery", Method::Post, move |mut request| {
@ -428,12 +451,6 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
.fn_handler("/", Method::Get, move |request| { .fn_handler("/", Method::Get, move |request| {
let mut response = request.into_ok_response()?; let mut response = request.into_ok_response()?;
response.write(include_bytes!("config.html"))?;let mut buf = [0_u8; 3072]; 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(()) anyhow::Ok(())
}) })
.unwrap(); .unwrap();
@ -447,6 +464,15 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
}) })
.unwrap(); .unwrap();
server server
.fn_handler("/bundle.js", Method::Get, |request| {
request
.into_ok_response()?
.write(include_bytes!("bundle.js"))?;
anyhow::Ok(())
})
.unwrap();
server
} }