ROS Resources: Documentation | Support | Discussion Forum | Index | Service Status | ros @ Robotics Stack Exchange
Ask Your Question

Revision history [back]

The answer is based on the turtlebot robot, but of course it can be used with any robot(s).

For multiple robots the key point is to manage the namespaces and tf_prefixes. Although usually we will set them to the same value for each robot in this example I will you different for purpose.

In setting up the navigation stack(s) each robot must operate in it's own namespace, but they do have to share the map (and usually robot_description). So the effect we want to achieve is:

                                  /map
       /robot1/robot_state_publisher    /robot2/robot_state_publisher
       /robot1/robot_pose_ekf           /robot2/robot_pose_ekf
       /robot1/amcl                     /robot2/amcl
       /robot1/move_base                /robot2/move_base

Simulation

First of all we have to launch the simulation, in our case gazebo. To make things more readable I split launch files in few files.

Launch file for one robot. We name it one_robot.launch:

<launch>
    <arg name="robot_name"/>
    <arg name="init_pose"/>

    <node name="spawn_minibot_model" pkg="gazebo" type="spawn_model"
          args="$(arg init_pose) -urdf -param /robot_description -model $(arg robot_name)"
          respawn="false" output="screen" />

    <node pkg="robot_state_publisher" type="state_publisher" 
          name="robot_state_publisher" output="screen"/>

    <!-- The odometry estimator, throttling, fake laser etc. -->
    <!-- All the stuff as from example -->
</launch>

Two things to mention for above launch file:

  • create two arguments to pass for robot initialization: name and position
  • notice that -param /robot_description is with the slash i.e. fully-qualified, as it will be shared.

Having the robot declaration we can now play with the namespaces. Your main launch file for multiple robots should look like (robots.launch):

<launch>
  <!-- No namespace here as we will share this description. 
       Access with slash at the beginning -->
  <param name="robot_description"
    command="$(find xacro)/xacro.py $(find turtlebot_description)/urdf/turtlebot.urdf.xacro" />

  <!-- BEGIN ROBOT 1-->
  <group ns="robot1">
    <param name="tf_prefix" value="robot1_tf" />
    <include file="$(find your_pkg)/launch/one_robot.launch" >
      <arg name="init_pose" value="-x 1 -y 1 -z 0" />
      <arg name="robot_name"  value="Robot1" />
    </include>
  </group>

  <!-- BEGIN ROBOT 2-->
  <group ns="robot2">
    <param name="tf_prefix" value="robot2_tf" />
    <include file="$(find your_pkg)/launch/one_robot.launch" >
      <arg name="init_pose" value="-x -1 -y 1 -z 0" />
      <arg name="robot_name"  value="Robot2" />
    </include>
  </group>
</launch>

Last thing of course is to include it in your simulation launch file:

<launch>
  <param name="/use_sim_time" value="true" />

  <!-- start world -->
  <node name="gazebo" pkg="gazebo" type="gazebo" 
   args="$(find your_pkg)/worlds/your.world" respawn="false" output="screen" />

  <!-- start gui -->
  <node name="gazebo_gui" pkg="gazebo" type="gui" respawn="false" output="screen"/>

  <!-- include our robots -->
  <include file="$(find your_pkg)/launch/robots.launch"/>
</launch>

Navigation

For setting up the navigation stack we follow the tutorial RobotSetup, but we have to add the namespace tricks for it.

We use the same approach of splitting files. First the move_base.launch for one robot, as from tutorial:

<launch>    
  <!--- Run AMCL -->
  <include file="$(find amcl)/examples/amcl_diff.launch" />

  <!-- Define your move_base node -->
  <node pkg="move_base" type="move_base" respawn="false" name="move_base" output="screen">
    <rosparam file="$(find your_pkg)/costmap_common_params.yaml" command="load" ns="global_costmap"/>
    <rosparam file="$(find your_pkg)/costmap_common_params.yaml" command="load" ns="local_costmap" />
    <rosparam file="$(find your_pkg)/local_costmap_params.yaml"  command="load" />
    <rosparam file="$(find your_pkg)/global_costmap_params.yaml" command="load" />
    <rosparam file="$(find your_pkg)/base_local_planner_params.yaml" command="load" />

    <remap from="map" to="/map" />
  </node>
</launch>

The important note is to notice this remapping at the end. This file will be wrapped with namespace so we have to remove namespace from all appearances of map (there is only one map).

Now we can launch our multiple navigation file:

