pamoja.can

Idiomatic CAN facade.

CAN is how the moving parts of a machine talk to each other: motor controllers, servos, battery management, and the engines and farm equipment that speak J1939 on top of it. This is the identifier and payload layer; the controller hardware handles the wire itself.

  1"""Idiomatic CAN facade.
  2
  3CAN is how the moving parts of a machine talk to each other: motor controllers,
  4servos, battery management, and the engines and farm equipment that speak J1939 on
  5top of it. This is the identifier and payload layer; the controller hardware
  6handles the wire itself.
  7"""
  8
  9from __future__ import annotations
 10
 11from enum import IntEnum
 12
 13from pamoja._native import CanFrame, J1939Message, Signals
 14from pamoja._native import can_dlc_to_len as _dlc_to_len
 15from pamoja._native import can_fd_frame as _fd_frame
 16from pamoja._native import can_frame as _frame
 17from pamoja._native import can_len_to_dlc as _len_to_dlc
 18from pamoja._native import can_remote_frame as _remote_frame
 19from pamoja._native import j1939_compose as _j1939_compose
 20from pamoja._native import j1939_broadcast as _j1939_broadcast
 21from pamoja._native import j1939_decode as _j1939_decode
 22from pamoja._native import j1939_limits as _limits
 23
 24__all__ = [
 25    "BROADCAST_ADDRESS",
 26    "NOT_AVAILABLE",
 27    "CanFrame",
 28    "J1939Message",
 29    "Priority",
 30    "Signals",
 31    "broadcast_j1939",
 32    "compose_j1939",
 33    "decode_j1939",
 34    "dlc_to_len",
 35    "fd_frame",
 36    "frame",
 37    "len_to_dlc",
 38    "remote_frame",
 39    "signals",
 40    "signals_from",
 41]
 42
 43_NOT_AVAILABLE, _BROADCAST_ADDRESS, _CONTROL, _DEFAULT, _LOWEST = _limits()
 44
 45#: The byte a J1939 sender writes for a signal it is not reporting.
 46NOT_AVAILABLE = _NOT_AVAILABLE
 47
 48#: The destination address every node on the bus reads.
 49BROADCAST_ADDRESS = _BROADCAST_ADDRESS
 50
 51
 52class Priority(IntEnum):
 53    """The priorities J1939 publishes, so a caller does not write the number out."""
 54
 55    CONTROL = _CONTROL
 56    """Ahead of ordinary traffic, for a message that controls something."""
 57
 58    DEFAULT = _DEFAULT
 59    """What ordinary traffic uses."""
 60
 61    LOWEST = _LOWEST
 62    """Yields to everything else on the bus."""
 63
 64
 65def signals() -> Signals:
 66    """Build a J1939 payload with every signal marked not available.
 67
 68    :returns: Eight bytes a controller writes only its own signals into.
 69    """
 70    return Signals()
 71
 72
 73def signals_from(data: bytes) -> Signals:
 74    """Read the eight data bytes of a frame that arrived off the bus.
 75
 76    :param data: The frame's payload.
 77    :returns: The payload, ready for its signals to be read out.
 78    :raises PamojaError: The payload is not exactly eight bytes.
 79    """
 80    return Signals.from_bytes(data)
 81
 82
 83def frame(identifier: int, data: bytes, extended: bool = False) -> CanFrame:
 84    """Build a classic CAN 2.0 frame.
 85
 86    :param identifier: The arbitration identifier, masked to the width ``extended``
 87        selects.
 88    :param data: The payload, at most eight bytes.
 89    :param extended: Whether the identifier is a 29-bit extended one.
 90    :returns: The frame.
 91    :raises PamojaError: If the payload is longer than a classic frame carries.
 92    """
 93    return _frame(identifier, extended, bytes(data))
 94
 95
 96def fd_frame(identifier: int, data: bytes, extended: bool = False) -> CanFrame:
 97    """Build a CAN-FD frame, which carries up to 64 bytes.
 98
 99    :param identifier: The arbitration identifier.
100    :param data: The payload, at one of the discrete CAN-FD lengths: 0 to 8, then
101        12, 16, 20, 24, 32, 48, or 64 bytes.
102    :param extended: Whether the identifier is a 29-bit extended one.
103    :returns: The frame.
104    :raises PamojaError: If the payload length is not one CAN-FD can carry.
105    """
106    return _fd_frame(identifier, extended, bytes(data))
107
108
109def remote_frame(identifier: int, length: int, extended: bool = False) -> CanFrame:
110    """Build a remote transmission request, which asks another node to send.
111
112    :param identifier: The arbitration identifier.
113    :param length: The data length being requested, clamped to eight bytes.
114    :param extended: Whether the identifier is a 29-bit extended one.
115    :returns: The frame, which carries no payload of its own.
116    """
117    return _remote_frame(identifier, extended, length)
118
119
120def len_to_dlc(length: int) -> int:
121    """Return the data length code that encodes a payload length.
122
123    :param length: The payload length in bytes.
124    :returns: The code, rounding up to the next length CAN-FD can carry.
125    """
126    return _len_to_dlc(length)
127
128
129def dlc_to_len(dlc: int) -> int:
130    """Return the payload length a data length code encodes.
131
132    :param dlc: The data length code.
133    :returns: The length in bytes.
134    """
135    return _dlc_to_len(dlc)
136
137
138def decode_j1939(identifier: int, extended: bool = True) -> J1939Message | None:
139    """Decode the J1939 fields out of an extended CAN identifier.
140
141    :param identifier: The identifier as it arrived.
142    :param extended: Whether it is a 29-bit extended identifier.
143    :returns: The decoded message, or ``None`` for a standard identifier, which
144        J1939 does not use.
145    """
146    return _j1939_decode(identifier, extended)
147
148
149def compose_j1939(priority: int, pgn: int, source: int, destination: int = 0) -> int:
150    """Compose the extended CAN identifier a set of J1939 fields describes.
151
152    :param priority: The message priority, 0 (highest) to 7.
153    :param pgn: The parameter group number.
154    :param source: The address of the sending node.
155    :param destination: The destination address, used only for an addressed (PDU1)
156        parameter group and ignored for a broadcast (PDU2) one.
157    :returns: The 29-bit identifier.
158    """
159    return _j1939_compose(priority, pgn, source, destination)
160
161
162def broadcast_j1939(priority: int, pgn: int, source: int) -> int:
163    """Compose the identifier of a J1939 broadcast, which every node reads.
164
165    Most parameter groups are broadcast, so this is the common case; it saves a
166    caller knowing that a broadcast is addressed to ``0xFF``.
167
168    :param priority: The message priority, 0 (highest) to 7.
169    :param pgn: The parameter group number.
170    :param source: The address of the sending node.
171    :returns: The 29-bit identifier.
172    """
173    return _j1939_broadcast(priority, pgn, source)
BROADCAST_ADDRESS = 255
NOT_AVAILABLE = 255
class CanFrame:

