cerulion Python package reads and publishes the same shared-memory topics a graph uses, raw or typed with the ROS 2 message classes, and a node can be written in Python too.
The Python client is built from a Cerulion source checkout: the release archives carry no wheel. It needs Python 3.11 or later, and it reaches topics on the local machine only; it opens no network connection.
Build and install the client
From a checkout of the Cerulion repository, in a virtual environment with Python 3.11 or later:pip refuses it on an older interpreter.
Read a topic the CLI publishes
Message classes live incerulion.msgs, one module per ROS 2 package: cerulion.msgs.geometry_msgs.Vector3 is the type a Rust node names as native_ros2_messages::geometry_msgs::Vector3. Pass the class to a subscriber, and each received frame views as an instance of it.
Save this as sub_vector3.py and start it:
frame.view() is a read-only, zero-copy view of the shared-memory slot, and msg.copy() makes an owned copy. Release each frame promptly, and drop its views first: a subscriber can hold only a few frames at once (sub.max_borrowed_samples, 2 by default).
Publish typed messages
A schema you define yourself works the same way. This script publishes aPoint twice, once from a dict and once by writing a zero-copy loan in place, and reads each back:
publish() of a dict that has missing, extra or ill-typed fields raises cerulion.EncodeError. Every client error derives from cerulion.CerulionError.
Read a recording
cerulion.open_bag(path) reads a finalized MCAP bag. topics() lists its channels, and messages(topics=None) yields (topic, record) pairs whose bytes are owned copies; view a record with record.view(Vector3), or record.view("pkg/Name", schemas=s) for a schema of your own.
Write a node in Python
A Python node is a node type like any other:cerulion node build compiles a small library that embeds CPython, and the node’s logic lives in nodes/<type>/node.py.
- It needs a source checkout.
node create --lang pythonrefuses outside one, because the embedding crate is not published to a registry. - Exactly one scheduling policy. Give
period_ms, one input markedtrigger=True, orsync_window_msfor several trigger inputs, sonode create --lang pythonneeds-Tor--policy. A Python node takes-iand-orepeatedly. - Edit
node.py, not the scaffold.node modifyrefuses Python nodes: changenode.pyand runcerulion node buildagain. - The interpreter is found at build time, in this order:
$CERULION_PYTHON,<workspace>/.venv/bin/python, thenpython3onPATH. Install the client into that interpreter. - One interpreter per process. Python execution is serialized through the GIL; threads your node creates and
fork()are not supported. It suits control and integration nodes, not kHz loops: every tick crosses into the interpreter. - No state capture, and no
Fatalstop. A Python node starts frominiton every run, and its errors carry no kind, so it cannot stop itself withNodeError::Fatal.
Next steps
Inspect topics
Find the topics to read, and their types, from the CLI.
Messages and schemas
The built-in message types and how to define your own.