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. CanBus is one node on a bus, simulated or a kernel interface through SocketCAN, and the rest 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. :class:`CanBus` is one node on a bus, simulated or a kernel interface
  6through SocketCAN, and the rest is the identifier and payload layer; the controller
  7hardware handles the wire itself.
  8"""
  9
 10from __future__ import annotations
 11
 12import enum
 13from enum import IntEnum
 14from typing import Iterable
 15
 16from pamoja._native import CanBus as _NativeBus
 17from pamoja._native import CanFilter, CanFrame, J1939Message, Signals
 18from pamoja._native import can_dlc_to_len as _dlc_to_len
 19from pamoja._native import can_fd_frame as _fd_frame
 20from pamoja._native import can_frame as _frame
 21from pamoja._native import can_len_to_dlc as _len_to_dlc
 22from pamoja._native import can_remote_frame as _remote_frame
 23from pamoja._native import j1939_compose as _j1939_compose
 24from pamoja._native import j1939_broadcast as _j1939_broadcast
 25from pamoja._native import j1939_decode as _j1939_decode
 26from pamoja._native import j1939_limits as _limits
 27
 28__all__ = [
 29    "BROADCAST_ADDRESS",
 30    "NOT_AVAILABLE",
 31    "CanBus",
 32    "CanBusKind",
 33    "CanFilter",
 34    "CanFrame",
 35    "J1939Message",
 36    "Priority",
 37    "Signals",
 38    "broadcast_j1939",
 39    "compose_j1939",
 40    "decode_j1939",
 41    "dlc_to_len",
 42    "fd_frame",
 43    "frame",
 44    "len_to_dlc",
 45    "remote_frame",
 46    "signals",
 47    "signals_from",
 48]
 49
 50_NOT_AVAILABLE, _BROADCAST_ADDRESS, _CONTROL, _DEFAULT, _LOWEST = _limits()
 51
 52#: The byte a J1939 sender writes for a signal it is not reporting.
 53NOT_AVAILABLE = _NOT_AVAILABLE
 54
 55#: The destination address every node on the bus reads.
 56BROADCAST_ADDRESS = _BROADCAST_ADDRESS
 57
 58
 59class Priority(IntEnum):
 60    """The priorities J1939 publishes, so a caller does not write the number out."""
 61
 62    CONTROL = _CONTROL
 63    """Ahead of ordinary traffic, for a message that controls something."""
 64
 65    DEFAULT = _DEFAULT
 66    """What ordinary traffic uses."""
 67
 68    LOWEST = _LOWEST
 69    """Yields to everything else on the bus."""
 70
 71
 72def signals() -> Signals:
 73    """Build a J1939 payload with every signal marked not available.
 74
 75    :returns: Eight bytes a controller writes only its own signals into.
 76    """
 77    return Signals()
 78
 79
 80def signals_from(data: bytes) -> Signals:
 81    """Read the eight data bytes of a frame that arrived off the bus.
 82
 83    :param data: The frame's payload.
 84    :returns: The payload, ready for its signals to be read out.
 85    :raises PamojaError: The payload is not exactly eight bytes.
 86    """
 87    return Signals.from_bytes(data)
 88
 89
 90def frame(identifier: int, data: bytes, extended: bool = False) -> CanFrame:
 91    """Build a classic CAN 2.0 frame.
 92
 93    :param identifier: The arbitration identifier, masked to the width ``extended``
 94        selects.
 95    :param data: The payload, at most eight bytes.
 96    :param extended: Whether the identifier is a 29-bit extended one.
 97    :returns: The frame.
 98    :raises PamojaError: If the payload is longer than a classic frame carries.
 99    """
100    return _frame(identifier, extended, bytes(data))
101
102
103def fd_frame(identifier: int, data: bytes, extended: bool = False) -> CanFrame:
104    """Build a CAN-FD frame, which carries up to 64 bytes.
105
106    :param identifier: The arbitration identifier.
107    :param data: The payload, at one of the discrete CAN-FD lengths: 0 to 8, then
108        12, 16, 20, 24, 32, 48, or 64 bytes.
109    :param extended: Whether the identifier is a 29-bit extended one.
110    :returns: The frame.
111    :raises PamojaError: If the payload length is not one CAN-FD can carry.
112    """
113    return _fd_frame(identifier, extended, bytes(data))
114
115
116def remote_frame(identifier: int, length: int, extended: bool = False) -> CanFrame:
117    """Build a remote transmission request, which asks another node to send.
118
119    :param identifier: The arbitration identifier.
120    :param length: The data length being requested, clamped to eight bytes.
121    :param extended: Whether the identifier is a 29-bit extended one.
122    :returns: The frame, which carries no payload of its own.
123    """
124    return _remote_frame(identifier, extended, length)
125
126
127def len_to_dlc(length: int) -> int:
128    """Return the data length code that encodes a payload length.
129
130    :param length: The payload length in bytes.
131    :returns: The code, rounding up to the next length CAN-FD can carry.
132    """
133    return _len_to_dlc(length)
134
135
136def dlc_to_len(dlc: int) -> int:
137    """Return the payload length a data length code encodes.
138
139    :param dlc: The data length code.
140    :returns: The length in bytes.
141    """
142    return _dlc_to_len(dlc)
143
144
145def decode_j1939(identifier: int, extended: bool = True) -> J1939Message | None:
146    """Decode the J1939 fields out of an extended CAN identifier.
147
148    :param identifier: The identifier as it arrived.
149    :param extended: Whether it is a 29-bit extended identifier.
150    :returns: The decoded message, or ``None`` for a standard identifier, which
151        J1939 does not use.
152    """
153    return _j1939_decode(identifier, extended)
154
155
156def compose_j1939(priority: int, pgn: int, source: int, destination: int = 0) -> int:
157    """Compose the extended CAN identifier a set of J1939 fields describes.
158
159    :param priority: The message priority, 0 (highest) to 7.
160    :param pgn: The parameter group number.
161    :param source: The address of the sending node.
162    :param destination: The destination address, used only for an addressed (PDU1)
163        parameter group and ignored for a broadcast (PDU2) one.
164    :returns: The 29-bit identifier.
165    """
166    return _j1939_compose(priority, pgn, source, destination)
167
168
169def broadcast_j1939(priority: int, pgn: int, source: int) -> int:
170    """Compose the identifier of a J1939 broadcast, which every node reads.
171
172    Most parameter groups are broadcast, so this is the common case; it saves a
173    caller knowing that a broadcast is addressed to ``0xFF``.
174
175    :param priority: The message priority, 0 (highest) to 7.
176    :param pgn: The parameter group number.
177    :param source: The address of the sending node.
178    :returns: The 29-bit identifier.
179    """
180    return _j1939_broadcast(priority, pgn, source)
181
182
183class CanBusKind(str, enum.Enum):
184    """What a node's bus is."""
185
186    #: A kernel CAN interface reached through SocketCAN.
187    DEVICE = "Device"
188    #: A bus inside the program.
189    SIMULATED = "Simulated"
190
191
192class CanBus:
193    """One node's place on a CAN bus.
194
195    :meth:`open` opens a kernel interface such as ``can0`` through SocketCAN on a Linux board,
196    once it is up with ``ip link set can0 up type can bitrate 250000``. :meth:`simulated` makes
197    a bus inside the program, and :meth:`join` puts another node on the same bus. A node hears
198    every frame the others send and none of its own, and keeps only the frames its filters
199    pass. A send and a receive release the interpreter while the bus is busy; a receive on a
200    simulated bus with nothing waiting returns ``None`` at once and counts its timeout in
201    :attr:`waited_micros`.
202
203    >>> engine = CanBus.simulated()
204    >>> gateway = engine.join()
205    >>> speed = (500).to_bytes(2, "big")
206    >>> engine.send(frame(0x20A, speed))
207    >>> bytes(gateway.receive(timeout=0.01).data) == speed
208    True
209    """
210
211    __slots__ = ("_native",)
212
213    def __init__(self, native: _NativeBus) -> None:
214        """Wrap a native node; use :meth:`open` or :meth:`simulated` instead."""
215        self._native = native
216
217    @classmethod
218    def open(cls, interface: str) -> CanBus:
219        """Open a kernel CAN interface through SocketCAN, as one node on its bus.
220
221        :param interface: ``can0`` for the first controller, ``vcan0`` for a virtual one.
222        :returns: The node.
223        :raises PamojaError: Anywhere but Linux, or when the interface does not exist.
224        """
225        return cls(_NativeBus.open(interface))
226
227    @classmethod
228    def simulated(cls) -> CanBus:
229        """A new bus inside the program, with this node the first on it.
230
231        :returns: The node.
232        """
233        return cls(_NativeBus.simulated())
234
235    def join(self) -> CanBus:
236        """Put another node on the same bus.
237
238        :returns: The new node.
239        """
240        return CanBus(self._native.join())
241
242    @property
243    def kind(self) -> CanBusKind:
244        """What the bus is."""
245        return CanBusKind(self._native.kind)
246
247    @property
248    def interface(self) -> str | None:
249        """The kernel interface the node is on, or ``None`` on a simulated bus."""
250        return self._native.interface
251
252    def send(self, frame: CanFrame) -> None:
253        """Send a frame to every other node on the bus.
254
255        :param frame: The frame.
256        :raises PamojaError: When the kernel refuses the frame.
257        """
258        self._native.send(frame)
259
260    def receive(self, timeout: float) -> CanFrame | None:
261        """Take the next frame the node keeps, waiting up to ``timeout`` for one.
262
263        :param timeout: How long to wait, in seconds.
264        :returns: The frame, or ``None`` when the timeout passed with nothing.
265        :raises PamojaError: When the interface fails.
266        """
267        if not timeout >= 0:
268            raise ValueError("a time must be zero or more seconds")
269        return self._native.receive(round(timeout * 1_000_000))
270
271    def set_filters(self, filters: Iterable[CanFilter]) -> None:
272        """Keep only the frames that pass at least one of the filters, from now on.
273
274        An empty list keeps nothing; :meth:`clear_filters` keeps everything again.
275
276        :param filters: ``CanFilter.pgn(61444)``, ``CanFilter.exact(0x20A)``, and the like.
277        """
278        self._native.set_filters(list(filters))
279
280    def clear_filters(self) -> None:
281        """Keep every frame again, as a node does when it joins."""
282        self._native.clear_filters()
283
284    @property
285    def sent(self) -> int:
286        """How many frames the node has sent."""
287        return self._native.sent
288
289    @property
290    def received(self) -> int:
291        """How many frames the node has received."""
292        return self._native.received
293
294    @property
295    def waited_micros(self) -> int:
296        """How long receives have waited without a frame, whether or not the process slept."""
297        return self._native.waited_micros
BROADCAST_ADDRESS = 255
NOT_AVAILABLE = 255
class CanBus:
193class CanBus:
194    """One node's place on a CAN bus.
195
196    :meth:`open` opens a kernel interface such as ``can0`` through SocketCAN on a Linux board,
197    once it is up with ``ip link set can0 up type can bitrate 250000``. :meth:`simulated` makes
198    a bus inside the program, and :meth:`join` puts another node on the same bus. A node hears
199    every frame the others send and none of its own, and keeps only the frames its filters
200    pass. A send and a receive release the interpreter while the bus is busy; a receive on a
201    simulated bus with nothing waiting returns ``None`` at once and counts its timeout in
202    :attr:`waited_micros`.
203
204    >>> engine = CanBus.simulated()
205    >>> gateway = engine.join()
206    >>> speed = (500).to_bytes(2, "big")
207    >>> engine.send(frame(0x20A, speed))
208    >>> bytes(gateway.receive(timeout=0.01).data) == speed
209    True
210    """
211
212    __slots__ = ("_native",)
213
214    def __init__(self, native: _NativeBus) -> None:
215        """Wrap a native node; use :meth:`open` or :meth:`simulated` instead."""
216        self._native = native
217
218    @classmethod
219    def open(cls, interface: str) -> CanBus:
220        """Open a kernel CAN interface through SocketCAN, as one node on its bus.
221
222        :param interface: ``can0`` for the first controller, ``vcan0`` for a virtual one.
223        :returns: The node.
224        :raises PamojaError: Anywhere but Linux, or when the interface does not exist.
225        """
226        return cls(_NativeBus.open(interface))
227
228    @classmethod
229    def simulated(cls) -> CanBus:
230        """A new bus inside the program, with this node the first on it.
231
232        :returns: The node.
233        """
234        return cls(_NativeBus.simulated())
235
236    def join(self) -> CanBus:
237        """Put another node on the same bus.
238
239        :returns: The new node.
240        """
241        return CanBus(self._native.join())
242
243    @property
244    def kind(self) -> CanBusKind:
245        """What the bus is."""
246        return CanBusKind(self._native.kind)
247
248    @property
249    def interface(self) -> str | None:
250        """The kernel interface the node is on, or ``None`` on a simulated bus."""
251        return self._native.interface
252
253    def send(self, frame: CanFrame) -> None:
254        """Send a frame to every other node on the bus.
255
256        :param frame: The frame.
257        :raises PamojaError: When the kernel refuses the frame.
258        """
259        self._native.send(frame)
260
261    def receive(self, timeout: float) -> CanFrame | None:
262        """Take the next frame the node keeps, waiting up to ``timeout`` for one.
263
264        :param timeout: How long to wait, in seconds.
265        :returns: The frame, or ``None`` when the timeout passed with nothing.
266        :raises PamojaError: When the interface fails.
267        """
268        if not timeout >= 0:
269            raise ValueError("a time must be zero or more seconds")
270        return self._native.receive(round(timeout * 1_000_000))
271
272    def set_filters(self, filters: Iterable[CanFilter]) -> None:
273        """Keep only the frames that pass at least one of the filters, from now on.
274
275        An empty list keeps nothing; :meth:`clear_filters` keeps everything again.
276
277        :param filters: ``CanFilter.pgn(61444)``, ``CanFilter.exact(0x20A)``, and the like.
278        """
279        self._native.set_filters(list(filters))
280
281    def clear_filters(self) -> None:
282        """Keep every frame again, as a node does when it joins."""
283        self._native.clear_filters()
284
285    @property
286    def sent(self) -> int:
287        """How many frames the node has sent."""
288        return self._native.sent
289
290    @property
291    def received(self) -> int:
292        """How many frames the node has received."""
293        return self._native.received
294
295    @property
296    def waited_micros(self) -> int:
297        """How long receives have waited without a frame, whether or not the process slept."""
298        return self._native.waited_micros

