Skip to content

Protocol

Tanmay Patil edited this page Sep 15, 2026 · 2 revisions

Protocol

The frame

CAN 2.0A, standard 11-bit identifier, 250 kbit/s, DLC = 5.

identifier   = 0x100 | target_node_id

data[0]  target node id        (253 = 0xFD, 254 = 0xFE)
data[1]  sender node id
data[2]  relay number          (1 or 2)
data[3]  state                 (0 = off, 1 = on)
data[4]  type                  (0 = CMD, 1 = ACK)

A command 253 → 254 ("relay 1 on") travels as:

can0  1FE   [5]  FE FD 01 01 00

and the applied-state acknowledgement comes back as:

can0  1FD   [5]  FD FE 01 01 01

Why the target lives in the identifier

CAN is content-addressed — the identifier field is the address and the priority. We could have put everything in the payload with a fixed ID, and it would have worked on the bench. Two reasons kept it in the ID:

  1. Hardware acceptance filters. Each node configures its TWAI filter to pass only 0x100 | its_own_id. Frames for the other floor are ACKed on the wire (every receiver must ACK — that is how CAN works) and then dropped by the filter without ever interrupting the CPU.
  2. Priority. If a real broadcast ever gets added (an "all off" emergency frame, for example) it gets a low identifier and wins arbitration deterministically. Payload addressing cannot do that.

What the firmware does

  • Buttons are INPUT_PULLUP, active-low, 40 ms debounce. Button mapping: B0 → relay1 ON, B1 → relay1 OFF, B2 → relay2 ON, B3 → relay2 OFF.
  • On a matching CMD frame the node switches the relay and returns an ACK frame so the sender's confirm LED blinks.
  • Alerts enabled: BUS_OFF, BUS_RECOVERED, ERR_PASS, TX_FAILED, RX_DATA. On bus-off the node calls twai_initiate_recovery() and restarts only after the driver reports BUS_RECOVERED; calling twai_start() early silently fails (we learned this the annoying way).

What the simulation models (and what it does not)

sim/can_sim.py is a bus model written to ISO 11898 semantics:

  • Builds the real bitstream: SOF, 11-bit ID, RTR/IDE/r0, DLC, data, CRC-15 (polynomial 0x4599), stuff bits, ACK slot, EOF, interframe space.
  • Arbitration is resolved bit-by-bit — dominant (0) beats recessive (1), losers stop driving and retry next frame.
  • The ACK slot is driven by every non-transmitting node, matching hardware.
  • TEC +8 on missing ACK, −1 on success; error-passive at ≥128, bus-off at ≥256, timed recovery after.

What it does not model, honestly:

  • The receive error counter (REC): the simulated channel is perfect, so receivers never accrue errors.
  • Bit-error detection, error frames (6-bit flag + delimiter), form/stuff checking on the receive side.
  • The physical layer itself: no rise times, no reflections, no real GPIO.
  • Extended 29-bit IDs and remote (RTR) frames; the protocol never uses them.
  • Identical-ID simultaneous frames merge arbitrarily rather than continuing arbitration into the data field.

Those are simplifications in our model; the protocol itself still behaves this way. If you want silicon-level truth, Espressif's QEMU fork emulates the TWAI peripheral and can bridge to a Linux vcan interface. Heavier to set up, but it runs the unmodified firmware.

CAN vs. the alternatives, briefly

Bus Multi-master Arbitration / error handling Cost/node Fit here
CAN yes in silicon ~₹700 any floor commands any floor
RS-485 + Modbus no (master polls) firmware ~₹400 needs a master + poll loop
KNX yes full stack, certified ₹4k+/channel industry-real, overkill
ESP-NOW yes WiFi MAC free nothing to demonstrate

RS-485 is cheaper and is what contractors actually install today. The honest defence of CAN here: this project's point is the protocol mechanics. In fact, a babbling node removing itself via bus-off is a demo Modbus cannot give you.

Clone this wiki locally