<launch>
  <param name="/use_sim_time" value="true"/>

  <!-- Run the map server -->
  <node name="map_server" pkg="map_server" type="map_server" args="$(find your_pkg)/map/map.yaml" >
    <param name="frame_id" value="/map" />
  </node>

  <group ns="robot1">
    <param name="tf_prefix" value="robot1_tf" />
    <param name="amcl/initial_pose_x" value="1" />
    <param name="amcl/initial_pose_y" value="1" />
    <include file="$(find your_pkg)/launch/move_base.launch" />
  </group>

  <group ns="robot2">
    <param name="tf_prefix" value="robot2_tf" />
    <param name="amcl/initial_pose_x" value="-1" />
    <param name="amcl/initial_pose_y" value="1" />
    <include file="$(find your_pkg)/launch/move_base.launch" />
  </group>

  <node pkg="rviz" type="rviz" name="rviz" args="-d $(find your_pkg)/config/multi.vcg"
   output="screen" />

</launch>

Two points to mention here.

  • Notice the param frame_id in map_server node. By default frame_id is set to map, and as map is shared for all robots we need only one map and therefore we have to force the frame_id to fully-qualified name /map.
  • Localization made by amcl need some help in providing the initial position on the map. For this purpose we introduce params carrying those values. Amcl node is in the robot namespace and those the whole path should be like /robot1/amcl/initial_pose_x, etc.

Hacking

Last thing to do is to correct some plugins for gazebo.

  1. gazebo_ros_laser.cpp in gazebo_plugins package - patch for tf_prefix resolving. Already done in fuerte, for electric see the ticket: https://code.ros.org/trac/ros-pkg/ticket/5511
  2. gazebo_ros_create.cpp in turtlebot_gazebo_plugins - two corrections needed
    • remove initial slashes from topics: cmd_vel, odom, joint_states (staring from line 157),
    • and replace line 36 to node_namespaceP_ = new ParamT<std::string>("robotNamespace","",0);

Visualization

Now it's time to play around. We can send your messages to /robot1/move_base_simple/goal or via actionlib to /robot2/move_base/goal.

In rviz we can add several Robot models, but we have to remember to put a correct tf_prefix for each robot_description:

Robot Description: robot_description
TF Prefix:         robot1_tf

In rviz configuration file we have to modify the topic for actions connected to tool buttons (your local vcg file):

Tool\ 2D\ Nav\ GoalTopic=/robot1/move_base_simple/goal
Tool\ 2D\ Pose\ EstimateTopic=/robot1/initialpose

If we want to control all robots from rviz we need to add more tool buttons as described here

Have fun.

The answer is based on the turtlebot robot, but of course it can be used with any robot(s).

For multiple robots the key point is to manage the namespaces and tf_prefixes. Although usually we will set them to the same value for each robot in this example I will you different for purpose.

In setting up the navigation stack(s) each robot must operate in it's own namespace, but they do have to share the map (and usually robot_description). So the effect we want to achieve is:

                                  /map
       /robot1/robot_state_publisher    /robot2/robot_state_publisher
       /robot1/robot_pose_ekf           /robot2/robot_pose_ekf
       /robot1/amcl                     /robot2/amcl
       /robot1/move_base                /robot2/move_base

Simulation

First of all we have to launch the simulation, in our case gazebo. To make things more readable I split launch files in few files.

Launch file for one robot. We name it one_robot.launch:

<launch>
    <arg name="robot_name"/>
    <arg name="init_pose"/>

    <node name="spawn_minibot_model" pkg="gazebo" type="spawn_model"
      args="$(arg init_pose) -urdf -param /robot_description -model $(arg robot_name)"
      respawn="false" output="screen" />

    <node pkg="robot_state_publisher" type="state_publisher" 
          name="robot_state_publisher" output="screen"/>

    <!-- The odometry estimator, throttling, fake laser etc. go here -->
    <!-- All the stuff as from example usual robot launch file -->
</launch>

Two things to mention for above launch file:

  • create two arguments to pass for robot initialization: name and position
  • notice that -param /robot_description is with the slash i.e. fully-qualified, as it will be shared.

Having the robot declaration we can now play with the namespaces. Your main launch file for multiple robots should look like (robots.launch):

<launch>
  <!-- No namespace here as we will share this description. 
       Access with slash at the beginning -->
  <param name="robot_description"
    command="$(find xacro)/xacro.py $(find turtlebot_description)/urdf/turtlebot.urdf.xacro" />

  <!-- BEGIN ROBOT 1-->
  <group ns="robot1">
    <param name="tf_prefix" value="robot1_tf" />
    <include file="$(find your_pkg)/launch/one_robot.launch" >
      <arg name="init_pose" value="-x 1 -y 1 -z 0" />
      <arg name="robot_name"  value="Robot1" />
    </include>
  </group>

  <!-- BEGIN ROBOT 2-->
  <group ns="robot2">
    <param name="tf_prefix" value="robot2_tf" />
    <include file="$(find your_pkg)/launch/one_robot.launch" >
      <arg name="init_pose" value="-x -1 -y 1 -z 0" />
      <arg name="robot_name"  value="Robot2" />
    </include>
  </group>