One node's place on a CAN bus.

open() opens a kernel interface such as can0 through SocketCAN on a Linux board, once it is up with ip link set can0 up type can bitrate 250000. simulated() makes a bus inside the program, and join() puts another node on the same bus. A node hears every frame the others send and none of its own, and keeps only the frames its filters pass. A send and a receive release the interpreter while the bus is busy; a receive on a simulated bus with nothing waiting returns None at once and counts its timeout in waited_micros.

>>> engine = CanBus.simulated()
>>> gateway = engine.join()
>>> speed = (500).to_bytes(2, "big")
>>> engine.send(frame(0x20A, speed))
>>> bytes(gateway.receive(timeout=0.01).data) == speed
True
CanBus(native: CanBus)
214    def __init__(self, native: _NativeBus) -> None:
215        """Wrap a native node; use :meth:`open` or :meth:`simulated` instead."""
216        self._native = native

Wrap a native node; use open() or simulated() instead.

@classmethod
def open(cls, interface: str) -> CanBus:
218    @classmethod
219    def open(cls, interface: str) -> CanBus:
220        """Open a kernel CAN interface through SocketCAN, as one node on its bus.
221
222        :param interface: ``can0`` for the first controller, ``vcan0`` for a virtual one.
223        :returns: The node.
224        :raises PamojaError: Anywhere but Linux, or when the interface does not exist.
225        """
226        return cls(_NativeBus.open(interface))

