Skip to content

About

Simulation-based autonomous mobile robot using ROS 2 Jazzy, Gazebo Harmonic, ros2_control, SLAM Toolbox, AMCL and Nav2 with automated validation.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

ROS 2 Autonomous Mobile Robot

CI

Differential-drive robot simulation for indoor mapping and navigation, with a C++ LiDAR velocity-safety monitor and a Python waypoint mission/evaluation client.

Simulation only: no hardware validation or safety certification is claimed. Target platform: Ubuntu 24.04, ROS 2 Jazzy, Gazebo Harmonic, RViz2, C++17 and Python 3.

Nav2 executing a goal with a global path and local costmap

Highlights

  • Custom URDF/Xacro robot with physically derived inertias, two driven wheels and a rear spherical support.
  • Four launch modes for simulation, SLAM mapping, AMCL localization and Nav2 navigation.
  • One stamped command path through a configurable C++ safety monitor, with manual/autonomy selection, stale-data stops and a latched stop service.
  • Sequential waypoint actions with timeout/cancellation handling, live mission status and measured CSV output.
  • Deterministic reference-map generation, map saving, MCAP recording, runtime graph checks and automated tests.

This project intentionally stays with 2D perception. The simulated IMU is available for inspection and recording; it is not fused into odometry.

Validated results

  • Headless/full and Gazebo GUI/full acceptance passed on the target machine; each completed all 12 route goals.
  • Maximum estimated final errors were 0.1574 m position and 0.1485 rad yaw across those two runs; these estimates are not ground-truth accuracy measurements.
  • Moving SLAM, saved-map reload, AMCL/Nav2 activation, safety stops, cancellation and stopped-state recovery checks passed.
  • Manual verification covered simulation, mapping, localization and navigation, including Gazebo/RViz interaction and keyboard teleoperation. MCAP recording was separately exercised and archived in the validation evidence.

See validation evidence for the exact environment, measurements, limitations and archived records. Public GitHub Actions source checks and ROS 2 Jazzy build/tests pass on Ubuntu 24.04. Graphical Gazebo/Nav2 acceptance remains a target-machine runtime validation.

Architecture

flowchart TD
    G["Gazebo Harmonic"] --> H["gz_ros2_control"]
    H --> D["Drive controller and wheel odometry"]
    G --> B["Sensor and clock bridge"]
    B --> L["SLAM Toolbox or AMCL"]
    D --> L
    L --> N["Nav2 planning and control"]
    N --> S["C++ safety monitor"]
    T["Keyboard teleoperation"] --> S
    B --> S
    S --> D
    M["Python waypoint missions"] --> N
Loading

Only one localization backend runs in a launch: SLAM Toolbox owns map -> odom during mapping; AMCL owns it during saved-map localization/navigation. Detailed data flow, TF ownership and design decisions explain the interfaces.

Repository structure

Path Responsibility
src/amr_description/ Robot Xacro, inertias and RViz views
src/amr_simulation/ SDF room, sensor bridges and ordered controller startup
src/amr_control/ Drivetrain configuration, C++ safety monitor and core tests
src/amr_navigation/ SLAM/AMCL/Nav2 launch and parameters, reference map, 12-goal route
src/amr_bringup/ Single mode-based entry point with a base-readiness gate
src/amr_tools/ Python action client, metrics, live graph checks and C++ TF authority observer
scripts/ Dependency setup, build, map saving, recording and evaluation
tests/ Source, configuration, Xacro, map and integration-contract tests
docs/ Architecture, evaluation, validation and troubleshooting
media/ Actual Gazebo/RViz screenshots and their capture provenance
results/ Ignored runtime outputs; no fabricated benchmark dataset
.github/workflows/ Source checks and ROS Jazzy build/test workflow

Prerequisites

Install ROS 2 Jazzy desktop on Ubuntu 24.04 using the official binary installation instructions. Use the Jazzy vendor integration for Gazebo Harmonic through ros_gz and gz_ros2_control; no Gazebo Classic installation is needed. This is the documented Jazzy integration.

A working OpenGL/EGL graphics stack is required by GPU LiDAR, including when the Gazebo GUI is disabled. A headless run still renders sensors. No robot hardware, CUDA toolkit, pretrained models or external Gazebo model downloads are required.

The repository root is the colcon workspace root. Build in the checkout where you intend to run it: CMake caches and symlink installs are not portable between paths.

Install and build

From the repository root:

bash scripts/install_deps.sh
bash scripts/build.sh
source /opt/ros/jazzy/setup.bash
source install/setup.bash

install_deps.sh checks the platform and declared dependencies. If anything is missing, it prints installation guidance for you to review and run manually. It does not request administrator privileges. Build using the system Python associated with ROS, not a development virtual environment.

In every additional terminal, enter this repository and source:

source /opt/ros/jazzy/setup.bash
source install/setup.bash

Run only one AMR launch at a time. Stop the previous launch with Ctrl+C and wait for shutdown before changing modes. Do not separately launch the robot-state publisher, SLAM Toolbox or an upstream Nav2 bringup alongside this entry point.

