//offer ota and config mode use std::{ collections::VecDeque, io::{BufRead, Read, Write}, str::from_utf8, sync::{atomic::AtomicBool, Arc}, }; use crate::{espota::OtaUpdate, map_range_moisture, plant_hal::PLANT_COUNT, BOARD_ACCESS}; use chrono::DateTime; use core::result::Result::Ok; use embedded_svc::http::Method; use esp_idf_hal::delay::Delay; use esp_idf_svc::http::server::{Configuration, EspHttpConnection, EspHttpServer, Request}; use heapless::String; use serde::{Deserialize, Serialize}; use crate::config::Config; #[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, Debug)] struct LoadData<'a> { rtc: &'a str, native: &'a str, moisture_a: Vec, moisture_b: Vec, } #[derive(Deserialize, Debug)] struct SetTime<'a> { time: &'a str, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct TestPump { pump: usize, } fn write_time( request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { 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)?); 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().unwrap(); board.set_rtc_time(&parsed.to_utc())?; anyhow::Ok(None) } fn get_data( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { let mut board = BOARD_ACCESS.lock().unwrap(); let native = board .time() .and_then(|t| Ok(t.to_rfc3339())) .unwrap_or("error".to_string()); let rtc = board .get_rtc_time() .and_then(|t| Ok(t.to_rfc3339())) .unwrap_or("error".to_string()); let mut a: Vec = Vec::new(); let mut b: Vec = Vec::new(); for plant in 0..2 { 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 a_pct = map_range_moisture(a_hz as f32); match a_pct { Ok(result) => { a.push(result); }, Err(err) => { a.push(200); } } let b_pct = map_range_moisture(b_hz as f32); match b_pct { Ok(result) => { b.push(result); }, Err(err) => { b.push(200); } } } let data = LoadData { rtc: rtc.as_str(), native: native.as_str(), 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, anyhow::Error> { let mut board = BOARD_ACCESS.lock().unwrap(); let json = match board.get_config() { Ok(config) => serde_json::to_string(&config)?, Err(_) => serde_json::to_string(&Config::default())?, }; anyhow::Ok(Some(json)) } fn set_config( request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { 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)?; let mut board = BOARD_ACCESS.lock().unwrap(); board.set_config(&config)?; anyhow::Ok(Some("saved".to_owned())) } fn get_version( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { let version_info = VersionInfo { git_hash: env!("VERGEN_GIT_DESCRIBE"), build_time: env!("VERGEN_BUILD_TIMESTAMP"), }; anyhow::Ok(Some(serde_json::to_string(&version_info)?)) } fn pump_test( request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { let mut buf = [0_u8; 3072]; let read = request.read(&mut buf)?; let pump_test: TestPump = serde_json::from_slice(&buf[0..read])?; let mut board = BOARD_ACCESS.lock().unwrap(); board.test_pump(pump_test.pump)?; anyhow::Ok(None) } fn wifi_scan( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { let mut board = BOARD_ACCESS.lock().unwrap(); let scan_result = board.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 ota( request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { 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; loop { let read = request.read(&mut buffer)?; total_read += read; println!("received {read} bytes ota {total_read}"); let to_write = &buffer[0..read]; ota.write(to_write)?; 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 mut finalizer = ota.finalize()?; println!("changing boot partition"); finalizer.set_as_boot_partition()?; finalizer.restart(); } pub fn httpd(_reboot_now: Arc) -> Box> { let server_config = Configuration { stack_size: 32768, ..Default::default() }; let mut server: Box> = Box::new(EspHttpServer::new(&server_config).unwrap()); server .fn_handler("/version", Method::Get, |request| { handle_error_to500(request, get_version) }) .unwrap(); server .fn_handler("/data", Method::Get, |request| { handle_error_to500(request, get_data) }) .unwrap(); server .fn_handler("/time", Method::Post, |request| { handle_error_to500(request, write_time) }) .unwrap(); server .fn_handler("/pumptest", Method::Post, |request| { handle_error_to500(request, pump_test) }) .unwrap(); server .fn_handler("/boardtest", Method::Post, move |_| { BOARD_ACCESS.lock().unwrap().test() }) .unwrap(); server .fn_handler("/wifiscan", Method::Post, move |request| { handle_error_to500(request, wifi_scan) }) .unwrap(); server .fn_handler("/ota", Method::Post, |mut request| { handle_error_to500(request, ota) }) .unwrap(); server .fn_handler("/get_config", Method::Get, move |request| { handle_error_to500(request, get_config) }) .unwrap(); server .fn_handler("/set_config", Method::Post, move |mut request| { handle_error_to500(request, set_config) }) .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); todo!("Write to storage before attempting to flash!"); 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 .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("/bundle.js", Method::Get, |request| { request .into_ok_response()? .write(include_bytes!("bundle.js"))?; anyhow::Ok(()) }) .unwrap(); server .fn_handler("/favicon.ico", Method::Get, |request| { request .into_ok_response()? .write(include_bytes!("favicon.ico"))?; anyhow::Ok(()) }) .unwrap(); server } type AnyhowHandler = fn(&mut Request<&mut EspHttpConnection>) -> Result, 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) => { let mut response = request.into_ok_response()?; response.write(json.as_bytes())?; response.flush()?; } None => {} }, Err(err) => { let error_text = err.to_string(); println!("error handling process {}", error_text); request .into_status_response(500)? .write(error_text.as_bytes())?; } } return anyhow::Ok(()); }