pamoja-can
CAN 2.0 and CAN FD frames with 11- and 29-bit identifiers, J1939 decode and compose, and a node on a bus, simulated or a Linux interface through SocketCAN. One capability of pamoja, one memory-safe Rust core with bindings for TypeScript, Python, and C#.
Install
pip install pamoja-can
from pamoja import can
This pulls in pamoja-native, the compiled engine. pip install pamoja is the whole framework in one package.
Example
The script the test suite runs, spliced here as it ran.
From bindings/python/guides/can.py:
from pamoja.can import (
NOT_AVAILABLE,
CanBus,
CanFilter,
CanFrame,
Priority,
broadcast_j1939,
compose_j1939,
decode_j1939,
fd_frame,
frame,
signals,
signals_from,
)
from pamoja.core import PamojaError
# The nodes by the address each answers to, and the two parameter groups in play.
ENGINE = 0
GATEWAY = 1
ENGINE_CONTROLLER_1 = 61_444 # carries engine speed
REQUEST = 59_904 # asks another node for a parameter group
# Where engine speed sits inside that group, and the scale the standard fixes for it.
ENGINE_SPEED_AT = 3
RPM_PER_BIT = 0.125
def reading(speed_id: int, rpm: float) -> CanFrame:
"""A reading: every signal marked not available but the engine's speed."""
reported = signals()
reported.set_u16(ENGINE_SPEED_AT, int(rpm / RPM_PER_BIT))
return frame(speed_id, reported.bytes, extended=True)
def rpm_of(received: CanFrame) -> float:
"""The engine speed a reading carries."""
return (signals_from(received.data).u16(ENGINE_SPEED_AT) or 0) * RPM_PER_BIT
# J1939 keeps its addressing inside the 29-bit identifier: a priority, the parameter group, and
# the sender's address. A broadcast names no destination.
speed_id = broadcast_j1939(Priority.CONTROL, ENGINE_CONTROLLER_1, ENGINE)
speed = decode_j1939(speed_id)
print(
f"engine speed 0x{speed_id:08X}: pgn {speed.pgn} at priority {speed.priority}, "
f"from node {ENGINE} to every node"
)
first = reading(speed_id, 1500)
unreported = sum(1 for byte in first.data if byte == NOT_AVAILABLE)
print(
f"payload {rpm_of(first):.1f} rpm in bytes {ENGINE_SPEED_AT + 1} and "
f"{ENGINE_SPEED_AT + 2}, the other {unreported} not available"
)
# Four nodes on one bus with nothing plugged in. On a Linux board each is
# CanBus.open("can0"), and nothing after this statement changes.
engine = CanBus.simulated()
gateway = engine.join()
laptop = engine.join()
sensor = engine.join()
# The gateway keeps engine speed and nothing else; the laptop keeps everything.
gateway.set_filters([CanFilter.pgn(ENGINE_CONTROLLER_1)])
# Two engine readings, and between them the coolant sensor, which speaks plain CAN: its level
# in percent on the 11-bit identifier 0x120.
engine.send(first)
sensor.send(frame(0x120, bytes([87])))
engine.send(reading(speed_id, 1512.5))
# Every node hears every frame but its own, and keeps what its filters pass.
while (kept := gateway.receive(timeout=0.01)) is not None:
source = decode_j1939(kept.id, kept.extended).source
print(f"gateway {rpm_of(kept):.1f} rpm from node {source}")
on_the_bus = engine.sent + sensor.sent
print(f"gateway kept {gateway.received} of the {on_the_bus} frames on the bus")
heard = []
while (received := laptop.receive(timeout=0.01)) is not None:
heard.append(received)
plain = next((f for f in heard if decode_j1939(f.id, f.extended) is None), None)
if plain is not None:
print(
f"laptop heard {len(heard)}, among them 0x{plain.id:03X}, "
"an 11-bit identifier and no J1939 message"
)
# A request is addressed rather than broadcast: below the PDU1 limit, eight bits of the
# identifier name the node it is for.
request = decode_j1939(compose_j1939(Priority.DEFAULT, REQUEST, GATEWAY, ENGINE))
print(f"request pgn {request.pgn} from node {request.source} to node {request.destination}")
# The engine goes quiet. A receive waits for a frame up to its timeout; on a simulated bus it
# returns at once and counts the wait instead of sleeping through it.
before = gateway.waited_micros
quiet = gateway.receive(timeout=0.5)
waited = (gateway.waited_micros - before) // 1_000
print(f"silent {0 if quiet is None else 1} frames in {waited} ms, counted and not slept")
# Above eight bytes CAN FD encodes a length in steps, and a classic frame refuses a ninth byte.
wide = fd_frame(speed_id, bytes(32), extended=True)
print(f"fd 32 bytes travel at data length code {wide.dlc}")
try:
frame(speed_id, bytes(9), extended=True)
except PamojaError as error:
print(f"classic refused nine bytes: {error}")
The same capability in every language
| Language | Package | Reference |
|---|---|---|
| Rust | pamoja-can |
reference, docs.rs, install |
| TypeScript | @pamoja/can |
reference, install |
| Python | pamoja-can |
reference, install |
| C# | Pamoja.Can |
reference, install |
Documentation
pamoja.canreference, every class and function in this module.- The CAN and J1939 guide, with the same example in Rust, TypeScript, and C#.
- Every capability, and the install page.
License
MIT
Release files for pamoja-can 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pamoja_can-0.2.0.tar.gz | 7.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pamoja_can-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 15.0 kB
Release files / pamoja_can-0.2.0.tar.gz
| Download URL | pamoja_can-0.2.0.tar.gz |
|---|---|
| Size | 7.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fb198bbfb18469eb125933aa04c6c875fe598434b20c6da70b81f39bf009087c
|
|
BLAKE2b-256 checksum How to use checksums |
40c1a9ed1493a99386097ee0013faba0f46a16e4be652308a217d5481f3a4b1b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|
Release files / pamoja_can-0.2.0-py3-none-any.whl
| Download URL | pamoja_can-0.2.0-py3-none-any.whl |
|---|---|
| Size | 7.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cad50ab123662349738f4c94341a66cd8fed47190b615f0149e86efda372a117
|
|
BLAKE2b-256 checksum How to use checksums |
b137ab078bf60ecc3dfefcd0cb03bf4de47cfc444328c4bf895ed3e96b1c2f92
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|