--- title: "CAN Bus IDs and Wire Format" date: 2026-05-21 draft: false description: "Quick reference for CAN bus identifiers, message formats, and on-the-wire data structures." tags: ["can", "protocol", "wire-format"] --- # CAN Bus IDs and Wire Format A concise technical reference for the PlantCtrl CAN bus protocol. ## Quick Reference Table | CAN ID (hex) | Message Type | Direction | Payload | |--------------|--------------|-----------|----------| | `0x03E8` | Moisture Data - Plant 0, Slot A | Sensor → Controller | u8 plant + u8 slot + u16 hz | | `0x0400` | Identify Command - Plant 0, Slot A | Controller → Sensor | *(empty)* | | `0x042F` | Moisture Data - Plant 15, Slot A | Sensor → Controller | u8 plant + u8 slot + u16 hz | | `0x0467` | Firmware Build - Plant 3, Slot B | Sensor → Controller | u32 build_minutes | ## ID Calculation Formula ``` ID = 0x03E8 + Message_Offset + Plant_Index (+ Slot_Offset if B) ``` ### Constants | Constant | Value (hex) | Description | |----------|-------------|-------------| | `SENSOR_BASE_ADDRESS` | `0x03E8` | Base address for all messages | | `MOISTURE_DATA_OFFSET` | `0x00` | Moisture data group | | `IDENTIFY_CMD_OFFSET` | `0x20` | Identify command group | | `FIRMWARE_BUILD_OFFSET` | `0x40` | Firmware build group | | `B_SLOT_OFFSET` | `0x10` | Offset for Slot B within a group | ### Message Type Offsets ```rust pub const MOISTURE_DATA_OFFSET: u16 = 0; // sensor → controller pub const IDENTIFY_CMD_OFFSET: u16 = 32; // controller → sensor pub const FIRMWARE_BUILD_OFFSET: u16 = 64; // sensor → controller ``` ## On-the-Wire Formats ### Moisture Data Frame (Sensor → Controller) **CAN ID**: `0x03E8 + offset + plant_index` (or `+ 16 + plant_index` for Slot B) | Byte | Field | Type | |------|-------|------| | 0 | `plant` | u8 (0–15) | | 1 | `sensor` | SensorSlot (A=0, B=1) | | 2-3 | `hz` | u16 big-endian | **Example**: Plant 7, Slot A, frequency 45 Hz ``` CAN ID: 0x0415 Payload: [07 00 00 2D] plant=7, sensor=A, hz=45 (0x002D) ``` ### Firmware Build Frame (Sensor → Controller) **CAN ID**: `0x03E8 + 64 + plant_index` (or `+ 80 + plant_index` for Slot B) | Byte | Field | Type | |------|-------|------| | 0-3 | `build_minutes` | u32 big-endian | **Example**: Build timestamp 1,745,239,200 minutes since epoch (May 2026) ``` CAN ID: 0x0440 Payload: [00 00 6A F8] build_minutes = 1,745,239,200 ``` ### Identify Command Frame (Controller → Sensor) **CAN ID**: `0x03E8 + 32 + plant_index` (or `+ 48 + plant_index` for Slot B) | Byte | Field | Type | |------|-------|------| | *(none)* | *(empty payload)* | - | **Example**: Identify Plant 5, Slot A ``` CAN ID: 0x0410 Payload: (empty) ``` ## Addressing Scheme Details ### Plant Index Range - **Valid**: 0–15 (decimal) or 1–8 on hardware jumpers (mapped internally as 0–7) - **Slot A**: `plant_index` = jumper value - 1 - **Slot B**: Same mapping, but ID offset differs by +16 ### Slot Selection | Hardware | Internal Value | |----------|---------------| | Jumper on PA3 (Low) | Slot A (0) | | Jumper on PA3 (High) | Slot B (1) | ## Error Detection IDs The sensor module monitors for unexpected messages: - **Moisture Data collision**: If a sensor receives moisture data addressed to itself, it triggers error code 1 (1 info blink, 2 warning blinks) - **CAN errors**: Bus-off, EWGF, EPVF flags trigger warning LED blinking ## Protocol Extensions To add new message types: 1. Define offset in `canapi/src/lib.rs`: ```rust pub const NEW_MESSAGE_OFFSET: u16 = 96; // Next available slot ``` 2. Implement message struct with bincode serialization 3. Add receive handler on both sides 4. Update documentation ## Binary Protocol Reference ### bincode v2 Serialization - **u8**: Single byte, no sign extension - **u16**: 2 bytes big-endian (network order) - **u32**: 4 bytes big-endian (network order) - No varints – fixed size for predictable CAN frame lengths ### CAN Frame Structure ``` | Arbitration Field | Control Field | Data Field (8 bytes) | |-------------------|---------------|----------------------| | 11-bit ID | RTR + IDE | Payload (max 4-6 bytes)| ``` All PlantCtrl messages fit within the 8-byte data field with room for CAN overhead.