startup and ota state detection working, now wifi_ap next

This commit is contained in:
2025-09-13 20:56:11 +02:00
parent 4160202cdc
commit be3c4a5095
9 changed files with 294 additions and 225 deletions

View File

@@ -10,6 +10,7 @@ esp_bootloader_esp_idf::esp_app_desc!();
use esp_backtrace as _;
use crate::config::PlantConfig;
use crate::tank::WATER_FROZEN_THRESH;
use crate::{
config::BoardVersion::INITIAL,
hal::{PlantHal, HAL, PLANT_COUNT},
@@ -20,23 +21,36 @@ use alloc::borrow::ToOwned;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::{format, vec};
use core::any::Any;
use anyhow::{bail, Context};
use chrono::{DateTime, Datelike, Timelike, Utc};
use chrono_tz::Tz::{self};
use core::sync::atomic::Ordering;
use embassy_executor::Spawner;
use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex};
use embassy_sync::lazy_lock::LazyLock;
use embassy_sync::mutex::Mutex;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::{Mutex, MutexGuard};
use embassy_sync::once_lock::OnceLock;
use embassy_time::Timer;
use esp_bootloader_esp_idf::ota::OtaImageState;
use esp_hal::rom::ets_delay_us;
use esp_hal::system::software_reset;
use esp_println::{logger, println};
use hal::battery::BatteryState;
use log::{log, LogMessage};
use plant_state::PlantState;
use portable_atomic::AtomicBool;
use serde::{Deserialize, Serialize};
use crate::tank::{TankError, WATER_FROZEN_THRESH};
#[no_mangle]
extern "C" fn custom_halt() -> ! {
println!("Fatal error occurred. Restarting in 10 seconds...");
for _delay in 0..30 {
ets_delay_us(1_000_000);
}
println!("resetting");
//give serial transmit time to finish
ets_delay_us(500_000);
software_reset()
}
//use tank::*;
mod config;
@@ -48,10 +62,8 @@ mod tank;
extern crate alloc;
//mod webserver;
pub static BOARD_ACCESS: LazyLock<Mutex<CriticalSectionRawMutex, HAL>> =
LazyLock::new(|| {
PlantHal::create().unwrap()
});
pub static BOARD_ACCESS: OnceLock<Mutex<CriticalSectionRawMutex, HAL>> = OnceLock::new();
pub static STAY_ALIVE: AtomicBool = AtomicBool::new(false);
#[derive(Serialize, Deserialize, Debug, PartialEq)]
@@ -140,14 +152,9 @@ async fn safe_main() -> anyhow::Result<()> {
version.git_hash, version.build_time
);
let _esp = BOARD_ACCESS.get().await.lock().await.board_hal.get_esp();
//TODO
//let count = unsafe { esp_ota_get_app_partition_count() };
//log::info!("Partition count is {}", count);
//let mut ota_state: esp_ota_img_states_t = 0;
//let running_partition = unsafe { esp_ota_get_running_partition() };
//let partition_address = unsafe { (*running_partition).address };
//log::info!("Partition address is {}", address);
let partition_address = 0x1337;
// TODO
//let ota_state_string = unsafe {
@@ -169,9 +176,9 @@ async fn safe_main() -> anyhow::Result<()> {
//}
//};
//log(LogMessage::PartitionState, 0, 0, "", ota_state_string);
let ota_state_string = "unknown";
let _ota_state_string = "unknown";
println!("faul led");
let mut board = BOARD_ACCESS.get().lock().await;
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.general_fault(false).await;
println!("faul led2");
let cur = board
@@ -184,7 +191,6 @@ async fn safe_main() -> anyhow::Result<()> {
board.board_hal.general_fault(true);
anyhow::Ok(board.board_hal.get_esp().time())
})?;
//check if we know the time current > 2020 (plausibility checks, this code is newer than 2020)
if cur.year() < 2020 {
to_config = true;
@@ -192,10 +198,10 @@ async fn safe_main() -> anyhow::Result<()> {
}
info!("cur is {}", cur);
update_charge_indicator().await;
update_charge_indicator(&mut board).await;
println!("faul led3");
if board.board_hal.get_esp().get_restart_to_conf() {
log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", "");
log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", "").await;
for _i in 0..2 {
board.board_hal.general_fault(true).await;
Timer::after_millis(100).await;
@@ -221,12 +227,15 @@ async fn safe_main() -> anyhow::Result<()> {
} else {
board.board_hal.general_fault(false).await;
}
} else {
info!("no mode override");
}
if board.board_hal.get_config().hardware.board == INITIAL
&& board.board_hal.get_config().network.ssid.is_none()
{
let _ = board.board_hal.get_esp().wifi_ap();
info!("No wifi configured, starting initial config mode");
board.board_hal.get_esp().wifi_ap().await.unwrap();
drop(board);
let reboot_now = Arc::new(AtomicBool::new(false));
//TODO
@@ -254,7 +263,6 @@ async fn safe_main() -> anyhow::Result<()> {
}
}
// let timezone = match &board.board_hal.get_config().timezone {
// Some(tz_str) => tz_str.parse::<Tz>().unwrap_or_else(|_| {
// info!("Invalid timezone '{}', falling back to UTC", tz_str);
@@ -262,25 +270,18 @@ async fn safe_main() -> anyhow::Result<()> {
// }),
// None => UTC, // Fallback to UTC if no timezone is set
// };
let timezone = Tz::UTC;
let _timezone = Tz::UTC;
let timezone_time = cur;//TODO.with_timezone(&timezone);
drop(board);
let timezone_time = cur; //TODO.with_timezone(&timezone);
info!(
"Running logic at utc {} and {} {}",
cur,
"todo timezone.name()",
timezone_time
cur, "todo timezone.name()", timezone_time
);
if let NetworkMode::WIFI { ref ip_address, .. } = network_mode {
publish_firmware_info(
version,
partition_address,
ota_state_string,
ip_address,
&timezone_time.to_rfc3339(),
)
.await;
publish_firmware_info(version, ip_address, &timezone_time.to_rfc3339()).await;
publish_battery_state().await;
let _ = publish_mppt_state().await;
}
@@ -299,10 +300,8 @@ async fn safe_main() -> anyhow::Result<()> {
.to_string()
.as_str(),
"",
).await;
//TODO must drop board here?
//drop(board);
)
.await;
if to_config {
//check if client or ap mode and init Wi-Fi
@@ -316,7 +315,7 @@ async fn safe_main() -> anyhow::Result<()> {
log(LogMessage::NormalRun, 0, 0, "", "").await;
}
let dry_run = false;
let _dry_run = false;
//
// let tank_state = determine_tank_state(&mut board);
//
@@ -353,7 +352,7 @@ async fn safe_main() -> anyhow::Result<()> {
// }
// }
let mut water_frozen = false;
let mut _water_frozen = false;
//TODO
let water_temp = anyhow::Ok(12_f32);
// board
@@ -364,14 +363,14 @@ async fn safe_main() -> anyhow::Result<()> {
if let Ok(res) = water_temp {
if res < WATER_FROZEN_THRESH {
water_frozen = true;
_water_frozen = true;
}
}
//publish_tank_state(&tank_state, &water_temp).await;
let plantstate: [PlantState; PLANT_COUNT] = [
board = BOARD_ACCESS.get().await.lock().await;
let _plantstate: [PlantState; PLANT_COUNT] = [
PlantState::read_hardware_state(0, &mut board).await,
PlantState::read_hardware_state(1, &mut board).await,
PlantState::read_hardware_state(2, &mut board).await,
@@ -468,7 +467,7 @@ async fn safe_main() -> anyhow::Result<()> {
.await
.unwrap_or(BatteryState::Unknown);
let mut light_state = LightState {
let light_state = LightState {
enabled: board.board_hal.get_config().night_lamp.enabled,
..Default::default()
};
@@ -611,7 +610,7 @@ pub async fn do_secure_pump(
let mut error = false;
let mut first_error = true;
let mut pump_time_s = 0;
let board = &mut BOARD_ACCESS.get().lock().await;
let board = &mut BOARD_ACCESS.get().await.lock().await;
if !dry_run {
// board
// .board_hal
@@ -733,8 +732,7 @@ pub async fn do_secure_pump(
})
}
async fn update_charge_indicator() {
let board = &mut BOARD_ACCESS.get().lock().await;
async fn update_charge_indicator(board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'_>>) {
//we have mppt controller, ask it for charging current
// let tank_state = determine_tank_state(&mut board);
//
@@ -779,7 +777,8 @@ async fn update_charge_indicator() {
else if let Ok(charging) = board
.board_hal
.get_battery_monitor()
.average_current_milli_ampere().await
.average_current_milli_ampere()
.await
{
let _ = board.board_hal.set_charge_indicator(charging > 20);
} else {
@@ -830,51 +829,45 @@ async fn update_charge_indicator() {
// }
// }
async fn publish_firmware_info(
version: VersionInfo,
address: u32,
ota_state_string: &str,
ip_address: &String,
timezone_time: &String,
) {
let board = &mut BOARD_ACCESS.get().lock().await;
let _ = board
.board_hal
.get_esp()
async fn publish_firmware_info(version: VersionInfo, ip_address: &String, timezone_time: &String) {
let board = &mut BOARD_ACCESS.get().await.lock().await;
let esp = board.board_hal.get_esp();
let _ = esp
.mqtt_publish("/firmware/address", ip_address.as_bytes())
.await;
let _ = board
.board_hal
.get_esp()
let _ = esp
.mqtt_publish("/firmware/githash", version.git_hash.as_bytes())
.await;
let _ = board
.board_hal
.get_esp()
let _ = esp
.mqtt_publish("/firmware/buildtime", version.build_time.as_bytes())
.await;
let _ = board.board_hal.get_esp().mqtt_publish(
"/firmware/last_online",
timezone_time.as_bytes(),
);
let _ = board
.board_hal
.get_esp()
.mqtt_publish("/firmware/ota_state", ota_state_string.as_bytes())
let _ = esp.mqtt_publish("/firmware/last_online", timezone_time.as_bytes());
let _ = esp
.mqtt_publish(
"/firmware/ota_state",
state_to_string(esp.ota_state).as_bytes(),
)
.await;
let _ = board.board_hal.get_esp().mqtt_publish(
"/firmware/partition_address",
format!("{:#06x}", address).as_bytes(),
);
let _ = board
.board_hal
.get_esp()
.mqtt_publish("/state", "online".as_bytes())
let slot = esp.slot;
let _ = esp
.mqtt_publish("/firmware/ota_slot", format!("slot{slot}").as_bytes())
.await;
let _ = esp.mqtt_publish("/state", "online".as_bytes()).await;
}
fn state_to_string(state: OtaImageState) -> &'static str {
match state {
OtaImageState::New => "New",
OtaImageState::PendingVerify => "PendingVerify",
OtaImageState::Valid => "Valid",
OtaImageState::Invalid => "Invalid",
OtaImageState::Aborted => "Aborted",
OtaImageState::Undefined => "Undefined",
}
}
async fn try_connect_wifi_sntp_mqtt() -> NetworkMode {
let board = &mut BOARD_ACCESS.get().lock().await;
let board = &mut BOARD_ACCESS.get().await.lock().await;
let nw_conf = &board.board_hal.get_config().network.clone();
match board.board_hal.get_esp().wifi(nw_conf).await {
Ok(ip_info) => {
@@ -941,6 +934,7 @@ async fn pump_info(
Ok(state) => {
let _ = BOARD_ACCESS
.get()
.await
.lock()
.await
.board_hal
@@ -957,7 +951,7 @@ async fn pump_info(
}
async fn publish_mppt_state() -> anyhow::Result<()> {
let board_hal = &mut BOARD_ACCESS.get().lock().await.board_hal;
let board_hal = &mut BOARD_ACCESS.get().await.lock().await.board_hal;
let current = board_hal.get_mptt_current().await?;
let voltage = board_hal.get_mptt_voltage().await?;
let solar_state = Solar {
@@ -975,8 +969,12 @@ async fn publish_mppt_state() -> anyhow::Result<()> {
}
async fn publish_battery_state() -> () {
let board = &mut BOARD_ACCESS.get().lock().await;
let state = board.board_hal.get_battery_monitor().get_battery_state().await;
let board = &mut BOARD_ACCESS.get().await.lock().await;
let state = board
.board_hal
.get_battery_monitor()
.get_battery_state()
.await;
if let Ok(serialized_battery_state_bytes) =
serde_json::to_string(&state).map(|s| s.into_bytes())
{
@@ -992,8 +990,9 @@ async fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
let mut led_count = 8;
let mut pattern_step = 0;
loop {
update_charge_indicator().await;
let mut board = BOARD_ACCESS.get().lock().await;
let mut board = BOARD_ACCESS.get().await.lock().await;
update_charge_indicator(&mut board).await;
match wait_type {
WaitType::MissingConfig => {
// Keep existing behavior: circular filling pattern
@@ -1023,7 +1022,7 @@ async fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
drop(board);
Timer::after_millis(delay).await;
let mut board = BOARD_ACCESS.get().lock().await;
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.general_fault(false);
// Clear all LEDs
@@ -1039,7 +1038,13 @@ async fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
if reboot_now.load(Ordering::Relaxed) {
//ensure clean http answer
Timer::after_millis(500).await;
BOARD_ACCESS.get().lock().await.board_hal.deep_sleep(1);
BOARD_ACCESS
.get()
.await
.lock()
.await
.board_hal
.deep_sleep(1);
}
}
}
@@ -1049,19 +1054,21 @@ async fn main(spawner: Spawner) {
// intialize embassy
logger::init_logger_from_env();
//force init here!
let board = BOARD_ACCESS.get().lock().await;
drop(board);
println!("test");
println!("Hal init");
match BOARD_ACCESS.init(PlantHal::create(spawner).unwrap()) {
Ok(_) => {}
Err(_) => {
panic!("Could not set hal to static")
}
}
println!("Hal init done, starting logic");
info!("Embassy initialized!");
let result = safe_main().await;
match result {
match safe_main().await {
// this should not get triggered, safe_main should not return but go into deep sleep with sensible
// timeout, this is just a fallback
Ok(_) => {
warn!("Main app finished, but should never do, restarting");
let board = &mut BOARD_ACCESS.get().lock().await.board_hal;
let board = &mut BOARD_ACCESS.get().await.lock().await.board_hal;
board.get_esp().set_restart_to_conf(false);
board.deep_sleep(1);