A CAN frame: an identifier, its flags, and its payload.

remote

Whether this is a remote transmission request, which carries no payload.

dlc

The data length code as it appears on the wire.

data

The payload, empty for a remote frame.

len

The data length: the payload length, or the length a remote frame requests.

id

The arbitration identifier, already masked to 11 or 29 bits.

extended

Whether the identifier is a 29-bit extended one.

fd

Whether this is a CAN-FD frame rather than classic CAN 2.0.

class J1939Message:

The fields J1939 packs into an extended CAN identifier.

destination

The destination address for an addressed (PDU1) message, or None for a broadcast (PDU2) one.

pgn

The parameter group number, which names what the message carries.

priority

The message priority, 0 (highest) to 7.

broadcast

Whether the message is a broadcast.

source

The source address: the node that sent the message.

pdu_format

The PDU format byte of the parameter group.

class Priority(enum.IntEnum):
53class Priority(IntEnum):
54    """The priorities J1939 publishes, so a caller does not write the number out."""
55
56    CONTROL = _CONTROL
57    """Ahead of ordinary traffic, for a message that controls something."""
58
59    DEFAULT = _DEFAULT
60    """What ordinary traffic uses."""
61
62    LOWEST = _LOWEST
63    """Yields to everything else on the bus."""

The priorities J1939 publishes, so a caller does not write the number out.

