Skip to content

Multi Robot Namespacing

hippo5329 edited this page Oct 15, 2026 · 12 revisions

Multi-Robot Namespacing

Set topic_prefix and a robot's topics and TF frames move under /<prefix>/, so two robots can share one ROS 2 network without colliding.

In This Guide: How Namespacing Works · Configuring topic_prefix · Two Robots on One Network · Silent Traps & Debugging

How it works

topic_prefix in a robot's config is consumed on both sides:

  • The board (firmware) prefixes every topic name it uses and every frame_id it stamps inside them. With topic_prefix: lino1 a robot publishes /lino1/odom/unfiltered, /lino1/imu/data, /lino1/imu/mag, /lino1/raw_scan and /lino1/sonar, and listens on /lino1/cmd_vel, with frames lino1/odom, lino1/base_footprint, lino1/base_link, lino1/imu_link and lino1/sonar_link. Both matter: a prefixed topic holding a bare frame names something absent from the robot's own TF tree, and its EKF will drop the message without a word. Because the prefix lives in the env partition, setting it is a 4 KB write — no rebuild.
  • The host stack (bringup, SLAM, Nav2) runs under the matching ROS namespace and prefixes its TF frames, so the EKF, SLAM and Nav2 all refer to this robot's topics and frames.
flowchart LR
    subgraph r1["Robot lino1"]
      b1["board → /lino1/…"] --- h1["host stack ns=/lino1<br/>frames lino1/…"]
    end
    subgraph r2["Robot lino2"]
      b2["board → /lino2/…"] --- h2["host stack ns=/lino2<br/>frames lino2/…"]
    end
    r1 --- dds(("one DDS domain")) --- r2
Loading

TF stays on the global /tf with the frame names prefixed (lino1/base_link, lino1/odom) — the ROS 2 multi-robot convention — so the two robots' transform trees coexist on one /tf without collision.

Setting it

Add to the robot's config base_controller:

base_controller:
  topic_prefix: lino1     # letters, digits, underscore and /; `lino1` and `lino1/` mean the same

Then re-deploy. The board's env is rewritten with the prefix, and bringup/SLAM/Nav2 pick it up from the same config. A nested prefix (fleet/lino1) is allowed; an invalid value is ignored rather than passed to the middleware (which would reject it and leave the robot silent).

Or a domain each

Robots that should not see each other at all go on different DDS domains instead:

base_controller:
  domain_id: 42           # 0..232; absent = the ROS_DOMAIN_ID the stack was started with

The board is flashed for it, and the Sim MCU, the 1-Click and every command the Web UI starts run on it (the ROS Domain ID field on MCU & Sim, tab 1, sets it). The micro-ROS agent puts a board in the domain the board asks for and does not read ROS_DOMAIN_ID, which is why the domain is written into the board's env rather than set on the agent. 233 and up have no port on Fast DDS's default mapping, and the agent refuses them.

Two robots on one network

Two robots on one subnet and one DDS domain, lino1 and lino2, each on its own robot computer, show:

check expect
topics both robots' full sets side by side, /lino1/… and /lino2/…, no collision
rates, measured from one machine each robot's /odom/unfiltered at 50 Hz and /scan at 10 Hz
TF one global /tf; frames lino1/base_link, lino2/base_link, scans stamped lino1/laser / lino2/laser
isolation driving /lino1/cmd_vel moves lino1 and leaves lino2 where it stands

Only the last needs two robots: everything else can be checked on one.

Silent traps

A namespace mismatch is not an error anywhere in ROS 2: it shows as an EKF that publishes nothing (no /lino1/odom, no odom → base transform) while the board's own odometry is perfect. Two rules keep it from happening, and one check each proves them:

A params file is matched by the node's fully-qualified name. Under a namespace the node is /lino1/ekf_filter_node, and a section keyed ekf_filter_node: matches nothing, so the node starts on its package defaults and reports them as its configuration. The launcher keys every section by its full path, as nav2's RewrittenYaml does. Ask the node what it was given:

ros2 param get /lino1/ekf_filter_node frequency   # the config's value, or the package default?

Every frame_id carries the namespace, not only the topic name. In this robot's TF tree robot_state_publisher publishes lino1/odom; a message stamped odom names a frame that does not exist there, and the EKF drops it without a word. The firmware prefixes every frame it stamps (odom, base_footprint, imu_link, sonar_link, base_link) as well as every topic:

ros2 topic echo /lino1/odom/unfiltered --once | grep frame_id   # expect lino1/odom

The firmware builds the prefixed names in a heap buffer of twenty-four names, more than it uses. If it ever runs out of room it says so on the console rather than drop the prefix quietly, because a silent drop would split one robot's graph across two namespaces:

[topic] no room to prefix "<name>" (slots <n>/24, arena <used>/<size>) -- publishing it unprefixed

Back to Home · Next: Building a Real Robot.

Clone this wiki locally