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"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" /> <excludeFolder url="file://$MODULE_DIR$/target" />
</content> </content>
<orderEntry type="inheritedJdk" /> <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::OtaImageState::Valid;
use esp_bootloader_esp_idf::ota::{Ota, OtaImageState}; use esp_bootloader_esp_idf::ota::{Ota, OtaImageState};
use esp_bootloader_esp_idf::partitions::{AppPartitionSubType, FlashRegion}; use esp_bootloader_esp_idf::partitions::{AppPartitionSubType, FlashRegion};
use esp_hal::Blocking;
use esp_hal::gpio::{Input, RtcPinWithResistors}; use esp_hal::gpio::{Input, RtcPinWithResistors};
use esp_hal::rng::Rng; use esp_hal::rng::Rng;
use esp_hal::rtc_cntl::{ use esp_hal::rtc_cntl::{
@@ -32,6 +33,7 @@ use esp_hal::rtc_cntl::{
Rtc, Rtc,
}; };
use esp_hal::system::software_reset; use esp_hal::system::software_reset;
use esp_hal::uart::Uart;
use esp_println::println; use esp_println::println;
use esp_radio::wifi::{ use esp_radio::wifi::{
AccessPointConfig, AccessPointInfo, AuthMethod, ClientConfig, ModeConfig, ScanConfig, AccessPointConfig, AccessPointInfo, AuthMethod, ClientConfig, ModeConfig, ScanConfig,
@@ -39,7 +41,7 @@ use esp_radio::wifi::{
}; };
use littlefs2::fs::Filesystem; use littlefs2::fs::Filesystem;
use littlefs2_core::{FileType, PathBuf, SeekFrom}; use littlefs2_core::{FileType, PathBuf, SeekFrom};
use log::{info, warn}; use log::{error, info, warn};
use mcutie::{ use mcutie::{
Error, McutieBuilder, McutieReceiver, McutieTask, MqttMessage, PublishDisplay, Publishable, Error, McutieBuilder, McutieReceiver, McutieTask, MqttMessage, PublishDisplay, Publishable,
QoS, Topic, QoS, Topic,
@@ -126,6 +128,7 @@ pub struct Esp<'a> {
// RTC-capable GPIO used as external wake source (store the raw peripheral) // RTC-capable GPIO used as external wake source (store the raw peripheral)
pub wake_gpio1: esp_hal::peripherals::GPIO1<'static>, pub wake_gpio1: esp_hal::peripherals::GPIO1<'static>,
pub uart0: Uart<'a, Blocking>,
pub ota: Ota<'static, MutexFlashStorage>, pub ota: Ota<'static, MutexFlashStorage>,
pub ota_target: &'static mut FlashRegion<'static, MutexFlashStorage>, pub ota_target: &'static mut FlashRegion<'static, MutexFlashStorage>,
@@ -152,6 +155,37 @@ macro_rules! mk_static {
} }
impl Esp<'_> { 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<()> { pub(crate) async fn delete_file(&self, filename: String) -> FatResult<()> {
let file = PathBuf::try_from(filename.as_str())?; let file = PathBuf::try_from(filename.as_str())?;
let access = self.fs.lock().await; let access = self.fs.lock().await;

View File

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

View File

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

View File

@@ -13,7 +13,7 @@
esp_bootloader_esp_idf::esp_app_desc!(); esp_bootloader_esp_idf::esp_app_desc!();
use esp_backtrace as _; use esp_backtrace as _;
use crate::config::{NetworkConfig, PlantConfig}; use crate::config::{NetworkConfig, PlantConfig, PlantControllerConfig};
use crate::fat_error::FatResult; use crate::fat_error::FatResult;
use crate::hal::esp::MQTT_STAY_ALIVE; use crate::hal::esp::MQTT_STAY_ALIVE;
use crate::hal::PROGRESS_ACTIVE; use crate::hal::PROGRESS_ACTIVE;
@@ -25,11 +25,12 @@ use crate::{
config::BoardVersion::Initial, config::BoardVersion::Initial,
hal::{PlantHal, HAL, PLANT_COUNT}, hal::{PlantHal, HAL, PLANT_COUNT},
}; };
use ::log::{info, warn}; use ::log::{error, info, warn};
use alloc::borrow::ToOwned; use alloc::borrow::ToOwned;
use alloc::string::{String, ToString}; use alloc::string::{String, ToString};
use alloc::sync::Arc; use alloc::sync::Arc;
use alloc::{format, vec}; use alloc::{format, vec};
use alloc::vec::Vec;
use chrono::{DateTime, Datelike, Timelike, Utc}; use chrono::{DateTime, Datelike, Timelike, Utc};
use chrono_tz::Tz::{self, UTC}; use chrono_tz::Tz::{self, UTC};
use core::sync::atomic::{AtomicBool, Ordering}; use core::sync::atomic::{AtomicBool, Ordering};
@@ -188,7 +189,15 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
.await; .await;
} }
info!("cur is {cur}"); 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() { if board.board_hal.get_esp().get_restart_to_conf() {
LOG_ACCESS LOG_ACCESS
.lock() .lock()
@@ -749,23 +758,18 @@ pub async fn do_secure_pump(
async fn update_charge_indicator( async fn update_charge_indicator(
board: &mut MutexGuard<'static, CriticalSectionRawMutex, HAL<'static>>, 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 //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 //we have mppt controller, ask it for charging current
if let Ok(current) = board.board_hal.get_mptt_current().await { let current = board.board_hal.get_mptt_current().await?;
let _ = board board
.board_hal .board_hal
.set_charge_indicator(current.as_milliamperes() > 20_f64) .set_charge_indicator(current.as_milliamperes() > 20_f64)
.await; .await?;
} else { Ok(())
//who knows
board.board_hal.general_fault(true).await;
log(LogMessage::MPPTError, 0, 0, "", "").await;
let _ = board.board_hal.set_charge_indicator(false).await;
}
} }
async fn publish_tank_state( async fn publish_tank_state(
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>, board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
tank_state: &TankState, tank_state: &TankState,
@@ -977,10 +981,27 @@ async fn wait_infinity(
let delay = wait_type.blink_pattern(); let delay = wait_type.blink_pattern();
let mut led_count = 8; let mut led_count = 8;
let mut pattern_step = 0; let mut pattern_step = 0;
let serial_config_receive = AtomicBool::new(false);
let mut suppress_further_mppt_error = false;
loop { loop {
{ {
let mut board = BOARD_ACCESS.get().await.lock().await; 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 // Skip default blink code when a progress display is active
if !PROGRESS_ACTIVE.load(Ordering::Relaxed) { if !PROGRESS_ACTIVE.load(Ordering::Relaxed) {
@@ -1033,6 +1054,7 @@ async fn wait_infinity(
reboot_now.store(true, Ordering::Relaxed); reboot_now.store(true, Ordering::Relaxed);
} }
if reboot_now.load(Ordering::Relaxed) { if reboot_now.load(Ordering::Relaxed) {
info!("Rebooting now");
//ensure clean http answer //ensure clean http answer
Timer::after_millis(500).await; Timer::after_millis(500).await;
BOARD_ACCESS 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] #[esp_rtos::main]
async fn main(spawner: Spawner) -> ! { async fn main(spawner: Spawner) -> ! {
// intialize embassy // intialize embassy