Gazebo transport defaults to loopback (127.0.0.1) for this single-machine project. Override with gz_ip:=<local-interface-address> if needed. An existing GZ_IP environment variable is respected. For separate gz CLI diagnostics, use export GZ_IP=127.0.0.1 in that terminal as well.

Simulation and teleoperation

Terminal 1:

ros2 launch amr_bringup amr.launch.py mode:=simulation

Terminal 2:

ros2 run amr_tools runtime_check --mode simulation --ros-args -p use_sim_time:=true
bash scripts/teleop.sh

Use i for forward, , for reverse, j/l for rotation and k to stop. Hold a key for repeated input. The script starts at 0.2 m/s and 0.5 rad/s; the safety node and controller impose additional limits. A released key stops generating fresh commands, so the deadman timeout brakes the robot. teleop_twist_keyboard repeat behavior is deliberately left disabled.

To disable both graphical windows:

ros2 launch amr_bringup amr.launch.py mode:=simulation headless:=true rviz:=false

To inspect only the URDF, with the simulation stopped:

ros2 launch amr_description display.launch.py

SLAM mapping and map saving

Stop simulation mode first, then launch mapping:

ros2 launch amr_bringup amr.launch.py mode:=mapping

In a second sourced terminal, run bash scripts/teleop.sh. Drive slowly around the room, observe all walls and both sides of obstacles, then revisit the start area for a loop closure. RViz uses map as its fixed frame in this mode. A brief missing-map status before the first scans are processed is expected.

Stop the robot with k, leave mapping running, and save from another terminal:

bash scripts/save_map.sh results/maps/indoor_slam

The script creates indoor_slam.yaml and indoor_slam.pgm and refuses to overwrite an existing map. This occupancy-map pair is suitable for Nav2/AMCL. It is not a serialized SLAM pose graph for resuming graph optimization.

Saved-map localization

Stop mapping before starting localization:

ros2 launch amr_bringup amr.launch.py mode:=localization \
  map:="$(realpath results/maps/indoor_slam.yaml)"

AMCL's configured initial estimate is (0, 0, 0) in map, matching the default spawn and the included reference map. For a map with a different coordinate origin, use RViz 2D Pose Estimate to set the correct position and heading before moving. Laser returns should align with mapped obstacles.

ros2 run amr_tools runtime_check --mode localization --ros-args -p use_sim_time:=true

The default map, src/amr_navigation/maps/reference.yaml, is generated deterministically from the SDF room geometry. It is explicitly synthetic reference geometry, not a measured SLAM result. It makes an initial saved-map navigation run possible before mapping. Regenerate/check it with:

python3 scripts/generate_reference_map.py --check

Autonomous navigation and RViz goals

Stop the previous mode, then launch the default reference-map navigation scenario:

ros2 launch amr_bringup amr.launch.py mode:=navigation

For your own map, add map:="$(realpath results/maps/indoor_slam.yaml)" to that command.

ros2 run amr_tools runtime_check --mode navigation --ros-args -p use_sim_time:=true

When checks pass and the LiDAR aligns with the map, select Nav2 Goal in RViz and drag a goal pose onto clear mapped floor. The global path appears in orange. Nav2 plans around mapped obstacles and uses live LiDAR in both costmaps. The local controller checks collisions; the custom safety filter provides a final conservative velocity gate.

Use one goal producer at a time: RViz or the mission runner. Do not send RViz goals while a mission is active; Nav2 may preempt the existing goal.

Waypoint mission and monitoring

With navigation running:

ros2 run amr_tools mission_runner \
  --mission "$(ros2 pkg prefix --share amr_navigation)/missions/indoor_route.yaml" \
  --output results/demo/missions.csv \
  --ros-args -p use_sim_time:=true

The included route contains 12 goals. --limit 1 runs a single goal for a first check; --repeats 1 is the default. Output files must be new: existing CSVs are never silently overwritten. The default stops on failure. --continue-on-failure continues after a confirmed terminal failure, while an unconfirmed cancellation always prevents further dispatch.

ros2 topic echo /mission/status
ros2 topic echo /diagnostics

Ctrl+C requests cancellation and waits for a terminal action result. If termination cannot be confirmed, the runner attempts to latch the safety stop and exits unsuccessfully. Inspect the action/launch logs before releasing that stop.

Custom LiDAR safety

/cmd_vel_teleop and /cmd_vel_nav carry geometry_msgs/msg/TwistStamped. The safety node selects one source and exclusively publishes /diff_drive_controller/cmd_vel, the Jazzy controller's stamped input. Mapping/simulation/localization select manual input; navigation selects Nav2.

The monitor transforms scan points into base_footprint, validates full scan coverage and ranges, computes front/left/rear/right distances, and limits speed using a circular footprint plus a stopping-distance allowance. Invalid/stale scans, stale commands, a paused/reset clock, or the emergency-stop latch cause zero commands. There is also an independent 0.5-second controller command timeout.

