#![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 { 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 {}