Repository navigation
Multi Robot Namespacing
Set
topic_prefixand 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
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: lino1a robot publishes/lino1/odom/unfiltered,/lino1/imu/data,/lino1/imu/mag,/lino1/raw_scanand/lino1/sonar, and listens on/lino1/cmd_vel, with frameslino1/odom,lino1/base_footprint,lino1/base_link,lino1/imu_linkandlino1/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
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.
Add to the robot's config base_controller:
base_controller:
topic_prefix: lino1 # letters, digits, underscore and /; `lino1` and `lino1/` mean the sameThen 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).
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 withThe 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 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.
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/odomThe 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.