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 out of CAN bus frames and sends frames as commands and actions. The driver uses python-can, so it works with SocketCAN on Linux and with the USB adapters python-can supports (PCAN, Kvaser, slcan, gs_usb and others). Each channel's map says which frame id carries the value and where it sits in the data bytes, so motor drives, battery systems, sensors and homemade nodes work without device-specific code.

What it supports

  • Classic CAN with 11-bit and 29-bit identifiers, and CAN FD with up to 64 data bytes.
  • Read channels of type number, bool and enum, decoded from any byte range or bit range, big or little endian, signed or unsigned, with scale, offset and a device unit.
  • Control channels that place a value in a frame and send the frame, where several channels can share one frame.
  • Actions that send a fixed frame, with action arguments placed in it.
  • describe() listens to the bus for a moment and proposes one read channel per frame id it receives.
  • discover() lists SocketCAN interfaces that are up and sends nothing.

Setup

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

On Linux, bring the interface up first, for example sudo ip link set can0 up type can bitrate 500000. Every node on the bus must use the same bitrate, and the bus needs 120 ohm termination at both ends.

Parameters

ParameterDefaultMeaning
channelrequiredThe interface or channel name, such as can0, PCAN_USBBUS1 or /dev/ttyACM0. The shorthand can: can0 sets it
interfacesocketcanAny python-can interface name, such as socketcan, pcan, kvaser, slcan, gs_usb or virtual
bitratenoneBitrate in bit/s for adapters that set it themselves. SocketCAN takes it from ip link instead
fdfalseUse CAN FD frames
data_bitratenoneCAN FD data phase bitrate
timeout1 sHow long a send may take before it fails

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
idrequiredThe frame identifier
extendedtrue above 0x7FFUse a 29-bit identifier. Set extended to true for low ids on 29-bit buses such as J1939 or VESC
byte, length0, 1Where the value starts and how many bytes it spans, 1 to 8
endianbigByte order, big or little
signedfalseTwo's complement
bit, bits0, the restThe bit range inside those bytes, counted from the least significant bit
scale, offset1, 0device value = raw × scale + offset
unitthe channel unitThe unit the device value is in, which the driver converts to the channel unit
on, off1, 0Raw values for bool channels
lookupnoneRaw numbers to enum values, such as { 0: coast, 1: eco }
datazerosCommand and action frames. The bytes the rest of the frame carries
dlcthe longest fieldCommand and action frames. The frame length in bytes
statenoneControl channels. A read map for the status frame that shows the result
argsnoneActions. A field map for each action argument

A change starts from the last payload the driver sent with that id, so changing enable keeps the setpoint that setpoint put in the same frame. After reconnecting the frame starts from data again.

How reads and commands behave

  • Because CAN only broadcasts, a background thread keeps the newest frame per id, and a read decodes that frame with the time the frame arrived. An id that hasn't been received since connecting reads as no value, which the kernel treats as unknown, and reads never put anything on the bus.
  • Values are rounded to the field's raw step, and the reading a command returns shows the value actually sent. A value the field can't hold fails the command, and nothing is sent or clamped.
  • A successful send means the controller put the frame on the bus, which doesn't prove the node acted, so use a state map or a read channel to check whether the node acted.
  • Frames are never queued or retried, so a failed send fails the command.

Example

openhal/drivers/can/examples/vesc-motor-controller.hal.yaml reads speed, current, temperatures and input voltage from a VESC motor controller and sets an ERPM target. The file's safe state sends the zero current frame so the motor coasts.

Errors

  • An interface that can't be opened raises DriverError with the ip link command to try.
  • A frame too short for a field, a raw value a bool or enum map doesn't name, and a bus that went down raise DriverError on read.
  • A refused send raises DriverError and a send that hangs raises TimeoutError, each with the frame in candump notation.
  • health() reports the bus as down after a receive error, and reports bus-off where the interface can tell.

Limitations

  • The driver reads raw signals only. CANopen SDO and NMT, J1939 multi-frame transport and ISO-TP are not handled, so values that need a request and reply protocol can't be read, but the periodic PDOs and PGNs of those protocols can be read.
  • The driver has no DBC file import, so make the maps from the node's documentation or DBC file by hand.
  • Remote frames are not supported.
  • Fields are at most 8 bytes long.

Hazard default and safe state

The default hazard is medium, because CAN nodes are usually motor drives, battery systems and actuators. CAN has no emergency behavior of its own, so safe_state_support is file. The hub sends the device file's safe state frames like any other frame, which needs the bus and the node to be up. Most drives stop on their own when command frames stop arriving, and because that timeout is the real fallback when the hub or the bus fails, configure the timeout on the node.

License note

python-can is licensed LGPL-3.0, and OpenHAL uses python-can unmodified as an optional dependency and copies none of its code.

Sources