Open a kernel CAN interface through SocketCAN, as one node on its bus.

Parameters
  • interface: can0 for the first controller, vcan0 for a virtual one. :returns: The node.
Raises
  • PamojaError: Anywhere but Linux, or when the interface does not exist.
@classmethod
def simulated(cls) -> CanBus:
228    @classmethod
229    def simulated(cls) -> CanBus:
230        """A new bus inside the program, with this node the first on it.
231
232        :returns: The node.
233        """
234        return cls(_NativeBus.simulated())

A new bus inside the program, with this node the first on it.

:returns: The node.

def join(self) -> CanBus:
236    def join(self) -> CanBus:
237        """Put another node on the same bus.
238
239        :returns: The new node.
240        """
241        return CanBus(self._native.join())

Put another node on the same bus.

:returns: The new node.

kind: CanBusKind
243    @property
244    def kind(self) -> CanBusKind:
245        """What the bus is."""
246        return CanBusKind(self._native.kind)

What the bus is.

interface: str | None
248    @property
249    def interface(self) -> str | None:
250        """The kernel interface the node is on, or ``None`` on a simulated bus."""
251        return self._native.interface

The kernel interface the node is on, or None on a simulated bus.

def send(self, frame: CanFrame) -> None:
253    def send(self, frame: CanFrame) -> None:
254        """Send a frame to every other node on the bus.
255
256        :param frame: The frame.
257        :raises PamojaError: When the kernel refuses the frame.
258        """
259        self._native.send(frame)