CONTROL = <Priority.CONTROL: 3>

Ahead of ordinary traffic, for a message that controls something.

DEFAULT = <Priority.DEFAULT: 6>

What ordinary traffic uses.

LOWEST = <Priority.LOWEST: 7>

Yields to everything else on the bus.

class Signals:

The eight data bytes of a J1939 frame, addressed by the signals inside them.

A parameter group places each signal at a fixed byte offset, little-endian. A payload starts with every signal marked not available, so a controller writes only the signals it actually reports.

def from_bytes(bytes):

Reads the eight data bytes of a frame that arrived off the bus.

def set_u8(self, /, at, value):

Writes a one-byte signal at the offset its parameter group defines.

def set_u16(self, /, at, value):

Writes a two-byte little-endian signal at the offset its group defines.

def u8(self, /, at):

Reads a one-byte signal, or None if the offset is past the payload.

def u16(self, /, at):

Reads a two-byte little-endian signal, or None if it would run past the payload.

bytes

The eight data bytes, ready to put in a frame.

def broadcast_j1939(priority: int, pgn: int, source: int) -> int:
163def broadcast_j1939(priority: int, pgn: int, source: int) -> int:
164    """Compose the identifier of a J1939 broadcast, which every node reads.
165
166    Most parameter groups are broadcast, so this is the common case; it saves a
167    caller knowing that a broadcast is addressed to ``0xFF``.
168
169    :param priority: The message priority, 0 (highest) to 7.
170    :param pgn: The parameter group number.
171    :param source: The address of the sending node.
172    :returns: The 29-bit identifier.
173    """
174    return _j1939_broadcast(priority, pgn, source)

Compose the identifier of a J1939 broadcast, which every node reads.

Most parameter groups are broadcast, so this is the common case; it saves a caller knowing that a broadcast is addressed to 0xFF.

Parameters
  • priority: The message priority, 0 (highest) to 7.
  • pgn: The parameter group number.
  • source: The address of the sending node. :returns: The 29-bit identifier.
def compose_j1939(priority: int, pgn: int, source: int, destination: int = 0) -> int:
150def compose_j1939(priority: int, pgn: int, source: int, destination: int = 0) -> int:
151    """Compose the extended CAN identifier a set of J1939 fields describes.
152
153    :param priority: The message priority, 0 (highest) to 7.
154    :param pgn: The parameter group number.
155    :param source: The address of the sending node.
156    :param destination: The destination address, used only for an addressed (PDU1)
157        parameter group and ignored for a broadcast (PDU2) one.
158    :returns: The 29-bit identifier.
159    """
160    return _j1939_compose(priority, pgn, source, destination)

Compose the extended CAN identifier a set of J1939 fields describes.

Parameters
  • priority: The message priority, 0 (highest) to 7.
  • pgn: The parameter group number.
  • source: The address of the sending node.
  • destination: The destination address, used only for an addressed (PDU1) parameter group and ignored for a broadcast (PDU2) one. :returns: The 29-bit identifier.
def decode_j1939(identifier: int, extended: bool = True) -> J1939Message | None:
139def decode_j1939(identifier: int, extended: bool = True) -> J1939Message | None:
140    """Decode the J1939 fields out of an extended CAN identifier.
141
142    :param identifier: The identifier as it arrived.
143    :param extended: Whether it is a 29-bit extended identifier.
144    :returns: The decoded message, or ``None`` for a standard identifier, which
145        J1939 does not use.
146    """
147    return _j1939_decode(identifier, extended)

Decode the J1939 fields out of an extended CAN identifier.

Parameters
  • identifier: The identifier as it arrived.
  • extended: Whether it is a 29-bit extended identifier. :returns: The decoded message, or None for a standard identifier, which J1939 does not use.
def dlc_to_len(dlc: int) -> int:
130def dlc_to_len(dlc: int) -> int:
131    """Return the payload length a data length code encodes.
132
133    :param dlc: The data length code.
134    :returns: The length in bytes.
135    """
136    return _dlc_to_len(dlc)

Return the payload length a data length code encodes.

Parameters
  • dlc: The data length code. :returns: The length in bytes.
