Add firmware build timestamp support for sensors; update detection workflows and UI accordingly.

This commit is contained in:
Kai Börnert
2026-04-27 16:46:24 +02:00
parent c04109a76c
commit e0b8acd55c
11 changed files with 204 additions and 33 deletions

View File

@@ -6,7 +6,8 @@ use crate::hal::esp::{hold_disable, hold_enable, Esp};
use crate::hal::rtc::{BackupHeader, RTCModuleInteraction, EEPROM_PAGE, X25};
use crate::hal::water::TankSensor;
use crate::hal::{
BoardInteraction, Detection, FreePeripherals, Moistures, Sensor, I2C_DRIVER, PLANT_COUNT,
BoardInteraction, Detection, DetectionRequest, FreePeripherals, Moistures, Sensor, I2C_DRIVER,
PLANT_COUNT,
};
use crate::log::{log, LogMessage};
use alloc::boxed::Box;
@@ -143,6 +144,11 @@ pub struct V4<'a> {
extra1: Output<'a>,
extra2: Output<'a>,
twai_config: Option<TwaiConfiguration<'static, Blocking>>,
/// Last known firmware build timestamps per sensor (minutes since Unix epoch).
/// Updated during detect_sensors; preserved across normal measurement cycles.
sensor_a_build_minutes: [Option<u32>; PLANT_COUNT],
sensor_b_build_minutes: [Option<u32>; PLANT_COUNT],
}
pub(crate) async fn create_v4(
@@ -272,6 +278,8 @@ pub(crate) async fn create_v4(
extra2,
can_power,
twai_config,
sensor_a_build_minutes: [None; PLANT_COUNT],
sensor_b_build_minutes: [None; PLANT_COUNT],
};
Ok(Box::new(v))
}
@@ -393,6 +401,20 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
self.twai_config.replace(config);
self.can_power.set_low();
// Persist any firmware build timestamps received alongside moisture data.
if let Ok(ref moistures) = res {
for (i, v) in moistures.sensor_a_build_minutes.iter().enumerate() {
if v.is_some() {
self.sensor_a_build_minutes[i] = *v;
}
}
for (i, v) in moistures.sensor_b_build_minutes.iter().enumerate() {
if v.is_some() {
self.sensor_b_build_minutes[i] = *v;
}
}
}
res
}
@@ -535,7 +557,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
Ok(header)
}
async fn detect_sensors(&mut self, request: Detection) -> FatResult<Detection> {
async fn detect_sensors(&mut self, request: DetectionRequest) -> FatResult<Detection> {
self.can_power.set_high();
Timer::after_millis(500).await;
let config = self.twai_config.take().context("twai config not set")?;
@@ -558,6 +580,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
} else {
request.plant[plant].sensor_b
};
if !detect {
continue;
}
@@ -602,7 +625,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
let result: Detection = moistures.into();
info!("Autodetection result: {result:?}");
Ok(result)
Ok((result, moistures.sensor_a_build_minutes, moistures.sensor_b_build_minutes))
})
.await;
@@ -610,7 +633,20 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
self.twai_config.replace(config);
self.can_power.set_low();
res
match res {
Ok((detection, a_builds, b_builds)) => {
self.sensor_a_build_minutes = a_builds;
self.sensor_b_build_minutes = b_builds;
Ok(detection)
}
Err(e) => Err(e),
}
}
fn get_sensor_build_minutes(
&self,
) -> ([Option<u32>; PLANT_COUNT], [Option<u32>; PLANT_COUNT]) {
(self.sensor_a_build_minutes, self.sensor_b_build_minutes)
}
}
@@ -631,10 +667,10 @@ async fn wait_for_can_measurements(
"received message of kind {:?} (plant: {}, sensor: {:?})",
msg.0, msg.1, msg.2
);
let plant = msg.1 as usize;
let sensor = msg.2;
let data = can_frame.data();
if msg.0 == MessageKind::MoistureData {
let plant = msg.1 as usize;
let sensor = msg.2;
let data = can_frame.data();
info!("Received moisture data: {:?}", data);
if let Ok(bytes) = data.try_into() {
let frequency = u32::from_be_bytes(bytes);
@@ -651,6 +687,23 @@ async fn wait_for_can_measurements(
} else {
error!("Received moisture data with invalid length: {} (expected 4)", data.len());
}
} else if msg.0 == MessageKind::FirmwareBuild {
info!("Received firmware build data: {:?}", data);
if let Ok(bytes) = data.try_into() {
let build_minutes = u32::from_be_bytes(bytes);
match sensor {
SensorSlot::A => {
moistures.sensor_a_build_minutes[plant - 1] =
Some(build_minutes);
}
SensorSlot::B => {
moistures.sensor_b_build_minutes[plant - 1] =
Some(build_minutes);
}
}
} else {
error!("Received firmware build data with invalid length: {} (expected 4)", data.len());
}
}
}
}
@@ -677,10 +730,17 @@ impl From<Moistures> for Detection {
fn from(value: Moistures) -> Self {
let mut result = Detection::default();
for (plant, sensor) in value.sensor_a_hz.iter().enumerate() {
result.plant[plant].sensor_a = sensor.is_some();
if sensor.is_some() {
// Sensor responded; include build timestamp (0 = timestamp not reported)
result.plant[plant].sensor_a =
Some(value.sensor_a_build_minutes[plant].unwrap_or(0));
}
}
for (plant, sensor) in value.sensor_b_hz.iter().enumerate() {
result.plant[plant].sensor_b = sensor.is_some();
if sensor.is_some() {
result.plant[plant].sensor_b =
Some(value.sensor_b_build_minutes[plant].unwrap_or(0));
}
}
result
}