Skip to main content

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#.

read the guide documentation API reference

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

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)

Source distribution for pamoja-can 0.2.0
File Size Uploaded
pamoja_can-0.2.0.tar.gz 7.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pamoja-can 0.2.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.18

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page