//offer ota and config mode use std::{ collections::VecDeque, io::{BufRead, Read, Write}, str::from_utf8, sync::{atomic::AtomicBool, Arc}, }; use crate::{espota::OtaUpdate, BOARD_ACCESS}; use core::result::Result::Ok; use embedded_svc::http::{Method}; use esp_idf_hal::delay::Delay; use esp_idf_svc::http::server::{Configuration, EspHttpServer}; use esp_idf_sys::vTaskDelay; use heapless::String; use serde::{Deserialize, Serialize}; use crate::{ config::{Config, WifiConfig}, plant_hal::PlantCtrlBoardInteraction, }; #[derive(Serialize, Debug)] struct SSIDList<'a> { ssids: Vec<&'a String<32>>, } #[derive(Serialize, Debug)] struct VersionInfo<'a> { git_hash: &'a str, build_time: &'a str, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct TestPump { pump: usize, } pub fn httpd_initial(reboot_now: Arc) -> Box> { let mut server = shared(); server .fn_handler("/", Method::Get, move |request| { let mut response = request.into_ok_response()?; response.write(include_bytes!("initial_config.html"))?; anyhow::Ok(()) }) .unwrap(); server .fn_handler("/wifiscan", Method::Post, move |request| { let mut response = request.into_ok_response()?; let mut board = BOARD_ACCESS.lock().unwrap(); match board.wifi_scan() { Err(error) => { response.write(format!("Error scanning wifi: {}", error).as_bytes())?; } Ok(scan_result) => { 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); response.write(ssid_json.as_bytes())?; } } anyhow::Ok(()) }) .unwrap(); server .fn_handler("/wifisave", Method::Post, move |mut request| { let mut buf = [0_u8; 2048]; let read = request.read(&mut buf); if read.is_err() { let error_text = read.unwrap_err().to_string(); println!("Could not parse wificonfig {}", error_text); request .into_status_response(500)? .write(error_text.as_bytes())?; return anyhow::Ok(()); } let actual_data = &buf[0..read.unwrap()]; println!("raw {:?}", actual_data); println!("Raw data {}", from_utf8(actual_data).unwrap()); let wifi_config: Result = serde_json::from_slice(actual_data); if wifi_config.is_err() { let error_text = wifi_config.unwrap_err().to_string(); println!("Could not parse wificonfig {}", error_text); request .into_status_response(500)? .write(error_text.as_bytes())?; return anyhow::Ok(()); } let mut board = BOARD_ACCESS.lock().unwrap(); board.set_wifi(&wifi_config.unwrap())?; let mut response = request.into_status_response(202)?; response.write("saved".as_bytes())?; reboot_now.store(true, std::sync::atomic::Ordering::Relaxed); anyhow::Ok(()) }) .unwrap(); server } pub fn httpd(reboot_now: Arc) -> Box> { let mut server = shared(); 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("/get_config", Method::Get, move |request| { let mut response = request.into_ok_response()?; let mut board = BOARD_ACCESS.lock().unwrap(); match board.get_config() { Ok(config) => { let config_json = serde_json::to_string(&config)?; response.write(config_json.as_bytes())?; } Err(_) => { let config_json = serde_json::to_string(&Config::default())?; response.write(config_json.as_bytes())?; } } anyhow::Ok(()) }) .unwrap(); server .fn_handler("/set_config", Method::Post, move |mut request| { let mut buf = [0_u8; 3072]; let read = request.read(&mut buf); if read.is_err() { let error_text = read.unwrap_err().to_string(); println!("Could not parse config {}", error_text); request .into_status_response(500)? .write(error_text.as_bytes())?; return anyhow::Ok(()); } let actual_data = &buf[0..read.unwrap()]; println!("Raw data {}", from_utf8(actual_data).unwrap()); let config: Result = serde_json::from_slice(actual_data); if config.is_err() { let error_text = config.unwrap_err().to_string(); println!("Could not parse config {}", error_text); request .into_status_response(500)? .write(error_text.as_bytes())?; return Ok(()); } let mut board = BOARD_ACCESS.lock().unwrap(); board.set_config(&config.unwrap())?; let mut response = request.into_status_response(202)?; response.write("saved".as_bytes())?; reboot_now.store(true, std::sync::atomic::Ordering::Relaxed); Ok(()) }) .unwrap(); server } pub fn shared() -> Box> { let server_config = Configuration { stack_size: 8192, ..Default::default() }; let mut server: Box> = Box::new(EspHttpServer::new(&server_config).unwrap()); server .fn_handler("/version", Method::Get, |request| { let mut response = request.into_ok_response()?; let git_hash = env!("VERGEN_GIT_DESCRIBE"); let build_time = env!("VERGEN_BUILD_TIMESTAMP"); let version_info = VersionInfo { git_hash, build_time, }; let version_info_json = serde_json::to_string(&version_info)?; response.write(version_info_json.as_bytes())?; anyhow::Ok(()) }) .unwrap(); server .fn_handler("/bundle.js", Method::Get, |request| { let mut response = request.into_ok_response()?; response.write(include_bytes!("bundle.js"))?; anyhow::Ok(()) }) .unwrap(); server .fn_handler("/favicon.ico", Method::Get, |request| { let mut response = request.into_ok_response()?; response.write(include_bytes!("favicon.ico"))?; anyhow::Ok(()) }) .unwrap(); server .fn_handler("/ota", Method::Post, |mut request| { let ota = OtaUpdate::begin(); if ota.is_err() { let error_text = ota.unwrap_err().to_string(); request .into_status_response(500)? .write(error_text.as_bytes())?; return anyhow::Ok(()); } let mut ota = ota.unwrap(); 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; loop { let read = request.read(&mut buffer).unwrap(); total_read += read; println!("received {read} bytes ota {total_read}"); let to_write = &buffer[0..read]; let write_result = ota.write(to_write); if write_result.is_err() { let error_text = write_result.unwrap_err().to_string(); request .into_status_response(500)? .write(error_text.as_bytes())?; return Ok(()); } println!("wrote {read} bytes ota {total_read}"); if read == 0 { break; } } println!("finish ota"); let partition = ota.raw_partition(); println!("finalizing and changing boot partition to {partition:?}"); let finalizer = ota.finalize(); if finalizer.is_err() { let error_text = finalizer.err().unwrap().to_string(); request .into_status_response(500)? .write(error_text.as_bytes())?; return Ok(()); } let mut finalizer = finalizer.unwrap(); println!("changing boot partition"); finalizer.set_as_boot_partition().unwrap(); finalizer.restart(); }) .unwrap(); server .fn_handler("/boardtest", Method::Post, move |_| { let mut board = BOARD_ACCESS.lock().unwrap(); board.test()?; anyhow::Ok(()) }) .unwrap(); server .fn_handler("/pumptest", Method::Post, |mut request| { let mut buf = [0_u8; 3072]; let read = request.read(&mut buf); if read.is_err() { let error_text = read.unwrap_err().to_string(); println!("Could not parse testrequest {}", error_text); request .into_status_response(500)? .write(error_text.as_bytes())?; return anyhow::Ok(()); } let actual_data = &buf[0..read.unwrap()]; println!("Raw data {}", from_utf8(actual_data).unwrap()); let pump_test: Result = serde_json::from_slice(actual_data); if pump_test.is_err() { let error_text = pump_test.unwrap_err().to_string(); println!("Could not parse TestPump {}", error_text); request .into_status_response(500)? .write(error_text.as_bytes())?; return Ok(()); } let mut board = BOARD_ACCESS.lock().unwrap(); board.test_pump(pump_test.unwrap().pump)?; anyhow::Ok(()) }) .unwrap(); server .fn_handler("/flashbattery", Method::Post, move |mut request| { let mut board = BOARD_ACCESS.lock().unwrap(); let mut buffer: [u8; 128] = [0; 128]; let mut line_buffer: VecDeque = VecDeque::new(); let is_dry_run = !request.uri().ends_with("?flash=true"); let mut total_read: usize = 0; let mut toggle = true; let delay = Delay::new(1); loop { delay.delay_us(2); let read = request.read(&mut buffer).unwrap(); total_read += read; if read == 0 { if line_buffer.len() > 0 { println!("No further body but no endline"); let mut line = std::string::String::new(); line_buffer.read_to_string(&mut line).unwrap(); let msg = format!("Finished reading, but there is still some leftover in buffer and no full line {line}
"); println!("{}", msg); let mut response = request.into_status_response(400_u16).unwrap(); response.write(msg.as_bytes()).unwrap(); response.flush().unwrap(); return anyhow::Ok(()) } break; } let to_write = &buffer[0..read]; line_buffer.write_all(to_write).unwrap(); board.general_fault(toggle); toggle = !toggle; loop { let has_line = line_buffer.contains(&b'\n'); if !has_line { break; } let mut line = std::string::String::new(); line_buffer.read_line(&mut line)?; let line2 = &line[0..line.len()-1]; println!("Processing dry:{} line {}", is_dry_run, line2); let validate = board.flash_bq34_z100(&line2, is_dry_run); delay.delay_us(2); if validate.is_err() { let mut response = request.into_status_response(400_u16).unwrap(); let err = validate.unwrap_err(); let err_str = err.to_string(); let err_msg = err_str.as_bytes(); println!("Error writing {}", err_str); response .write(err_msg) .unwrap(); return anyhow::Ok(()) } } } let mut response = request.into_status_response(200_u16).unwrap(); let msg = format!("Finished writing {total_read} bytes
"); response.write(msg.as_bytes()).unwrap(); board.general_fault(false); anyhow::Ok(()) }) .unwrap(); server }