Send a frame to every other node on the bus.

Parameters
  • frame: The frame.
Raises
  • PamojaError: When the kernel refuses the frame.
def receive(self, timeout: float) -> CanFrame | None:
261    def receive(self, timeout: float) -> CanFrame | None:
262        """Take the next frame the node keeps, waiting up to ``timeout`` for one.
263
264        :param timeout: How long to wait, in seconds.
265        :returns: The frame, or ``None`` when the timeout passed with nothing.
266        :raises PamojaError: When the interface fails.
267        """
268        if not timeout >= 0:
269            raise ValueError("a time must be zero or more seconds")
270        return self._native.receive(round(timeout * 1_000_000))

Take the next frame the node keeps, waiting up to timeout for one.

Parameters
  • timeout: How long to wait, in seconds. :returns: The frame, or None when the timeout passed with nothing.
Raises
  • PamojaError: When the interface fails.
def set_filters(self, filters: Iterable[CanFilter]) -> None:
272    def set_filters(self, filters: Iterable[CanFilter]) -> None:
273        """Keep only the frames that pass at least one of the filters, from now on.
274
275        An empty list keeps nothing; :meth:`clear_filters` keeps everything again.
276
277        :param filters: ``CanFilter.pgn(61444)``, ``CanFilter.exact(0x20A)``, and the like.
278        """
279        self._native.set_filters(list(filters))

