Fix ota, use MMU to determine running partition, use RMW wrapper for ota_data partition (littelfs handles this internally, so it was no problem prior)
This commit is contained in:
@@ -13,6 +13,10 @@ test = false
|
||||
bench = false
|
||||
doc = false
|
||||
|
||||
[features]
|
||||
default = ["esp32c6"]
|
||||
esp32c6 = []
|
||||
|
||||
#this strips the bootloader, we need that tho
|
||||
#strip = true
|
||||
|
||||
@@ -87,6 +91,8 @@ edge-nal = "0.5.0"
|
||||
edge-nal-embassy = "0.6.0"
|
||||
edge-http = { version = "0.6.1", features = ["log"] }
|
||||
|
||||
esp32c6 = { version = "0.22.0" }
|
||||
|
||||
# Hardware abstraction traits and HAL adapters
|
||||
embedded-hal = "1.0.0"
|
||||
embedded-storage = "0.3.1"
|
||||
@@ -95,7 +101,7 @@ embedded-can = "0.4.1"
|
||||
nb = "1.1.0"
|
||||
|
||||
# Concrete hardware drivers and sensors/IO expanders
|
||||
lib-bms-protocol = {git = "https://gitea.wlandt.de/judge/ch32-bms.git" , default-features = false }
|
||||
lib-bms-protocol = { git = "https://gitea.wlandt.de/judge/ch32-bms.git", default-features = false }
|
||||
onewire = "0.4.0"
|
||||
ds323x = "0.7.0"
|
||||
eeprom24x = "0.7.2"
|
||||
|
||||
@@ -52,6 +52,7 @@ pub enum FatError {
|
||||
SpawnError {
|
||||
error: SpawnError,
|
||||
},
|
||||
OTAError,
|
||||
PartitionError {
|
||||
error: esp_bootloader_esp_idf::partitions::Error,
|
||||
},
|
||||
@@ -106,6 +107,9 @@ impl fmt::Display for FatError {
|
||||
}
|
||||
FatError::SNTPError { error } => write!(f, "SNTPError {error:?}"),
|
||||
FatError::BMSError { error } => write!(f, "BMSError, {error}"),
|
||||
FatError::OTAError => {
|
||||
write!(f, "OTA missing partition")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -323,13 +327,12 @@ impl From<sntpc::Error> for FatError {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl From<BmsProtocolError> for FatError{
|
||||
impl From<BmsProtocolError> for FatError {
|
||||
fn from(value: BmsProtocolError) -> Self {
|
||||
match value {
|
||||
BmsProtocolError::I2cCommunicationError => {
|
||||
FatError::String{error: "I2C communication error".to_string()}
|
||||
}
|
||||
BmsProtocolError::I2cCommunicationError => FatError::String {
|
||||
error: "I2C communication error".to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,11 +21,10 @@ use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_sync::mutex::{Mutex, MutexGuard};
|
||||
use embassy_sync::once_lock::OnceLock;
|
||||
use embassy_time::{Duration, Timer, WithTimeout};
|
||||
use embedded_storage::nor_flash::{check_erase, NorFlash, ReadNorFlash};
|
||||
use embedded_storage::nor_flash::{check_erase, NorFlash, ReadNorFlash, RmwNorFlashStorage};
|
||||
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::{
|
||||
@@ -34,6 +33,7 @@ use esp_hal::rtc_cntl::{
|
||||
};
|
||||
use esp_hal::system::software_reset;
|
||||
use esp_hal::uart::Uart;
|
||||
use esp_hal::Blocking;
|
||||
use esp_println::println;
|
||||
use esp_radio::wifi::{
|
||||
AccessPointConfig, AccessPointInfo, AuthMethod, ClientConfig, ModeConfig, ScanConfig,
|
||||
@@ -130,7 +130,7 @@ pub struct Esp<'a> {
|
||||
pub wake_gpio1: esp_hal::peripherals::GPIO1<'static>,
|
||||
pub uart0: Uart<'a, Blocking>,
|
||||
|
||||
pub ota: Ota<'static, MutexFlashStorage>,
|
||||
pub ota: Ota<'static, RmwNorFlashStorage<'static, &'static mut MutexFlashStorage>>,
|
||||
pub ota_target: &'static mut FlashRegion<'static, MutexFlashStorage>,
|
||||
pub current: AppPartitionSubType,
|
||||
pub slot0_state: OtaImageState,
|
||||
@@ -155,7 +155,6 @@ 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();
|
||||
@@ -171,7 +170,7 @@ impl Esp<'_> {
|
||||
}
|
||||
line.push(c);
|
||||
}
|
||||
Err(error ) => {
|
||||
Err(error) => {
|
||||
if line.is_empty() {
|
||||
return Ok(None);
|
||||
} else {
|
||||
@@ -252,6 +251,7 @@ impl Esp<'_> {
|
||||
|
||||
pub(crate) async fn write_ota(&mut self, offset: u32, buf: &[u8]) -> Result<(), FatError> {
|
||||
let _ = check_erase(self.ota_target, offset, offset + 4096);
|
||||
info!("erasing and writing block 0x{offset:x}");
|
||||
self.ota_target.erase(offset, offset + 4096)?;
|
||||
|
||||
let mut temp = vec![0; buf.len()];
|
||||
@@ -288,8 +288,6 @@ impl Esp<'_> {
|
||||
info!("switched state for new partition");
|
||||
let state_new = self.ota.current_ota_state()?;
|
||||
info!("state on new partition now {state_new:?}");
|
||||
//determine nextslot crc
|
||||
|
||||
self.set_restart_to_conf(true);
|
||||
Ok(())
|
||||
}
|
||||
@@ -597,10 +595,13 @@ impl Esp<'_> {
|
||||
// Mark the current OTA image as valid if we reached here while in pending verify.
|
||||
if let Ok(cur) = self.ota.current_ota_state() {
|
||||
if cur == OtaImageState::PendingVerify {
|
||||
info!("Marking OTA image as valid");
|
||||
self.ota
|
||||
.set_current_ota_state(Valid)
|
||||
.expect("Could not set image to valid");
|
||||
}
|
||||
} else {
|
||||
info!("No OTA image to mark as valid");
|
||||
}
|
||||
|
||||
if duration_in_ms == 0 {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use esp_hal::uart::Config as UartConfig;
|
||||
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;
|
||||
@@ -9,7 +9,6 @@ mod shared_flash;
|
||||
mod v4_hal;
|
||||
mod water;
|
||||
|
||||
use lib_bms_protocol::ProtocolVersion;
|
||||
use crate::alloc::string::ToString;
|
||||
use crate::hal::rtc::{DS3231Module, RTCModuleInteraction};
|
||||
use esp_hal::peripherals::Peripherals;
|
||||
@@ -36,6 +35,7 @@ use esp_hal::peripherals::GPIO6;
|
||||
use esp_hal::peripherals::GPIO7;
|
||||
use esp_hal::peripherals::GPIO8;
|
||||
use esp_hal::peripherals::TWAI0;
|
||||
use lib_bms_protocol::ProtocolVersion;
|
||||
|
||||
use crate::{
|
||||
bail,
|
||||
@@ -66,19 +66,21 @@ use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_sync::blocking_mutex::CriticalSectionMutex;
|
||||
use esp_bootloader_esp_idf::partitions::{
|
||||
AppPartitionSubType, DataPartitionSubType, FlashRegion, PartitionEntry,
|
||||
AppPartitionSubType, DataPartitionSubType, FlashRegion, PartitionEntry, PartitionTable,
|
||||
PartitionType,
|
||||
};
|
||||
use esp_hal::clock::CpuClock;
|
||||
use esp_hal::gpio::{Input, InputConfig, Pull};
|
||||
use measurements::{Current, Voltage};
|
||||
|
||||
use crate::fat_error::{ContextExt, FatError, FatResult};
|
||||
use crate::hal::battery::{WCHI2CSlave};
|
||||
use crate::hal::battery::WCHI2CSlave;
|
||||
use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem;
|
||||
use crate::hal::water::TankSensor;
|
||||
use crate::log::LOG_ACCESS;
|
||||
use embassy_sync::mutex::Mutex;
|
||||
use embassy_sync::once_lock::OnceLock;
|
||||
use embedded_storage::nor_flash::RmwNorFlashStorage;
|
||||
use embedded_storage::ReadStorage;
|
||||
use esp_alloc as _;
|
||||
use esp_backtrace as _;
|
||||
@@ -93,8 +95,8 @@ use esp_hal::rtc_cntl::{Rtc, SocResetReason};
|
||||
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_hal::Blocking;
|
||||
use esp_radio::{init, Controller};
|
||||
use esp_storage::FlashStorage;
|
||||
use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem};
|
||||
@@ -138,12 +140,6 @@ pub struct HAL<'a> {
|
||||
pub board_hal: Box<dyn BoardInteraction<'a> + Send>,
|
||||
}
|
||||
|
||||
pub struct DetectionRequest {
|
||||
pub sensorsa: [Sensor; PLANT_COUNT],
|
||||
pub sensorsb: [Sensor; PLANT_COUNT],
|
||||
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub trait BoardInteraction<'a> {
|
||||
fn get_tank_sensor(&mut self) -> Result<&mut TankSensor<'a>, FatError>;
|
||||
@@ -169,7 +165,7 @@ pub trait BoardInteraction<'a> {
|
||||
async fn can_power(&mut self, state: bool) -> FatResult<()>;
|
||||
|
||||
// Return JSON string with autodetected sensors per plant. Default: not supported.
|
||||
async fn detect_sensors(&mut self, request: Detection) -> FatResult<Detection> {
|
||||
async fn detect_sensors(&mut self, _request: Detection) -> FatResult<Detection> {
|
||||
bail!("Autodetection is only available on v4 HAL with CAN bus");
|
||||
}
|
||||
|
||||
@@ -333,31 +329,31 @@ impl PlantHal {
|
||||
);
|
||||
|
||||
let ota_data = mk_static!(
|
||||
FlashRegion<MutexFlashStorage>,
|
||||
ota_data.as_embedded_storage(flash_storage_2)
|
||||
FlashRegion<RmwNorFlashStorage<&mut MutexFlashStorage>>,
|
||||
ota_data.as_embedded_storage(mk_static!(
|
||||
RmwNorFlashStorage<&mut MutexFlashStorage>,
|
||||
RmwNorFlashStorage::new(flash_storage_2, mk_static!([u8; 4096], [0_u8; 4096]))
|
||||
))
|
||||
);
|
||||
|
||||
let state_0 = ota_state(AppPartitionSubType::Ota0, ota_data);
|
||||
let state_1 = ota_state(AppPartitionSubType::Ota1, ota_data);
|
||||
let mut ota = Ota::new(ota_data, 2)?;
|
||||
let running = get_current_slot_and_fix_ota_data(&mut ota, state_0, state_1)?;
|
||||
let running = get_current_slot(&pt, &mut ota)?;
|
||||
let target = next_partition(running)?;
|
||||
|
||||
info!("Currently running OTA slot: {running:?}");
|
||||
info!("Updates will be stored in OTA slot: {target:?}");
|
||||
info!("Slot0 state: {state_0:?}");
|
||||
info!("Slot1 state: {state_1:?}");
|
||||
|
||||
//get current_state and next_state here!
|
||||
let ota_target = match target {
|
||||
AppPartitionSubType::Ota0 => pt
|
||||
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App(
|
||||
AppPartitionSubType::Ota0,
|
||||
))?
|
||||
.find_partition(PartitionType::App(AppPartitionSubType::Ota0))?
|
||||
.context("Partition table invalid no ota0")?,
|
||||
AppPartitionSubType::Ota1 => pt
|
||||
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App(
|
||||
AppPartitionSubType::Ota1,
|
||||
))?
|
||||
.find_partition(PartitionType::App(AppPartitionSubType::Ota1))?
|
||||
.context("Partition table invalid no ota1")?,
|
||||
_ => {
|
||||
bail!("Invalid target partition");
|
||||
@@ -401,8 +397,8 @@ impl PlantHal {
|
||||
lfs2Filesystem::mount(alloc, lfs2filesystem).expect("Could not mount lfs2 filesystem"),
|
||||
));
|
||||
|
||||
let uart0 = Uart::new(peripherals.UART0, UartConfig::default())
|
||||
.map_err(|_| FatError::String {
|
||||
let uart0 =
|
||||
Uart::new(peripherals.UART0, UartConfig::default()).map_err(|_| FatError::String {
|
||||
error: "Uart creation failed".to_string(),
|
||||
})?;
|
||||
|
||||
@@ -421,7 +417,7 @@ impl PlantHal {
|
||||
current: running,
|
||||
slot0_state: state_0,
|
||||
slot1_state: state_1,
|
||||
uart0
|
||||
uart0,
|
||||
};
|
||||
|
||||
//init,reset rtc memory depending on cause
|
||||
@@ -551,9 +547,9 @@ impl PlantHal {
|
||||
// initial_hal::create_initial_board(free_pins, config, esp)?
|
||||
//}
|
||||
//BoardVersion::V4 => {
|
||||
v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)
|
||||
.await?;
|
||||
//}
|
||||
v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)
|
||||
.await?;
|
||||
//}
|
||||
//};
|
||||
|
||||
HAL { board_hal }
|
||||
@@ -571,9 +567,14 @@ impl PlantHal {
|
||||
)
|
||||
.await;
|
||||
HAL {
|
||||
board_hal: v4_hal::create_v4(free_pins, esp, PlantControllerConfig::default(),
|
||||
Box::new(NoBatteryMonitor {}), rtc_module)
|
||||
.await?
|
||||
board_hal: v4_hal::create_v4(
|
||||
free_pins,
|
||||
esp,
|
||||
PlantControllerConfig::default(),
|
||||
Box::new(NoBatteryMonitor {}),
|
||||
rtc_module,
|
||||
)
|
||||
.await?,
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -584,7 +585,7 @@ impl PlantHal {
|
||||
|
||||
fn ota_state(
|
||||
slot: AppPartitionSubType,
|
||||
ota_data: &mut FlashRegion<MutexFlashStorage>,
|
||||
ota_data: &mut FlashRegion<RmwNorFlashStorage<&mut MutexFlashStorage>>,
|
||||
) -> OtaImageState {
|
||||
// Read and log OTA states for both slots before constructing Ota
|
||||
// Each OTA select entry is 32 bytes: [seq:4][label:20][state:4][crc:4]
|
||||
@@ -600,56 +601,40 @@ fn ota_state(
|
||||
OtaImageState::try_from(raw_state).unwrap_or(OtaImageState::Undefined)
|
||||
}
|
||||
|
||||
fn get_current_slot_and_fix_ota_data(
|
||||
ota: &mut Ota<MutexFlashStorage>,
|
||||
state0: OtaImageState,
|
||||
state1: OtaImageState,
|
||||
fn get_current_slot(
|
||||
pt: &PartitionTable,
|
||||
ota: &mut Ota<RmwNorFlashStorage<&mut MutexFlashStorage>>,
|
||||
) -> Result<AppPartitionSubType, FatError> {
|
||||
let state = ota.current_ota_state().unwrap_or_default();
|
||||
let swap = match state {
|
||||
OtaImageState::Invalid => true,
|
||||
OtaImageState::Aborted => true,
|
||||
OtaImageState::Undefined => {
|
||||
info!("Undefined image in current slot, bootloader wrong?");
|
||||
false
|
||||
let booted = pt.booted_partition()?.ok_or(FatError::OTAError)?;
|
||||
let booted_type = booted.partition_type();
|
||||
let booted_ota_type = match booted_type {
|
||||
PartitionType::App(subtype) => subtype,
|
||||
_ => {
|
||||
bail!("Booted partition is not an app partition");
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
let current = ota.current_app_partition()?;
|
||||
if swap {
|
||||
let other = match current {
|
||||
AppPartitionSubType::Ota0 => state1,
|
||||
AppPartitionSubType::Ota1 => state0,
|
||||
_ => OtaImageState::Invalid,
|
||||
};
|
||||
|
||||
match other {
|
||||
OtaImageState::Invalid => {
|
||||
bail!(
|
||||
"cannot recover slot, as both slots in invalid state {:?} {:?} {:?}",
|
||||
current,
|
||||
state0,
|
||||
state1
|
||||
);
|
||||
}
|
||||
OtaImageState::Aborted => {
|
||||
bail!(
|
||||
"cannot recover slot, as both slots in invalid state {:?} {:?} {:?}",
|
||||
current,
|
||||
state0,
|
||||
state1
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
info!("Current slot has state {state:?} other state has {other:?} swapping");
|
||||
let next = next_partition(current)?;
|
||||
ota.set_current_app_partition(next)?;
|
||||
let expected_partition = ota.current_app_partition()?;
|
||||
if expected_partition == booted_ota_type {
|
||||
info!("Booted partition matches expected partition");
|
||||
} else {
|
||||
info!("Booted partition does not match expected partition, fixing ota entry");
|
||||
ota.set_current_app_partition(booted_ota_type)?;
|
||||
}
|
||||
|
||||
//we actually booted other slot, than partition table assumes
|
||||
return Ok(ota.current_app_partition()?);
|
||||
};
|
||||
Ok(current)
|
||||
let fixed = ota.current_app_partition()?;
|
||||
let state = ota.current_ota_state();
|
||||
info!("Expected partition: {expected_partition:?}, current partition: {booted_ota_type:?}, state: {state:?}");
|
||||
|
||||
if fixed != booted_ota_type {
|
||||
bail!(
|
||||
"Could not fix ota entry, booted partition is still not correct: {:?} != {:?}",
|
||||
booted_ota_type,
|
||||
fixed
|
||||
);
|
||||
}
|
||||
|
||||
Ok(booted_ota_type)
|
||||
}
|
||||
|
||||
pub fn next_partition(current: AppPartitionSubType) -> FatResult<AppPartitionSubType> {
|
||||
@@ -699,4 +684,3 @@ pub struct DetectionSensorResult {
|
||||
sensor_a: bool,
|
||||
sensor_b: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use embassy_sync::blocking_mutex::CriticalSectionMutex;
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
||||
use embedded_storage::ReadStorage;
|
||||
use esp_storage::{FlashStorage, FlashStorageError};
|
||||
use log::info;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MutexFlashStorage {
|
||||
@@ -52,6 +53,7 @@ impl NorFlash for MutexFlashStorage {
|
||||
const ERASE_SIZE: usize = 4096;
|
||||
|
||||
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
info!("Erasing flash from 0x{:x} to 0x{:x}", from, to);
|
||||
self.inner
|
||||
.lock(|f| NorFlash::erase(f.borrow_mut().deref_mut(), from, to))
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::hal::{
|
||||
};
|
||||
use crate::log::{LogMessage, LOG_ACCESS};
|
||||
use alloc::boxed::Box;
|
||||
use alloc::string::{ToString};
|
||||
use alloc::string::ToString;
|
||||
use async_trait::async_trait;
|
||||
use canapi::id::{classify, plant_id, MessageKind, IDENTIFY_CMD_OFFSET};
|
||||
use canapi::SensorSlot;
|
||||
@@ -23,7 +23,6 @@ use esp_hal::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig, Pull}
|
||||
use esp_hal::i2c::master::I2c;
|
||||
use esp_hal::twai::{EspTwaiError, EspTwaiFrame, StandardId, Twai, TwaiConfiguration, TwaiMode};
|
||||
use esp_hal::{twai, Async, Blocking};
|
||||
use esp_println::println;
|
||||
use ina219::address::{Address, Pin};
|
||||
use ina219::calibration::UnCalibrated;
|
||||
use ina219::configuration::{Configuration, OperatingMode, Resolution};
|
||||
@@ -133,10 +132,9 @@ pub struct V4<'a> {
|
||||
|
||||
extra1: Output<'a>,
|
||||
extra2: Output<'a>,
|
||||
twai_config: Option<TwaiConfiguration<'static, Blocking>>
|
||||
twai_config: Option<TwaiConfiguration<'static, Blocking>>,
|
||||
}
|
||||
|
||||
|
||||
pub(crate) async fn create_v4(
|
||||
peripherals: FreePeripherals<'static>,
|
||||
esp: Esp<'static>,
|
||||
@@ -263,12 +261,11 @@ pub(crate) async fn create_v4(
|
||||
extra1,
|
||||
extra2,
|
||||
can_power,
|
||||
twai_config
|
||||
twai_config,
|
||||
};
|
||||
Ok(Box::new(v))
|
||||
}
|
||||
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl<'a> BoardInteraction<'a> for V4<'a> {
|
||||
fn get_tank_sensor(&mut self) -> Result<&mut TankSensor<'a>, FatError> {
|
||||
@@ -377,69 +374,6 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
||||
Ok(moistures)
|
||||
}
|
||||
|
||||
async fn detect_sensors(&mut self, request: Detection) -> FatResult<Detection> {
|
||||
self.can_power.set_high();
|
||||
let config = self.twai_config.take().expect("twai config not set");
|
||||
let mut twai = config.into_async().start();
|
||||
|
||||
Timer::after_millis(1000).await;
|
||||
info!("Sending info messages now");
|
||||
// Send a few test messages per potential sensor node
|
||||
for plant in 0..PLANT_COUNT {
|
||||
for sensor in [Sensor::A, Sensor::B] {
|
||||
let detect = if sensor == Sensor::A {
|
||||
request.plant[plant].sensor_a
|
||||
} else {
|
||||
request.plant[plant].sensor_b
|
||||
};
|
||||
if !detect {
|
||||
continue;
|
||||
}
|
||||
let target =
|
||||
StandardId::new(plant_id(IDENTIFY_CMD_OFFSET, sensor.into(), (plant +1) as u16))
|
||||
.context(">> Could not create address for sensor! (plant: {}) <<")?;
|
||||
let can_buffer = [0_u8; 0];
|
||||
info!("Sending test message to plant {} sensor {sensor:?} with id {}", plant +1, target.as_raw());
|
||||
if let Some(frame) = EspTwaiFrame::new(target, &can_buffer) {
|
||||
// Try a few times; we intentionally ignore rx here and rely on stub logic
|
||||
let resu = twai
|
||||
.transmit_async(&frame)
|
||||
.with_timeout(Duration::from_millis(3000))
|
||||
.await;
|
||||
match resu {
|
||||
Ok(_) => {
|
||||
}
|
||||
Err(err) => {
|
||||
info!(
|
||||
"Error sending test message to plant {} sensor {sensor:?}: {err:?}", plant +1
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info!("Error building CAN frame");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut moistures = Moistures::default();
|
||||
let _ = wait_for_can_measurements(&mut twai, &mut moistures)
|
||||
.with_timeout(Duration::from_millis(3000))
|
||||
.await;
|
||||
|
||||
|
||||
let config = twai.stop().into_blocking();
|
||||
self.twai_config.replace(config);
|
||||
|
||||
self.can_power.set_low();
|
||||
|
||||
|
||||
let result = moistures.into();
|
||||
|
||||
info!("Autodetection result: {result:?}");
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
|
||||
async fn general_fault(&mut self, enable: bool) {
|
||||
hold_disable(23);
|
||||
self.general_fault.set_level(enable.into());
|
||||
@@ -482,7 +416,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
||||
LOG_ACCESS
|
||||
.lock()
|
||||
.await
|
||||
.log(LogMessage::TestSensor, a, b, &(plant+1).to_string(), "")
|
||||
.log(LogMessage::TestSensor, a, b, &(plant + 1).to_string(), "")
|
||||
.await;
|
||||
}
|
||||
Timer::after_millis(10).await;
|
||||
@@ -509,8 +443,74 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn detect_sensors(&mut self, request: Detection) -> FatResult<Detection> {
|
||||
self.can_power.set_high();
|
||||
let config = self.twai_config.take().expect("twai config not set");
|
||||
let mut twai = config.into_async().start();
|
||||
|
||||
Timer::after_millis(1000).await;
|
||||
info!("Sending info messages now");
|
||||
// Send a few test messages per potential sensor node
|
||||
for plant in 0..PLANT_COUNT {
|
||||
for sensor in [Sensor::A, Sensor::B] {
|
||||
let detect = if sensor == Sensor::A {
|
||||
request.plant[plant].sensor_a
|
||||
} else {
|
||||
request.plant[plant].sensor_b
|
||||
};
|
||||
if !detect {
|
||||
continue;
|
||||
}
|
||||
let target = StandardId::new(plant_id(
|
||||
IDENTIFY_CMD_OFFSET,
|
||||
sensor.into(),
|
||||
(plant + 1) as u16,
|
||||
))
|
||||
.context(">> Could not create address for sensor! (plant: {}) <<")?;
|
||||
let can_buffer = [0_u8; 0];
|
||||
info!(
|
||||
"Sending test message to plant {} sensor {sensor:?} with id {}",
|
||||
plant + 1,
|
||||
target.as_raw()
|
||||
);
|
||||
if let Some(frame) = EspTwaiFrame::new(target, &can_buffer) {
|
||||
// Try a few times; we intentionally ignore rx here and rely on stub logic
|
||||
let resu = twai
|
||||
.transmit_async(&frame)
|
||||
.with_timeout(Duration::from_millis(3000))
|
||||
.await;
|
||||
match resu {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
info!(
|
||||
"Error sending test message to plant {} sensor {sensor:?}: {err:?}",
|
||||
plant + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info!("Error building CAN frame");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut moistures = Moistures::default();
|
||||
let _ = wait_for_can_measurements(&mut twai, &mut moistures)
|
||||
.with_timeout(Duration::from_millis(3000))
|
||||
.await;
|
||||
|
||||
let config = twai.stop().into_blocking();
|
||||
self.twai_config.replace(config);
|
||||
|
||||
self.can_power.set_low();
|
||||
|
||||
let result = moistures.into();
|
||||
|
||||
info!("Autodetection result: {result:?}");
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_can_measurements(
|
||||
as_async: &mut Twai<'_, Async>,
|
||||
@@ -538,10 +538,12 @@ async fn wait_for_can_measurements(
|
||||
let frequency = u32::from_be_bytes(bytes);
|
||||
match sensor {
|
||||
SensorSlot::A => {
|
||||
moistures.sensor_a_hz[plant-1] = Some(frequency as f32);
|
||||
moistures.sensor_a_hz[plant - 1] =
|
||||
Some(frequency as f32);
|
||||
}
|
||||
SensorSlot::B => {
|
||||
moistures.sensor_b_hz[plant-1] = Some(frequency as f32);
|
||||
moistures.sensor_b_hz[plant - 1] =
|
||||
Some(frequency as f32);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -29,8 +29,8 @@ 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 alloc::{format, vec};
|
||||
use chrono::{DateTime, Datelike, Timelike, Utc};
|
||||
use chrono_tz::Tz::{self, UTC};
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
@@ -501,16 +501,17 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
||||
|
||||
info!("state of charg");
|
||||
let is_day = board.board_hal.is_day();
|
||||
let battery_state = board.board_hal.get_battery_monitor().get_state().await.unwrap_or(BatteryState::Unknown);
|
||||
let battery_state = board
|
||||
.board_hal
|
||||
.get_battery_monitor()
|
||||
.get_state()
|
||||
.await
|
||||
.unwrap_or(BatteryState::Unknown);
|
||||
info!("Battery state is {battery_state:?}");
|
||||
|
||||
let state_of_charge = match &battery_state {
|
||||
BatteryState::Unknown => {
|
||||
0
|
||||
}
|
||||
BatteryState::Info(data) => {
|
||||
data.state_of_charge
|
||||
}
|
||||
BatteryState::Unknown => 0,
|
||||
BatteryState::Info(data) => data.state_of_charge,
|
||||
};
|
||||
|
||||
let mut light_state = LightState {
|
||||
@@ -529,22 +530,10 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
||||
board.board_hal.get_config().night_lamp.night_lamp_hour_end,
|
||||
);
|
||||
|
||||
if state_of_charge
|
||||
< board
|
||||
.board_hal
|
||||
.get_config()
|
||||
.night_lamp
|
||||
.low_soc_cutoff
|
||||
{
|
||||
if state_of_charge < board.board_hal.get_config().night_lamp.low_soc_cutoff {
|
||||
board.board_hal.get_esp().set_low_voltage_in_cycle();
|
||||
info!("Set low voltage in cycle");
|
||||
} else if state_of_charge
|
||||
> board
|
||||
.board_hal
|
||||
.get_config()
|
||||
.night_lamp
|
||||
.low_soc_restore
|
||||
{
|
||||
} else if state_of_charge > board.board_hal.get_config().night_lamp.low_soc_restore {
|
||||
board.board_hal.get_esp().clear_low_voltage_in_cycle();
|
||||
info!("Clear low voltage in cycle");
|
||||
}
|
||||
@@ -793,7 +782,7 @@ pub async fn do_secure_pump(
|
||||
|
||||
async fn update_charge_indicator(
|
||||
board: &mut MutexGuard<'static, CriticalSectionRawMutex, HAL<'static>>,
|
||||
) -> FatResult<()>{
|
||||
) -> 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
|
||||
let current = board.board_hal.get_mptt_current().await?;
|
||||
@@ -801,10 +790,9 @@ async fn update_charge_indicator(
|
||||
.board_hal
|
||||
.set_charge_indicator(current.as_milliamperes() > 20_f64)
|
||||
.await?;
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
async fn publish_tank_state(
|
||||
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
|
||||
tank_state: &TankState,
|
||||
@@ -984,11 +972,7 @@ async fn publish_mppt_state(
|
||||
async fn publish_battery_state(
|
||||
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
|
||||
) -> () {
|
||||
let state = board
|
||||
.board_hal
|
||||
.get_battery_monitor()
|
||||
.get_state()
|
||||
.await;
|
||||
let state = board.board_hal.get_battery_monitor().get_state().await;
|
||||
let value = match state {
|
||||
Ok(state) => {
|
||||
let json = serde_json::to_string(&state).unwrap().to_owned();
|
||||
@@ -1021,7 +1005,7 @@ async fn wait_infinity(
|
||||
loop {
|
||||
{
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
match update_charge_indicator(&mut board).await{
|
||||
match update_charge_indicator(&mut board).await {
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
if !suppress_further_mppt_error {
|
||||
@@ -1104,35 +1088,38 @@ async fn wait_infinity(
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_serial_config(board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'_>>, serial_config_receive: &AtomicBool, reboot_now: &AtomicBool) -> FatResult<()> {
|
||||
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(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(())
|
||||
}
|
||||
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(())
|
||||
@@ -1158,7 +1145,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
}
|
||||
}
|
||||
println!("Hal init done, starting logic");
|
||||
|
||||
panic!("test");
|
||||
match safe_main(spawner).await {
|
||||
// this should not get triggered, safe_main should not return but go into deep sleep or reboot
|
||||
Ok(_) => {
|
||||
|
||||
@@ -16,7 +16,6 @@ pub enum MoistureSensorError {
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize)]
|
||||
pub enum MoistureSensorState {
|
||||
Disabled,
|
||||
MoistureValue { raw_hz: f32, moisture_percent: f32 },
|
||||
SensorError(MoistureSensorError),
|
||||
}
|
||||
@@ -117,12 +116,11 @@ impl PlantState {
|
||||
plant_id: usize,
|
||||
board: &mut HAL<'_>,
|
||||
) -> Self {
|
||||
let sensor_a = { //if board.board_hal.get_config().plants[plant_id].sensor_a {
|
||||
let sensor_a = {
|
||||
//if board.board_hal.get_config().plants[plant_id].sensor_a {
|
||||
let raw = moistures.sensor_a_hz[plant_id];
|
||||
match raw {
|
||||
None => {
|
||||
MoistureSensorState::SensorError(MoistureSensorError::NoMessage)
|
||||
}
|
||||
None => MoistureSensorState::SensorError(MoistureSensorError::NoMessage),
|
||||
Some(raw) => {
|
||||
match map_range_moisture(
|
||||
raw,
|
||||
@@ -141,17 +139,15 @@ impl PlantState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}; // else {
|
||||
// MoistureSensorState::Disabled
|
||||
//};
|
||||
// MoistureSensorState::Disabled
|
||||
//};
|
||||
|
||||
let sensor_b = { //if board.board_hal.get_config().plants[plant_id].sensor_b {
|
||||
let sensor_b = {
|
||||
//if board.board_hal.get_config().plants[plant_id].sensor_b {
|
||||
let raw = moistures.sensor_b_hz[plant_id];
|
||||
match raw {
|
||||
None => {
|
||||
MoistureSensorState::SensorError(MoistureSensorError::NoMessage)
|
||||
}
|
||||
None => MoistureSensorState::SensorError(MoistureSensorError::NoMessage),
|
||||
Some(raw) => {
|
||||
match map_range_moisture(
|
||||
raw,
|
||||
@@ -171,8 +167,8 @@ impl PlantState {
|
||||
}
|
||||
}
|
||||
}; // else {
|
||||
// MoistureSensorState::Disabled
|
||||
//};
|
||||
// MoistureSensorState::Disabled
|
||||
//};
|
||||
|
||||
let previous_pump = board.board_hal.get_esp().last_pump_time(plant_id);
|
||||
let consecutive_pump_count = board.board_hal.get_esp().consecutive_pump_count(plant_id);
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::plant_state::{MoistureSensorState, PlantState};
|
||||
use crate::tank::determine_tank_state;
|
||||
use crate::{get_version, BOARD_ACCESS};
|
||||
use alloc::format;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use chrono_tz::Tz;
|
||||
use core::str::FromStr;
|
||||
@@ -43,7 +43,6 @@ where
|
||||
plant_state.push(PlantState::read_hardware_state(moistures, i, &mut board).await);
|
||||
}
|
||||
let a = Vec::from_iter(plant_state.iter().map(|s| match &s.sensor_a {
|
||||
MoistureSensorState::Disabled => "disabled".to_string(),
|
||||
MoistureSensorState::MoistureValue {
|
||||
raw_hz,
|
||||
moisture_percent,
|
||||
@@ -53,7 +52,6 @@ where
|
||||
MoistureSensorState::SensorError(err) => format!("{err:?}"),
|
||||
}));
|
||||
let b = Vec::from_iter(plant_state.iter().map(|s| match &s.sensor_b {
|
||||
MoistureSensorState::Disabled => "disabled".to_string(),
|
||||
MoistureSensorState::MoistureValue {
|
||||
raw_hz,
|
||||
moisture_percent,
|
||||
@@ -128,11 +126,7 @@ pub(crate) async fn get_battery_state<T, const N: usize>(
|
||||
_request: &mut Connection<'_, T, N>,
|
||||
) -> FatResult<Option<String>> {
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
let battery_state = board
|
||||
.board_hal
|
||||
.get_battery_monitor()
|
||||
.get_state()
|
||||
.await?;
|
||||
let battery_state = board.board_hal.get_battery_monitor().get_state().await?;
|
||||
Ok(Some(serde_json::to_string(&battery_state)?))
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ where
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
board.board_hal.progress(chunk as u32).await;
|
||||
// Erase next block if we are at a 4K boundary (including the first block at offset 0)
|
||||
info!("erasing and writing block 0x{offset:x}");
|
||||
board
|
||||
.board_hal
|
||||
.get_esp()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::config::PlantControllerConfig;
|
||||
use crate::fat_error::FatResult;
|
||||
use crate::hal::{esp_set_time, Detection, DetectionRequest};
|
||||
use crate::hal::{esp_set_time, Detection};
|
||||
use crate::webserver::read_up_to_bytes_from_request;
|
||||
use crate::{do_secure_pump, BOARD_ACCESS};
|
||||
use alloc::string::{String, ToString};
|
||||
@@ -142,9 +142,6 @@ where
|
||||
|
||||
board.board_hal.can_power(can_power_request.state).await?;
|
||||
let enable = can_power_request.state;
|
||||
info!(
|
||||
"set can power to {enable}"
|
||||
);
|
||||
info!("set can power to {enable}");
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user