Skip to content

can

stable

Encode and decode CAN 2.0, CAN FD, and RTR frames, work with J1939 29-bit identifiers (PGN, priority, source address), and apply acceptance filters — pure protocol helpers with no bus I/O.

use plugin can::{encode_frame, decode_frame, j1939_pgn, …}
13 functions Systems
/ filter jk navigate Esc clear
Functions (13)
  1. encode_frame Encode a CAN 2.0 data frame into bytes
  2. decode_frame Decode a frame back into id, data, and flags
  3. j1939_pgn Extract the Parameter Group Number from a J1939 ID
  4. j1939_source Extract the source address from a J1939 ID
  5. j1939_priority Extract the priority from a J1939 ID
  6. build_j1939_id Build a 29-bit J1939 CAN ID from its parts
  7. dlc_to_length Map a DLC code to its payload length
  8. length_to_dlc Map a payload length to its DLC code
  9. is_standard_id Check if an ID fits in 11 bits
  10. is_extended_id Check if an ID fits in 29 bits
  11. encode_rtr Encode a remote transmission request frame
  12. filter_match Test an ID against a filter/mask pair
  13. encode_fd_frame Encode a CAN FD frame with up to 64 data bytes

Overview

The can plugin is a pure, I/O-free toolkit for the Controller Area Network family of protocols: classic CAN 2.0, CAN FD, and the SAE J1939 higher-layer protocol used in heavy-duty vehicles. It has no concept of a live bus — instead it turns frames into byte buffers and back, packs and unpacks 29-bit J1939 identifiers, maps between DLC codes and payload lengths, and evaluates hardware-style acceptance filters. Frames are represented as plain byte arrays produced by the encode_* functions and read back with decode_frame, which returns a table of {id, data, extended, rtr, dlc}. Reach for it when you need to build, parse, or route CAN traffic in software while the actual transmit/receive happens elsewhere (a driver, a socket, or a test harness).

Common patterns

Round-trip a classic frame through encode and decode:

use plugin can::{encode_frame, decode_frame}

let frame = encode_frame(0x1A0, [0x11, 0x22, 0x33], false)
let msg = decode_frame(frame)
print("id={msg["id"]} dlc={msg["dlc"]} rtr={msg["rtr"]}")

Compose and then decompose a J1939 identifier:

use plugin can::{build_j1939_id, j1939_pgn, j1939_priority, j1939_source}

let id = build_j1939_id(6, 65265, 17)   // priority 6, PGN 65265, source 0x11
print("pgn={j1939_pgn(id)} prio={j1939_priority(id)} src={j1939_source(id)}")

Apply an acceptance filter before processing an incoming frame:

use plugin can::{decode_frame, filter_match}

let msg = decode_frame(incoming)
// Only accept IDs whose top nibble is 0x1
if filter_match(msg["id"], 0x100, 0x700) {
  print("accepted id {msg["id"]}")
}

Encode a CAN 2.0 data frame into bytes

Encodes a classic CAN 2.0 data frame as id (4 bytes big-endian) + flags (1 byte: bit0=extended, bit1=rtr) + DLC (1 byte) + data. data may be at most 8 bytes; extended is optional and defaults to false. Errors if a standard ID exceeds 0x7FF or an extended ID exceeds 0x1FFFFFFF.

use plugin can::{encode_frame}

let frame = encode_frame(0x123, [0x01, 0x02, 0x03, 0x04], false)
print("frame: {frame}")

An extended (29-bit) frame sets the extended flag, which lets the ID exceed 0x7FF:

use plugin can::{encode_frame, decode_frame}

let frame = encode_frame(0x18FF0011, [0xAA, 0xBB], true)
let msg = decode_frame(frame)
print("extended={msg["extended"]} id={msg["id"]}")

Decode a frame back into id, data, and flags

Decodes a frame produced by encode_frame, encode_rtr, or encode_fd_frame back into its parts. Errors if the input is shorter than the 6-byte header.

use plugin can::{encode_frame, decode_frame}

let frame = encode_frame(0x123, [0xDE, 0xAD], false)
let msg = decode_frame(frame)
print("id={msg["id"]} dlc={msg["dlc"]} extended={msg["extended"]}")

Extract the Parameter Group Number from a J1939 ID

