Autonomous Warehouse AMR with ROS 2 Jazzy, Gazebo Harmonic and Nav2

Hi everyone,

I’d like to share my first complete autonomous mobile robot project: an Autonomous Warehouse AMR simulation built with ROS 2 Jazzy, Gazebo Harmonic and Nav2.

The robot is a differential-drive AMR equipped with a simulated 2D GPU LiDAR and can autonomously navigate inside a warehouse environment using:

  • AMCL for localization
  • robot_localization EKF for odometry / TF
  • SmacPlanner2D for global path planning
  • Regulated Pure Pursuit for path tracking
  • Local and global obstacle-aware costmaps
  • Nav2 Collision Monitor for safety

I also built a small mission system where missions are defined in YAML files, for example:

PICKUP → SHELFA → DROPOFF → HOME

The mission manager supports retries, pause/resume, cancellation, status tracking and mission logging.

For testing dynamic obstacles, the simulation includes a moving warehouse worker. The robot detects the worker through LiDAR, updates the Nav2 costmaps and reacts accordingly.

I also created a simple Tkinter operator dashboard for selecting missions, monitoring mission progress, viewing the current/next station, elapsed time, event logs and Nav2 lifecycle status.

The complete system can be launched with a single ROS 2 launch command.

The project is available on GitHub:
https://github.com/Anastasios03git/autonomous-warehouse-amr

I’d be very interested in feedback, especially regarding the ROS 2 / Nav2 architecture and ideas for improving the project further.

Thanks!

Went through the repo, and the mission layer is more complete than most showcase projects: retries with costmap clears between attempts, pause implemented as cancel plus re-send with a refreshed stamp (the right way to do it, since Nav2 has no native pause), and a shared status topic driving both the dashboard and the CLI. Two small things I noticed while reading, offered as polish:

  1. The mission manager resolves mission_points.yaml, missions/, and mission_logs/ from a hardcoded ~/warehouse_ws path. Installing those with data_files and resolving them through get_package_share_directory, or exposing the paths as a ROS parameter, would let the same code run for anyone who clones into a differently named workspace. This kind of thing only ever bites the second user, which is why it is easy to miss as the first.
  2. /mission_status publishes only on state changes, so a dashboard started mid-mission sees nothing until the next transition. Publishing with transient_local durability (and matching QoS on the subscriber) hands late joiners the last known state immediately. One line on each side, and the dashboard gets noticeably more robust.

Curious how the moving worker interacts with the Collision Monitor in practice: does it trigger actual stops and slowdowns in your runs, or does Nav2 usually replan around it before the monitor engages?

Thanks a lot for going through the project in that much detail — both points are very useful.

I agree about the hardcoded ~/warehouse_ws paths. The current version was originally built around my development workspace, so making mission resources package-relative with get_package_share_directory() or exposing them as ROS parameters would make the project much more portable.

The transient_local suggestion for /mission_status also makes sense. Right now the status is event-driven, so a dashboard joining mid-mission can indeed wait until the next transition before receiving anything. I’ll look at using matching transient-local QoS on both the mission manager and dashboard.

Regarding the moving worker: in my tests the LiDAR detects the worker and the robot reacts to it through the Nav2 obstacle pipeline. I’ve observed stopping / avoidance behavior, but I haven’t yet instrumented the run closely enough to say that every stop is specifically caused by Collision Monitor rather than the controller/costmap response occurring first. That would be a good next test — monitoring the Collision Monitor state and velocity commands while the worker crosses the path.

That was fast. Package-relative resources will make it clone-and-run for anyone, and transient_local on the status topic is one of those small QoS details that makes a project feel production-shaped. Your Collision Monitor observation matches what I have seen elsewhere: with a slow obstacle the costmap replan usually wins the race, and the monitor earns its keep when something appears inside the lookahead faster than a control cycle. If you ever want to force it during testing, have the worker step out from behind a shelf at close range; occlusion is the thing that defeats the costmap path. Nice work again.

1 Like