//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, 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, } impl Handler for HTTPRequestRouter { type Error = 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( request: &mut Connection<'_, T, N>, limit: Option, ) -> FatResult> 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, 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>, ) -> FatResult where T: Read + Write, ::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) } } }