136 lines
4.9 KiB
Rust
136 lines
4.9 KiB
Rust
#![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)
|
|
|
|
|
|
#[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 {}
|