# Latch the simulated stop.
ros2 service call /safety_monitor/emergency_stop std_srvs/srv/SetBool '{data: true}'

# After inspecting the cause, clear the latch; fresh valid input is still required.
ros2 service call /safety_monitor/emergency_stop std_srvs/srv/SetBool '{data: false}'

Cancel any running navigation action before switching to manual control:

ros2 service call /safety_monitor/set_autonomy std_srvs/srv/SetBool '{data: false}'
bash scripts/teleop.sh

Use the same service with data: true to select Nav2 again. A mode change clears the stored command. Parameters are loaded from amr_control/config/safety.yaml at startup. See safety assumptions. This is a simulation software safeguard, not a certified physical emergency stop.

Recording

In a sourced terminal with the simulation running:

bash scripts/record_bag.sh results/bags/demo_01

Ctrl+C finalizes the MCAP bag. It includes clock, TF, sensors, odometry, command streams, mission/action status and diagnostics. Use ros2 bag info results/bags/demo_01 to inspect it. For playback analysis, stop the live simulation, publish /clock only from the bag player and do not run a second command source.

Testing and evaluation

ROS-independent checks, in a separate development terminal:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements-dev.txt
bash scripts/check.sh

ROS build and package tests, in a fresh terminal using the system Python:

bash scripts/build.sh
source /opt/ros/jazzy/setup.bash
source install/setup.bash
colcon test --event-handlers console_direct+
colcon test-result --verbose

With all AMR launches stopped, run the integrated Ubuntu acceptance suite:

python3 scripts/acceptance.py --full

It starts/stops its own simulation processes and checks two clean startups, commanded motion against wheel odometry and Gazebo model pose, deadman and latched stops, moving SLAM, map saving and reload, reference-map localization, Nav2 activation, planning and navigation motion, and confirmed cancellation followed by healthy servers and a stopped robot. --full runs all 12 goals; omit it for a one-goal smoke test. Add --gui to also open Gazebo. RViz is checked separately. A diagnostic subset such as --stage mapping or --stage navigation --full is explicitly recorded as a partial run.

Analyze a mission's actual results:

python3 scripts/analyze_results.py results/demo/missions.csv --output results/demo/analysis

The CSV records action outcome, wall/simulation time, estimated final position/yaw error, wheel-odometry path length, recovery count and minimum observed obstacle distance. Missing measurements remain blank. Evaluation definitions and the repeatable 12-mission protocol explain limitations and success criteria.

Additional screenshots and demo

Actual captures from the target machine on 2026-09-09:

Custom robot and indoor world in Gazebo Harmonic

Live SLAM map, robot and LiDAR in RViz

The SLAM capture shows a partial map from a short maneuver. The navigation capture uses the explicitly synthetic reference map. Capture provenance and optional demo sequence distinguish these workflows.

Troubleshooting

Symptom First check
Duplicate SLAM nodes or conflicting map -> odom Stop earlier launches; use a single amr.launch.py mode
RViz fixed-frame error Simulation uses odom; mapping/localization/navigation use map after the backend starts
Robot model or LiDAR missing Use included RViz config; verify /robot_description durability, /scan best-effort QoS and TF
Controller manager timeout Inspect the first Gazebo/plugin error; verify dependencies and that simulation is running
Robot refuses to move Inspect /diagnostics, selected safety mode, stamped command timestamps and stop latch
Nav2 inactive Run runtime_check --mode navigation; inspect the first lifecycle configure/activate error
Headless LiDAR absent Check OpenGL/EGL support; try --gui acceptance or a regular GUI launch

The troubleshooting guide includes focused commands and fixes for stale overlays, simulation time, rendering and map alignment.

Limitations and future improvements

  • Simulation validation covers the included static room and route. It does not establish hardware performance or general navigation reliability across environments.
  • Single robot, flat floor, known static reference world and a planar laser model. No multi-robot namespacing or hardware driver is provided.
  • Wheel odometry can drift or slip. The IMU is recorded but not fused; estimated goal error is not ground-truth error.
  • The circular safety envelope is conservative and can prevent escape from an obstacle already inside the stop zone. It cannot detect cliffs, overhead obstacles outside the scan plane, or a sensor falsely reporting only no returns.
  • Watchdogs use both ROS timestamps and wall time. Very low simulation performance may trigger a stop; this should be investigated before changing timeouts.
  • Ordered startup and a fixed world support repeatable workflows; physics, sensor noise and navigation are not bitwise deterministic.
  • CI specifies build/source checks; graphical and navigation acceptance remains an Ubuntu runtime gate.

After measured validation, sensible extensions are odometry/IMU fusion with explicitly transferred TF ownership, additional worlds, and dynamic-obstacle experiments. 3D perception and docking are intentionally outside this release.

License and contributing

The source is licensed under Apache-2.0. See contributing and the completion changes.

About

Simulation-based autonomous mobile robot using ROS 2 Jazzy, Gazebo Harmonic, ros2_control, SLAM Toolbox, AMCL and Nav2 with automated validation.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages