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

Bringup packages

A Bringup pkg is the declarative glue that ties your Node packages together into a runnable topology. It owns the launch file, the wiring between nodes, and the per-target deploy config — all without any compiled code of its own.

Pre-requisite: You’ve scaffolded your Node packages following the Node packages guide. This page adds the demo_bringup layer between them and the Entry package that boots everything.


Prereqs

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

A. Front door (bare machine OK — no Rust, no just):

./scripts/bootstrap.sh

Installs rustup if needed and builds the in-tree nros CLI from source at packages/cli/target/release/nros, leaving it on PATH for this shell (nano-ros is a source distribution — no prebuilt nros).

B. Already have cargo (equivalent — same build, same binary):

git submodule update --init packages/cli/third-party/play_launch
cargo build --release --manifest-path packages/cli/Cargo.toml --bin nros
export PATH="$PWD/packages/cli/target/release:$PATH"

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

What a Bringup pkg is

A Bringup pkg is pure declarative — no Cargo.toml, no CMakeLists.txt, no src/. Its job is to describe which nodes run, how they’re wired, and where they deploy. Naming convention: <system>_bringup (aliased <system>_launch), matching nav2 / Autoware / turtlebot3.

It is optional: required only when two or more Entry packages share one topology. A single-Entry workspace can fold launch/ + system.toml directly into the Entry pkg.


Anatomy

src/demo_bringup/
├── package.xml          # ROS 2 manifest; <exec_depend> per node pkg
├── system.toml          # [system] + [[component]] + [image.<id>] + [host.<name>]
├── launch/
│   └── system.launch.xml   # ROS 2 launch schema, verbatim
└── config/                 # optional — params.yaml, per-target overrides

system.toml — node wiring + deploy targets

The system.toml is the machine-readable topology. It lists every node the system runs, its class path, and one or more deploy targets (which board, which RMW, which domain).

Below is a minimal two-node example adapted from the real fixture at packages/testing/nros-tests/fixtures/orchestration_e2e/demo_pkg_bringup/system.toml:

[system]
name = "demo"
rmw = "zenoh"
domain_id = 0

[[component]]
pkg = "talker_pkg"
class = "talker_pkg::Talker"
name = "talker"

[[component]]
pkg = "listener_pkg"
class = "listener_pkg::Listener"
name = "listener"

[image.native]
board = "native"

[host.native]

Key fields:

FieldMeaning
[system] nameLogical system name; used by nros plan/check
[system] rmwDefault RMW for all components (zenoh, xrce, cyclonedds)
[system] domain_idROS 2 domain (compile-time on embedded, runtime env on host)
[[component]] pkgThe ROS package name (matches <name> in package.xml)
[[component]] classFully-qualified Rust type (crate::TypeName)
[[component]] nameNode name at runtime
[image.<id>]A buildable image; read by nros build, nros check and Entry codegen
[image.<id>] boardThe board this image is built for; its descriptor supplies the rustc triple
[host.<name>]A machine nodes run on. No nodes means every node
[board_config.<board>]Site facts for that board (SDK roots, netstack)

For multi-domain setups or cross-domain bridges add [[domain]] and [[bridge]] sections — see docs/design/0024-multi-node-workspace-layout.md §11 for the full schema.


launch/system.launch.xml — ROS 2 launch schema

The launch file uses the ROS 2 launch XML schema verbatim — nano-ros reads it with the same parser so existing nav2/Autoware/turtlebot3 XML pastes in and Just Works.

<launch>
  <node pkg="talker_pkg" exec="talker" name="talker"/>
  <node pkg="listener_pkg" exec="listener" name="listener"/>
</launch>

v1 tag set

TagPurpose
<launch>Root element
<arg name="…" default="…"/>Declare a launch argument
<node pkg="…" exec="…" name="…"/>Instantiate a node
<param name="…" value="…"/>Set one parameter inline (nested inside <node>)
<param from="…param.yaml"/>Load a ROS parameter FILE (nested inside <node>)
<remap from="…" to="…"/>Topic/service remapping (nested inside <node>)
<group ns="…">Namespace a group of nodes
<include file="…"/>Nest another launch file

Substitutions

  • $(find <pkg>) — resolves to the package’s install/source path
  • $(var <arg>) — expands a launch argument
  • $(env <name>) — reads an environment variable

A richer example using args and remapping (taken from the real fixture):

<launch>
  <arg name="talker_name" default="talker" />

  <node pkg="talker_pkg" exec="talker" name="$(var talker_name)" output="screen">
    <param name="rate_hz" value="25" />
    <remap from="chatter" to="/chatter" />
  </node>

  <node pkg="listener_pkg" exec="listener" name="listener"/>
