Multi-Robot Navigation with EasyNavο
This HowTo demonstrates how to run multiple EasyNav robots simultaneously in a shared simulation. Each robot operates independently using its own namespaced set of nodes, topics, and frames.
Overviewο
Multi-robot navigation with EasyNav is straightforward, but it requires discipline when naming topics and managing TF frames. This tutorial explains how to set up multiple robots without topic or TF conflicts, using the Kobuki PlayGround.
Setupο
Build EasyNav and the Kobuki PlayGround as described in Getting Started.
1. Topic Naming and TF Managementο
Fully-qualified topic names (plugin developers)ο
Each plugin should publish topics under its fully qualified name, which includes the node and plugin names. This prevents collisions when several robots run the same plugins.
Example (in C++):
path_pub_ = node->create_publisher<nav_msgs::msg::Path>(
node->get_fully_qualified_name() + std::string("/") + plugin_name + "/path",
10);
This ensures topics look like /r1/planner_node/simple/path and /r2/planner_node/simple/path
instead of clashing on /path.
TF topic remappingο
Each robot typically has its own TF tree. To isolate TF data, remap the global TF topics
(/tf and /tf_static) to relative ones, so they are automatically namespaced:
-r /tf:=tf -r /tf_static:=tf_static
This yields separate TF topics for each robot: /r1/tf and /r1/tf_static, /r2/tf and
/r2/tf_static.
Note
Do not apply this remap if both robots are designed to share the same TF tree (which is rare).
Namespaced framesο
If you configure a TF prefix for each system node (system_node.tf_prefix, e.g. r1), then
all frames include this prefix: r1/map, r1/odom, r1/base_link, etc.
r1/system_node:
ros__parameters:
tf_prefix: r1
The simulated robots must publish their frames with the same prefix: the PlayGroundβs
kobuki.launch.yaml does it when launched with a namespace.
2. Launching in Simulationο
The whole system (two Kobukis, r1 at (0, 0) and r2 at (2, 1), each with its own EasyNav and
RViz2) starts with:
ros2 launch easynav_playground_kobuki easynav_multirobot.launch.yaml
Send 2D Goal Poses independently in each RViz2 window.
Step by step, this is what the launch file does, and what you would do with your own robots:
Start the simulator with two robots (
multirobot_gazebo_sim.launch.yaml): each Kobuki in its namespace, with its TF topics (/r1/tf) and prefixed frames (r1/base_link).ros2 launch easynav_playground_kobuki_worlds multirobot_gazebo_sim.launch.yaml gui:=false
Start EasyNav for each robot, with the same parameter file, its own namespace and the TF remapping:
ros2 run easynav_system system_main \ --ros-args \ --params-file $(ros2 pkg prefix easynav_playground_kobuki)/share/easynav_playground_kobuki/params/costmap_multirobot.params.yaml \ -r __ns:=/r1 -r /tf:=tf -r /tf_static:=tf_static
and the same with
__ns:=/r2in another terminal.Start RViz2 for each robot, in its namespace, with the TF remapping and its
mapframe as fixed frame:ros2 run rviz2 rviz2 \ -d $(ros2 pkg prefix easynav_playground_kobuki)/share/easynav_playground_kobuki/rviz/easynav_multirobot.rviz \ -f r1/map \ --ros-args -r __ns:=/r1 -r /tf:=tf -r /tf_static:=tf_static -p use_sim_time:=true
The RViz2 configuration uses relative topic names, so the same file works for both robots.
3. Example Parametersο
params/costmap_multirobot.params.yaml has one section per robot (r1/..., r2/...),
because each EasyNav runs in its namespace. Each section is the configuration of
Navigating with the Costmap Stack, with that robotβs tf_prefix and initial pose. Robot r1:
r1/controller_node:
ros__parameters:
use_sim_time: true
robot_limits:
max_linear_vel: 0.6
min_linear_vel: -0.3
max_angular_vel: 1.2
max_linear_acc: 1.0
max_linear_decel: 1.0
max_angular_acc: 2.0
max_angular_decel: 2.0
controller_types: [rpp]
rpp:
rt_freq: 30.0
plugin: easynav_regulated_pp_controller/RegulatedPurePursuitController
# ... (as in costmap.rpp.params.yaml)
r1/localizer_node:
ros__parameters:
use_sim_time: true
localizer_types: [costmap]
costmap:
rt_freq: 50.0
freq: 5.0
reseed_freq: 1.0
plugin: easynav_costmap_localizer/AMCLLocalizer
num_particles: 100
noise_translation: 0.05
noise_rotation: 0.1
noise_translation_to_rotation: 0.1
initial_pose:
x: 0.0
y: 0.0
yaw: 0.0
std_dev_xy: 0.1
std_dev_yaw: 0.01
r1/maps_manager_node:
ros__parameters:
use_sim_time: true
map_types: [costmap]
costmap:
freq: 10.0
plugin: easynav_costmap_maps_manager/CostmapMapsManager
package: easynav_playground_kobuki
map_path_file: maps/home2.yaml
filters: [obstacles, inflation]
obstacles:
plugin: easynav_costmap_maps_manager/CostmapMapsManager/ObstaclesFilter
inflation:
plugin: easynav_costmap_maps_manager/CostmapMapsManager/InflationFilter
inflation_radius: 1.0
cost_scaling_factor: 5.0
r1/planner_node:
ros__parameters:
use_sim_time: true
planner_types: [simple]
simple:
freq: 0.5
plugin: easynav_costmap_planner/CostmapPlanner
cost_factor: 10.0
continuous_replan: true
r1/sensors_node:
ros__parameters:
use_sim_time: true
forget_time: 0.5
sensors: [laser1]
laser1:
topic: scan_raw
type: sensor_msgs/msg/LaserScan
r1/system_node:
ros__parameters:
robot_geometry:
radius: 0.178
inscribed_radius: 0.178
height: 0.5
use_sim_time: true
use_real_time: true
tf_prefix: r1
position_tolerance: 0.3
angle_tolerance: 0.15
r1/recovery_node:
ros__parameters:
# ... (as in costmap.rpp.params.yaml)
The r2/... sections are the same, with tf_prefix: r2 and initial_pose at
x: 2.0, y: 1.0.
Tips & Gotchasο
Namespaces everywhere: Verify all relative topics (e.g.,
scan_raw) are correctly resolved under each robot namespace (/r1/scan_raw,/r2/scan_raw).Avoid over-remapping: Only remap
/tfand/tf_staticto relative topics when each robot manages its own TF tree.Frame references: With
tf_prefixset, refer to frames asr1/base_link,r1/odom, etc.ROS Domain IDs: If you want isolation or multiple networks, assign different
ROS_DOMAIN_IDper fleet. Otherwise, keep the same domain for shared visualization.Several EasyNav on one computer: each one writes its own time statistics log, named after its namespace (
/tmp/easynav_r1.log);ros2 easynav timestats --namespace r1selects one (see ros2 easynav β EasyNav CLI Extensions).
With this setup, each robot runs a full EasyNav navigation stack under its own namespace, enabling coordinated multi-robot simulation in Gazebo and RViz2.