Skip to content

can

tested on simulator

Tier Tier 3
Protocol CAN 2.0A and 2.0B, CAN FD, SocketCAN and USB adapters through python-can
Covers motor controllers (VESC, ODrive, CubeMars), battery management systems, CAN sensor and IO nodes, vehicle and machine buses with documented frames, DIY CAN nodes
Hazard default medium
Conformance full
Tested with python-can virtual bus with a simulated motor drive

Estimated reach. Hundreds of documented node types. Any node whose broadcast frames and command frames are documented, or listed in a DBC file, can be described with byte and bit fields. That covers hobby and research motor drives such as VESC and ODrive, many battery management systems, and machines that publish periodic CANopen PDOs or J1939 PGNs. Values that need a request and reply protocol (CANopen SDO, J1939 transport, ISO-TP) are not covered, which leaves out much of the automotive diagnostic world.

The can driver reads signals from CAN frames and sends frames as commands and actions, through python-can on SocketCAN or USB adapters such as PCAN, Kvaser, slcan and gs_usb. Classic CAN with 11-bit and 29-bit ids and CAN FD up to 64 bytes both work. The driver is tested on a simulated bus and not yet on a real adapter.

Setup

uv tool install --compile-bytecode "halkit[can] @ git+https://github.com/sohan-bhat/OpenHAL"

On Linux, run sudo ip link set can0 up type can bitrate 500000 first. All nodes need the same bitrate and the bus needs 120 ohm termination at both ends. discover() lists SocketCAN interfaces that are up, and describe() suggests one channel per frame id heard.

ParameterDefaultMeaning
channelrequiredInterface such as can0, PCAN_USBBUS1 or /dev/ttyACM0. can: can0 is shorthand
interfacesocketcanAny python-can interface, such as pcan, kvaser, slcan, gs_usb or virtual
bitratenoneBit/s for adapters that set the rate. SocketCAN uses ip link
fdfalseCAN FD frames
data_bitratenoneCAN FD data phase bitrate
timeout1 sSend deadline

Channel maps

read:
  speed: { unit: rpm, map: { id: 0x101, byte: 0, length: 2 } }
  motor_temp: { unit: degC, map: { id: 0x101, byte: 2, offset: -40 } }
  fault: { type: bool, map: { id: 0x101, byte: 3, bit: 1, bits: 1 } }
write:
  setpoint: { unit: rpm, min: 0, max: 3000, map: { id: 0x201, byte: 0, length: 2, data: [0, 0, 0] } }
  enable:
    type: bool
    map: { id: 0x201, byte: 2, bits: 1, data: [0, 0, 0], state: { id: 0x101, byte: 3, bits: 1 } }
actions:
  clear_fault: { map: { id: 0x2FF, data: [1] } }
KeyDefaultMeaning
idrequiredFrame id
extendedtrue above 0x7FF29-bit id. Set for low ids on 29-bit buses such as J1939 or VESC
byte, length0, 1Start byte and length, 1 to 8
endianbigbig or little
signedfalseTwo's complement
bit, bits0, the restBit range from the least significant bit
scale, offset1, 0device value = raw × scale + offset
unitthe channel unitDevice unit, converted to the channel unit
on, off1, 0Raw bool values
lookupnoneRaw numbers to enum values, such as { 0: coast, 1: eco }
datazerosCommand and action frames. The other bytes
dlcthe longest fieldCommand and action frames. Frame length
statenoneControl channels. Status frame map that shows the result
argsnoneActions. Field map per argument

A command starts from the last payload sent with that id, so enable keeps the setpoint in the same frame. A reconnect starts again from data.

Behavior

  • Reads decode the newest frame per id and never transmit. An id not heard since connecting reads as no value.
  • Values round to the field's raw step and the reading returned shows the value sent. A value the field can't hold fails with nothing sent.
  • A sent frame doesn't prove the node acted, so check a state map or a read.
  • Frames are never queued or retried. A failed send raises DriverError and a hung send TimeoutError, with the frame in candump notation.
  • An interface that fails to open raises DriverError with the ip link command to try. Short frames, unmapped raw values and a downed bus raise DriverError on read.
  • health() reports receive errors and bus-off as down.

Example

openhal/drivers/can/examples/vesc-motor-controller.hal.yaml reads a VESC motor controller and sets an ERPM target. The safe state sends zero current so the motor coasts.

Limitations

  • Raw signals only. No CANopen SDO or NMT, J1939 multi-frame transport or ISO-TP, though periodic PDOs and PGNs read fine.
  • No DBC import, no remote frames, and fields of at most 8 bytes.

Hazard default and safe state

Medium. safe_state_support is file, so safe state frames need the bus and node up. Set the node's own command timeout, which stops most drives when frames stop.

License note

python-can is LGPL-3.0, used unmodified as an optional dependency with no code copied.

Sources