</launch>

Parameter files

<param from="…"/> takes a standard ROS parameter file. Relative paths resolve against the launch file’s own directory.

# config/talker.param.yaml
/**:                      # applies to every node
  ros__parameters:
    use_sim_time: false
talker:                   # applies to the node named `talker` only
  ros__parameters:
    rate_hz: 25
    limits:
      max_accel: 1.5      # reaches the node as `limits.max_accel`
<node pkg="talker_pkg" exec="talker" name="talker">
  <param from="config/talker.param.yaml"/>
  <param name="rate_hz" value="50"/>   <!-- wins over the file -->
</node>

Precedence matches ROS: the /** wildcard block first, then the node-specific block, then inline <param name= value=> last. A node block may be keyed by fully-qualified name (/ns/talker), bare name (talker), or /**/talker.

These values are resolved and baked into the generated entry at build time — embedded targets do no runtime file loading, the same approach the domain ID uses. Editing the YAML re-runs codegen. A from= naming a file that doesn’t exist is a build error, not a silent skip.

Note: the baked values are seeded through the parameter services, so they reach nodes only when param_services is enabled in system.toml.

Note: Python .launch.py files ARE resolved by nros sync (the RFC-0060 resolver executes them with its bundled CPython; $(find-pkg-share) against ROS packages needs a sourced AMENT_PREFIX_PATH). The XML schema above is the native authoring format.


package.xml

A standard ROS 2 manifest. List each Node package as an <exec_depend>:

<?xml version="1.0"?>
<package format="3">
  <name>demo_bringup</name>
  <version>0.1.0</version>
  <description>Bringup package for the demo system</description>
  <maintainer email="you@example.com">Your Name</maintainer>
  <license>Apache-2.0</license>

  <exec_depend>talker_pkg</exec_depend>
  <exec_depend>listener_pkg</exec_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

No <build_depend> entries — there is nothing to compile.


Workflow: check → run

Once your Bringup pkg is written, use nros check to validate and cargo run to execute the topology:

# 1. Lint the bringup pkg (pure-declarative check — no Cargo.toml, stray files, etc.)
nros check --bringup src/demo_bringup

# 2. Lint the whole workspace (pkg/class rows, duplicate system.toml, etc.)
nros check --workspace .

# 3. Run the composed Entry binary (boots all nodes in a single process)
ZENOH_CONFIG_OVERRIDE='listen/endpoints=["tcp/127.0.0.1:7447"];scouting/multicast/enabled=false' ros2 run rmw_zenoh_cpp rmw_zenohd &   # router — in another shell
cargo run -p native_entry

Both nros check forms pass for the canonical template at examples/workspaces/rust/.

Caveat — nros plan with this template

  • nros plan demo_bringup resolves a topology into plan.json for static type/QoS checks, but it currently requires pre-collected source-metadata sidecars (record.json + per-pkg _metadata/*.json). The automatic metadata-build path (nros metadata --build) is not yet wired for lib-only Node pkgs, so nros plan does not produce a plan straight from this template. See packages/testing/nros-tests/fixtures/orchestration_e2e/ for the pre-collected-sidecar pipeline.

The canonical template README at examples/workspaces/rust/README.md is the source of truth for the current CLI state.


Runnable copy-out

examples/workspaces/rust/ is the canonical Rust 3-role workspace that pairs with this guide. Copy the whole directory out and rename the packages. nros sync materializes generated message crates, nros codegen-system bakes the Bringup package, and cargo build -p native_entry builds the Entry pkg.

The workspace README at examples/workspaces/rust/README.md documents the exact CLI commands that are verified green today.


When you don’t need a Bringup pkg

If you have a single Entry pkg and don’t plan to share the topology across multiple boards, fold launch/ + config/ directly into the Entry pkg. The nros::main! macro’s launch = argument names the bringup package and, optionally, a launch file within it — the SAME inputs you author. The build resolves them to a SystemModel under <workspace>/build/nros/models/ (run nros sync, or let the build system drive it); you never reference the model file, though you can inspect it there:

#![allow(unused)]
fn main() {
// Multi-node: the bringup's default launch (system.launch.xml)
nros::main!(launch = "demo_bringup");

// A named launch file in the bringup pkg
nros::main!(launch = "demo_bringup:sim.launch.xml");

// A launch-argument binding, declared as [[model]] in system.toml
nros::main!(launch = "demo_bringup:multihost.launch.xml", args = [("host", "robot1")]);
}

If the launch files live inside the Entry pkg itself, point at it by name. The Entry package page covers this in full.


Where to go next