Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Node packages

This page is part of the Multi-Node Projects group. Previous: Project layout — Next: Bringup packages

A Node pkg is the unit of reusable behaviour in a multi-node workspace. It is a Rust library — a [lib] crate — that implements one node and registers it with nros::node!(T). The Entry pkg is what boots the binary; the Node pkg is what runs inside it.


Prereqs

Pick one path from a fresh checkout — just is NOT a prereq.

A. Bare machine (no Rust, no just, no cargo):

./scripts/bootstrap.sh base

Installs rustup, just, builds the in-tree nros CLI at packages/cli/target/release/nros, leaves the binary on PATH for this shell.

B. Already have cargo (most contributors):

cargo build --release --manifest-path packages/cli/Cargo.toml --bin nros
export PATH="$PWD/packages/cli/target/release:$PATH"

C. Tagged release, no Rust at all:

./scripts/install-nros-prebuilt.sh

Downloads the matching nros-<triple>.tar.gz from the GitHub release, sha256-verifies, installs to packages/cli/target/release/nros.

Every subsequent shell sources the workspace env via one of:

direnv allow                  # if you use direnv
source ./activate.sh          # bash / zsh
source ./activate.fish        # fish

Then provision the native host:

nros setup native --rmw zenoh

Scaffolding a Node pkg

Use nros new to create a skeleton:

nros new talker --platform native --lang rust

nros new creates a project skeleton. For a workspace, move (or create) the result under src/talker_pkg/ so the workspace root’s Cargo.toml can include it as a member:

# workspace Cargo.toml
[workspace]
resolver = "2"
members = ["src/talker_pkg", "src/listener_pkg", "src/native_entry"]

Node pkg anatomy

A Node pkg has three files:

src/talker_pkg/
├── package.xml          # ROS 2 manifest — <exec_depend> per message package
├── Cargo.toml           # [lib] + [package.metadata.nros.node] metadata
└── src/lib.rs           # impl Node + ExecutableNode; ends with nros::node!(Talker);

No fn main() here — a Node pkg is a library linked into an Entry pkg. The Entry pkg’s macro-generated runtime owns nros::init, executor open, RMW registration, and the spin/yield loop.


Cargo.toml — the [package.metadata.nros.node] block

The metadata block is what the nros CLI reads to discover, name, and wire this node into a topology. From examples/stm32f4/rust/talker_pkg/Cargo.toml:

[lib]
crate-type = ["rlib"]

[dependencies]
nros = { path = "../../../../packages/core/nros", default-features = false,
         features = ["alloc", "rmw-cffi", "platform-bare-metal", "ros-humble"] }

[package.metadata.nros.node]
class = "stm32f4_talker_pkg::Talker"
name = "talker"
default_namespace = "/"

The three fields in [package.metadata.nros.node]:

FieldPurpose
classFully-qualified Rust path to the type that impls Node + ExecutableNode
nameDefault ROS 2 node name (remappable at launch)
default_namespaceDefault namespace (remappable at launch)

For a native workspace the nros dep would use features = ["std", "rmw-cffi", "platform-posix", "ros-humble"] instead of platform-bare-metal. The RMW feature (rmw-zenoh, rmw-xrce, rmw-cyclonedds) is chosen at build time — it is not baked into the Node pkg itself.


src/lib.rs — the node implementation

A Node pkg implements two traits: Node (declarative registration) and ExecutableNode (per-callback body), then calls nros::node! to export the trampolines the Entry macro expects.

Here is the essential shape, drawn from examples/stm32f4/rust/talker_pkg/src/lib.rs (see that file for the full worked version):

#![allow(unused)]
fn main() {
use nros::{
    CallbackCtx, ExecutableNode, Node, NodeContext, NodeOptions, NodeResult,
    TimerDuration,
};

pub struct Talker;

impl Node for Talker {
    const NAME: &'static str = "talker";

    fn register(ctx: &mut NodeContext<'_>) -> NodeResult<()> {
        let mut node = ctx.create_node(NodeOptions::new("talker"))?;
        let chatter = node.create_publisher_for_topic::<MyMsg>("/chatter")?;
        let _timer =
            node.create_timer_for_callback_name("on_tick", TimerDuration::from_millis(1000))?;
        node.callback_for_name("on_tick")
            .publishes_entity(&chatter)?;
        Ok(())
    }
}

impl ExecutableNode for Talker {
    type State = i32;

    fn init() -> Self::State { 0 }

    // See the full example for the callback body.
}

nros::node!(Talker);   // <-- exports the trampolines; this is the last line
}

Key points:

  • Node::register is declarative — it runs once at startup to declare publishers, subscriptions, timers, and callback edges. No message bytes flow here.
  • ExecutableNode::on_callback is the body — called by the Entry pkg’s executor each time a callback fires. state is your per-node mutable storage.
  • nros::node!(Talker) must be the last public API call in the file. It generates the extern "C" trampolines the Entry macro imports.
  • There is no fn main() in a Node pkg.

package.xml — the ROS 2 manifest

A Node pkg that uses generated message types lists them as <exec_depend> entries. Minimal example:

<?xml version="1.0"?>
<package format="3">
  <name>talker_pkg</name>
  <version>0.1.0</version>
  <description>Talker node</description>
  <maintainer email="dev@example.com">Developer</maintainer>
  <license>MIT OR Apache-2.0</license>

  <depend>std_msgs</depend>

  <export>
    <build_type>ament_cargo</build_type>
  </export>
</package>

If your node uses no external message packages, the <depend> line can be omitted.


Building

From the workspace root, sync generated interfaces first, then let Cargo compile the Node pkgs and Entry pkg:

# From examples/workspaces/rust/ (or your workspace root):
nros ws sync
nros codegen-system --bringup demo_bringup
cargo build -p native_entry

No per-Node-pkg invocation is needed — the workspace resolver handles dependency ordering.

To cross-compile for an embedded target, pass --target and ensure .cargo/config.toml in the workspace root sets the right linker and target:

cargo build --target thumbv7em-none-eabihf

Next steps

  • Bringup packages — wire the Node pkgs together into a topology.
  • Entry packages — build the binary that boots the topology on real hardware or a host process.
  • Role reference — the full reference for all three roles, metadata fields, and the nros::main!() four forms.