An autonomous maze-solving robot simulation built with ROS2 Jazzy and Gazebo Harmonic, featuring frontier-based exploration, EKF-fused localization, wheel slip detection, and full Nav2 integration.
🎥 Full exploration demo:
Demos/DemoExploration.mp4
- 🗺️ Frontier-based autonomous exploration — robot maps and solves the maze without any prior knowledge
- 🔬 EKF sensor fusion — wheel odometry + IMU fused via
robot_localizationfor accurate pose estimation - 🛡️ LiDAR-based wheel slip detection — corrects odometry when the robot is stuck against a wall
- 🧭 SLAM Toolbox integration with tuned parameters for tight indoor environments
- ⚡ Nav2 stack with MPPI controller for smooth, fast path following
- 🎮 Manual teleoperation via keyboard or joystick
- 📡 RViz2 visualization with custom config
| Dependency | Version |
|---|---|
| ROS2 | Jazzy |
| Gazebo | Harmonic |
| Nav2 | ros-jazzy-nav2-bringup |
| robot_localization | ros-jazzy-robot-localization |
| slam_toolbox | ros-jazzy-slam-toolbox |
| teleop_twist_joy | Optional – joystick control |
Install dependencies:
sudo apt install -y \
ros-jazzy-ros-gz \
ros-jazzy-ros-gz-bridge \
ros-jazzy-xacro \
ros-jazzy-joint-state-publisher \
ros-jazzy-teleop-twist-keyboard \
ros-jazzy-teleop-twist-joy \
ros-jazzy-nav2-bringup \
ros-jazzy-navigation2 \
ros-jazzy-slam-toolbox \
ros-jazzy-robot-localization \mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
git clone https://github.com/adios-07/maze_solver_bot.git
cd ~/ros2_ws
colcon build --packages-select maze_solver_bot
source install/setup.bashSpawns the robot in Gazebo Harmonic with SLAM Toolbox and RViz2. Drive the robot manually to build a map.
ros2 launch maze_solver_bot launch_sim.launch.pyKeyboard control (in a new terminal):
ros2 run teleop_twist_keyboard teleop_twist_keyboardJoystick control:
# Joy node runs by default — configure button mappings in:
config/joystick.yamlSpawns the robot and runs the full autonomous exploration pipeline. The robot will map the entire maze by itself using frontier-based exploration.
ros2 launch maze_solver_bot launch_sim.launch.pyThen in a new terminal:
ros2 run maze_solver_bot frontier_explorer.pyThe explorer will:
- Wait for the SLAM map and Nav2 to initialize
- Detect frontier boundaries between known and unknown space
- Navigate to the highest-value frontier (largest + closest)
- Repeat until the maze is fully mapped
Load a previously saved map and navigate autonomously to goal poses.
ros2 launch maze_solver_bot navigation_launch.pyTo navigate:
- In RViz2, click 2D Pose Estimate → set the robot's starting position
- Click Nav2 Goal → send the robot to a target location
maze_solver_bot/
├── config/
│ ├── diff_drive_controller.yaml # ros2_control controller config
│ ├── ekf.yaml # Extended Kalman Filter params
│ ├── gz_bridge.yaml # Gazebo ↔ ROS2 topic bridge
│ ├── joystick.yaml # Joystick button mappings
│ ├── mapper_params_online_async.yaml # SLAM Toolbox tuned config
│ ├── nav2_params.yaml # Nav2 planner/controller params
│ ├── JustBot.rviz # RViz config (sim mode)
│ └── R2_Navigation.rviz # RViz config (nav mode)
│
├── description/ # Robot model (URDF/Xacro)
│ ├── robot.urdf.xacro
│ ├── robot_base.xacro
│ ├── ros2_control.xacro
│ ├── imu.xacro
│ ├── lidar.xacro
│ └── inertial_macros.xacro
│
├── launch/
│ ├── launch_sim.launch.py # Main simulation launch
│ ├── navigation_launch.py # Nav2 autonomous navigation
│ ├── localization_launch.py # Localization on saved map
│ ├── joystick.launch.py
│ └── rsp.launch.py
│
├── maps/ # Saved SLAM maps
│ └── *.yaml / *.pgm / *.data / *.posegraph
│
├── src/ # Custom ROS2 Python nodes
│ ├── frontier_explorer.py # Frontier-based maze exploration
│ ├── slip_detection.py # LiDAR wheel slip correction
│ └── path_detector.py # Available direction logger
│
├── worlds/
│ └── Maze.world # Gazebo maze environment
│
├── CMakeLists.txt
├── package.xml
└── README.md
Gazebo Simulation
│
├── /scan (LiDAR)
├── /imu/data
└── /diff_drive_base_controller/odom
│
▼
slip_detection.py ←── /scan
│
▼ /odom/corrected
robot_localization (EKF)
│
▼ /odom/filtered + odom→base_footprint TF
SLAM Toolbox ←── /scan
│
▼ /map
Nav2 Stack (MPPI Controller)
│
▼ /cmd_vel
frontier_explorer.py ──► NavigateToPose action
Fuses wheel odometry velocities with IMU angular velocity for a smooth, drift-corrected pose estimate. TF publishing is handled by the EKF — enable_odom_tf: false is set in the diff_drive controller to avoid conflicts.
Compares consecutive LiDAR scans to detect when the robot is stuck against a wall but the wheels are still reporting motion. When slip is detected, the corrected odometry is published with zeroed velocity and the last known good pose.
| Parameter | Default | Description |
|---|---|---|
scan_diff_threshold |
0.01m | Environment change threshold |
velocity_threshold |
0.005 m/s | Minimum velocity to consider "moving" |
stuck_count_threshold |
5 | Consecutive stuck frames before triggering |
odom_cache_size |
10 | Frames to cache for pose rewind |
Scores frontiers using size / distance — larger unexplored regions that are closer score higher. Goals are validated against the occupancy grid before being sent to Nav2.
| Parameter | Default | Description |
|---|---|---|
min_frontier_size |
5 cells | Filters noise frontiers |
min_goal_distance |
0.5m | Skips already-nearby frontiers |
nav_timeout_sec |
30.0s | Goal timeout before retry |
unknown_threshold |
0.15 | Fraction of unknown space to declare done |
Tuned for tight indoor maze environments — higher map update rate, finer resolution, and more aggressive loop closure detection.
- The robot uses a differential drive configuration managed via
ros2_control - Gazebo topics are bridged to ROS2 using the
gz_bridgeconfig - The EKF publishes the
odom → base_footprintTF — the diff_drive controller hasenable_odom_tf: false - Nav2 consumes
/odom/filteredinstead of raw/odom
This project is open source. Feel free to use, modify, and build upon it.

