379 lines
12 KiB
Rust
379 lines
12 KiB
Rust
//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<u8>,
|
|
moisture_b: Vec<u8>,
|
|
}
|
|
|
|
#[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<Option<std::string::String>, 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<Option<std::string::String>, 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<u8> = Vec::new();
|
|
let mut b: Vec<u8> = 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<Option<std::string::String>, 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<Option<std::string::String>, 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<Option<std::string::String>, 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<Option<std::string::String>, 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<Option<std::string::String>, 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<Option<std::string::String>, 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<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)
|
|
})
|
|
.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<u8> = 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}<br>");
|
|
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<br>");
|
|
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<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) => {
|
|
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(());
|
|
} |