add: implement UART-based serial configuration handling and improve error handling in charge indicator updates

This commit is contained in:
2026-01-05 19:57:57 +01:00
parent 8fc2a89503
commit 1de40085fb
5 changed files with 124 additions and 24 deletions

View File

@@ -3,6 +3,7 @@
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />

View File

@@ -25,6 +25,7 @@ use embedded_storage::nor_flash::{check_erase, NorFlash, ReadNorFlash};
use esp_bootloader_esp_idf::ota::OtaImageState::Valid;
use esp_bootloader_esp_idf::ota::{Ota, OtaImageState};
use esp_bootloader_esp_idf::partitions::{AppPartitionSubType, FlashRegion};
use esp_hal::Blocking;
use esp_hal::gpio::{Input, RtcPinWithResistors};
use esp_hal::rng::Rng;
use esp_hal::rtc_cntl::{
@@ -32,6 +33,7 @@ use esp_hal::rtc_cntl::{
Rtc,
};
use esp_hal::system::software_reset;
use esp_hal::uart::Uart;
use esp_println::println;
use esp_radio::wifi::{
AccessPointConfig, AccessPointInfo, AuthMethod, ClientConfig, ModeConfig, ScanConfig,
@@ -39,7 +41,7 @@ use esp_radio::wifi::{
};
use littlefs2::fs::Filesystem;
use littlefs2_core::{FileType, PathBuf, SeekFrom};
use log::{info, warn};
use log::{error, info, warn};
use mcutie::{
Error, McutieBuilder, McutieReceiver, McutieTask, MqttMessage, PublishDisplay, Publishable,
QoS, Topic,
@@ -126,6 +128,7 @@ pub struct Esp<'a> {
// RTC-capable GPIO used as external wake source (store the raw peripheral)
pub wake_gpio1: esp_hal::peripherals::GPIO1<'static>,
pub uart0: Uart<'a, Blocking>,
pub ota: Ota<'static, MutexFlashStorage>,
pub ota_target: &'static mut FlashRegion<'static, MutexFlashStorage>,
@@ -152,6 +155,37 @@ macro_rules! mk_static {
}
impl Esp<'_> {
pub(crate) async fn read_serial_line(&mut self) -> FatResult<Option<alloc::string::String>> {
let mut buf = [0u8; 1];
let mut line = String::new();
loop {
match self.uart0.read_buffered(&mut buf) {
Ok(read) => {
if (read == 0) {
return Ok(None);
}
let c = buf[0] as char;
if c == '\n' {
return Ok(Some(line));
}
line.push(c);
}
Err(error ) => {
if line.is_empty() {
return Ok(None);
} else {
error!("Error reading serial line: {error:?}");
// If we already have some data, we should probably wait a bit or just return what we have?
// But the protocol expects a full line or message.
// For simplicity in config mode, we can block here or just return None if nothing is there yet.
// However, if we started receiving, we should probably finish or timeout.
continue;
}
}
}
}
}
pub(crate) async fn delete_file(&self, filename: String) -> FatResult<()> {
let file = PathBuf::try_from(filename.as_str())?;
let access = self.fs.lock().await;

View File

@@ -1,4 +1,5 @@
use lib_bms_protocol::BmsReadable;
use esp_hal::uart::{Config as UartConfig};
pub(crate) mod battery;
// mod can_api; // replaced by external canapi crate
pub mod esp;
@@ -49,6 +50,7 @@ use crate::{
};
use alloc::boxed::Box;
use alloc::format;
use alloc::string::String;
use alloc::sync::Arc;
use async_trait::async_trait;
use bincode::{Decode, Encode};
@@ -94,6 +96,7 @@ use esp_hal::system::reset_reason;
use esp_hal::time::Rate;
use esp_hal::timer::timg::TimerGroup;
use esp_hal::Blocking;
use esp_hal::uart::Uart;
use esp_radio::{init, Controller};
use esp_storage::FlashStorage;
use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem};
@@ -272,10 +275,6 @@ impl PlantHal {
let pcnt_module = Pcnt::new(peripherals.PCNT);
let free_pins = FreePeripherals {
// can: peripherals.can,
// adc1: peripherals.adc1,
// pcnt0: peripherals.pcnt0,
// pcnt1: peripherals.pcnt1,
gpio0: peripherals.GPIO0,
gpio2: peripherals.GPIO2,
gpio3: peripherals.GPIO3,
@@ -397,6 +396,11 @@ impl PlantHal {
lfs2Filesystem::mount(alloc, lfs2filesystem).expect("Could not mount lfs2 filesystem"),
));
let uart0 = Uart::new(peripherals.UART0, UartConfig::default())
.map_err(|_| FatError::String {
error: "Uart creation failed".to_string(),
})?;
let ap = interfaces.ap;
let sta = interfaces.sta;
let mut esp = Esp {
@@ -412,6 +416,7 @@ impl PlantHal {
current: running,
slot0_state: state_0,
slot1_state: state_1,
uart0
};
//init,reset rtc memory depending on cause

View File

@@ -11,7 +11,8 @@ use crate::hal::{
};
use crate::log::{LogMessage, LOG_ACCESS};
use alloc::boxed::Box;
use alloc::string::ToString;
use alloc::string::{String, ToString};
use alloc::vec;
use async_trait::async_trait;
use canapi::id::{classify, plant_id, MessageKind, IDENTIFY_CMD_OFFSET};
use canapi::SensorSlot;
@@ -130,6 +131,7 @@ pub struct V4<'a> {
>,
twai_config: Option<TwaiConfiguration<'static, Blocking>>,
can_power: Output<'static>,
extra1: Output<'a>,
extra2: Output<'a>,
}
@@ -254,7 +256,7 @@ pub(crate) async fn create_v4(
charger,
extra1,
extra2,
can_power,
can_power
};
Ok(Box::new(v))
}

View File

@@ -13,7 +13,7 @@
esp_bootloader_esp_idf::esp_app_desc!();
use esp_backtrace as _;
use crate::config::{NetworkConfig, PlantConfig};
use crate::config::{NetworkConfig, PlantConfig, PlantControllerConfig};
use crate::fat_error::FatResult;
use crate::hal::esp::MQTT_STAY_ALIVE;
use crate::hal::PROGRESS_ACTIVE;
@@ -25,11 +25,12 @@ use crate::{
config::BoardVersion::Initial,
hal::{PlantHal, HAL, PLANT_COUNT},
};
use ::log::{info, warn};
use ::log::{error, info, warn};
use alloc::borrow::ToOwned;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::{format, vec};
use alloc::vec::Vec;
use chrono::{DateTime, Datelike, Timelike, Utc};
use chrono_tz::Tz::{self, UTC};
use core::sync::atomic::{AtomicBool, Ordering};
@@ -188,7 +189,15 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
.await;
}
info!("cur is {cur}");
update_charge_indicator(&mut board).await;
match update_charge_indicator(&mut board).await {
Ok(_) => {}
Err(error) => {
board.board_hal.general_fault(true).await;
error!("Error updating charge indicator: {error}");
log(LogMessage::MPPTError, 0, 0, "", "").await;
let _ = board.board_hal.set_charge_indicator(false).await;
}
}
if board.board_hal.get_esp().get_restart_to_conf() {
LOG_ACCESS
.lock()
@@ -749,23 +758,18 @@ pub async fn do_secure_pump(
async fn update_charge_indicator(
board: &mut MutexGuard<'static, CriticalSectionRawMutex, HAL<'static>>,
) {
) -> FatResult<()>{
//FIXME add config and code to allow power supply mode, in this case this is a nop
//we have mppt controller, ask it for charging current
if let Ok(current) = board.board_hal.get_mptt_current().await {
let _ = board
.board_hal
.set_charge_indicator(current.as_milliamperes() > 20_f64)
.await;
} else {
//who knows
board.board_hal.general_fault(true).await;
log(LogMessage::MPPTError, 0, 0, "", "").await;
let _ = board.board_hal.set_charge_indicator(false).await;
}
let current = board.board_hal.get_mptt_current().await?;
board
.board_hal
.set_charge_indicator(current.as_milliamperes() > 20_f64)
.await?;
Ok(())
}
async fn publish_tank_state(
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
tank_state: &TankState,
@@ -977,10 +981,27 @@ async fn wait_infinity(
let delay = wait_type.blink_pattern();
let mut led_count = 8;
let mut pattern_step = 0;
let serial_config_receive = AtomicBool::new(false);
let mut suppress_further_mppt_error = false;
loop {
{
let mut board = BOARD_ACCESS.get().await.lock().await;
update_charge_indicator(&mut board).await;
match update_charge_indicator(&mut board).await{
Ok(_) => {}
Err(error) => {
if !suppress_further_mppt_error {
error!("Error updating charge indicator: {error}");
suppress_further_mppt_error = true;
}
}
};
match handle_serial_config(&mut board, &serial_config_receive, &reboot_now).await {
Ok(_) => {}
Err(e) => {
error!("Error handling serial config: {e}");
}
}
// Skip default blink code when a progress display is active
if !PROGRESS_ACTIVE.load(Ordering::Relaxed) {
@@ -1033,6 +1054,7 @@ async fn wait_infinity(
reboot_now.store(true, Ordering::Relaxed);
}
if reboot_now.load(Ordering::Relaxed) {
info!("Rebooting now");
//ensure clean http answer
Timer::after_millis(500).await;
BOARD_ACCESS
@@ -1047,6 +1069,42 @@ async fn wait_infinity(
}
}
async fn handle_serial_config(board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'_>>, serial_config_receive: &AtomicBool, reboot_now: &AtomicBool) -> FatResult<()> {
match board.board_hal.get_esp().read_serial_line().await {
Ok(serial_line) => {
match serial_line {
None => {
Ok(())
}
Some(line) => {
if serial_config_receive.load(Ordering::Relaxed) {
let ll = line.as_str();
let config: PlantControllerConfig = serde_json::from_str(ll)?;
board.board_hal.get_esp().save_config(Vec::from(ll.as_bytes())).await?;
board.board_hal.set_config(config);
serial_config_receive.store(false, Ordering::Relaxed);
info!("Config received, rebooting");
board.board_hal.get_esp().set_restart_to_conf(false);
reboot_now.store(true, Ordering::Relaxed);
Ok(())
} else {
if line == "automation:streamconfig" {
serial_config_receive.store(true, Ordering::Relaxed);
info!("streamconfig:recieving");
}
Ok(())
}
}
}
}
Err(_) => {
error!("Error reading serial line");
Ok(())
}
}
}
#[esp_rtos::main]
async fn main(spawner: Spawner) -> ! {
// intialize embassy