cleanup reboot logic, fix json upload

This commit is contained in:
Empire 2025-01-04 00:04:40 +01:00
parent 0f77ac163a
commit c070e68349
5 changed files with 196 additions and 92 deletions

View File

@ -10,7 +10,17 @@ use chrono_tz::{Europe::Berlin, Tz};
use config::Mode;
use esp_idf_hal::delay::Delay;
use esp_idf_sys::{
esp_deep_sleep, esp_ota_get_app_partition_count, esp_ota_get_running_partition, esp_ota_get_state_partition, esp_ota_img_states_t, esp_ota_img_states_t_ESP_OTA_IMG_ABORTED, esp_ota_img_states_t_ESP_OTA_IMG_INVALID, esp_ota_img_states_t_ESP_OTA_IMG_NEW, esp_ota_img_states_t_ESP_OTA_IMG_PENDING_VERIFY, esp_ota_img_states_t_ESP_OTA_IMG_UNDEFINED, esp_ota_img_states_t_ESP_OTA_IMG_VALID, esp_restart, esp_sleep_enable_ext1_wakeup, esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW, vTaskDelay, CONFIG_FREERTOS_HZ
esp_ota_get_app_partition_count,
esp_ota_get_running_partition,
esp_ota_get_state_partition,
esp_ota_img_states_t,
esp_ota_img_states_t_ESP_OTA_IMG_ABORTED,
esp_ota_img_states_t_ESP_OTA_IMG_INVALID,
esp_ota_img_states_t_ESP_OTA_IMG_NEW,
esp_ota_img_states_t_ESP_OTA_IMG_PENDING_VERIFY,
esp_ota_img_states_t_ESP_OTA_IMG_UNDEFINED,
esp_ota_img_states_t_ESP_OTA_IMG_VALID,
vTaskDelay
};
use log::error;
use once_cell::sync::Lazy;
@ -460,8 +470,7 @@ fn safe_main() -> anyhow::Result<()> {
board.last_pump_time(plant);
state.active = true;
for _ in 0..plant_config.pump_time_s {
unsafe { vTaskDelay(CONFIG_FREERTOS_HZ) };
//info message or something?
Delay::new_default().delay_ms(1000);
}
board.pump(plant, false)?;
@ -557,10 +566,7 @@ fn safe_main() -> anyhow::Result<()> {
let _webserver = httpd(reboot_now.clone());
wait_infinity(WaitType::MqttConfig, reboot_now.clone());
}
unsafe {
esp_sleep_enable_ext1_wakeup(0b10u64, esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW);
esp_deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64)
};
board.deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64);
}
fn publish_battery_state(
@ -949,8 +955,7 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
}
}
if reboot_now.load(std::sync::atomic::Ordering::Relaxed) {
println!("Rebooting");
esp_restart();
BOARD_ACCESS.lock().unwrap().deep_sleep( 1);
}
}
}
@ -961,7 +966,7 @@ fn main() {
match result {
Ok(_) => {
println!("Main app finished, restarting");
unsafe { esp_restart() };
BOARD_ACCESS.lock().unwrap().deep_sleep(1);
}
Err(err) => {
println!("Failed main {}", err);

View File

@ -7,6 +7,7 @@ use embedded_hal_bus::i2c::MutexDevice;
use embedded_svc::wifi::{
AccessPointConfiguration, AccessPointInfo, AuthMethod, ClientConfiguration, Configuration,
};
use esp_idf_hal::adc::oneshot::config::AdcChannelConfig;
use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver};
use esp_idf_hal::adc::{attenuation, Resolution};
@ -23,6 +24,8 @@ use esp_idf_svc::wifi::EspWifi;
use measurements::Temperature;
use once_cell::sync::Lazy;
use plant_ctrl2::sipo::ShiftRegister40;
use esp_idf_sys::esp_deep_sleep;
use esp_idf_sys::esp_restart;
use anyhow::{anyhow, Context};
use anyhow::{bail, Ok, Result};
@ -53,6 +56,7 @@ use esp_idf_sys::{esp, esp_spiffs_check, gpio_hold_dis, gpio_hold_en, vTaskDelay
use one_wire_bus::OneWire;
use crate::config::{self, PlantControllerConfig};
use crate::espota::mark_app_valid;
use crate::{plant_hal, to_string, STAY_ALIVE};
//Only support for 8 right now!
@ -196,6 +200,21 @@ pub struct BatteryState {
}
impl PlantCtrlBoard<'_> {
pub fn deep_sleep(&mut self, duration_in_ms:u64) -> !{
unsafe {
//if we dont do this here, we might just revert a newly flashed firmeware
mark_app_valid();
//allow early wakup by pressing the boot button
if duration_in_ms == 0 {
esp_restart();
} else {
// esp_sleep_enable_ext1_wakeup(0b10u64, esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW);
esp_deep_sleep(duration_in_ms);
}
};
}
pub fn get_battery_state(&mut self) -> BatteryState {
let bat = BatteryState {
voltage_milli_volt: to_string(self.voltage_milli_volt()),

View File

@ -4,8 +4,6 @@ use std::{
str::from_utf8,
sync::{atomic::AtomicBool, Arc},
};
use esp_idf_svc::io::BufRead;
use crate::{
espota::OtaUpdate, get_version, map_range_moisture, plant_hal::FileInfo, BOARD_ACCESS,
};
@ -56,11 +54,8 @@ pub struct TestPump {
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 actual_data = read_up_to_bytes_from_request(request, None)?;
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())?;
@ -144,16 +139,14 @@ fn get_config(
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: PlantControllerConfig = serde_json::from_slice(actual_data)?;
let all = read_up_to_bytes_from_request(request, Some(3072))?;
let config: PlantControllerConfig = serde_json::from_slice(&all)?;
let mut board = BOARD_ACCESS.lock().unwrap();
board.set_config(&config)?;
anyhow::Ok(Some("saved".to_owned()))
}
fn get_battery_state(
_request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> {
@ -172,9 +165,8 @@ fn get_version_web(
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 actual_data = read_up_to_bytes_from_request(request, None)?;
let pump_test: TestPump = serde_json::from_slice(&actual_data)?;
let mut board = BOARD_ACCESS.lock().unwrap();
board.test_pump(pump_test.pump)?;
anyhow::Ok(None)
@ -285,7 +277,7 @@ fn query_param(uri: &str, param_name: &str) -> Option<std::string::String> {
}
}
pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
let server_config = Configuration {
stack_size: 32768,
..Default::default()
@ -358,6 +350,25 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
handle_error_to500(request, list_files)
})
.unwrap();
let reboot_now_for_reboot = reboot_now.clone();
server
.fn_handler("/reboot", Method::Post, move |_| {
BOARD_ACCESS
.lock()
.unwrap()
.set_restart_to_conf(true);
reboot_now_for_reboot.store(true, std::sync::atomic::Ordering::Relaxed);
anyhow::Ok(())
})
.unwrap();
let reboot_now_for_exit = reboot_now.clone();
server
.fn_handler("/exit", Method::Post, move |_| {
reboot_now_for_exit.store(true, std::sync::atomic::Ordering::Relaxed);
anyhow::Ok(())
})
.unwrap();
server
.fn_handler("/file", Method::Get, move |request| {
let filename = query_param(request.uri(), "filename").unwrap();
@ -407,6 +418,8 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
.unwrap()
.get_file_handle(&filename, true);
match file_handle {
//TODO get free filesystem size, check against during write if not to large
Ok(mut file_handle) => {
const BUFFER_SIZE: usize = 512;
let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
@ -482,7 +495,6 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
anyhow::Ok(())
})
.unwrap();
server
.fn_handler("/", Method::Get, move |request| {
let mut response = request.into_ok_response()?;
@ -559,3 +571,25 @@ fn handle_error_to500(
}
return anyhow::Ok(());
}
fn read_up_to_bytes_from_request(request: &mut Request<&mut EspHttpConnection<'_>>, limit: Option<usize>) -> Result<Vec<u8>, anyhow::Error> {
let max_read = limit.unwrap_or(1024);
let mut data_store = Vec::new();
let mut total_read = 0;
loop{
let mut buf = [0_u8; 64];
let read = request.read(&mut buf)?;
if read == 0 {
break;
}
let actual_data = &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 allvec = data_store.concat();
println!("Raw data {}", from_utf8(&allvec)?);
Ok(allvec)
}

View File

@ -116,6 +116,9 @@
<br>
<textarea id="json" cols=50 rows=10></textarea>
<script src="bundle.js"></script>
<button id="exit">Exit</button>
<button id="reboot">Reboot</button>
</div>
<div id="progressPane" class="progressPane">

View File

@ -24,7 +24,7 @@ export class Controller {
controller.timeView.update(time.native, time.rtc)
})
.catch(error => {
controller.timeView.update("n/a","n/a")
controller.timeView.update("n/a", "n/a")
console.log(error);
});
}
@ -43,12 +43,12 @@ export class Controller {
uploadNewFirmware(file: File) {
var current = 0;
var max = 100;
controller.progressview.addProgress("ota_upload", (current/max) *100 , "Uploading firmeware ("+current+"/" + max+")")
controller.progressview.addProgress("ota_upload", (current / max) * 100, "Uploading firmeware (" + current + "/" + max + ")")
var ajax = new XMLHttpRequest();
ajax.upload.addEventListener("progress", event => {
current = event.loaded/1000;
max = event.total/1000;
controller.progressview.addProgress("ota_upload", (current/max) *100 , "Uploading firmeware ("+current+"/" + max+")")
current = event.loaded / 1000;
max = event.total / 1000;
controller.progressview.addProgress("ota_upload", (current / max) * 100, "Uploading firmeware (" + current + "/" + max + ")")
}, false);
ajax.addEventListener("load", () => {
//TODO wait for reboot here!
@ -97,7 +97,7 @@ export class Controller {
.then(text => statusCallback(text))
controller.progressview.removeProgress("set_config")
}
syncRTCFromBrowser(){
syncRTCFromBrowser() {
controller.progressview.addIndeterminate("write_rtc", "Writing RTC")
var value: SetTime = {
time: new Date().toISOString()
@ -122,12 +122,12 @@ export class Controller {
testPlant(plantId: number) {
let counter = 0
let limit = 30
controller.progressview.addProgress("test_pump", counter/limit*100, "Testing pump " + (plantId+1) + " for " + (limit-counter)+"s")
controller.progressview.addProgress("test_pump", counter / limit * 100, "Testing pump " + (plantId + 1) + " for " + (limit - counter) + "s")
let timerId: string | number | NodeJS.Timeout | undefined
function updateProgress(){
function updateProgress() {
counter++;
controller.progressview.addProgress("test_pump", counter/limit*100, "Testing pump " + (plantId+1) + " for " + (limit-counter)+"s")
controller.progressview.addProgress("test_pump", counter / limit * 100, "Testing pump " + (plantId + 1) + " for " + (limit - counter) + "s")
timerId = setTimeout(updateProgress, 1000);
}
@ -151,7 +151,7 @@ export class Controller {
)
}
getConfig(): PlantControllerConfig{
getConfig(): PlantControllerConfig {
return {
network: controller.networkView.getConfig(),
tank: controller.tankView.getConfig(),
@ -163,12 +163,12 @@ export class Controller {
scanWifi() {
let counter = 0
let limit = 5
controller.progressview.addProgress("scan_ssid", counter/limit*100, "Scanning for SSIDs for " + (limit-counter)+"s")
controller.progressview.addProgress("scan_ssid", counter / limit * 100, "Scanning for SSIDs for " + (limit - counter) + "s")
let timerId: string | number | NodeJS.Timeout | undefined
function updateProgress(){
function updateProgress() {
counter++;
controller.progressview.addProgress("scan_ssid", counter/limit*100, "Scanning for SSIDs for " + (limit-counter)+"s")
controller.progressview.addProgress("scan_ssid", counter / limit * 100, "Scanning for SSIDs for " + (limit - counter) + "s")
timerId = setTimeout(updateProgress, 1000);
}
@ -200,15 +200,15 @@ export class Controller {
this.plantViews.setConfig(current.plants);
}
measure_moisture (){
measure_moisture() {
let counter = 0
let limit = 2
controller.progressview.addProgress("measure_moisture", counter/limit*100, "Measure Moisture " + (limit-counter)+"s")
controller.progressview.addProgress("measure_moisture", counter / limit * 100, "Measure Moisture " + (limit - counter) + "s")
let timerId: string | number | NodeJS.Timeout | undefined
function updateProgress(){
function updateProgress() {
counter++;
controller.progressview.addProgress("measure_moisture", counter/limit*100, "Measure Moisture " + (limit-counter)+"s")
controller.progressview.addProgress("measure_moisture", counter / limit * 100, "Measure Moisture " + (limit - counter) + "s")
timerId = setTimeout(updateProgress, 1000);
}
@ -230,14 +230,48 @@ export class Controller {
});
}
exit() {
fetch(PUBLIC_URL + "/exit", {
method: "POST",
})
controller.progressview.addIndeterminate("rebooting", "Returned to normal mode, you can close this site now")
}
waitForReboot() {
console.log("Check if controller online again")
fetch(PUBLIC_URL + "/version", {
method: "POST",
signal: AbortSignal.timeout(5000)
}).then(response => {
console.log("Reached controller, reloading")
window.location.reload();
})
.catch(err => {
console.log("Not reached yet, retrying")
setTimeout(controller.waitForReboot, 1000)
})
}
reboot() {
fetch(PUBLIC_URL + "/reboot", {
method: "POST",
})
controller.progressview.addIndeterminate("rebooting", "Rebooting")
setTimeout(this.waitForReboot, 1000)
}
readonly rebootBtn: HTMLButtonElement
readonly exitBtn: HTMLButtonElement
readonly timeView: TimeView;
readonly plantViews: PlantViews;
readonly networkView: NetworkConfigView;
readonly tankView: TankConfigView;
readonly nightLampView: NightLampView;
readonly submitView: SubmitView;
readonly firmWareView : OTAView;
readonly firmWareView: OTAView;
readonly progressview: ProgressView;
readonly batteryView: BatteryView;
constructor() {
@ -250,6 +284,14 @@ export class Controller {
this.submitView = new SubmitView(this)
this.firmWareView = new OTAView(this)
this.progressview = new ProgressView(this)
this.rebootBtn = document.getElementById("reboot") as HTMLButtonElement
this.rebootBtn.onclick = () => {
controller.reboot();
}
this.exitBtn = document.getElementById("exit") as HTMLButtonElement
this.exitBtn.onclick = () => {
controller.exit();
}
}
}
const controller = new Controller();
@ -258,4 +300,5 @@ controller.updateBatteryData();
controller.downloadConfig();
//controller.measure_moisture();
controller.version();
controller.progressview.removeProgress("rebooting");