Keep only the frames that pass at least one of the filters, from now on.

An empty list keeps nothing; clear_filters() keeps everything again.

Parameters
def clear_filters(self) -> None:
281    def clear_filters(self) -> None:
282        """Keep every frame again, as a node does when it joins."""
283        self._native.clear_filters()

Keep every frame again, as a node does when it joins.

sent: int
285    @property
286    def sent(self) -> int:
287        """How many frames the node has sent."""
288        return self._native.sent

How many frames the node has sent.

received: int
290    @property
291    def received(self) -> int:
292        """How many frames the node has received."""
293        return self._native.received

How many frames the node has received.

waited_micros: int
295    @property
296    def waited_micros(self) -> int:
297        """How long receives have waited without a frame, whether or not the process slept."""
298        return self._native.waited_micros

How long receives have waited without a frame, whether or not the process slept.

class CanBusKind(builtins.str, enum.Enum):
184class CanBusKind(str, enum.Enum):
185    """What a node's bus is."""
186
187    #: A kernel CAN interface reached through SocketCAN.
188    DEVICE = "Device"
189    #: A bus inside the program.
190    SIMULATED = "Simulated"

What a node's bus is.

DEVICE = <CanBusKind.DEVICE: 'Device'>
SIMULATED = <CanBusKind.SIMULATED: 'Simulated'>
class CanFilter:

A frame a node keeps: one whose identifier, masked, equals id, masked, and whose format is the filter's.

def exact(id, extended=False):

A filter that passes one identifier and nothing else.

def pgn(pgn):

A filter that passes one J1939 parameter group at any priority, from any source, and for an addressed group, to any destination.

def matches(self, /, id, extended=False):

Whether a frame with an identifier passes the filter.

id

The identifier to match.

mask

The identifier bits that have to match.

extended

Whether the identifier is a 29-bit extended one.

class CanFrame:

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

id

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

dlc

The data length code as it appears on the wire.

extended

Whether the identifier is a 29-bit extended one.

fd

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

remote

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

len

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

data

The payload, empty for a remote frame.

class J1939Message:

The fields J1939 packs into an extended CAN identifier.

broadcast

Whether the message is a broadcast.

destination

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

source

The source address: the node that sent the message.

