Testbot is a ROS 2 (Humble) differential-drive robot package built for autonomous navigation in simulation. It integrates Gazebo Fortress (Ignition), Nav2, SLAM Toolbox, AMCL, and ros2_control for a full navigation stack — from mapping to localization to goal-based path planning.
Note: Change the Folder name to "testbot" (package name) or edit xml and CMakelist accordingly before building
- Requirements
- Package Structure
- Installation & Build
- Usage
- Configuration Files
- Key Components
- Troubleshooting
- Next Steps
Robot shown in Husarion world and TurtleBot Arena with TF tree visualization
| Dependency | Version |
|---|---|
| ROS 2 | Humble |
| Gazebo | Fortress (Ignition) |
| Nav2 | Humble |
| SLAM Toolbox | Humble |
| ros2_control | Humble |
| robot_localization | Humble |
| topic_tools | Humble |
| twist_mux | Humble |
Install all ROS 2 dependencies:
cd ~/botdev
rosdep install --from-paths src --ignore-src -r -ytestbot/
├── config/
│ ├── nav.yaml # Nav2 full stack parameters (costmaps, planner, controller)
│ ├── amcloc.yaml # AMCL localization parameters
│ ├── slam_map.yaml # SLAM Toolbox mapping parameters
│ ├── slam_loc.yaml # SLAM Toolbox localization parameters
│ ├── ekf.yaml # robot_localization EKF parameters
│ ├── twistmux.yaml # Twist multiplexer configuration
│ └── 4w_diff_drive_controller_velocity.yaml # ros2_control diff drive config
├── description/
│ └── 4w_testbot.xacro # Main robot URDF/Xacro model
├── launch/
│ ├── 4w_rsp.launch.py # Core launch: Gazebo, RSP, EKF, bridges, controllers
│ ├── slamap.launch.py # SLAM mapping mode
│ ├── slamloc.launch.py # SLAM localization mode (on existing map)
│ ├── amcloc.launch.py # Standalone AMCL localization
│ └── nav.launch.py # Full Nav2 navigation (AMCL + Nav2 + RViz)
├── maps/
│ ├── testv1.yaml / testv1.pgm # Default navigation map
│ ├── testhus_map.yaml / .pgm # Husarion world map
│ └── v2.yaml / v2.pgm # Alternative map
├── rviz/
│ ├── nav.rviz # Navigation RViz config (Nav2 + costmaps)
│ ├── async_map.rviz # SLAM mapping RViz config
│ └── amcl.rviz # AMCL localization RViz config
└── worlds/
└── husarion_world.sdf # Husarion simulation world
# Clone into your workspace (if not already present)
cd ~/botdev/src
# Build the package
cd ~/botdev
colcon build --packages-select testbot --symlink-install
# Source the workspace
source install/setup.bashUse
--symlink-installduring development so changes to launch files, configs, and RViz files take effect without rebuilding.
All launch commands assume you have sourced your workspace:
source ~/botdev/install/setup.bashLaunches Gazebo Fortress with the robot, robot_state_publisher, EKF, ros2_control, and sensor bridges. No navigation stack.
ros2 launch testbot 4w_rsp.launch.pyOptional arguments:
| Argument | Default | Description |
|---|---|---|
use_sim_time |
true |
Use Gazebo simulation clock |
use_rviz |
true |
Launch RViz |
run_headless |
false |
Run Gazebo without GUI |
gz_verbosity |
3 |
Gazebo log verbosity (0–4) |
log_level |
warn |
ROS 2 node log level |
Example — run headless:
ros2 launch testbot 4w_rsp.launch.py run_headless:=true use_rviz:=falseLaunches Gazebo + SLAM Toolbox in online async mode. Drive the robot around to build a map.
ros2 launch testbot slamap.launch.pyOptional arguments:
| Argument | Default | Description |
|---|---|---|
use_sim_time |
true |
Use simulation clock |
rviz |
true |
Launch RViz |
rviz_config |
async_map.rviz |
RViz config file name |
Teleoperate the robot to map the environment:
ros2 run teleop_twist_keyboard teleop_twist_keyboard --ros-args -r /cmd_vel:=/cmd_velWhen done mapping, save the map (see Saving a Map).
Localizes the robot on a previously saved map using SLAM Toolbox in localization mode.
ros2 launch testbot slamloc.launch.pyOptional arguments:
| Argument | Default | Description |
|---|---|---|
use_sim_time |
true |
Use simulation clock |
rviz |
true |
Launch RViz |
rviz_config |
async_map.rviz |
RViz config file name |
Make sure the serialized map files (
serialized.data/serialized.posegraph) are referenced inconfig/slam_loc.yaml.
Launches map server + AMCL + lifecycle manager for localization only, without the full Nav2 stack.
ros2 launch testbot amcloc.launch.pyOptional arguments:
| Argument | Default | Description |
|---|---|---|
map |
maps/testhus_map.yaml |
Full path to map YAML file |
Use a different map:
ros2 launch testbot amcloc.launch.py map:=/path/to/your_map.yamlLaunches the complete navigation stack: Gazebo, robot, AMCL localization, Nav2 (planner + controller + costmaps), and RViz with the Nav2 panel.
ros2 launch testbot nav.launch.pyOptional arguments:
| Argument | Default | Description |
|---|---|---|
yaml_filename |
maps/testv1.yaml |
Map file for localization |
nav_params_file |
config/nav.yaml |
Nav2 parameters file |
loc_params_file |
config/amcloc.yaml |
AMCL parameters file |
use_sim_time |
true |
Use simulation clock |
Use a different map:
ros2 launch testbot nav.launch.py yaml_filename:=/full/path/to/map.yamlOnce launched:
- RViz opens with the Nav2 panel on the left
- Use 2D Pose Estimate in RViz to set the robot's initial position on the map
- Use Nav2 Goal in RViz (or the Nav2 panel) to send a navigation goal
- The robot will plan and follow a path autonomously
After mapping with SLAM, save the map using nav2_map_server:
ros2 run nav2_map_server map_saver_cli -f ~/botdev/src/testbot/maps/my_mapThis creates my_map.pgm and my_map.yaml in the maps directory. Then rebuild so the new map is installed:
cd ~/botdev && colcon build --packages-select testbot --symlink-installFull Nav2 parameter file covering:
- AMCL — particle filter localization (differential motion model)
- bt_navigator — behavior tree navigator
- controller_server — MPPI controller for local path following
- local_costmap — rolling window costmap using VoxelLayer + InflationLayer
- global_costmap — static + obstacle + inflation layers
- planner_server — SmacPlannerHybrid (Dubins motion model)
- behavior_server — spin, backup, drive_on_heading, wait behaviors
- velocity_smoother — smooths cmd_vel output
- collision_monitor — footprint-based approach collision checking
Standalone AMCL parameters used by amcloc.launch.py and nav.launch.py for localization.
robot_localization EKF node configuration for fusing wheel odometry and IMU data into a stable /odom estimate.
SLAM Toolbox parameters for online async mapping and localization modes respectively.
- Defined in
description/4w_testbot.xacro - 4-wheel differential drive with
base_footprint,base_link,lidar_link,imu_link,camera_link - Gazebo plugins: diff drive controller, LIDAR (LaserScan), IMU, camera
The ros_gz_bridge node bridges the following topics from Ignition to ROS 2:
| Ignition Topic | ROS 2 Topic | Message Type |
|---|---|---|
/scan |
/scan |
sensor_msgs/LaserScan |
/imu |
/imu |
sensor_msgs/Imu |
/clock |
/clock |
rosgraph_msgs/Clock |
/scan/points |
/scan/points |
sensor_msgs/PointCloud2 |
Nav2 cmd_vel → twist_mux → /cmd_vel → relay → diff_drive_base_controller → wheels
map → odom → base_footprint → base_link → lidar_link
→ imu_link
→ camera_link → camera_frame
→ fl_wheel / fr_wheel / rl_wheel / rr_wheel
- Confirm
localization_launch.pyis receiving themapargument — check terminal output formap_serverstartup logs - In RViz, verify the
Mapdisplay under the Displays panel is enabled and subscribed to/mapwithDurability: Transient Local - Check that AMCL is active:
ros2 lifecycle get /amcl
- Set a 2D Pose Estimate in RViz to give AMCL an initial position hint
- Ensure
/scantopic is publishing:ros2 topic hz /scan - Verify
base_frame_idinamcloc.yamlmatches your URDF (base_footprint)
- Check all Nav2 nodes are in
activelifecycle state:ros2 lifecycle get /bt_navigator - Confirm the global and local costmaps are receiving scan data:
ros2 topic hz /global_costmap/costmap
- Caused by fixed joint lumping when converting URDF to SDF. Ensure the following is in your xacro for the lidar joint:
<gazebo reference="laser_joint">
<preserveFixedJoint>true</preserveFixedJoint>
</gazebo>- Controllers must be loaded after
spawn_entitycompletes. The launch file handles this viaRegisterEventHandler/OnProcessExitchains — check thatspawn_entitysucceeded first - Verify controller names match those in
4w_diff_drive_controller_velocity.yaml
- Caused by
odomframe drift from IMU noise during fast rotation - Reduce rotational speed during teleoperation while mapping
- Verify EKF is running and fusing IMU + odom:
ros2 topic echo /odometry/filtered
Map generated after resolving TF frame alignment issue
- If you see
nvml error: driver not loaded, reinstall NVIDIA drivers and reconfigure the NVIDIA Container Toolkit - Verify with:
nvidia-smi
- Improve localization stability with better EKF tuning
- Optimize MPPI controller parameters for smoother trajectories
- Add 3D obstacle avoidance using PointCloud2 from LIDAR
- Validate navigation stack on physical hardware