</launch>

Last thing of course is to include it in your simulation launch file:

<launch>
  <param name="/use_sim_time" value="true" />

  <!-- start world -->
  <node name="gazebo" pkg="gazebo" type="gazebo" 
   args="$(find your_pkg)/worlds/your.world" respawn="false" output="screen" />

  <!-- start gui -->
  <node name="gazebo_gui" pkg="gazebo" type="gui" respawn="false" output="screen"/>

  <!-- include our robots -->
  <include file="$(find your_pkg)/launch/robots.launch"/>
</launch>

Navigation

For setting up the navigation stack we follow the tutorial RobotSetup, but we have to add the namespace tricks for it.

We use the same approach of splitting files. First the move_base.launch for one robot, as from tutorial:

<launch>    
  <!--- Run AMCL -->
  <include file="$(find amcl)/examples/amcl_diff.launch" />

  <!-- Define your move_base node -->
  <node pkg="move_base" type="move_base" respawn="false" name="move_base" output="screen">
    <rosparam file="$(find your_pkg)/costmap_common_params.yaml" command="load" ns="global_costmap"/>
    <rosparam file="$(find your_pkg)/costmap_common_params.yaml" command="load" ns="local_costmap" />
    <rosparam file="$(find your_pkg)/local_costmap_params.yaml"  command="load" />
    <rosparam file="$(find your_pkg)/global_costmap_params.yaml" command="load" />
    <rosparam file="$(find your_pkg)/base_local_planner_params.yaml" command="load" />

    <remap from="map" to="/map" />
  </node>
</launch>

The important note is to notice this remapping at the end. This file will be wrapped with namespace so we have to remove namespace from all appearances of map (there is only one map).

Now we can launch our multiple navigation file:

<launch>
  <param name="/use_sim_time" value="true"/>

  <!-- Run the map server -->
  <node name="map_server" pkg="map_server" type="map_server" args="$(find your_pkg)/map/map.yaml" >
    <param name="frame_id" value="/map" />
  </node>

  <group ns="robot1">
    <param name="tf_prefix" value="robot1_tf" />
    <param name="amcl/initial_pose_x" value="1" />
    <param name="amcl/initial_pose_y" value="1" />
    <include file="$(find your_pkg)/launch/move_base.launch" />
  </group>

  <group ns="robot2">
    <param name="tf_prefix" value="robot2_tf" />
    <param name="amcl/initial_pose_x" value="-1" />
    <param name="amcl/initial_pose_y" value="1" />
    <include file="$(find your_pkg)/launch/move_base.launch" />
  </group>

  <node pkg="rviz" type="rviz" name="rviz" args="-d $(find your_pkg)/config/multi.vcg"
   output="screen" />

</launch>

Two points to mention here.

  • Notice the param frame_id in map_server node. By default frame_id is set to map, and as map is shared for all robots we need only one map and therefore we have to force the frame_id to fully-qualified name /map.
  • Localization made by amcl need some help in providing the initial position on the map. For this purpose we introduce params carrying those values. Amcl node is in the robot namespace and those the whole path should be like /robot1/amcl/initial_pose_x, etc.

Hacking

Last thing to do is to correct some plugins for gazebo.

  1. gazebo_ros_laser.cpp in gazebo_plugins package - patch for tf_prefix resolving. Already done in fuerte, for electric see the ticket: https://code.ros.org/trac/ros-pkg/ticket/5511
  2. gazebo_ros_create.cpp in turtlebot_gazebo_plugins - two corrections needed
    • remove initial slashes from topics: cmd_vel, odom, joint_states (staring from line 157),
    • and replace line 36 to node_namespaceP_ = new ParamT<std::string>("robotNamespace","",0);

Visualization

Now it's time to play around. We can send your messages to /robot1/move_base_simple/goal or via actionlib to /robot2/move_base/goal.

In rviz we can add several Robot models, but we have to remember to put a correct tf_prefix for each robot_description:

Robot Description: robot_description
TF Prefix:         robot1_tf

In rviz configuration file we have to modify the topic for actions connected to tool buttons (your local vcg file):

Tool\ 2D\ Nav\ GoalTopic=/robot1/move_base_simple/goal
Tool\ 2D\ Pose\ EstimateTopic=/robot1/initialpose

If we want to control all robots from rviz we need to add more tool buttons as described here

Have fun.