def fd_frame(identifier: int, data: bytes, extended: bool = False) -> CanFrame:
 97def fd_frame(identifier: int, data: bytes, extended: bool = False) -> CanFrame:
 98    """Build a CAN-FD frame, which carries up to 64 bytes.
 99
100    :param identifier: The arbitration identifier.
101    :param data: The payload, at one of the discrete CAN-FD lengths: 0 to 8, then
102        12, 16, 20, 24, 32, 48, or 64 bytes.
103    :param extended: Whether the identifier is a 29-bit extended one.
104    :returns: The frame.
105    :raises PamojaError: If the payload length is not one CAN-FD can carry.
106    """
107    return _fd_frame(identifier, extended, bytes(data))

Build a CAN-FD frame, which carries up to 64 bytes.

Parameters
  • identifier: The arbitration identifier.
  • data: The payload, at one of the discrete CAN-FD lengths: 0 to 8, then 12, 16, 20, 24, 32, 48, or 64 bytes.
  • extended: Whether the identifier is a 29-bit extended one. :returns: The frame.
Raises
  • PamojaError: If the payload length is not one CAN-FD can carry.
def frame(identifier: int, data: bytes, extended: bool = False) -> CanFrame:
84def frame(identifier: int, data: bytes, extended: bool = False) -> CanFrame:
85    """Build a classic CAN 2.0 frame.
86
87    :param identifier: The arbitration identifier, masked to the width ``extended``
88        selects.
89    :param data: The payload, at most eight bytes.
90    :param extended: Whether the identifier is a 29-bit extended one.
91    :returns: The frame.
92    :raises PamojaError: If the payload is longer than a classic frame carries.
93    """
94    return _frame(identifier, extended, bytes(data))

Build a classic CAN 2.0 frame.

Parameters
  • identifier: The arbitration identifier, masked to the width extended selects.
  • data: The payload, at most eight bytes.
  • extended: Whether the identifier is a 29-bit extended one. :returns: The frame.
Raises
  • PamojaError: If the payload is longer than a classic frame carries.
def len_to_dlc(length: int) -> int:
121def len_to_dlc(length: int) -> int:
122    """Return the data length code that encodes a payload length.
123
124    :param length: The payload length in bytes.
125    :returns: The code, rounding up to the next length CAN-FD can carry.
126    """
127    return _len_to_dlc(length)

Return the data length code that encodes a payload length.

Parameters
  • length: The payload length in bytes. :returns: The code, rounding up to the next length CAN-FD can carry.
def remote_frame(identifier: int, length: int, extended: bool = False) -> CanFrame:
110def remote_frame(identifier: int, length: int, extended: bool = False) -> CanFrame:
111    """Build a remote transmission request, which asks another node to send.
112
113    :param identifier: The arbitration identifier.
114    :param length: The data length being requested, clamped to eight bytes.
115    :param extended: Whether the identifier is a 29-bit extended one.
116    :returns: The frame, which carries no payload of its own.
117    """
118    return _remote_frame(identifier, extended, length)

Build a remote transmission request, which asks another node to send.

Parameters
  • identifier: The arbitration identifier.
  • length: The data length being requested, clamped to eight bytes.
  • extended: Whether the identifier is a 29-bit extended one. :returns: The frame, which carries no payload of its own.
def signals() -> Signals:
66def signals() -> Signals:
67    """Build a J1939 payload with every signal marked not available.
68
69    :returns: Eight bytes a controller writes only its own signals into.
70    """
71    return Signals()

Build a J1939 payload with every signal marked not available.

:returns: Eight bytes a controller writes only its own signals into.

def signals_from(data: bytes) -> Signals:
74def signals_from(data: bytes) -> Signals:
75    """Read the eight data bytes of a frame that arrived off the bus.
76
77    :param data: The frame's payload.
78    :returns: The payload, ready for its signals to be read out.
79    :raises PamojaError: The payload is not exactly eight bytes.
80    """
81    return Signals.from_bytes(data)

Read the eight data bytes of a frame that arrived off the bus.

Parameters
  • data: The frame's payload. :returns: The payload, ready for its signals to be read out.
Raises
  • PamojaError: The payload is not exactly eight bytes.