Refactor async logging to synchronous; improve error handling consistency across modules.

This commit is contained in:
Kai Börnert
2026-04-13 17:03:47 +02:00
parent 964bdb0454
commit 8ce00c9d95
12 changed files with 104 additions and 121 deletions

View File

@@ -1,8 +1,6 @@
[connection]
format = "EspIdf"
[[usb_device]]
vid = "303a"
pid = "1001"
[idf_format_args]
[flash]
size = "16MB"

View File

@@ -607,16 +607,14 @@ impl Esp<'_> {
0,
"",
"",
)
.await;
);
log(
LogMessage::LowVoltage,
LOW_VOLTAGE_DETECTED as u32,
0,
"",
"",
)
.await;
);
// is executed before main, no other code will alter these values during printing
#[allow(static_mut_refs)]
for (i, time) in LAST_WATERING_TIMESTAMP.iter().enumerate() {
@@ -695,9 +693,9 @@ impl Esp<'_> {
))?;
spawner.spawn(mqtt_runner(task))?;
log(LogMessage::StayAlive, 0, 0, "", &stay_alive_topic).await;
log(LogMessage::StayAlive, 0, 0, "", &stay_alive_topic);
log(LogMessage::MqttInfo, 0, 0, "", mqtt_url).await;
log(LogMessage::MqttInfo, 0, 0, "", mqtt_url);
let mqtt_timeout = 15000;
let res = async {
@@ -839,10 +837,10 @@ async fn mqtt_incoming_task(
true => 1,
false => 0,
};
log(LogMessage::MqttStayAliveRec, a, 0, "", "").await;
log(LogMessage::MqttStayAliveRec, a, 0, "", "");
MQTT_STAY_ALIVE.store(value, Ordering::Relaxed);
} else {
log(LogMessage::UnknownTopic, 0, 0, "", &topic).await;
log(LogMessage::UnknownTopic, 0, 0, "", &topic);
}
}
},

View File

