add: implement UART-based serial configuration handling and improve error handling in charge indicator updates
This commit is contained in:
1
Software/MainBoard/rust/.idea/plant-ctrl2.iml
generated
1
Software/MainBoard/rust/.idea/plant-ctrl2.iml
generated
@@ -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" />
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user