started cleanup and moving from std to no_std

This commit is contained in:
2025-09-11 22:47:11 +02:00
parent f853b6f2b2
commit 242e748ca0
15 changed files with 199 additions and 197 deletions

View File

@@ -1,38 +1,41 @@
#![no_std]
#![no_main]
#![deny(
clippy::mem_forget,
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
holding buffers for the duration of a data transfer."
)]
use crate::config::PlantConfig;
use crate::{
config::BoardVersion::INITIAL,
hal::{PlantHal, HAL, PLANT_COUNT},
webserver::httpd,
//webserver::httpd,
};
use alloc::borrow::ToOwned;
use alloc::fmt::format;
use alloc::string::{String, ToString};
use anyhow::{bail, Context};
use chrono::{DateTime, Datelike, Timelike, Utc};
use chrono_tz::Tz::{self, UTC};
use esp_idf_hal::delay::Delay;
use esp_idf_sys::{
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,
};
use esp_ota::{mark_app_valid, rollback_and_reboot};
use embassy_executor::Spawner;
use hal::battery::BatteryState;
use log::{log, LogMessage};
use once_cell::sync::Lazy;
use plant_state::PlantState;
use serde::{Deserialize, Serialize};
use std::sync::{atomic::AtomicBool, Arc, Mutex, MutexGuard};
use embassy_sync::{Mutex, LazyLock};
use portable_atomic::AtomicBool;
use tank::*;
mod config;
mod hal;
mod log;
mod plant_state;
mod tank;
mod webserver;
pub static BOARD_ACCESS: Lazy<Mutex<HAL>> = Lazy::new(|| PlantHal::create().unwrap());
pub static STAY_ALIVE: Lazy<AtomicBool> = Lazy::new(|| AtomicBool::new(false));
extern crate alloc;
//mod webserver;
pub static BOARD_ACCESS: LazyLock<Mutex<HAL>> = LazyLock::new(|| PlantHal::create().unwrap());
pub static STAY_ALIVE: LazyLock<AtomicBool> = LazyLock::new(|| AtomicBool::new(false));
#[derive(Serialize, Deserialize, Debug, PartialEq)]
enum WaitType {
@@ -110,55 +113,45 @@ enum NetworkMode {
}
fn safe_main() -> anyhow::Result<()> {
// It is necessary to call this function once. Otherwise, some patches to the runtime
// implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
esp_idf_svc::sys::link_patches();
// Bind the log crate to the ESP Logging facilities
esp_idf_svc::log::EspLogger::initialize_default();
if esp_idf_sys::CONFIG_MAIN_TASK_STACK_SIZE < 25000 {
bail!(
"stack too small: {} bail!",
esp_idf_sys::CONFIG_MAIN_TASK_STACK_SIZE
)
}
println!("Startup Rust");
log::info!("Startup Rust");
let mut to_config = false;
let version = get_version();
println!(
log::info!(
"Version using git has {} build on {}",
version.git_hash, version.build_time
);
let count = unsafe { esp_ota_get_app_partition_count() };
println!("Partition count is {}", count);
let mut ota_state: esp_ota_img_states_t = 0;
let running_partition = unsafe { esp_ota_get_running_partition() };
let address = unsafe { (*running_partition).address };
println!("Partition address is {}", address);
//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 address = unsafe { (*running_partition).address };
//log::info!("Partition address is {}", address);
let ota_state_string = unsafe {
esp_ota_get_state_partition(running_partition, &mut ota_state);
if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_NEW {
"ESP_OTA_IMG_NEW"
} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_PENDING_VERIFY {
"ESP_OTA_IMG_PENDING_VERIFY"
} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_VALID {
"ESP_OTA_IMG_VALID"
} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_INVALID {
"ESP_OTA_IMG_INVALID"
} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_ABORTED {
"ESP_OTA_IMG_ABORTED"
} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_UNDEFINED {
"ESP_OTA_IMG_UNDEFINED"
} else {
&format!("unknown {ota_state}")
}
};
log(LogMessage::PartitionState, 0, 0, "", ota_state_string);
// TODO
//let ota_state_string = unsafe {
//esp_ota_get_state_partition(running_partition, &mut ota_state);
//if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_NEW {
//"ESP_OTA_IMG_NEW"
//} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_PENDING_VERIFY {
//"ESP_OTA_IMG_PENDING_VERIFY"
//} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_VALID {
//"ESP_OTA_IMG_VALID"
//} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_INVALID {
//"ESP_OTA_IMG_INVALID"
//} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_ABORTED {
//"ESP_OTA_IMG_ABORTED"
//} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_UNDEFINED {
//"ESP_OTA_IMG_UNDEFINED"
//} else {
//&format!("unknown {ota_state}")
//}
//};
//log(LogMessage::PartitionState, 0, 0, "", ota_state_string);
let mut board = BOARD_ACCESS
.lock()
@@ -170,7 +163,7 @@ fn safe_main() -> anyhow::Result<()> {
.get_rtc_module()
.get_rtc_time()
.or_else(|err| {
println!("rtc module error: {:?}", err);
log::info!("rtc module error: {:?}", err);
board.board_hal.general_fault(true);
board.board_hal.get_esp().time()
})
@@ -185,7 +178,7 @@ fn safe_main() -> anyhow::Result<()> {
log(LogMessage::YearInplausibleForceConfig, 0, 0, "", "");
}
println!("cur is {}", cur);
log::info!("cur is {}", cur);
update_charge_indicator(&mut board);
if board.board_hal.get_esp().get_restart_to_conf() {
@@ -223,40 +216,41 @@ fn safe_main() -> anyhow::Result<()> {
let _ = board.board_hal.get_esp().wifi_ap();
drop(board);
let reboot_now = Arc::new(AtomicBool::new(false));
let _webserver = httpd(reboot_now.clone());
//TODO
//let _webserver = httpd(reboot_now.clone());
wait_infinity(WaitType::MissingConfig, reboot_now.clone());
}
println!("attempting to connect wifi");
log::info!("attempting to connect wifi");
let network_mode = if board.board_hal.get_config().network.ssid.is_some() {
try_connect_wifi_sntp_mqtt(&mut board)
} else {
println!("No wifi configured");
log::info!("No wifi configured");
//the current sensors require this amount to stabilize, in case of wifi this is already handles for sure;
board.board_hal.get_esp().delay.delay_ms(100);
NetworkMode::OFFLINE
};
if matches!(network_mode, NetworkMode::OFFLINE) && to_config {
println!("Could not connect to station and config mode forced, switching to ap mode!");
log::info!("Could not connect to station and config mode forced, switching to ap mode!");
match board.board_hal.get_esp().wifi_ap() {
Ok(_) => {
println!("Started ap, continuing")
log::info!("Started ap, continuing")
}
Err(err) => println!("Could not start config override ap mode due to {}", err),
Err(err) => log::info!("Could not start config override ap mode due to {}", err),
}
}
let timezone = match &board.board_hal.get_config().timezone {
Some(tz_str) => tz_str.parse::<Tz>().unwrap_or_else(|_| {
println!("Invalid timezone '{}', falling back to UTC", tz_str);
log::info!("Invalid timezone '{}', falling back to UTC", tz_str);
UTC
}),
None => UTC, // Fallback to UTC if no timezone is set
};
let timezone_time = cur.with_timezone(&timezone);
println!(
log::info!(
"Running logic at utc {} and {} {}",
cur,
timezone.name(),
@@ -294,11 +288,12 @@ fn safe_main() -> anyhow::Result<()> {
if to_config {
//check if client or ap mode and init Wi-Fi
println!("executing config mode override");
log::info!("executing config mode override");
//config upload will trigger reboot!
drop(board);
let reboot_now = Arc::new(AtomicBool::new(false));
let _webserver = httpd(reboot_now.clone());
//TODO
//let _webserver = httpd(reboot_now.clone());
wait_infinity(WaitType::ConfigButton, reboot_now.clone());
} else {
log(LogMessage::NormalRun, 0, 0, "", "");
@@ -506,7 +501,7 @@ fn safe_main() -> anyhow::Result<()> {
board.board_hal.light(false)?;
}
println!("Lightstate is {:?}", light_state);
log::info!("Lightstate is {:?}", light_state);
}
match serde_json::to_string(&light_state) {
@@ -517,7 +512,7 @@ fn safe_main() -> anyhow::Result<()> {
.mqtt_publish("/light", state.as_bytes());
}
Err(err) => {
println!("Error publishing lightstate {}", err);
log::info!("Error publishing lightstate {}", err);
}
};
@@ -551,24 +546,27 @@ fn safe_main() -> anyhow::Result<()> {
//is light out of work trigger soon?
//is battery low ??
//is deep sleep
mark_app_valid();
//TODO
//mark_app_valid();
let stay_alive_mqtt = STAY_ALIVE.load(std::sync::atomic::Ordering::Relaxed);
let stay_alive = stay_alive_mqtt;
println!("Check stay alive, current state is {}", stay_alive);
log::info!("Check stay alive, current state is {}", stay_alive);
if stay_alive {
println!("Go to stay alive move");
log::info!("Go to stay alive move");
drop(board);
let reboot_now = Arc::new(AtomicBool::new(false));
let _webserver = httpd(reboot_now.clone());
//TODO
//let _webserver = httpd(reboot_now.clone());
wait_infinity(WaitType::MqttConfig, reboot_now.clone());
}
board.board_hal.get_esp().set_restart_to_conf(false);
board
.board_hal
.deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64);
anyhow::Ok(())
}
pub fn do_secure_pump(
@@ -604,12 +602,12 @@ pub fn do_secure_pump(
flow_collector[step] = flow_value;
let flow_value_ml = flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse;
println!(
log::info!(
"Flow value is {} ml, limit is {} ml raw sensor {}",
flow_value_ml, plant_config.pump_limit_ml, flow_value
);
if flow_value_ml > plant_config.pump_limit_ml as f32 {
println!("Flow value is reached, stopping");
log::info!("Flow value is reached, stopping");
break;
}
@@ -659,7 +657,7 @@ pub fn do_secure_pump(
}
Err(err) => {
if !plant_config.ignore_current_error {
println!("Error getting pump current: {}", err);
log::info!("Error getting pump current: {}", err);
log(
LogMessage::PumpMissingSensorCurrent,
plant_id as u32,
@@ -685,7 +683,7 @@ pub fn do_secure_pump(
.unwrap()
.get_flow_meter_value();
let flow_value_ml = final_flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse;
println!(
log::info!(
"Final flow value is {} with {} ml",
final_flow_value, flow_value_ml
);
@@ -736,7 +734,7 @@ fn publish_tank_state(
.mqtt_publish("/water", state.as_bytes());
}
Err(err) => {
println!("Error publishing tankstate {}", err);
log::info!("Error publishing tankstate {}", err);
}
};
}
@@ -762,7 +760,7 @@ fn publish_plant_states(
board.board_hal.get_esp().delay.delay_ms(200);
}
Err(err) => {
println!("Error publishing plant state {}", err);
log::info!("Error publishing plant state {}", err);
}
};
}
@@ -812,12 +810,12 @@ fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard<HAL>) -> NetworkMode {
Ok(ip_info) => {
let sntp_mode: SntpMode = match board.board_hal.get_esp().sntp(1000 * 10) {
Ok(new_time) => {
println!("Using time from sntp");
log::info!("Using time from sntp");
let _ = board.board_hal.get_rtc_module().set_rtc_time(&new_time);
SntpMode::SYNC { current: new_time }
}
Err(err) => {
println!("sntp error: {}", err);
log::info!("sntp error: {}", err);
board.board_hal.general_fault(true);
SntpMode::OFFLINE
}
@@ -826,11 +824,11 @@ fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard<HAL>) -> NetworkMode {
let nw_config = &board.board_hal.get_config().network.clone();
match board.board_hal.get_esp().mqtt(nw_config) {
Ok(_) => {
println!("Mqtt connection ready");
log::info!("Mqtt connection ready");
true
}
Err(err) => {
println!("Could not connect mqtt due to {}", err);
log::info!("Could not connect mqtt due to {}", err);
false
}
}
@@ -844,7 +842,7 @@ fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard<HAL>) -> NetworkMode {
}
}
Err(_) => {
println!("Offline mode");
log::info!("Offline mode");
board.board_hal.general_fault(true);
NetworkMode::OFFLINE
}
@@ -879,7 +877,7 @@ fn pump_info(
Delay::new_default().delay_ms(200);
}
Err(err) => {
println!("Error publishing pump state {}", err);
log::info!("Error publishing pump state {}", err);
}
};
}
@@ -975,13 +973,26 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
}
}
fn main() {
#[esp_hal_embassy::main]
async fn main(spawner: Spawner) {
// intialize embassy
esp_log::info::logger::init_logger_from_env();
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
let peripherals = esp_hal::init(config);
esp_alloc::heap_allocator!(size: 64 * 1024);
let timer0 = SystemTimer::new(peripherals.SYSTIMER);
esp_hal_embassy::init(timer0.alarm0);
info!("Embassy initialized!");
let result = safe_main();
match result {
// this should not get triggered, safe_main should not return but go into deep sleep with sensible
// timeout, this is just a fallback
Ok(_) => {
println!("Main app finished, restarting");
log::info!("Main app finished, restarting");
BOARD_ACCESS
.lock()
.unwrap()
@@ -992,8 +1003,9 @@ fn main() {
}
// if safe_main exists with an error, rollback to a known good ota version
Err(err) => {
println!("Failed main {}", err);
let _rollback_successful = rollback_and_reboot();
log::info!("Failed main {}", err);
//TODO
//let _rollback_successful = rollback_and_reboot();
panic!("Failed to rollback :(");
}
}
@@ -1014,8 +1026,10 @@ fn get_version() -> VersionInfo {
let branch = env!("VERGEN_GIT_BRANCH").to_owned();
let hash = &env!("VERGEN_GIT_SHA")[0..8];
let running_partition = unsafe { esp_ota_get_running_partition() };
let address = unsafe { (*running_partition).address };
//TODO
//let running_partition = unsafe { esp_ota_get_running_partition() };
let address = 0;
//let address = unsafe { (*running_partition).address };
let partition = if address > 100000 {
"ota_1 @ "
} else {