pdu_format

The PDU format byte of the parameter group.

pgn

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

priority

The message priority, 0 (highest) to 7.

class Priority(enum.IntEnum):
60class Priority(IntEnum):
61    """The priorities J1939 publishes, so a caller does not write the number out."""
62
63    CONTROL = _CONTROL
64    """Ahead of ordinary traffic, for a message that controls something."""
65
66    DEFAULT = _DEFAULT
67    """What ordinary traffic uses."""
68
69    LOWEST = _LOWEST
70    """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:
170def broadcast_j1939(priority: int, pgn: int, source: int) -> int:
171    """Compose the identifier of a J1939 broadcast, which every node reads.
172
173    Most parameter groups are broadcast, so this is the common case; it saves a
174    caller knowing that a broadcast is addressed to ``0xFF``.
175
176    :param priority: The message priority, 0 (highest) to 7.
177    :param pgn: The parameter group number.
178    :param source: The address of the sending node.
179    :returns: The 29-bit identifier.
180    """
181    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:
157def compose_j1939(priority: int, pgn: int, source: int, destination: int = 0) -> int:
158    """Compose the extended CAN identifier a set of J1939 fields describes.
159
160    :param priority: The message priority, 0 (highest) to 7.
161    :param pgn: The parameter group number.
162    :param source: The address of the sending node.
163    :param destination: The destination address, used only for an addressed (PDU1)
164        parameter group and ignored for a broadcast (PDU2) one.
165    :returns: The 29-bit identifier.
166    """
167    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:
146def decode_j1939(identifier: int, extended: bool = True) -> J1939Message | None:
147    """Decode the J1939 fields out of an extended CAN identifier.
148
149    :param identifier: The identifier as it arrived.
150    :param extended: Whether it is a 29-bit extended identifier.
151    :returns: The decoded message, or ``None`` for a standard identifier, which
152        J1939 does not use.
153    """
154    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:
137def dlc_to_len(dlc: int) -> int:
138    """Return the payload length a data length code encodes.
139
140    :param dlc: The data length code.
141    :returns: The length in bytes.
142    """
143    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:
104def fd_frame(identifier: int, data: bytes, extended: bool = False) -> CanFrame:
105    """Build a CAN-FD frame, which carries up to 64 bytes.
106
107    :param identifier: The arbitration identifier.
108    :param data: The payload, at one of the discrete CAN-FD lengths: 0 to 8, then
109        12, 16, 20, 24, 32, 48, or 64 bytes.
110    :param extended: Whether the identifier is a 29-bit extended one.
111    :returns: The frame.
112    :raises PamojaError: If the payload length is not one CAN-FD can carry.
113    """
114    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:
 91def frame(identifier: int, data: bytes, extended: bool = False) -> CanFrame:
 92    """Build a classic CAN 2.0 frame.
 93
 94    :param identifier: The arbitration identifier, masked to the width ``extended``
 95        selects.
 96    :param data: The payload, at most eight bytes.
 97    :param extended: Whether the identifier is a 29-bit extended one.
 98    :returns: The frame.
 99    :raises PamojaError: If the payload is longer than a classic frame carries.
100    """
101    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:
128def len_to_dlc(length: int) -> int:
129    """Return the data length code that encodes a payload length.
130
131    :param length: The payload length in bytes.
132    :returns: The code, rounding up to the next length CAN-FD can carry.
133    """
134    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:
117def remote_frame(identifier: int, length: int, extended: bool = False) -> CanFrame:
118    """Build a remote transmission request, which asks another node to send.
119
120    :param identifier: The arbitration identifier.
121    :param length: The data length being requested, clamped to eight bytes.
122    :param extended: Whether the identifier is a 29-bit extended one.
123    :returns: The frame, which carries no payload of its own.
124    """
125    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:
73def signals() -> Signals:
74    """Build a J1939 payload with every signal marked not available.
75
76    :returns: Eight bytes a controller writes only its own signals into.
77    """
78    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:
81def signals_from(data: bytes) -> Signals:
82    """Read the eight data bytes of a frame that arrived off the bus.
83
84    :param data: The frame's payload.
85    :returns: The payload, ready for its signals to be read out.
86    :raises PamojaError: The payload is not exactly eight bytes.
87    """
88    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.