Skip to main content
By the end of this guide, you’ll have a camera publisher sending images to a display subscriber — communicating via zero-copy shared memory with sub-microsecond latency.

Prerequisites

Cerulion installed — run cerulion doctor to verify
If you haven’t installed yet, follow the Installation guide first.

What You’ll Build

A simple two-node pipeline: camera_pub publishes images that display_sub receives
  • camera_pub — Publishes an Image every 100ms
  • display_sub — Receives images and renders them

Step 1: Create a Workspace

Create the workspace

cerulion workspace create my_robot_workspace
cd my_robot_workspace
This creates:
my_robot_workspace
graphs
nodes
Cargo.toml

Step 2: Create the Nodes

Create the camera publisher

cerulion node create camera_pub --period-ms 100
This creates a periodic node that runs every 100 milliseconds.

Create the display subscriber

cerulion node create display_sub --ext-trigger
This creates an event-triggered node that runs when it receives input.
Your workspace now looks like:
my_robot_workspace
graphs
nodes
camera_pub
Cargo.toml
camera_pub.rs
display_sub
Cargo.toml
display_sub.rs
Cargo.toml

Step 3: Write the Node Code

Open nodes/camera_pub/camera_pub.rs and replace its contents:
nodes/camera_pub/camera_pub.rs
use cerulion_core_v2::prelude::*;

/// Publishes an Image every 100ms.
/// The #[out("image")] attribute names the output port.
#[node(period_ms = 100)]
pub fn camera_pub() -> (#[out("image")] Image) {
    // Create a new Image message
    let mut img = Image::new();
    img.set_height(480);
    img.set_width(640);
    img.set_encoding("rgb8");

    // Capture pixels (simulated here)
    let data = capture_pixels();
    img.set_data(&data);

    (img)
}

/// Simulates capturing pixels from a camera.
fn capture_pixels() -> Vec<u8> {
    vec![0; 480 * 640 * 3]
}
Image is a built-in ROS2-compatible message type. Cerulion provides all standard ROS2 message primitives.

Step 4: Create the Graph

Create the graph definition

cerulion graph create launch
This creates graphs/launch.crln.

Wire the nodes together

Open graphs/launch.crln and replace its contents:
graphs/launch.crln
name: my_robot

nodes:
  - id: camera
    type: camera_pub

  - id: display
    type: display_sub
    inputs:
      - name: image
        source_node: camera
        source_output_name: image
This connects the image output of camera_pub to the image input of display_sub.

Step 5: Run It

Launch the graph

cerulion graph run launch
You’ll see:
Building nodes...
  ✓ camera_pub
  ✓ display_sub

Starting graph: my_robot
  ✓ camera (camera_pub)
  ✓ display (display_sub)

Graph running. Press Ctrl+C to stop.

Open the TUI

In a new terminal:
cerulion tui
The TUI shows your graph running in real-time with node status, topic flow, and message rates.

What Just Happened?

Zero-Copy Transport

Images flow through shared memory. No serialization, no copying — the subscriber reads directly from where the publisher wrote.

Sub-μs Latency

The image message reaches the subscriber in under 1 microsecond. That’s 1000x faster than network transport.

Automatic Scheduling

camera_pub runs every 100ms automatically. display_sub triggers only when new data arrives.

Type-Safe Messages

Image is validated at compile time. Schema mismatches are caught before you run.

Next Steps


Troubleshooting

Check your launch.crln wiring:
  • source_node must match a node id in the same file
  • source_output_name must match an #[out("name")] in the source node
  • name must match a parameter name in the target node
Ensure you have Rust installed and up to date:
rustup update stable
Then rebuild:
cerulion node build camera_pub
Shared memory requires proper permissions. On Linux:
# Check shared memory limits
cat /proc/sys/kernel/shmmax

# Increase if needed (requires root)
sudo sysctl -w kernel.shmmax=2147483648
On macOS, this usually works out of the box. If not, try restarting your terminal.
Make sure your graph is running in another terminal:
# Terminal 1
cerulion graph run launch

# Terminal 2
cerulion tui
Topics only appear when nodes are actively publishing.