303 lines
11 KiB
Rust
303 lines
11 KiB
Rust
//offer ota and config mode
|
|
|
|
mod backup_manager;
|
|
mod file_manager;
|
|
mod get_json;
|
|
mod get_log;
|
|
mod get_static;
|
|
mod ota;
|
|
mod post_json;
|
|
|
|
use crate::fat_error::{FatError, FatResult};
|
|
use crate::webserver::backup_manager::{backup_config, backup_info, get_backup_config};
|
|
use crate::webserver::file_manager::{file_operations, list_files};
|
|
use crate::webserver::get_json::{
|
|
get_battery_state, get_config, get_live_moisture, get_log_localization_config, get_solar_state,
|
|
get_time, get_timezones, get_version_web, tank_info,
|
|
};
|
|
use crate::webserver::get_log::get_log;
|
|
use crate::webserver::get_static::{serve_bundle, serve_favicon, serve_index};
|
|
use crate::webserver::ota::ota_operations;
|
|
use crate::webserver::post_json::{
|
|
board_test, night_lamp_test, pump_test, set_config, wifi_scan, write_time, detect_sensors,
|
|
};
|
|
use crate::{bail, BOARD_ACCESS};
|
|
use alloc::borrow::ToOwned;
|
|
use alloc::string::{String, ToString};
|
|
use alloc::sync::Arc;
|
|
use alloc::vec::Vec;
|
|
use core::fmt::{Debug, Display};
|
|
use core::net::{IpAddr, Ipv4Addr, SocketAddr};
|
|
use core::result::Result::Ok;
|
|
use core::sync::atomic::{AtomicBool, Ordering};
|
|
use edge_http::io::server::{Connection, Handler, Server};
|
|
use edge_http::Method;
|
|
use edge_nal::TcpBind;
|
|
use edge_nal_embassy::{Tcp, TcpBuffers};
|
|
use embassy_net::Stack;
|
|
use embassy_time::Instant;
|
|
use embedded_io_async::{Read, Write};
|
|
use log::{error, info};
|
|
// 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()?;
|
|
// log::info!("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 {
|
|
// board.board_hal.general_fault(iter % 5 == 0);
|
|
// for i in 0..PLANT_COUNT {
|
|
// let _ = board.board_hal.fault(i, iter == i);
|
|
// }
|
|
// lastiter = iter;
|
|
// }
|
|
//
|
|
// ota.write(to_write)?;
|
|
// if read == 0 {
|
|
// break;
|
|
// }
|
|
// }
|
|
// log::info!("wrote bytes ota {total_read}");
|
|
// log::info!("finish ota");
|
|
// let partition = ota.raw_partition();
|
|
// log::info!("finalizing and changing boot partition to {partition:?}");
|
|
//
|
|
// let mut finalizer = ota.finalize()?;
|
|
// log::info!("changing boot partition");
|
|
// board.board_hal.get_esp().set_restart_to_conf(true);
|
|
// drop(board);
|
|
// finalizer.set_as_boot_partition()?;
|
|
// anyhow::Ok(None)
|
|
// }
|
|
//
|
|
|
|
struct HTTPRequestRouter {
|
|
reboot_now: Arc<AtomicBool>,
|
|
}
|
|
|
|
impl Handler for HTTPRequestRouter {
|
|
type Error<E: Debug> = FatError;
|
|
async fn handle<'a, T, const N: usize>(
|
|
&self,
|
|
_task_id: impl Display + Copy,
|
|
conn: &mut Connection<'a, T, N>,
|
|
) -> Result<(), FatError>
|
|
where
|
|
T: Read + Write,
|
|
{
|
|
let start = Instant::now();
|
|
let headers = conn.headers()?;
|
|
|
|
let method = headers.method;
|
|
let path = headers.path;
|
|
|
|
let prefix = "/file?filename=";
|
|
let status = if path.starts_with(prefix) {
|
|
file_operations(conn, method, &path, &prefix).await?
|
|
} else if path == "/ota" {
|
|
ota_operations(conn, method).await.map_err(|e| {
|
|
error!("Error handling ota: {}", e);
|
|
e
|
|
})?
|
|
} else {
|
|
match method {
|
|
Method::Get => match path {
|
|
"/favicon.ico" => serve_favicon(conn).await?,
|
|
"/" => serve_index(conn).await?,
|
|
"/bundle.js" => serve_bundle(conn).await?,
|
|
"/log" => get_log(conn).await?,
|
|
"/get_backup_config" => get_backup_config(conn).await?,
|
|
&_ => {
|
|
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),
|
|
"/tank" => Some(tank_info(conn).await),
|
|
"/backup_info" => Some(backup_info(conn).await),
|
|
"/timezones" => Some(get_timezones().await),
|
|
"/moisture" => Some(get_live_moisture(conn).await),
|
|
_ => None,
|
|
};
|
|
match json {
|
|
None => None,
|
|
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),
|
|
"/time" => Some(write_time(conn).await),
|
|
"/backup_config" => Some(backup_config(conn).await),
|
|
"/pumptest" => Some(pump_test(conn).await),
|
|
"/lamptest" => Some(night_lamp_test(conn).await),
|
|
"/boardtest" => Some(board_test().await),
|
|
"/detect_sensors" => Some(detect_sensors().await),
|
|
"/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(Ok(None))
|
|
}
|
|
"/exit" => {
|
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
|
board.board_hal.get_esp().set_restart_to_conf(false);
|
|
self.reboot_now.store(true, Ordering::Relaxed);
|
|
Some(Ok(None))
|
|
}
|
|
_ => None,
|
|
};
|
|
match json {
|
|
None => None,
|
|
Some(json) => Some(handle_json(conn, json).await?),
|
|
}
|
|
}
|
|
Method::Options | Method::Delete | Method::Head | Method::Put => None,
|
|
_ => None,
|
|
}
|
|
};
|
|
let code = match status {
|
|
None => {
|
|
conn.initiate_response(404, Some("Not found"), &[]).await?;
|
|
404
|
|
}
|
|
Some(code) => code,
|
|
};
|
|
|
|
conn.complete().await?;
|
|
let response_time = Instant::now().duration_since(start).as_millis();
|
|
|
|
info!("\"{method} {path}\" {code} {response_time}ms");
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
async fn read_up_to_bytes_from_request<T, const N: usize>(
|
|
request: &mut Connection<'_, T, N>,
|
|
limit: Option<usize>,
|
|
) -> FatResult<Vec<u8>>
|
|
where
|
|
T: Read + Write,
|
|
{
|
|
let max_read = limit.unwrap_or(1024);
|
|
let mut data_store = Vec::new();
|
|
let mut total_read = 0;
|
|
loop {
|
|
let left = max_read - total_read;
|
|
let mut buf = [0_u8; 64];
|
|
let s_buf = if buf.len() <= left {
|
|
&mut buf
|
|
} else {
|
|
&mut buf[0..left]
|
|
};
|
|
let read = request.read(s_buf).await?;
|
|
if read == 0 {
|
|
break;
|
|
}
|
|
let actual_data = &s_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 final_buffer = data_store.concat();
|
|
Ok(final_buffer)
|
|
}
|
|
|
|
#[embassy_executor::task]
|
|
pub async fn http_server(reboot_now: Arc<AtomicBool>, stack: Stack<'static>) {
|
|
let buffer: TcpBuffers<2, 1024, 1024> = TcpBuffers::new();
|
|
let tcp = Tcp::new(stack, &buffer);
|
|
let acceptor = tcp
|
|
.bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 80))
|
|
.await
|
|
.unwrap();
|
|
|
|
let mut server: Server<2, 512, 15> = Server::new();
|
|
server
|
|
.run(Some(5000), acceptor, HTTPRequestRouter { reboot_now })
|
|
.await
|
|
.expect("Tcp stack error");
|
|
info!("Webserver started and waiting for connections");
|
|
|
|
//TODO https if mbed_esp lands
|
|
}
|
|
|
|
async fn handle_json<'a, T, const N: usize>(
|
|
conn: &mut Connection<'a, T, N>,
|
|
chain: FatResult<Option<String>>,
|
|
) -> FatResult<u32>
|
|
where
|
|
T: Read + Write,
|
|
<T as embedded_io_async::ErrorType>::Error: Debug,
|
|
{
|
|
match chain {
|
|
Ok(answer) => match answer {
|
|
Some(json) => {
|
|
conn.initiate_response(
|
|
200,
|
|
Some("OK"),
|
|
&[
|
|
("Access-Control-Allow-Origin", "*"),
|
|
("Access-Control-Allow-Headers", "*"),
|
|
("Access-Control-Allow-Methods", "*"),
|
|
("Content-Type", "application/json"),
|
|
],
|
|
)
|
|
.await?;
|
|
conn.write_all(json.as_bytes()).await?;
|
|
Ok(200)
|
|
}
|
|
None => {
|
|
conn.initiate_response(
|
|
200,
|
|
Some("OK"),
|
|
&[
|
|
("Access-Control-Allow-Origin", "*"),
|
|
("Access-Control-Allow-Headers", "*"),
|
|
("Access-Control-Allow-Methods", "*"),
|
|
],
|
|
)
|
|
.await?;
|
|
Ok(200)
|
|
}
|
|
},
|
|
Err(err) => {
|
|
let error_text = err.to_string();
|
|
info!("error handling process {}", error_text);
|
|
conn.initiate_response(
|
|
500,
|
|
Some("OK"),
|
|
&[
|
|
("Access-Control-Allow-Origin", "*"),
|
|
("Access-Control-Allow-Headers", "*"),
|
|
("Access-Control-Allow-Methods", "*"),
|
|
],
|
|
)
|
|
.await?;
|
|
conn.write_all(error_text.as_bytes()).await?;
|
|
Ok(500)
|
|
}
|
|
}
|
|
}
|