Merge branch 'develop' into housekeeping and clippy and spellcheck
This commit is contained in:
716
rust/src/webserver/mod.rs
Normal file
716
rust/src/webserver/mod.rs
Normal file
@@ -0,0 +1,716 @@
|
||||
//offer ota and config mode
|
||||
|
||||
use crate::{
|
||||
config::PlantControllerConfig,
|
||||
determine_tank_state, get_version,
|
||||
hal::PLANT_COUNT,
|
||||
log::LogMessage,
|
||||
plant_state::{MoistureSensorState, PlantState},
|
||||
BOARD_ACCESS,
|
||||
};
|
||||
use anyhow::bail;
|
||||
use chrono::DateTime;
|
||||
use core::result::Result::Ok;
|
||||
use embedded_svc::http::Method;
|
||||
use esp_idf_svc::http::server::{Configuration, EspHttpConnection, EspHttpServer, Request};
|
||||
use esp_idf_sys::{settimeofday, timeval, vTaskDelay};
|
||||
use esp_ota::OtaUpdate;
|
||||
use heapless::String;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
str::from_utf8,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct SSIDList<'a> {
|
||||
ssids: Vec<&'a String<32>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct LoadData<'a> {
|
||||
rtc: &'a str,
|
||||
native: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct Moistures {
|
||||
moisture_a: Vec<std::string::String>,
|
||||
moisture_b: Vec<std::string::String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct SolarState {
|
||||
mppt_voltage: f32,
|
||||
mppt_current: f32,
|
||||
is_day: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct SetTime<'a> {
|
||||
time: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub struct TestPump {
|
||||
pump: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
pub struct WebBackupHeader {
|
||||
timestamp: std::string::String,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct NightLampCommand {
|
||||
active: bool,
|
||||
}
|
||||
|
||||
fn write_time(
|
||||
request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let actual_data = read_up_to_bytes_from_request(request, None)?;
|
||||
let time: SetTime = serde_json::from_slice(&actual_data)?;
|
||||
let parsed = DateTime::parse_from_rfc3339(time.time).map_err(|err| anyhow::anyhow!(err))?;
|
||||
let mut board = BOARD_ACCESS.lock().expect("board access");
|
||||
|
||||
let now = timeval {
|
||||
tv_sec: parsed.to_utc().timestamp(),
|
||||
tv_usec: 0,
|
||||
};
|
||||
unsafe { settimeofday(&now, core::ptr::null_mut()) };
|
||||
board.board_hal.set_rtc_time(&parsed.to_utc())?;
|
||||
anyhow::Ok(None)
|
||||
}
|
||||
|
||||
fn get_time(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let mut board = BOARD_ACCESS.lock().expect("board access");
|
||||
let native = board
|
||||
.board_hal
|
||||
.get_esp()
|
||||
.time()
|
||||
.map(|t| t.to_rfc3339())
|
||||
.unwrap_or("error".to_string());
|
||||
let rtc = board
|
||||
.board_hal
|
||||
.get_rtc_time()
|
||||
.map(|t| t.to_rfc3339())
|
||||
.unwrap_or("error".to_string());
|
||||
|
||||
let data = LoadData {
|
||||
rtc: rtc.as_str(),
|
||||
native: native.as_str(),
|
||||
};
|
||||
let json = serde_json::to_string(&data)?;
|
||||
|
||||
anyhow::Ok(Some(json))
|
||||
}
|
||||
|
||||
fn get_timezones(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
// Get all timezones using chrono-tz
|
||||
let timezones: Vec<&'static str> = chrono_tz::TZ_VARIANTS.iter().map(|tz| tz.name()).collect();
|
||||
|
||||
// Convert to JSON
|
||||
let json = serde_json::to_string(&timezones)?;
|
||||
anyhow::Ok(Some(json))
|
||||
}
|
||||
|
||||
fn get_live_moisture(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let mut board = BOARD_ACCESS.lock().expect("Should never fail");
|
||||
let plant_state =
|
||||
Vec::from_iter((0..PLANT_COUNT).map(|i| PlantState::read_hardware_state(i, &mut board)));
|
||||
let a = Vec::from_iter(plant_state.iter().map(|s| match &s.sensor_a {
|
||||
MoistureSensorState::Disabled => "disabled".to_string(),
|
||||
MoistureSensorState::MoistureValue {
|
||||
raw_hz,
|
||||
moisture_percent,
|
||||
} => {
|
||||
format!("{moisture_percent:.2}% {raw_hz}hz",)
|
||||
}
|
||||
MoistureSensorState::SensorError(err) => format!("{err:?}"),
|
||||
}));
|
||||
let b = Vec::from_iter(plant_state.iter().map(|s| match &s.sensor_b {
|
||||
MoistureSensorState::Disabled => "disabled".to_string(),
|
||||
MoistureSensorState::MoistureValue {
|
||||
raw_hz,
|
||||
moisture_percent,
|
||||
} => {
|
||||
format!("{moisture_percent:.2}% {raw_hz}hz",)
|
||||
}
|
||||
MoistureSensorState::SensorError(err) => format!("{err:?}"),
|
||||
}));
|
||||
|
||||
let data = Moistures {
|
||||
moisture_a: a,
|
||||
moisture_b: b,
|
||||
};
|
||||
let json = serde_json::to_string(&data)?;
|
||||
|
||||
anyhow::Ok(Some(json))
|
||||
}
|
||||
|
||||
fn get_config(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let mut board = BOARD_ACCESS.lock().expect("Should never fail");
|
||||
let json = serde_json::to_string(&board.board_hal.get_config())?;
|
||||
anyhow::Ok(Some(json))
|
||||
}
|
||||
|
||||
fn backup_config(
|
||||
request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let all = read_up_to_bytes_from_request(request, Some(3072))?;
|
||||
let mut board = BOARD_ACCESS.lock().expect("board access");
|
||||
board.board_hal.backup_config(&all)?;
|
||||
anyhow::Ok(Some("saved".to_owned()))
|
||||
}
|
||||
|
||||
fn get_backup_config(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let mut board = BOARD_ACCESS.lock().expect("board access");
|
||||
let json = match board.board_hal.get_backup_config() {
|
||||
Ok(config) => from_utf8(&config)?.to_owned(),
|
||||
Err(err) => {
|
||||
println!("Error get backup config {:?}", err);
|
||||
err.to_string()
|
||||
}
|
||||
};
|
||||
anyhow::Ok(Some(json))
|
||||
}
|
||||
|
||||
fn backup_info(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let mut board = BOARD_ACCESS.lock().expect("Should never fail");
|
||||
let header = board.board_hal.get_backup_info();
|
||||
let json = match header {
|
||||
Ok(h) => {
|
||||
let timestamp = DateTime::from_timestamp_millis(h.timestamp).unwrap();
|
||||
let wbh = WebBackupHeader {
|
||||
timestamp: timestamp.to_rfc3339(),
|
||||
size: h.size,
|
||||
};
|
||||
serde_json::to_string(&wbh)?
|
||||
}
|
||||
Err(_) => {
|
||||
//TODO make better
|
||||
let wbh = WebBackupHeader {
|
||||
timestamp: "no backup".to_owned(),
|
||||
size: 0,
|
||||
};
|
||||
serde_json::to_string(&wbh)?
|
||||
}
|
||||
};
|
||||
anyhow::Ok(Some(json))
|
||||
}
|
||||
|
||||
fn set_config(
|
||||
request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let all = read_up_to_bytes_from_request(request, Some(3072))?;
|
||||
let config: PlantControllerConfig = serde_json::from_slice(&all)?;
|
||||
|
||||
let mut board = BOARD_ACCESS.lock().expect("board access");
|
||||
let _ = board.board_hal.set_config(config);
|
||||
anyhow::Ok(Some("saved".to_owned()))
|
||||
}
|
||||
|
||||
fn get_solar_state(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let mut board = BOARD_ACCESS.lock().expect("board access");
|
||||
let state = SolarState {
|
||||
mppt_voltage: board.board_hal.get_mptt_voltage()?.as_volts() as f32,
|
||||
mppt_current: board.board_hal.get_mptt_current()?.as_amperes() as f32,
|
||||
is_day: board.board_hal.is_day(),
|
||||
};
|
||||
anyhow::Ok(Some(serde_json::to_string(&state)?))
|
||||
}
|
||||
|
||||
fn get_battery_state(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let mut board = BOARD_ACCESS.lock().expect("board access");
|
||||
let battery_state = board.board_hal.get_battery_monitor().get_battery_state();
|
||||
anyhow::Ok(Some(serde_json::to_string(&battery_state)?))
|
||||
}
|
||||
|
||||
fn get_log(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let output = crate::log::get_log();
|
||||
anyhow::Ok(Some(serde_json::to_string(&output)?))
|
||||
}
|
||||
|
||||
fn get_log_localization_config() -> Result<std::string::String, anyhow::Error> {
|
||||
anyhow::Ok(serde_json::to_string(
|
||||
&LogMessage::to_log_localisation_config(),
|
||||
)?)
|
||||
}
|
||||
|
||||
fn get_version_web(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
anyhow::Ok(Some(serde_json::to_string(&get_version())?))
|
||||
}
|
||||
|
||||
fn pump_test(
|
||||
request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let actual_data = read_up_to_bytes_from_request(request, None)?;
|
||||
let pump_test: TestPump = serde_json::from_slice(&actual_data)?;
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
board.board_hal.test_pump(pump_test.pump)?;
|
||||
anyhow::Ok(None)
|
||||
}
|
||||
|
||||
fn tank_info(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
let tank_info = determine_tank_state(&mut board);
|
||||
//should be multsampled
|
||||
let water_temp = board.board_hal.water_temperature_c();
|
||||
Ok(Some(serde_json::to_string(&tank_info.as_mqtt_info(
|
||||
&board.board_hal.get_config().tank,
|
||||
&water_temp,
|
||||
))?))
|
||||
}
|
||||
|
||||
fn night_lamp_test(
|
||||
request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let actual_data = read_up_to_bytes_from_request(request, None)?;
|
||||
let light_command: NightLampCommand = serde_json::from_slice(&actual_data)?;
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
board.board_hal.light(light_command.active)?;
|
||||
anyhow::Ok(None)
|
||||
}
|
||||
|
||||
fn wifi_scan(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
let scan_result = board.board_hal.get_esp().wifi_scan()?;
|
||||
let mut ssids: Vec<&String<32>> = Vec::new();
|
||||
scan_result.iter().for_each(|s| ssids.push(&s.ssid));
|
||||
let ssid_json = serde_json::to_string(&SSIDList { ssids })?;
|
||||
println!("Sending ssid list {}", &ssid_json);
|
||||
anyhow::Ok(Some(ssid_json))
|
||||
}
|
||||
|
||||
fn list_files(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let mut board = BOARD_ACCESS
|
||||
.lock()
|
||||
.expect("It should be possible to lock the board for exclusive fs access");
|
||||
let result = board.board_hal.get_esp().list_files();
|
||||
let file_list_json = serde_json::to_string(&result)?;
|
||||
anyhow::Ok(Some(file_list_json))
|
||||
}
|
||||
|
||||
fn ota(
|
||||
request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
let mut ota = OtaUpdate::begin()?;
|
||||
println!("start ota");
|
||||
|
||||
//having a larger buffer is not really faster, requires more stack and prevents the progress bar from working ;)
|
||||
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 read = request.read(&mut buffer)?;
|
||||
total_read += read;
|
||||
let to_write = &buffer[0..read];
|
||||
//delay for watchdog and wifi stuff
|
||||
board.board_hal.get_esp().delay.delay_ms(1);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ota.write(to_write)?;
|
||||
if read == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
println!("wrote bytes ota {total_read}");
|
||||
println!("finish ota");
|
||||
let partition = ota.raw_partition();
|
||||
println!("finalizing and changing boot partition to {partition:?}");
|
||||
|
||||
let mut finalizer = ota.finalize()?;
|
||||
println!("changing boot partition");
|
||||
board.board_hal.get_esp().set_restart_to_conf(true);
|
||||
drop(board);
|
||||
finalizer.set_as_boot_partition()?;
|
||||
anyhow::Ok(None)
|
||||
}
|
||||
|
||||
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().find(|it| it.0 == param_name);
|
||||
match value {
|
||||
Some(found) => Some(found.1.into_owned()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
||||
let server_config = Configuration {
|
||||
stack_size: 32768,
|
||||
..Default::default()
|
||||
};
|
||||
let mut server: Box<EspHttpServer<'static>> =
|
||||
Box::new(EspHttpServer::new(&server_config).unwrap());
|
||||
server
|
||||
.fn_handler("/version", Method::Get, |request| {
|
||||
handle_error_to500(request, get_version_web)
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/log", Method::Get, |request| {
|
||||
handle_error_to500(request, get_log)
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/log_localization", Method::Get, |request| {
|
||||
cors_response(request, 200, &get_log_localization_config().unwrap())
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/battery", Method::Get, |request| {
|
||||
handle_error_to500(request, get_battery_state)
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/solar", Method::Get, |request| {
|
||||
handle_error_to500(request, get_solar_state)
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/time", Method::Get, |request| {
|
||||
handle_error_to500(request, get_time)
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/moisture", Method::Get, |request| {
|
||||
handle_error_to500(request, get_live_moisture)
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/time", Method::Post, |request| {
|
||||
handle_error_to500(request, write_time)
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/tank", Method::Get, |request| {
|
||||
handle_error_to500(request, tank_info)
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/pumptest", Method::Post, |request| {
|
||||
handle_error_to500(request, pump_test)
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/lamptest", Method::Post, |request| {
|
||||
handle_error_to500(request, night_lamp_test)
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/boardtest", Method::Post, move |_| {
|
||||
BOARD_ACCESS.lock().unwrap().board_hal.test()
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/wifiscan", Method::Post, move |request| {
|
||||
handle_error_to500(request, wifi_scan)
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/ota", Method::Post, |request| {
|
||||
handle_error_to500(request, ota)
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/ota", Method::Options, |request| {
|
||||
cors_response(request, 200, "")
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/get_config", Method::Get, move |request| {
|
||||
handle_error_to500(request, get_config)
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/get_backup_config", Method::Get, move |request| {
|
||||
handle_error_to500(request, get_backup_config)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
server
|
||||
.fn_handler("/set_config", Method::Post, move |request| {
|
||||
handle_error_to500(request, set_config)
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/backup_config", Method::Post, move |request| {
|
||||
handle_error_to500(request, backup_config)
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/backup_info", Method::Get, move |request| {
|
||||
handle_error_to500(request, backup_info)
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/files", Method::Get, move |request| {
|
||||
handle_error_to500(request, list_files)
|
||||
})
|
||||
.unwrap();
|
||||
let reboot_now_for_reboot = reboot_now.clone();
|
||||
server
|
||||
.fn_handler("/reboot", Method::Post, move |_| {
|
||||
BOARD_ACCESS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.board_hal
|
||||
.get_esp()
|
||||
.set_restart_to_conf(true);
|
||||
reboot_now_for_reboot.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
unsafe { vTaskDelay(1) };
|
||||
|
||||
let reboot_now_for_exit = reboot_now.clone();
|
||||
server
|
||||
.fn_handler("/exit", Method::Post, move |_| {
|
||||
reboot_now_for_exit.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.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;
|
||||
}
|
||||
}
|
||||
println!("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();
|
||||
println!("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();
|
||||
println!("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| {
|
||||
let filename = query_param(request.uri(), "filename").unwrap();
|
||||
let copy = filename.clone();
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
match board.board_hal.get_esp().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("/file", Method::Options, |request| {
|
||||
cors_response(request, 200, "")
|
||||
})
|
||||
.unwrap();
|
||||
unsafe { vTaskDelay(1) };
|
||||
server
|
||||
.fn_handler("/", Method::Get, move |request| {
|
||||
let mut response = request.into_ok_response()?;
|
||||
response.write(include_bytes!("index.html"))?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/favicon.ico", Method::Get, |request| {
|
||||
request
|
||||
.into_ok_response()?
|
||||
.write(include_bytes!("favicon.ico"))?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/bundle.js", Method::Get, |request| {
|
||||
request
|
||||
.into_ok_response()?
|
||||
.write(include_bytes!("bundle.js"))?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/timezones", Method::Get, move |request| {
|
||||
handle_error_to500(request, get_timezones)
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
}
|
||||
|
||||
fn cors_response(
|
||||
request: Request<&mut EspHttpConnection>,
|
||||
status: u16,
|
||||
body: &str,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let headers = [
|
||||
("Access-Control-Allow-Origin", "*"),
|
||||
("Access-Control-Allow-Headers", "*"),
|
||||
("Access-Control-Allow-Methods", "*"),
|
||||
];
|
||||
let mut response = request.into_response(status, None, &headers)?;
|
||||
response.write(body.as_bytes())?;
|
||||
response.flush()?;
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
type AnyhowHandler =
|
||||
fn(&mut Request<&mut EspHttpConnection>) -> Result<Option<std::string::String>, anyhow::Error>;
|
||||
fn handle_error_to500(
|
||||
mut request: Request<&mut EspHttpConnection>,
|
||||
chain: AnyhowHandler,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let error = chain(&mut request);
|
||||
match error {
|
||||
Ok(answer) => match answer {
|
||||
Some(json) => {
|
||||
cors_response(request, 200, &json)?;
|
||||
}
|
||||
None => {
|
||||
cors_response(request, 200, "")?;
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
let error_text = err.to_string();
|
||||
println!("error handling process {}", error_text);
|
||||
cors_response(request, 500, &error_text)?;
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
fn read_up_to_bytes_from_request(
|
||||
request: &mut Request<&mut EspHttpConnection<'_>>,
|
||||
limit: Option<usize>,
|
||||
) -> Result<Vec<u8>, anyhow::Error> {
|
||||
let max_read = limit.unwrap_or(1024);
|
||||
let mut data_store = Vec::new();
|
||||
let mut total_read = 0;
|
||||
loop {
|
||||
let mut buf = [0_u8; 64];
|
||||
let read = request.read(&mut buf)?;
|
||||
if read == 0 {
|
||||
break;
|
||||
}
|
||||
let actual_data = &buf[0..read];
|
||||
total_read += read;
|
||||
if total_read > max_read {
|
||||
bail!("Request too large {total_read} > {max_read}");
|
||||
}
|
||||
data_store.push(actual_data.to_owned());
|
||||
}
|
||||
let allvec = data_store.concat();
|
||||
println!("Raw data {}", from_utf8(&allvec)?);
|
||||
Ok(allvec)
|
||||
}
|
||||
Reference in New Issue
Block a user