switch to bulk measurements
This commit is contained in:
@@ -37,6 +37,8 @@ partition_table = "partitions.csv"
|
||||
|
||||
|
||||
[dependencies]
|
||||
# Shared CAN API
|
||||
canapi = { path = "canapi" }
|
||||
#ESP stuff
|
||||
esp-bootloader-esp-idf = { version = "0.2.0", features = ["esp32c6"] }
|
||||
esp-hal = { version = "=1.0.0-rc.0", features = [
|
||||
|
@@ -1,5 +1,3 @@
|
||||
use std::process::Command;
|
||||
|
||||
use vergen::EmitBuilder;
|
||||
|
||||
fn linker_be_nice() {
|
||||
@@ -50,72 +48,6 @@ fn linker_be_nice() {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
//webpack();
|
||||
linker_be_nice();
|
||||
let _ = EmitBuilder::builder().all_git().all_build().emit();
|
||||
}
|
||||
|
||||
fn webpack() {
|
||||
//println!("cargo:rerun-if-changed=./src/src_webpack");
|
||||
Command::new("rm")
|
||||
.arg("./src/webserver/bundle.js.gz")
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
match Command::new("cmd").spawn() {
|
||||
Ok(_) => {
|
||||
println!("Assuming build on windows");
|
||||
let output = Command::new("cmd")
|
||||
.arg("/K")
|
||||
.arg("npx")
|
||||
.arg("webpack")
|
||||
.current_dir("./src_webpack")
|
||||
.output()
|
||||
.unwrap();
|
||||
println!("status: {}", output.status);
|
||||
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
||||
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
||||
assert!(output.status.success());
|
||||
|
||||
// move webpack results to rust webserver src
|
||||
let _ = Command::new("cmd")
|
||||
.arg("/K")
|
||||
.arg("move")
|
||||
.arg("./src_webpack/bundle.js.gz")
|
||||
.arg("./src/webserver")
|
||||
.output()
|
||||
.unwrap();
|
||||
let _ = Command::new("cmd")
|
||||
.arg("/K")
|
||||
.arg("move")
|
||||
.arg("./src_webpack/index.html.gz")
|
||||
.arg("./src/webserver")
|
||||
.output()
|
||||
.unwrap();
|
||||
}
|
||||
Err(_) => {
|
||||
println!("Assuming build on linux");
|
||||
let output = Command::new("npx")
|
||||
.arg("webpack")
|
||||
.current_dir("./src_webpack")
|
||||
.output()
|
||||
.unwrap();
|
||||
println!("status: {}", output.status);
|
||||
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
||||
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
||||
assert!(output.status.success());
|
||||
|
||||
// move webpack results to rust webserver src
|
||||
let _ = Command::new("mv")
|
||||
.arg("./src_webpack/bundle.js.gz")
|
||||
.arg("./src/webserver")
|
||||
.output()
|
||||
.unwrap();
|
||||
let _ = Command::new("mv")
|
||||
.arg("./src_webpack/index.html.gz")
|
||||
.arg("./src/webserver")
|
||||
.output()
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
14
rust/canapi/Cargo.toml
Normal file
14
rust/canapi/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "canapi"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "canapi"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
bincode = { version = "2.0.1", default-features = false, features = ["derive"] }
|
138
rust/canapi/src/lib.rs
Normal file
138
rust/canapi/src/lib.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
#![no_std]
|
||||
//! CAN bus API shared crate for PlantCtrl sensors and controller.
|
||||
//! Addressing and messages are defined here to be reused by all bus participants.
|
||||
|
||||
use bincode::{Decode, Encode};
|
||||
|
||||
/// Total plants supported by addressing (0..=15)
|
||||
pub const MAX_PLANTS: u8 = 16;
|
||||
|
||||
/// Sensors per plant: 0..=1 => A/B
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
|
||||
#[repr(u8)]
|
||||
pub enum SensorSlot {
|
||||
A = 0,
|
||||
B = 1,
|
||||
}
|
||||
|
||||
impl SensorSlot {
|
||||
pub const fn from_index(idx: u8) -> Option<Self> {
|
||||
match idx {
|
||||
0 => Some(SensorSlot::A),
|
||||
1 => Some(SensorSlot::B),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Legacy sensor base address kept for compatibility with existing code.
|
||||
/// Each plant uses SENSOR_BASE_ADDRESS + plant_index (0..PLANT_COUNT-1).
|
||||
/// 11-bit standard ID space, safe range.
|
||||
pub const SENSOR_BASE_ADDRESS: u16 = 1000;
|
||||
|
||||
/// Typed topics within the SENSOR_BASE space.
|
||||
/// Additional offsets allow distinct message semantics while keeping plant-indexed layout.
|
||||
pub mod id {
|
||||
use crate::{SensorSlot, MAX_PLANTS, SENSOR_BASE_ADDRESS};
|
||||
|
||||
/// Number of plants addressable per sensor slot group
|
||||
pub const PLANTS_PER_GROUP: u16 = MAX_PLANTS as u16; // 16
|
||||
/// Offset applied for SensorSlot::B within a message group
|
||||
pub const B_OFFSET: u16 = PLANTS_PER_GROUP; // 16
|
||||
|
||||
// Message group base offsets relative to SENSOR_BASE_ADDRESS
|
||||
pub const MOISTURE_DATA_OFFSET: u16 = 0; // periodic data from sensor (sensor -> controller)
|
||||
pub const IDENTIFY_CMD_OFFSET: u16 = 32; // identify LED command (controller -> sensor)
|
||||
|
||||
// Convenience constants for per-slot base offsets
|
||||
pub const IDENTIFY_CMD_OFFSET_A: u16 = IDENTIFY_CMD_OFFSET + 0;
|
||||
pub const IDENTIFY_CMD_OFFSET_B: u16 = IDENTIFY_CMD_OFFSET + B_OFFSET;
|
||||
|
||||
#[inline]
|
||||
pub const fn plant_id(message_type_offset: u16, sensor: SensorSlot, plant: u16) -> u16 {
|
||||
match sensor {
|
||||
SensorSlot::A => SENSOR_BASE_ADDRESS + message_type_offset + plant,
|
||||
SensorSlot::B => SENSOR_BASE_ADDRESS + message_type_offset + B_OFFSET + plant,
|
||||
}
|
||||
}
|
||||
|
||||
/// Kinds of message spaces recognized by the addressing scheme.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MessageKind {
|
||||
MoistureData, // sensor -> controller
|
||||
IdentifyCmd, // controller -> sensor
|
||||
}
|
||||
|
||||
/// Try to classify a received 11-bit standard ID into a known message kind and extract plant and sensor slot.
|
||||
/// Returns (kind, plant, slot) on success.
|
||||
#[inline]
|
||||
pub const fn classify(id: u16) -> Option<(MessageKind, u8, SensorSlot)> {
|
||||
// Ensure the ID is within our base space
|
||||
if id < SENSOR_BASE_ADDRESS {
|
||||
return None;
|
||||
}
|
||||
let rel = id - SENSOR_BASE_ADDRESS;
|
||||
|
||||
// Helper: decode within a given group offset
|
||||
const fn decode_in_group(rel: u16, group_base: u16) -> Option<(u8, SensorSlot)> {
|
||||
if rel < group_base { return None; }
|
||||
let inner = rel - group_base;
|
||||
if inner < PLANTS_PER_GROUP { // A slot
|
||||
Some((inner as u8, SensorSlot::A))
|
||||
} else if inner >= B_OFFSET && inner < B_OFFSET + PLANTS_PER_GROUP { // B slot
|
||||
Some(((inner - B_OFFSET) as u8, SensorSlot::B))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Check known groups in order
|
||||
if let Some((plant, slot)) = decode_in_group(rel, MOISTURE_DATA_OFFSET) {
|
||||
return Some((MessageKind::MoistureData, plant, slot));
|
||||
}
|
||||
if let Some((plant, slot)) = decode_in_group(rel, IDENTIFY_CMD_OFFSET) {
|
||||
return Some((MessageKind::IdentifyCmd, plant, slot));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns Some((plant, slot)) regardless of message kind, if the id falls into any known group; otherwise None.
|
||||
#[inline]
|
||||
pub const fn extract_plant_slot(id: u16) -> Option<(u8, SensorSlot)> {
|
||||
match classify(id) {
|
||||
Some((_kind, plant, slot)) => Some((plant, slot)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if an id corresponds exactly to the given message kind, plant and slot.
|
||||
#[inline]
|
||||
pub const fn is_identify_for(id: u16, plant: u8, slot: SensorSlot) -> bool {
|
||||
id == plant_id(IDENTIFY_CMD_OFFSET, slot, plant as u16)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn is_moisture_data_for(id: u16, plant: u8, slot: SensorSlot) -> bool {
|
||||
id == plant_id(MOISTURE_DATA_OFFSET, slot, plant as u16)
|
||||
}
|
||||
}
|
||||
|
||||
/// Periodic moisture data sent by sensors.
|
||||
/// Fits into 5 bytes with bincode-v2 (no varint): u8 + u8 + u16 = 4, alignment may keep 4.
|
||||
#[derive(Debug, Clone, Copy, Encode, Decode)]
|
||||
pub struct MoistureData {
|
||||
pub plant: u8, // 0..MAX_PLANTS-1
|
||||
pub sensor: SensorSlot, // A/B
|
||||
pub hz: u16, // measured frequency of moisture sensor
|
||||
}
|
||||
|
||||
/// Request a sensor to report immediately (controller -> sensor).
|
||||
#[derive(Debug, Clone, Copy, Encode, Decode)]
|
||||
pub struct MoistureRequest {
|
||||
pub plant: u8,
|
||||
pub sensor: SensorSlot, // target sensor (sensor filters by this)
|
||||
}
|
||||
|
||||
/// Control a sensor's identify LED, if received by sensor, blink for a few seconds
|
||||
#[derive(Debug, Clone, Copy, Encode, Decode)]
|
||||
pub struct IdentifyLed {}
|
@@ -1,6 +1,5 @@
|
||||
use crate::fat_error::{FatError, FatResult};
|
||||
use crate::hal::Box;
|
||||
use alloc::string::String;
|
||||
use async_trait::async_trait;
|
||||
use bq34z100::{Bq34z100g1, Bq34z100g1Driver, Flags};
|
||||
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
||||
@@ -37,12 +36,6 @@ pub struct BatteryInfo {
|
||||
pub temperature: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub enum BatteryError {
|
||||
NoBatteryMonitor,
|
||||
CommunicationError(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub enum BatteryState {
|
||||
Unknown,
|
||||
|
@@ -595,8 +595,7 @@ impl Esp<'_> {
|
||||
if duration_in_ms == 0 {
|
||||
software_reset();
|
||||
} else {
|
||||
///let timer = TimerWakeupSource::new(core::time::Duration::from_millis(duration_in_ms));
|
||||
let timer = TimerWakeupSource::new(core::time::Duration::from_millis(5000));
|
||||
let timer = TimerWakeupSource::new(core::time::Duration::from_millis(duration_in_ms));
|
||||
let mut wake_pins: [(&mut dyn RtcPinWithResistors, WakeupLevel); 1] =
|
||||
[(&mut self.wake_gpio1, WakeupLevel::Low)];
|
||||
let ext1 = esp_hal::rtc_cntl::sleep::Ext1WakeupSource::new(&mut wake_pins);
|
||||
|
@@ -3,7 +3,7 @@ use crate::fat_error::{FatError, FatResult};
|
||||
use crate::hal::esp::Esp;
|
||||
use crate::hal::rtc::{BackupHeader, RTCModuleInteraction};
|
||||
use crate::hal::water::TankSensor;
|
||||
use crate::hal::{BoardInteraction, FreePeripherals, Sensor, TIME_ACCESS};
|
||||
use crate::hal::{BoardInteraction, FreePeripherals, Moistures, TIME_ACCESS};
|
||||
use crate::{
|
||||
bail,
|
||||
config::PlantControllerConfig,
|
||||
@@ -117,14 +117,11 @@ impl<'a> BoardInteraction<'a> for Initial<'a> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
|
||||
async fn measure_moisture_hz(
|
||||
&mut self,
|
||||
_plant: usize,
|
||||
_sensor: Sensor,
|
||||
) -> Result<f32, FatError> {
|
||||
async fn measure_moisture_hz(&mut self) -> Result<Moistures, FatError> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
|
||||
|
||||
async fn general_fault(&mut self, enable: bool) {
|
||||
self.general_fault.set_level(enable.into());
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
pub(crate) mod battery;
|
||||
mod can_api;
|
||||
// mod can_api; // replaced by external canapi crate
|
||||
pub mod esp;
|
||||
mod initial_hal;
|
||||
mod little_fs2storage_adapter;
|
||||
@@ -7,7 +7,7 @@ pub(crate) mod rtc;
|
||||
mod v3_hal;
|
||||
mod v3_shift_register;
|
||||
mod v4_hal;
|
||||
mod v4_sensor;
|
||||
pub(crate) mod v4_sensor;
|
||||
mod water;
|
||||
use crate::alloc::string::ToString;
|
||||
use crate::hal::rtc::{DS3231Module, RTCModuleInteraction};
|
||||
@@ -60,6 +60,7 @@ use bincode::{Decode, Encode};
|
||||
use bq34z100::Bq34z100g1Driver;
|
||||
use chrono::{DateTime, FixedOffset, Utc};
|
||||
use core::cell::RefCell;
|
||||
use canapi::SensorSlot;
|
||||
use ds323x::ic::DS3231;
|
||||
use ds323x::interface::I2cInterface;
|
||||
use ds323x::{DateTimeAccess, Ds323x};
|
||||
@@ -105,6 +106,8 @@ use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem};
|
||||
use littlefs2::object_safe::DynStorage;
|
||||
use log::{error, info, warn};
|
||||
use portable_atomic::AtomicBool;
|
||||
use serde::Serialize;
|
||||
|
||||
|
||||
pub static TIME_ACCESS: OnceLock<Mutex<CriticalSectionRawMutex, Rtc>> = OnceLock::new();
|
||||
|
||||
@@ -124,6 +127,15 @@ pub enum Sensor {
|
||||
B,
|
||||
}
|
||||
|
||||
impl Into<SensorSlot> for Sensor {
|
||||
fn into(self) -> SensorSlot {
|
||||
match self {
|
||||
Sensor::A => SensorSlot::A,
|
||||
Sensor::B => SensorSlot::B,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PlantHal {}
|
||||
|
||||
pub struct HAL<'a> {
|
||||
@@ -142,19 +154,19 @@ pub trait BoardInteraction<'a> {
|
||||
|
||||
fn is_day(&self) -> bool;
|
||||
//should be multsampled
|
||||
async fn light(&mut self, enable: bool) -> Result<(), FatError>;
|
||||
async fn pump(&mut self, plant: usize, enable: bool) -> Result<(), FatError>;
|
||||
async fn pump_current(&mut self, plant: usize) -> Result<Current, FatError>;
|
||||
async fn fault(&mut self, plant: usize, enable: bool) -> Result<(), FatError>;
|
||||
async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32, FatError>;
|
||||
async fn light(&mut self, enable: bool) -> FatResult<()>;
|
||||
async fn pump(&mut self, plant: usize, enable: bool) -> FatResult<()>;
|
||||
async fn pump_current(&mut self, plant: usize) -> FatResult<Current>;
|
||||
async fn fault(&mut self, plant: usize, enable: bool) -> FatResult<()>;
|
||||
async fn measure_moisture_hz(&mut self) -> Result<Moistures, FatError>;
|
||||
async fn general_fault(&mut self, enable: bool);
|
||||
async fn test(&mut self) -> Result<(), FatError>;
|
||||
async fn test(&mut self) -> FatResult<()>;
|
||||
fn set_config(&mut self, config: PlantControllerConfig);
|
||||
async fn get_mptt_voltage(&mut self) -> Result<Voltage, FatError>;
|
||||
async fn get_mptt_current(&mut self) -> Result<Current, FatError>;
|
||||
async fn get_mptt_voltage(&mut self) -> FatResult<Voltage>;
|
||||
async fn get_mptt_current(&mut self) -> FatResult<Current>;
|
||||
|
||||
// Return JSON string with autodetected sensors per plant. Default: not supported.
|
||||
async fn detect_sensors(&mut self) -> Result<alloc::string::String, FatError> {
|
||||
async fn detect_sensors(&mut self) -> FatResult<DetectionResult> {
|
||||
bail!("Autodetection is only available on v4 HAL with CAN bus");
|
||||
}
|
||||
|
||||
@@ -685,3 +697,19 @@ pub async fn esp_set_time(time: DateTime<FixedOffset>) -> FatResult<()> {
|
||||
.set_rtc_time(&time.to_utc())
|
||||
.await
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize)]
|
||||
pub struct Moistures{
|
||||
pub sensor_a_hz: [f32; PLANT_COUNT],
|
||||
pub sensor_b_hz: [f32; PLANT_COUNT],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct DetectionResult {
|
||||
plant: [DetectionSensorResult; crate::hal::PLANT_COUNT]
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct DetectionSensorResult{
|
||||
sensor_a: bool,
|
||||
sensor_b: bool,
|
||||
}
|
@@ -4,7 +4,7 @@ use crate::hal::esp::{hold_disable, hold_enable};
|
||||
use crate::hal::rtc::RTCModuleInteraction;
|
||||
use crate::hal::v3_shift_register::ShiftRegister40;
|
||||
use crate::hal::water::TankSensor;
|
||||
use crate::hal::{BoardInteraction, FreePeripherals, Sensor, PLANT_COUNT, TIME_ACCESS};
|
||||
use crate::hal::{BoardInteraction, FreePeripherals, Moistures, Sensor, PLANT_COUNT, TIME_ACCESS};
|
||||
use crate::log::{LogMessage, LOG_ACCESS};
|
||||
use crate::{
|
||||
config::PlantControllerConfig,
|
||||
@@ -170,6 +170,112 @@ pub(crate) fn create_v3(
|
||||
}))
|
||||
}
|
||||
|
||||
impl V3<'_> {
|
||||
|
||||
async fn inner_measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32, FatError> {
|
||||
let mut results = [0_f32; REPEAT_MOIST_MEASURE];
|
||||
for repeat in 0..REPEAT_MOIST_MEASURE {
|
||||
self.signal_counter.pause();
|
||||
self.signal_counter.clear();
|
||||
//Disable all
|
||||
{
|
||||
let shift_register = self.shift_register.lock().await;
|
||||
shift_register.decompose()[MS_4].set_high()?;
|
||||
}
|
||||
|
||||
let sensor_channel = match sensor {
|
||||
Sensor::A => match plant {
|
||||
0 => SENSOR_A_1,
|
||||
1 => SENSOR_A_2,
|
||||
2 => SENSOR_A_3,
|
||||
3 => SENSOR_A_4,
|
||||
4 => SENSOR_A_5,
|
||||
5 => SENSOR_A_6,
|
||||
6 => SENSOR_A_7,
|
||||
7 => SENSOR_A_8,
|
||||
_ => bail!("Invalid plant id {}", plant),
|
||||
},
|
||||
Sensor::B => match plant {
|
||||
0 => SENSOR_B_1,
|
||||
1 => SENSOR_B_2,
|
||||
2 => SENSOR_B_3,
|
||||
3 => SENSOR_B_4,
|
||||
4 => SENSOR_B_5,
|
||||
5 => SENSOR_B_6,
|
||||
6 => SENSOR_B_7,
|
||||
7 => SENSOR_B_8,
|
||||
_ => bail!("Invalid plant id {}", plant),
|
||||
},
|
||||
};
|
||||
|
||||
let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 };
|
||||
{
|
||||
let shift_register = self.shift_register.lock().await;
|
||||
let pin_0 = &mut shift_register.decompose()[MS_0];
|
||||
let pin_1 = &mut shift_register.decompose()[MS_1];
|
||||
let pin_2 = &mut shift_register.decompose()[MS_2];
|
||||
let pin_3 = &mut shift_register.decompose()[MS_3];
|
||||
if is_bit_set(0) {
|
||||
pin_0.set_high()?;
|
||||
} else {
|
||||
pin_0.set_low()?;
|
||||
}
|
||||
if is_bit_set(1) {
|
||||
pin_1.set_high()?;
|
||||
} else {
|
||||
pin_1.set_low()?;
|
||||
}
|
||||
if is_bit_set(2) {
|
||||
pin_2.set_high()?;
|
||||
} else {
|
||||
pin_2.set_low()?;
|
||||
}
|
||||
if is_bit_set(3) {
|
||||
pin_3.set_high()?;
|
||||
} else {
|
||||
pin_3.set_low()?;
|
||||
}
|
||||
|
||||
shift_register.decompose()[MS_4].set_low()?;
|
||||
shift_register.decompose()[SENSOR_ON].set_high()?;
|
||||
}
|
||||
let measurement = 100; //how long to measure and then extrapolate to hz
|
||||
let factor = 1000f32 / measurement as f32; //scale raw cound by this number to get hz
|
||||
|
||||
//give some time to stabilize
|
||||
Timer::after_millis(10).await;
|
||||
self.signal_counter.resume();
|
||||
Timer::after_millis(measurement).await;
|
||||
self.signal_counter.pause();
|
||||
{
|
||||
let shift_register = self.shift_register.lock().await;
|
||||
shift_register.decompose()[MS_4].set_high()?;
|
||||
shift_register.decompose()[SENSOR_ON].set_low()?;
|
||||
}
|
||||
Timer::after_millis(10).await;
|
||||
let unscaled = self.signal_counter.value();
|
||||
let hz = unscaled as f32 * factor;
|
||||
LOG_ACCESS
|
||||
.lock()
|
||||
.await
|
||||
.log(
|
||||
LogMessage::RawMeasure,
|
||||
unscaled as u32,
|
||||
hz as u32,
|
||||
&plant.to_string(),
|
||||
&format!("{sensor:?}"),
|
||||
)
|
||||
.await;
|
||||
results[repeat] = hz;
|
||||
}
|
||||
results.sort_by(|a, b| a.partial_cmp(b).unwrap()); // floats don't seem to implement total_ord
|
||||
|
||||
let mid = results.len() / 2;
|
||||
let median = results[mid];
|
||||
Ok(median)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl<'a> BoardInteraction<'a> for V3<'a> {
|
||||
fn get_tank_sensor(&mut self) -> Result<&mut TankSensor<'a>, FatError> {
|
||||
@@ -275,108 +381,24 @@ impl<'a> BoardInteraction<'a> for V3<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32, FatError> {
|
||||
let mut results = [0_f32; REPEAT_MOIST_MEASURE];
|
||||
for repeat in 0..REPEAT_MOIST_MEASURE {
|
||||
self.signal_counter.pause();
|
||||
self.signal_counter.clear();
|
||||
//Disable all
|
||||
{
|
||||
let shift_register = self.shift_register.lock().await;
|
||||
shift_register.decompose()[MS_4].set_high()?;
|
||||
}
|
||||
|
||||
let sensor_channel = match sensor {
|
||||
Sensor::A => match plant {
|
||||
0 => SENSOR_A_1,
|
||||
1 => SENSOR_A_2,
|
||||
2 => SENSOR_A_3,
|
||||
3 => SENSOR_A_4,
|
||||
4 => SENSOR_A_5,
|
||||
5 => SENSOR_A_6,
|
||||
6 => SENSOR_A_7,
|
||||
7 => SENSOR_A_8,
|
||||
_ => bail!("Invalid plant id {}", plant),
|
||||
},
|
||||
Sensor::B => match plant {
|
||||
0 => SENSOR_B_1,
|
||||
1 => SENSOR_B_2,
|
||||
2 => SENSOR_B_3,
|
||||
3 => SENSOR_B_4,
|
||||
4 => SENSOR_B_5,
|
||||
5 => SENSOR_B_6,
|
||||
6 => SENSOR_B_7,
|
||||
7 => SENSOR_B_8,
|
||||
_ => bail!("Invalid plant id {}", plant),
|
||||
},
|
||||
};
|
||||
|
||||
let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 };
|
||||
{
|
||||
let shift_register = self.shift_register.lock().await;
|
||||
let pin_0 = &mut shift_register.decompose()[MS_0];
|
||||
let pin_1 = &mut shift_register.decompose()[MS_1];
|
||||
let pin_2 = &mut shift_register.decompose()[MS_2];
|
||||
let pin_3 = &mut shift_register.decompose()[MS_3];
|
||||
if is_bit_set(0) {
|
||||
pin_0.set_high()?;
|
||||
} else {
|
||||
pin_0.set_low()?;
|
||||
}
|
||||
if is_bit_set(1) {
|
||||
pin_1.set_high()?;
|
||||
} else {
|
||||
pin_1.set_low()?;
|
||||
}
|
||||
if is_bit_set(2) {
|
||||
pin_2.set_high()?;
|
||||
} else {
|
||||
pin_2.set_low()?;
|
||||
}
|
||||
if is_bit_set(3) {
|
||||
pin_3.set_high()?;
|
||||
} else {
|
||||
pin_3.set_low()?;
|
||||
}
|
||||
|
||||
shift_register.decompose()[MS_4].set_low()?;
|
||||
shift_register.decompose()[SENSOR_ON].set_high()?;
|
||||
}
|
||||
let measurement = 100; //how long to measure and then extrapolate to hz
|
||||
let factor = 1000f32 / measurement as f32; //scale raw cound by this number to get hz
|
||||
|
||||
//give some time to stabilize
|
||||
Timer::after_millis(10).await;
|
||||
self.signal_counter.resume();
|
||||
Timer::after_millis(measurement).await;
|
||||
self.signal_counter.pause();
|
||||
{
|
||||
let shift_register = self.shift_register.lock().await;
|
||||
shift_register.decompose()[MS_4].set_high()?;
|
||||
shift_register.decompose()[SENSOR_ON].set_low()?;
|
||||
}
|
||||
Timer::after_millis(10).await;
|
||||
let unscaled = self.signal_counter.value();
|
||||
let hz = unscaled as f32 * factor;
|
||||
async fn measure_moisture_hz(&mut self) -> Result<Moistures, FatError> {
|
||||
let mut result = Moistures::default();
|
||||
for plant in 0..PLANT_COUNT {
|
||||
let a = self.inner_measure_moisture_hz(plant, Sensor::A).await;
|
||||
let b = self.inner_measure_moisture_hz(plant, Sensor::B).await;
|
||||
let aa = a.unwrap_or_else(|_| u32::MAX as f32);
|
||||
let bb = b.unwrap_or_else(|_| u32::MAX as f32);
|
||||
LOG_ACCESS
|
||||
.lock()
|
||||
.await
|
||||
.log(
|
||||
LogMessage::RawMeasure,
|
||||
unscaled as u32,
|
||||
hz as u32,
|
||||
&plant.to_string(),
|
||||
&format!("{sensor:?}"),
|
||||
)
|
||||
.log(LogMessage::TestSensor, aa as u32, bb as u32, &plant.to_string(), "")
|
||||
.await;
|
||||
results[repeat] = hz;
|
||||
result.sensor_a_hz[plant] = aa;
|
||||
result.sensor_b_hz[plant] = bb;
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
results.sort_by(|a, b| a.partial_cmp(b).unwrap()); // floats don't seem to implement total_ord
|
||||
|
||||
let mid = results.len() / 2;
|
||||
let median = results[mid];
|
||||
Ok(median)
|
||||
}
|
||||
|
||||
async fn general_fault(&mut self, enable: bool) {
|
||||
hold_disable(6);
|
||||
@@ -410,23 +432,7 @@ impl<'a> BoardInteraction<'a> for V3<'a> {
|
||||
self.pump(i, false).await?;
|
||||
Timer::after_millis(100).await;
|
||||
}
|
||||
for plant in 0..PLANT_COUNT {
|
||||
let a = self.measure_moisture_hz(plant, Sensor::A).await;
|
||||
let b = self.measure_moisture_hz(plant, Sensor::B).await;
|
||||
let aa = match a {
|
||||
Ok(a) => a as u32,
|
||||
Err(_) => u32::MAX,
|
||||
};
|
||||
let bb = match b {
|
||||
Ok(b) => b as u32,
|
||||
Err(_) => u32::MAX,
|
||||
};
|
||||
LOG_ACCESS
|
||||
.lock()
|
||||
.await
|
||||
.log(LogMessage::TestSensor, aa, bb, &plant.to_string(), "")
|
||||
.await;
|
||||
}
|
||||
self.measure_moisture_hz().await?;
|
||||
Timer::after_millis(10).await;
|
||||
Ok(())
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ use crate::hal::esp::{hold_disable, hold_enable, Esp};
|
||||
use crate::hal::rtc::RTCModuleInteraction;
|
||||
use crate::hal::v4_sensor::{SensorImpl, SensorInteraction};
|
||||
use crate::hal::water::TankSensor;
|
||||
use crate::hal::{BoardInteraction, FreePeripherals, Sensor, I2C_DRIVER, PLANT_COUNT, TIME_ACCESS};
|
||||
use crate::hal::{BoardInteraction, DetectionResult, FreePeripherals, Moistures, I2C_DRIVER, PLANT_COUNT, TIME_ACCESS};
|
||||
use crate::log::{LogMessage, LOG_ACCESS};
|
||||
use alloc::boxed::Box;
|
||||
use alloc::string::ToString;
|
||||
@@ -387,8 +387,8 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32, FatError> {
|
||||
self.sensor.measure_moisture_hz(plant, sensor).await
|
||||
async fn measure_moisture_hz(&mut self) -> Result<Moistures, FatError> {
|
||||
self.sensor.measure_moisture_hz().await
|
||||
}
|
||||
|
||||
async fn general_fault(&mut self, enable: bool) {
|
||||
@@ -426,21 +426,14 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
||||
self.pump(i, false).await?;
|
||||
Timer::after_millis(100).await;
|
||||
}
|
||||
let moisture = self.measure_moisture_hz().await?;
|
||||
for plant in 0..PLANT_COUNT {
|
||||
let a = self.measure_moisture_hz(plant, Sensor::A).await;
|
||||
let b = self.measure_moisture_hz(plant, Sensor::B).await;
|
||||
let aa = match a {
|
||||
Ok(a) => a as u32,
|
||||
Err(_) => u32::MAX,
|
||||
};
|
||||
let bb = match b {
|
||||
Ok(b) => b as u32,
|
||||
Err(_) => u32::MAX,
|
||||
};
|
||||
let a = moisture.sensor_a_hz[plant] as u32;
|
||||
let b = moisture.sensor_b_hz[plant] as u32;
|
||||
LOG_ACCESS
|
||||
.lock()
|
||||
.await
|
||||
.log(LogMessage::TestSensor, aa, bb, &plant.to_string(), "")
|
||||
.log(LogMessage::TestSensor, a, b, &plant.to_string(), "")
|
||||
.await;
|
||||
}
|
||||
Timer::after_millis(10).await;
|
||||
@@ -451,30 +444,15 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
||||
self.config = config;
|
||||
}
|
||||
|
||||
async fn get_mptt_voltage(&mut self) -> Result<Voltage, FatError> {
|
||||
async fn get_mptt_voltage(&mut self) -> FatResult<Voltage> {
|
||||
self.charger.get_mptt_voltage()
|
||||
}
|
||||
|
||||
async fn get_mptt_current(&mut self) -> Result<Current, FatError> {
|
||||
async fn get_mptt_current(&mut self) -> FatResult<Current> {
|
||||
self.charger.get_mppt_current()
|
||||
}
|
||||
|
||||
async fn detect_sensors(&mut self) -> Result<alloc::string::String, FatError> {
|
||||
// Delegate to sensor autodetect and build JSON
|
||||
let detected = self.sensor.autodetect().await?;
|
||||
// Build JSON manually to avoid exposing internal types
|
||||
let mut s = alloc::string::String::from("{\"plants\":[");
|
||||
for (i, (a, b)) in detected.iter().enumerate() {
|
||||
if i != 0 {
|
||||
s.push(',');
|
||||
}
|
||||
s.push_str("{\"a\":");
|
||||
s.push_str(if *a { "true" } else { "false" });
|
||||
s.push_str(",\"b\":");
|
||||
s.push_str(if *b { "true" } else { "false" });
|
||||
s.push('}');
|
||||
}
|
||||
s.push_str("]}");
|
||||
Ok(s)
|
||||
async fn detect_sensors(&mut self) -> FatResult<DetectionResult> {
|
||||
self.sensor.autodetect().await
|
||||
}
|
||||
}
|
||||
|
@@ -1,31 +1,33 @@
|
||||
use canapi::id::{classify, plant_id, MessageKind, IDENTIFY_CMD_OFFSET};
|
||||
use crate::bail;
|
||||
use crate::fat_error::{ContextExt, FatError, FatResult};
|
||||
use crate::hal::can_api::ResponseMoisture;
|
||||
use crate::hal::Sensor;
|
||||
use crate::hal::{can_api, Box};
|
||||
use canapi::{SensorSlot};
|
||||
use crate::hal::{DetectionResult, Moistures, Sensor};
|
||||
use crate::hal::Box;
|
||||
use crate::log::{LogMessage, LOG_ACCESS};
|
||||
use alloc::format;
|
||||
use alloc::string::ToString;
|
||||
use async_trait::async_trait;
|
||||
use bincode::config;
|
||||
use bincode::error::DecodeError;
|
||||
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_time::{Instant, Timer, WithTimeout};
|
||||
use embedded_can::Frame;
|
||||
use embedded_can::{Frame, Id};
|
||||
use esp_hal::gpio::Output;
|
||||
use esp_hal::i2c::master::I2c;
|
||||
use esp_hal::pcnt::unit::Unit;
|
||||
use esp_hal::twai::{EspTwaiFrame, StandardId, Twai, TwaiConfiguration};
|
||||
use esp_hal::{Async, Blocking};
|
||||
use log::info;
|
||||
use esp_hal::{Blocking};
|
||||
use log::{error, info, warn};
|
||||
use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
|
||||
|
||||
const REPEAT_MOIST_MEASURE: usize = 10;
|
||||
|
||||
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub trait SensorInteraction {
|
||||
async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> FatResult<f32>;
|
||||
async fn measure_moisture_hz(&mut self) -> FatResult<Moistures>;
|
||||
}
|
||||
|
||||
const MS0: u8 = 1_u8;
|
||||
@@ -49,13 +51,162 @@ pub enum SensorImpl {
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl SensorInteraction for SensorImpl {
|
||||
async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> FatResult<f32> {
|
||||
async fn measure_moisture_hz(&mut self) -> FatResult<Moistures> {
|
||||
match self {
|
||||
SensorImpl::PulseCounter {
|
||||
signal_counter,
|
||||
sensor_expander,
|
||||
..
|
||||
} => {
|
||||
let mut result = Moistures::default();
|
||||
for plant in 0..crate::hal::PLANT_COUNT{
|
||||
result.sensor_a_hz[plant] = Self::inner_pulse(plant, Sensor::A, signal_counter, sensor_expander).await?;
|
||||
info!("Sensor {} {:?}: {}", plant, Sensor::A, result.sensor_a_hz[plant]);
|
||||
result.sensor_b_hz[plant] = Self::inner_pulse(plant, Sensor::B, signal_counter, sensor_expander).await?;
|
||||
info!("Sensor {} {:?}: {}", plant, Sensor::B, result.sensor_b_hz[plant]);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
SensorImpl::CanBus {
|
||||
twai_config,
|
||||
can_power,
|
||||
} => {
|
||||
can_power.set_high();
|
||||
let config = twai_config.take().expect("twai config not set");
|
||||
let mut twai = config.start();
|
||||
|
||||
loop {
|
||||
let rec = twai.receive();
|
||||
match rec {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
info!("Error receiving CAN message: {:?}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer::after_millis(10).await;
|
||||
let can = Self::inner_can(&mut twai).await;
|
||||
|
||||
can_power.set_low();
|
||||
|
||||
let config = twai.stop();
|
||||
twai_config.replace(config);
|
||||
|
||||
let value = can?;
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl SensorImpl {
|
||||
pub async fn autodetect(&mut self) -> FatResult<DetectionResult> {
|
||||
match self {
|
||||
SensorImpl::PulseCounter { .. } => {
|
||||
bail!("Only CAN bus implementation supports autodetection")
|
||||
}
|
||||
SensorImpl::CanBus {
|
||||
twai_config,
|
||||
can_power,
|
||||
} => {
|
||||
// Power on CAN transceiver and start controller
|
||||
can_power.set_high();
|
||||
let config = twai_config.take().expect("twai config not set");
|
||||
let mut as_async = config.into_async().start();
|
||||
// Give CAN some time to stabilize
|
||||
Timer::after_millis(10).await;
|
||||
|
||||
// Send a few test messages per potential sensor node
|
||||
for plant in 0..crate::hal::PLANT_COUNT {
|
||||
for sensor in [Sensor::A, Sensor::B] {
|
||||
let target = StandardId::new(plant_id(IDENTIFY_CMD_OFFSET, sensor.into(), plant as u16)).context(">> Could not create address for sensor! (plant: {}) <<")?;
|
||||
let can_buffer = [0_u8; 0];
|
||||
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 = as_async.transmit_async(&frame).await;
|
||||
match resu {
|
||||
Ok(_) => {
|
||||
info!(
|
||||
"Sent test message to plant {} sensor {:?}",
|
||||
plant, sensor
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
info!("Error sending test message to plant {} sensor {:?}: {:?}", plant, sensor, err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info!("Error building CAN frame");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
let mut result = DetectionResult::default();
|
||||
loop {
|
||||
match as_async.receive_async().with_deadline(Instant::from_millis(100)).await {
|
||||
Ok(or) => {
|
||||
match or {
|
||||
Ok(can_frame) => {
|
||||
match can_frame.id() {
|
||||
Id::Standard(id) => {
|
||||
let rawid = id.as_raw();
|
||||
match classify(rawid) {
|
||||
None => {}
|
||||
Some(msg) => {
|
||||
if msg.0 == MessageKind::MoistureData {
|
||||
let plant = msg.1 as usize;
|
||||
let sensor = msg.2;
|
||||
match sensor {
|
||||
SensorSlot::A => {
|
||||
result.plant[plant].sensor_a = true;
|
||||
}
|
||||
SensorSlot::B => {
|
||||
result.plant[plant].sensor_b = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Id::Extended(ext) => {
|
||||
warn!("Received extended ID: {:?}", ext);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Err(err ) => {
|
||||
error!("Error receiving CAN message: {:?}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
info!("Received CAN message: {:?}", or);
|
||||
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Timeout receiving CAN message: {:?}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let config = as_async.stop().into_blocking();
|
||||
can_power.set_low();
|
||||
twai_config.replace(config);
|
||||
|
||||
info!("Autodetection result: {:?}", result);
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn inner_pulse(plant: usize, sensor: Sensor, signal_counter: &mut Unit<'_, 0>, sensor_expander: &mut Pca9535Immediate<I2cDevice<'static, CriticalSectionRawMutex, I2c<'static, Blocking>>>) -> FatResult<f32> {
|
||||
|
||||
let mut results = [0_f32; REPEAT_MOIST_MEASURE];
|
||||
for repeat in 0..REPEAT_MOIST_MEASURE {
|
||||
signal_counter.pause();
|
||||
@@ -129,140 +280,13 @@ impl SensorInteraction for SensorImpl {
|
||||
let mid = results.len() / 2;
|
||||
let median = results[mid];
|
||||
Ok(median)
|
||||
}
|
||||
|
||||
SensorImpl::CanBus {
|
||||
twai_config,
|
||||
can_power,
|
||||
} => {
|
||||
can_power.set_high();
|
||||
let config = twai_config.take().expect("twai config not set");
|
||||
let mut twai = config.start();
|
||||
|
||||
loop {
|
||||
let rec = twai.receive();
|
||||
match rec {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
info!("Error receiving CAN message: {:?}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer::after_millis(10).await;
|
||||
let can = Self::inner_can(plant, sensor, &mut twai).await;
|
||||
|
||||
can_power.set_low();
|
||||
|
||||
let config = twai.stop();
|
||||
twai_config.replace(config);
|
||||
|
||||
let value = can?;
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SensorImpl {
|
||||
pub async fn autodetect(&mut self) -> FatResult<[(bool, bool); crate::hal::PLANT_COUNT]> {
|
||||
match self {
|
||||
SensorImpl::PulseCounter { .. } => {
|
||||
bail!("Only CAN bus implementation supports autodetection")
|
||||
}
|
||||
SensorImpl::CanBus {
|
||||
twai_config,
|
||||
can_power,
|
||||
} => {
|
||||
// Power on CAN transceiver and start controller
|
||||
can_power.set_high();
|
||||
let mut config = twai_config.take().expect("twai config not set");
|
||||
let mut as_async = config.into_async().start();
|
||||
// Give CAN some time to stabilize
|
||||
Timer::after_millis(10).await;
|
||||
|
||||
// Send a few test messages per potential sensor node
|
||||
for plant in 0..crate::hal::PLANT_COUNT {
|
||||
for sensor in [Sensor::A, Sensor::B] {
|
||||
// Reuse CAN addressing scheme from moisture request
|
||||
let can_buffer = [0_u8; 8];
|
||||
let cfg = config::standard();
|
||||
if let Some(address) =
|
||||
StandardId::new(can_api::SENSOR_BASE_ADDRESS + plant as u16)
|
||||
{
|
||||
if let Some(frame) = EspTwaiFrame::new(address, &can_buffer) {
|
||||
// Try a few times; we intentionally ignore rx here and rely on stub logic
|
||||
let resu = as_async.transmit_async(&frame).await;
|
||||
match resu {
|
||||
Ok(_) => {
|
||||
info!(
|
||||
"Sent test message to plant {} sensor {:?}",
|
||||
plant, sensor
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
info!("Error sending test message to plant {} sensor {:?}: {:?}", plant, sensor, err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info!("Error building CAN frame");
|
||||
}
|
||||
} else {
|
||||
info!("Error creating address for sensor");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Poll for messages for ~100 ms
|
||||
let detect_timeout = Instant::now()
|
||||
.checked_add(embassy_time::Duration::from_millis(100))
|
||||
.unwrap();
|
||||
|
||||
loop {
|
||||
match as_async.receive() {
|
||||
Ok(or) => {
|
||||
info!("Received CAN message: {:?}", or);
|
||||
}
|
||||
Err(nb::Error::WouldBlock) => {
|
||||
if Instant::now() > detect_timeout {
|
||||
break;
|
||||
}
|
||||
Timer::after_millis(10).await;
|
||||
}
|
||||
Err(nb::Error::Other(err)) => {
|
||||
info!("Error receiving CAN message: {:?}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let config = as_async.stop().into_blocking();
|
||||
can_power.set_low();
|
||||
twai_config.replace(config);
|
||||
|
||||
// Stub: return no detections yet
|
||||
let mut result = [(false, false); crate::hal::PLANT_COUNT];
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn inner_can(
|
||||
plant: usize,
|
||||
sensor: Sensor,
|
||||
twai: &mut Twai<'static, Blocking>,
|
||||
) -> FatResult<f32> {
|
||||
let can_sensor: Sensor = sensor.into();
|
||||
//let request = RequestMoisture { sensor: can_sensor };
|
||||
let can_buffer = [0_u8; 8];
|
||||
let config = config::standard();
|
||||
//let encoded = bincode::encode_into_slice(&request, &mut can_buffer, config)?;
|
||||
|
||||
let address = StandardId::new(can_api::SENSOR_BASE_ADDRESS + plant as u16)
|
||||
.context(">> Could not create address for sensor! (plant: {}) <<")?;
|
||||
let request =
|
||||
EspTwaiFrame::new(address, &can_buffer[0..8]).context("Error building CAN frame")?;
|
||||
twai.transmit(&request)?;
|
||||
) -> FatResult<Moistures> {
|
||||
[0_u8; 8];
|
||||
config::standard();
|
||||
|
||||
let timeout = Instant::now()
|
||||
.checked_add(embassy_time::Duration::from_millis(100))
|
||||
@@ -271,26 +295,7 @@ impl SensorImpl {
|
||||
let answer = twai.receive();
|
||||
match answer {
|
||||
Ok(answer) => {
|
||||
let data = EspTwaiFrame::data(&answer);
|
||||
let response: Result<(ResponseMoisture, usize), DecodeError> =
|
||||
bincode::decode_from_slice(&data, config);
|
||||
info!("Can answer {response:?}");
|
||||
let value = response?.0;
|
||||
if (value.plant as usize) != plant {
|
||||
bail!(
|
||||
"Received answer for wrong plant! Expected: {}, got: {}",
|
||||
plant,
|
||||
value.plant
|
||||
);
|
||||
}
|
||||
if value.sensor != can_sensor {
|
||||
bail!(
|
||||
"Received answer for wrong sensor! Expected: {:?}, got: {:?}",
|
||||
can_sensor,
|
||||
value.sensor
|
||||
);
|
||||
}
|
||||
return Ok(value.hz as f32);
|
||||
info!("Received CAN message: {:?}", answer);
|
||||
}
|
||||
Err(error) => match error {
|
||||
nb::Error::Other(error) => {
|
||||
|
@@ -399,15 +399,17 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
||||
|
||||
publish_tank_state(&mut board, &tank_state, water_temp).await;
|
||||
|
||||
let moisture = board.board_hal.measure_moisture_hz().await?;
|
||||
|
||||
let plantstate: [PlantState; PLANT_COUNT] = [
|
||||
PlantState::read_hardware_state(0, &mut board).await,
|
||||
PlantState::read_hardware_state(1, &mut board).await,
|
||||
PlantState::read_hardware_state(2, &mut board).await,
|
||||
PlantState::read_hardware_state(3, &mut board).await,
|
||||
PlantState::read_hardware_state(4, &mut board).await,
|
||||
PlantState::read_hardware_state(5, &mut board).await,
|
||||
PlantState::read_hardware_state(6, &mut board).await,
|
||||
PlantState::read_hardware_state(7, &mut board).await,
|
||||
PlantState::read_hardware_state(moisture,0, &mut board).await,
|
||||
PlantState::read_hardware_state(moisture,1, &mut board).await,
|
||||
PlantState::read_hardware_state(moisture,2, &mut board).await,
|
||||
PlantState::read_hardware_state(moisture,3, &mut board).await,
|
||||
PlantState::read_hardware_state(moisture,4, &mut board).await,
|
||||
PlantState::read_hardware_state(moisture,5, &mut board).await,
|
||||
PlantState::read_hardware_state(moisture,6, &mut board).await,
|
||||
PlantState::read_hardware_state(moisture,7, &mut board).await,
|
||||
];
|
||||
|
||||
publish_plant_states(&mut board, &timezone_time.clone(), &plantstate).await;
|
||||
|
@@ -1,9 +1,9 @@
|
||||
use crate::hal::Moistures;
|
||||
use crate::{
|
||||
config::PlantConfig,
|
||||
hal::{Sensor, HAL},
|
||||
hal::HAL,
|
||||
in_time_range,
|
||||
};
|
||||
use alloc::string::{String, ToString};
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
use chrono_tz::Tz;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -15,7 +15,6 @@ const MOIST_SENSOR_MIN_FREQUENCY: f32 = 150.; // this is really, really dry, thi
|
||||
pub enum MoistureSensorError {
|
||||
ShortCircuit { hz: f32, max: f32 },
|
||||
OpenLoop { hz: f32, min: f32 },
|
||||
BoardError(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize)]
|
||||
@@ -116,14 +115,10 @@ fn map_range_moisture(
|
||||
}
|
||||
|
||||
impl PlantState {
|
||||
pub async fn read_hardware_state(plant_id: usize, board: &mut HAL<'_>) -> Self {
|
||||
pub async fn read_hardware_state(moistures: Moistures, plant_id: usize, board: &mut HAL<'_>) -> Self {
|
||||
let sensor_a = if board.board_hal.get_config().plants[plant_id].sensor_a {
|
||||
match board
|
||||
.board_hal
|
||||
.measure_moisture_hz(plant_id, Sensor::A)
|
||||
.await
|
||||
{
|
||||
Ok(raw) => match map_range_moisture(
|
||||
let raw = moistures.sensor_a_hz[plant_id];
|
||||
match map_range_moisture(
|
||||
raw,
|
||||
board.board_hal.get_config().plants[plant_id].moisture_sensor_min_frequency,
|
||||
board.board_hal.get_config().plants[plant_id].moisture_sensor_max_frequency,
|
||||
@@ -133,22 +128,14 @@ impl PlantState {
|
||||
moisture_percent,
|
||||
},
|
||||
Err(err) => MoistureSensorState::SensorError(err),
|
||||
},
|
||||
Err(err) => MoistureSensorState::SensorError(MoistureSensorError::BoardError(
|
||||
err.to_string(),
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
MoistureSensorState::Disabled
|
||||
};
|
||||
|
||||
let sensor_b = if board.board_hal.get_config().plants[plant_id].sensor_b {
|
||||
match board
|
||||
.board_hal
|
||||
.measure_moisture_hz(plant_id, Sensor::B)
|
||||
.await
|
||||
{
|
||||
Ok(raw) => match map_range_moisture(
|
||||
let raw = moistures.sensor_b_hz[plant_id];
|
||||
match map_range_moisture(
|
||||
raw,
|
||||
board.board_hal.get_config().plants[plant_id].moisture_sensor_min_frequency,
|
||||
board.board_hal.get_config().plants[plant_id].moisture_sensor_max_frequency,
|
||||
@@ -158,10 +145,6 @@ impl PlantState {
|
||||
moisture_percent,
|
||||
},
|
||||
Err(err) => MoistureSensorState::SensorError(err),
|
||||
},
|
||||
Err(err) => MoistureSensorState::SensorError(MoistureSensorError::BoardError(
|
||||
err.to_string(),
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
MoistureSensorState::Disabled
|
||||
|
@@ -37,9 +37,10 @@ where
|
||||
T: Read + Write,
|
||||
{
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
let moistures = board.board_hal.measure_moisture_hz().await?;
|
||||
let mut plant_state = Vec::new();
|
||||
for i in 0..PLANT_COUNT {
|
||||
plant_state.push(PlantState::read_hardware_state(i, &mut board).await);
|
||||
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(),
|
||||
|
@@ -52,7 +52,8 @@ pub(crate) async fn board_test() -> FatResult<Option<String>> {
|
||||
|
||||
pub(crate) async fn detect_sensors() -> FatResult<Option<String>> {
|
||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||
let json = board.board_hal.detect_sensors().await?;
|
||||
let result = board.board_hal.detect_sensors().await?;
|
||||
let json = serde_json::to_string(&result)?;
|
||||
Ok(Some(json))
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user