Extracts the 18-bit Parameter Group Number from a 29-bit J1939 CAN ID. For PDU1 messages (PDU Format < 240) the PDU Specific byte is a destination address and is excluded from the PGN; for PDU2 messages it is included.

use plugin can::{j1939_pgn}

// Engine speed (EEC1) message
let pgn = j1939_pgn(0x0CF00400)
print("PGN: {pgn}")  // 61444

Extract the source address from a J1939 ID

Extracts the source address (bits 0-7) from a 29-bit J1939 CAN ID, identifying which ECU sent the message.

use plugin can::{j1939_source}

let src = j1939_source(0x0CF00400)
print("source: {src}")  // 0

Extract the priority from a J1939 ID

Extracts the message priority (bits 26-28) from a 29-bit J1939 CAN ID. Priority 0 is highest, 7 is lowest.

use plugin can::{j1939_priority}

let prio = j1939_priority(0x0CF00400)
print("priority: {prio}")  // 3

Build a 29-bit J1939 CAN ID from its parts

Builds a 29-bit J1939 CAN ID from a priority (0-7), an 18-bit PGN (≤ 0x3FFFF), and a source address (0-255). Inverse of the j1939_* extractors.

use plugin can::{build_j1939_id, j1939_pgn}

let id = build_j1939_id(3, 61444, 0)
print(j1939_pgn(id) == 61444)  // true

Use it to address a specific ECU by source, then encode a frame for that ID:

use plugin can::{build_j1939_id, encode_frame}

let id = build_j1939_id(6, 65265, 0x11)
let frame = encode_frame(id, [0x00, 0x7D, 0x7D], true)
print("frame bytes: {frame}")

Map a DLC code to its payload length

Maps a DLC code (0-15) to the actual payload length in bytes. DLC 0-8 map directly; DLC 9-15 use the CAN FD lengths 12, 16, 20, 24, 32, 48, and 64.

use plugin can::{dlc_to_length}

print(dlc_to_length(8))   // 8
print(dlc_to_length(15))  // 64

Map a payload length to its DLC code

Maps a payload length (0-64 bytes) to its DLC code, rounding up to the next valid CAN FD length. Inverse of dlc_to_length.

use plugin can::{length_to_dlc}

print(length_to_dlc(8))   // 8
print(length_to_dlc(20))  // 11
print(length_to_dlc(33))  // 14 (rounds up to 48 bytes)

Check if an ID fits in 11 bits

Returns true if the ID fits in 11 bits (≤ 0x7FF), i.e. it is valid as a standard CAN identifier.

use plugin can::{is_standard_id}

print(is_standard_id(0x7FF))  // true
print(is_standard_id(0x800))  // false

Check if an ID fits in 29 bits

Returns true if the ID fits in 29 bits (≤ 0x1FFFFFFF), i.e. it is valid as an extended CAN identifier.

use plugin can::{is_extended_id}

print(is_extended_id(0x0CF00400))  // true
print(is_extended_id(0x20000000))  // false

Encode a remote transmission request frame

Encodes a remote transmission request frame: same layout as encode_frame but with the RTR flag set and no data bytes. dlc (0-8) tells the responder how many bytes are being requested; extended is optional and defaults to false.

use plugin can::{encode_rtr, decode_frame}

let req = encode_rtr(0x123, 8, false)
let msg = decode_frame(req)
print("rtr={msg["rtr"]} dlc={msg["dlc"]}")  // rtr=true dlc=8

Test an ID against a filter/mask pair

Tests a CAN ID against an acceptance filter, returning true when (id & mask) == (filter & mask) — the same semantics used by hardware acceptance filters. Bits where the mask is 0 are don't-care.

use plugin can::{filter_match}

// Accept any ID in the 0x100-0x10F range
print(filter_match(0x105, 0x100, 0x7F0))  // true
print(filter_match(0x205, 0x100, 0x7F0))  // false

Encode a CAN FD frame with up to 64 data bytes

Encodes a CAN FD frame with up to 64 data bytes, computing the DLC code from the data length automatically. The flags byte sets bit2=fd, plus bit0 when extended and bit3 when brs (bit rate switch); both flags are optional and default to false.

use plugin can::{encode_fd_frame, decode_frame}

let data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
let frame = encode_fd_frame(0x123, data, false, true)
let msg = decode_frame(frame)
print("dlc={msg["dlc"]}")  // dlc=9 (12 bytes)
enespt-br