Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.cerulion.com/llms.txt

Use this file to discover all available pages before exploring further.

Every node port carries a typed message. This guide shows how to use the built-in ROS 2 message types, how fixed and variable fields differ when you write them, and how to define your own workspace schema. For the full type list and reference details, see the message types reference and graph and schema files reference.

Use a ROS 2 message type

Cerulion ships native_ros2_messages, generated from ROS 2 Jazzy .msg files. Import the type you need from its package module:
use cerulion_core::prelude::*;
use native_ros2_messages::sensor_msgs::Image;
Reference a type as a port by giving it a field on the node struct:
#[cerulion_node(period_ms = 33)]
#[derive(Default)]
struct Camera {
    #[output(data, encoding)]
    image: Image,
}
In a graph, schemas are written with a slash (sensor_msgs/Image). The colon form sensor_msgs::Image is also accepted and normalized to the slash form.

Fixed vs variable fields

How you write a message field depends on its size:
  • Fixed primitive fields (numbers, bytes — for example height, width, is_bigendian) live in a fixed section and are written directly. The macro derefs to that section and writes straight to shared memory.
  • Variable-length fields (string, T[], nested types) have no fixed size, so the macro routes them through a generated setter. You must list each one in the #[output(...)] attribute.
For sensor_msgs/Image, height and width are fixed, while encoding (a string) and data (a uint8[]) are variable:
#[cerulion_node_impl]
impl Camera {
    fn tick(&mut self) -> Result<(), NodeError> {
        self.image.height = 480;        // fixed field, direct shared-memory write
        self.image.width = 640;         // fixed field, direct shared-memory write
        self.image.encoding = "rgb8";   // variable field → rewritten to set_encoding(...)?
        Ok(())
    }
}
A variable field that is not listed in #[output(...)] cannot be written through self.<port>.<field>. List simple variable fields by name — #[output(data, encoding)] — and nested-typed fields with the complex(...) form, for example #[output(data, complex(header))].
The ROS 2 primitive types map to Rust as follows (a representative subset):
ROS 2 typeRust type
boolbool
uint8 / byteu8
int32i32
uint32u32
float64f64
stringString
T[]Vec<T>

Define a custom schema

When no built-in type fits, define a workspace schema.

Create the schema file

cerulion schema create <name> writes schemas/<PascalName>.yaml with a skeleton.
cerulion schema create Reading
Created schema 'Reading'
The generated schemas/Reading.yaml:
schemas:
  Reading:
    description: ""
    fields:
      # Add fields: type name

Add fields

Edit schemas/Reading.yaml to declare fields as type name. Use ROS 2 primitive types; T[] marks a variable-length array.
schemas:
  Reading:
    description: "A single sensor reading"
    fields:
      float64 value:
      uint64 timestamp:

Inspect it

cerulion schema info <name> reports the name, description, field count, and a hash.
cerulion schema info Reading
Schema: Reading
Description: A single sensor reading
Fields: 2
Hash: 0x0123456789abcdef
schema info first tries a ROS 2 .msg lookup for qualified names like sensor_msgs::Image, printing fields, Rust types, and the minimum wire size. If no .msg is found, it falls back to the workspace YAML schema and prints the description, fields, and FNV-1a hash shown above.
Delete a schema you no longer need:
cerulion schema delete Reading
Deleted schema 'Reading'

Next steps

Define a node

Put these message types to work on node ports.

Message types

Every available package and type.