@@ -85,14 +85,13 @@ use esp_alloc as _;
use esp_backtrace as _;
use esp_bootloader_esp_idf::ota::{Ota, OtaImageState};
use esp_hal::delay::Delay;
use esp_hal::i2c::master::{BusTimeout, Config, FsmTimeout, I2c, SoftwareTimeout};
use esp_hal::i2c::master::{BusTimeout, Config, FsmTimeout, I2c};
use esp_hal::interrupt::software::SoftwareInterruptControl;
use esp_hal::pcnt::unit::Unit;
use esp_hal::pcnt::Pcnt;
use esp_hal::rng::Rng;
use esp_hal::rtc_cntl::{Rtc, SocResetReason};
use esp_hal::system::reset_reason;
use esp_hal::time::Rate;
use esp_hal::timer::timg::{MwdtStage, TimerGroup, Wdt};
use esp_hal::uart::Uart;
use esp_hal::Blocking;
@@ -255,7 +254,8 @@ impl PlantHal {
esp_alloc::heap_allocator!(size: 64 * 1024);
esp_alloc::heap_allocator!(#[link_section = ".dram2_uninit"] size: 64000);
let rtc_peripheral: Rtc = Rtc::new(peripherals.LPWR);
let mut rtc_peripheral: Rtc = Rtc::new(peripherals.LPWR);
rtc_peripheral.rwdt.disable();
let timg0 = TimerGroup::new(peripherals.TIMG0);
let sw_int = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
@@ -469,8 +469,7 @@ impl PlantHal {
to_config_mode as u32,
"",
&format!("{reasons:?}"),
)
.await;
);
esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode)
.await;
@@ -575,8 +574,7 @@ impl PlantHal {
0,
"",
&err.to_string(),
)
.await;
);
HAL {
board_hal: v4_hal::create_v4(
free_pins,

View File

@@ -1,5 +1,5 @@
use crate::hal::Box;
use crate::fat_error::FatResult;
use crate::hal::Box;
use async_trait::async_trait;
use bincode::{Decode, Encode};
use chrono::{DateTime, Utc};
@@ -26,7 +26,7 @@ pub trait RTCModuleInteraction {
async fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> FatResult<()>;
fn write(&mut self, offset: u32, data: &[u8]) -> FatResult<()>;
fn read(&mut self, offset:u32, data: &mut [u8]) -> FatResult<()>;
fn read(&mut self, offset: u32, data: &mut [u8]) -> FatResult<()>;
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Default, Encode, Decode)]
@@ -67,7 +67,7 @@ impl RTCModuleInteraction for DS3231Module {
Ok(())
}
fn read(&mut self, offset:u32, data: &mut [u8]) -> FatResult<()> {
fn read(&mut self, offset: u32, data: &mut [u8]) -> FatResult<()> {
self.storage.read(offset, data)?;
Ok(())
}

View File

@@ -136,9 +136,7 @@ impl SavegameManager {
let slot = self.storage.scan()?;
match slot {
None => Ok(None),
Some(slot) => {
self.load_slot(slot.idx)
}
Some(slot) => self.load_slot(slot.idx),
}
}

View File

@@ -350,8 +350,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
}
Some(pump_ina) => {
let raw = pump_ina.shunt_voltage()?;
let shunt_voltage =
Voltage::from_microvolts(raw.shunt_voltage_uv().abs() as f64);
let shunt_voltage = Voltage::from_microvolts(raw.shunt_voltage_uv().abs() as f64);
let shut_value = Resistance::from_ohms(0.05_f64);
let current = shunt_voltage.as_volts() / shut_value.as_ohms();
Ok(Current::from_amperes(current))
@@ -516,8 +515,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
for plant in 0..PLANT_COUNT {
let a = moisture.sensor_a_hz[plant].unwrap_or(0.0) as u32;
let b = moisture.sensor_b_hz[plant].unwrap_or(0.0) as u32;
log(LogMessage::TestSensor, a, b, &(plant + 1).to_string(), "")
.await;
log(LogMessage::TestSensor, a, b, &(plant + 1).to_string(), "");
}
Timer::after_millis(10).await;
Ok(())

View File

@@ -3,7 +3,7 @@ use alloc::vec::Vec;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::blocking_mutex::Mutex as BlockingMutex;
use embassy_sync::mutex::Mutex;
use log::{LevelFilter, Log, Metadata, Record};
use log::{error, LevelFilter, Log, Metadata, Record};
pub struct InterceptorLogger {
// Async mutex for start/stop capture from async context
@@ -34,9 +34,13 @@ impl InterceptorLogger {
}
pub fn init(&'static self) {
log::set_logger(self)
.map(|()| log::set_max_level(LevelFilter::Info))
.unwrap();
match log::set_logger(self)
.map(|()| log::set_max_level(LevelFilter::Info)) {
Ok(()) => {}
Err(e) => {
error!("Logger already set: {}", e);
}
}
}
}

View File

@@ -115,28 +115,25 @@ impl From<LogEntryInner> for LogEntry {
}
}
pub async fn log(
message_key: LogMessage,
number_a: u32,
number_b: u32,
txt_short: &str,
txt_long: &str,
) {
pub fn log(message_key: LogMessage, number_a: u32, number_b: u32, txt_short: &str, txt_long: &str) {
let mut txt_short_stack: heapless::String<TXT_SHORT_LENGTH> = heapless::String::new();
let mut txt_long_stack: heapless::String<TXT_LONG_LENGTH> = heapless::String::new();
limit_length(txt_short, &mut txt_short_stack);
limit_length(txt_long, &mut txt_long_stack);
LOG_CHANNEL
.send(LogRequest {
message_key,
number_a,
number_b,
txt_short: txt_short_stack,
txt_long: txt_long_stack,
})
.await;
match LOG_CHANNEL.try_send(LogRequest {
message_key,
number_a,
number_b,
txt_short: txt_short_stack,
txt_long: txt_long_stack,
}) {
Ok(_) => {}
Err(_) => {
warn!("Log channel full, dropping log entry");
}
}
}
impl LogArray {
@@ -316,6 +313,10 @@ pub enum LogMessage {
PumpMissingSensorCurrent,
#[strum(serialize = "MPPT Current sensor could not be reached")]
MPPTError,
#[strum(
serialize = "Trace: a: ${number_a} b: ${number_b} txt_s ${txt_short} long ${txt_long}"
)]
Trace,
#[strum(serialize = "Parsing error reading message")]
UnknownMessage,
}

View File

@@ -42,7 +42,7 @@ use embassy_sync::once_lock::OnceLock;
use embassy_time::{Duration, Instant, Timer};
use esp_hal::rom::ets_delay_us;
use esp_hal::system::software_reset;
use esp_println::{logger, println};
use esp_println::println;
use hal::battery::BatteryState;
use log::LogMessage;
use option_lock::OptionLock;
@@ -185,7 +185,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
//check if we know the time current > 2020 (plausibility checks, this code is newer than 2020)
if cur.year() < 2020 {
to_config = true;
log(LogMessage::YearInplausibleForceConfig, 0, 0, "", "").await;
log(LogMessage::YearInplausibleForceConfig, 0, 0, "", "");
}
info!("cur is {cur}");
match update_charge_indicator(&mut board).await {
@@ -193,12 +193,12 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
Err(error) => {
board.board_hal.general_fault(true).await;
error!("Error updating charge indicator: {error}");
log(LogMessage::MPPTError, 0, 0, "", "").await;
log(LogMessage::MPPTError, 0, 0, "", "");
let _ = board.board_hal.set_charge_indicator(false).await;
}
}
if board.board_hal.get_esp().get_restart_to_conf() {
log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", "").await;
log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", "");
for _i in 0..2 {
board.board_hal.general_fault(true).await;
Timer::after_millis(100).await;
@@ -210,7 +210,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
board.board_hal.get_esp().set_restart_to_conf(false);
} else if board.board_hal.get_esp().mode_override_pressed() {
board.board_hal.general_fault(true).await;
log(LogMessage::ConfigModeButtonOverride, 0, 0, "", "").await;
log(LogMessage::ConfigModeButtonOverride, 0, 0, "", "");
for _i in 0..5 {
board.board_hal.general_fault(true).await;
Timer::after_millis(100).await;
@@ -308,8 +308,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
.to_string()
.as_str(),
"",
)
.await;
);
if to_config {
//check if client or ap mode and init Wi-Fi
@@ -324,7 +323,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
}
wait_infinity(board, WaitType::ConfigButton, reboot_now.clone()).await;
} else {
log(LogMessage::NormalRun, 0, 0, "", "").await;
log(LogMessage::NormalRun, 0, 0, "", "");
}
let dry_run = MQTT_STAY_ALIVE.load(Ordering::Relaxed);
@@ -335,28 +334,22 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
if let Some(err) = tank_state.got_error(&board.board_hal.get_config().tank) {
match err {
TankError::SensorDisabled => { /* unreachable */ }
TankError::SensorMissing(raw_value_mv) => {
log(
LogMessage::TankSensorMissing,
raw_value_mv as u32,
0,
"",
"",
)
.await
}
TankError::SensorValueError { value, min, max } => {
log(
LogMessage::TankSensorValueRangeError,
min as u32,
max as u32,
&format!("{value}"),
"",
)
.await
}
TankError::SensorMissing(raw_value_mv) => log(
LogMessage::TankSensorMissing,
raw_value_mv as u32,
0,
"",
"",
),
TankError::SensorValueError { value, min, max } => log(
LogMessage::TankSensorValueRangeError,
min as u32,
max as u32,
&format!("{value}"),
"",
),
TankError::BoardError(err) => {
log(LogMessage::TankSensorBoardError, 0, 0, "", &err.to_string()).await
log(LogMessage::TankSensorBoardError, 0, 0, "", &err.to_string())
}
}
// disabled cannot trigger this because of wrapping if is_enabled
@@ -365,7 +358,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
.warn_level(&board.board_hal.get_config().tank)
.is_ok_and(|warn| warn)
{
log(LogMessage::TankWaterLevelLow, 0, 0, "", "").await;
log(LogMessage::TankWaterLevelLow, 0, 0, "", "");
board.board_hal.general_fault(true).await;
}
}
@@ -414,7 +407,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
.any(|(it, conf)| it.needs_to_be_watered(conf, &timezone_time))
&& !water_frozen;
if pump_required {
log(LogMessage::EnableMain, dry_run as u32, 0, "", "").await;
log(LogMessage::EnableMain, dry_run as u32, 0, "", "");
for (plant_id, (state, plant_config)) in plantstate
.iter()
.zip(&board.board_hal.get_config().plants.clone())
@@ -435,8 +428,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
plant_config.max_consecutive_pump_count as u32,
&(plant_id + 1).to_string(),
"",
)
.await;
);
board.board_hal.fault(plant_id, true).await?;
}
log(
@@ -445,8 +437,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
plant_config.pump_time_s as u32,
&dry_run.to_string(),
"",
)
.await;
);
board
.board_hal
.get_esp()
@@ -677,8 +668,7 @@ pub async fn do_secure_pump(
current_ma as u32,
plant_config.max_pump_current_ma.to_string().as_str(),
step.to_string().as_str(),
)
.await;
);
error = true;
} else if high_current && first_error {
log(
@@ -687,8 +677,7 @@ pub async fn do_secure_pump(
current_ma as u32,
plant_config.max_pump_current_ma.to_string().as_str(),
step.to_string().as_str(),
)
.await;
);
board.board_hal.general_fault(true).await;
board.board_hal.fault(plant_id, true).await?;
if !plant_config.ignore_current_error {
@@ -705,8 +694,7 @@ pub async fn do_secure_pump(
current_ma as u32,
plant_config.min_pump_current_ma.to_string().as_str(),
step.to_string().as_str(),
)
.await;
);
board.board_hal.general_fault(true).await;
board.board_hal.fault(plant_id, true).await?;
if !plant_config.ignore_current_error {
@@ -725,8 +713,7 @@ pub async fn do_secure_pump(
0,
"",
"",
)
.await;
);
error = true;
break;
} else {

View File

@@ -1,6 +1,6 @@
use bincode::{Decode, Encode};
use crate::hal::Moistures;
use crate::{config::PlantConfig, hal::HAL, in_time_range};
use bincode::{Decode, Encode};
use chrono::{DateTime, TimeDelta, Utc};
use chrono_tz::Tz;
use serde::{Deserialize, Serialize};

View File

@@ -123,9 +123,7 @@ pub(crate) async fn get_config<T, const N: usize>(
let mut board = BOARD_ACCESS.get().await.lock().await;
let json = match saveidx {
None => serde_json::to_string(board.board_hal.get_config())?,
Some(idx) => {
board.board_hal.get_esp().load_config_slot(idx).await?
}
Some(idx) => board.board_hal.get_esp().load_config_slot(idx).await?,
};
Ok(Some(json))
}
@@ -174,7 +172,12 @@ pub(crate) async fn get_time<T, const N: usize>(
},
};
let native = board.board_hal.get_time().await.with_timezone(&tz).to_rfc3339();
let native = board
.board_hal
.get_time()
.await
.with_timezone(&tz)
.to_rfc3339();
let rtc = match board.board_hal.get_rtc_module().get_rtc_time().await {
Ok(time) => time.with_timezone(&tz).to_rfc3339(),

View File

@@ -47,28 +47,26 @@ export class Controller {
});
}
loadLogLocaleConfig() {
return fetch(PUBLIC_URL + "/log_localization")
.then(response => response.json())
.then(json => json as LogLocalisation)
.then(loglocale => {
controller.logView.setLogLocalisation(loglocale)
})
.catch(error => {
console.log(error);
});
async loadLogLocaleConfig() {
try {
const response = await fetch(PUBLIC_URL + "/log_localization");
const json = await response.json();
const loglocale = json as LogLocalisation;
controller.logView.setLogLocalisation(loglocale);
} catch (error) {
console.log(error);
}
}
loadLog() {
return fetch(PUBLIC_URL + "/log")
.then(response => response.json())
.then(json => json as LogArray)
.then(logs => {
controller.logView.setLog(logs)
})
.catch(error => {
console.log(error);
});
async loadLog() {
try {
const response = await fetch(PUBLIC_URL + "/log");
const json = await response.json();
const logs = json as LogArray;
controller.logView.setLog(logs);
} catch (error) {
console.log(error);
}
}
async getBackupInfo(): Promise<void> {
@@ -241,11 +239,11 @@ export class Controller {
return response.status
})
.then(status => {
controller.progressview.removeProgress("set_config");
if (status == 200) {
controller.progressview.removeProgress("set_config");
setTimeout(() => {
controller.downloadConfig().then(r => {
controller.updateSaveList().then(r => {
controller.downloadConfig().then(() => {
controller.updateSaveList().then(() => {
});
});
}, 250)
@@ -669,7 +667,7 @@ async function executeTasksSequentially() {
}
}
executeTasksSequentially().then(r => {
executeTasksSequentially().then(() => {
controller.progressview.removeProgress("initial")
});