A highly optimized Dynamic Window Approach (DWA) local motion planner for ROS 2, delivering collision-free, efficient trajectory generation for mobile robots in complex environments.
This project provides a production-ready single-file DWA local planner optimized for TurtleBot3 robots running in Gazebo simulations. The planner generates smooth, collision-free velocity commands by:
- Sampling dynamic velocity windows based on robot kinematics
- Simulating short-horizon candidate trajectories using unicycle model
- Scoring trajectories on goal progress, obstacle clearance, and smoothness
- Publishing optimal commands at 10 Hz control rate
✨ Fully Parameterized — All 15+ tunable parameters exposed as ROS2 parameters
✨ Real-Time Performance — ~10 ms planning cycle on modest hardware
✨ Conservative Safety — Built-in collision checking with configurable safety margins
✨ RViz Integration — Visualize all sampled trajectories and chosen path
✨ Modular Design — Easy to extend with custom scoring functions
Watch the DWA planner in action:
- ROS 2 (Humble/Foxy or later)
- TurtleBot3 simulation packages
- Gazebo (11+)
- Python 3.8+ with
rclpy,numpy
# Install ROS 2 dependencies
sudo apt install ros-humble-rclpy ros-humble-geometry-msgs ros-humble-nav-msgs \
ros-humble-sensor-msgs ros-humble-turtlebot3-gazebocd ~/ros2_ws
colcon build --packages-select dwa_local_planner
source install/setup.bashexport TURTLEBOT3_MODEL=waffle_pi
ros2 launch dwa_local_planner dwa_gazebo.launch.pyIn a new terminal:
source install/setup.bash
# Navigate to position (2.0, 1.0)
ros2 topic pub --once /goal_pose geometry_msgs/msg/PoseStamped "{
header: {frame_id: 'odom'},
pose: {position: {x: 2.0, y: 1.0, z: 0.0}, orientation: {w: 1.0}}
}"🎉 The robot will now plan and execute a collision-free path to the goal!
┌─────────────────────────────────────────────────────────────┐
│ SENSORS │
│ /odom (Odometry) + /scan (LaserScan) │
└─────────────────────────┬───────────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────────┐
│ DWA LOCAL PLANNER NODE │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. Sample (v, ω) candidates within dynamic window │ │
│ │ 2. Simulate trajectories using unicycle kinematics │ │
│ │ 3. Check collisions against sensor data │ │
│ │ 4. Score valid trajectories │ │
│ │ 5. Select best command │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────┬───────────────────────────────────┘
│
/cmd_vel (Twist)
│
┌─────────────────────────▼───────────────────────────────────┐
│ ROBOT EXECUTION │
│ TurtleBot3 (Gazebo Simulation) │
└─────────────────────────────────────────────────────────────┘
src/dwa_local_planner/
├── dwa_local_planner/
│ ├── dwa_node.py # Main DWA planner implementation
│ └── __init__.py
├── launch/
│ └── dwa_gazebo.launch.py # Integrated launch file
├── test/
│ ├── test_flake8.py # Style checks
│ ├── test_pep257.py # Documentation checks
│ └── test_copyright.py # License checks
├── package.xml # ROS 2 dependencies
├── setup.py # Python package setup
└── LAUNCH.md # Detailed launch guide
All parameters are ROS2 parameters and can be set via launch file or dynamically:
| Parameter | Default | Description |
|---|---|---|
max_speed |
0.15 m/s | Maximum forward velocity |
max_turn |
2.5 rad/s | Maximum angular velocity |
step_time |
0.1 s | Control loop timestep |
num_samples |
200 | Candidate trajectories per cycle |
safety_margin |
0.3 m | Collision buffer radius |
goal_weight |
5.0 | Priority for reaching goal |
heading_weight |
2.0 | Priority for moving toward goal |
obstacle_weight |
1.0 | Priority for avoiding obstacles |
robot_radius |
0.105 m | Robot physical radius (TurtleBot3) |
For detailed tuning guidance, see DWA_TUNING_GUIDE.md
Robot heads directly toward hardcoded goal:
ros2 run dwa_local_planner dwa_nodeModify goal in dwa_node.py:
self.declare_parameter('goal_x', 2.0)
self.declare_parameter('goal_y', 1.0)Full stack with Gazebo + DWA + RViz visualization:
ros2 launch dwa_local_planner dwa_gazebo.launch.py goal_x:=3.0 goal_y:=2.0Combine with global planner for complex environments — see USAGE_GUIDE.md
colcon test --packages-select dwa_local_planner
colcon test-result --verbose# Style checks only
colcon test --packages-select dwa_local_planner --pytest-args "-k 'flake8'"
# Documentation checks
colcon test --packages-select dwa_local_planner --pytest-args "-k 'pep257'"| Topic | Type | Description |
|---|---|---|
/odom |
nav_msgs/Odometry |
Robot odometry (position, velocity) |
/scan |
sensor_msgs/LaserScan |
Laser scan for obstacle detection |
| Topic | Type | Description |
|---|---|---|
/cmd_vel |
geometry_msgs/Twist |
Velocity commands to robot |
/visual_paths |
visualization_msgs/Marker |
All candidate trajectories (RViz) |
/dwa/best_trajectory |
visualization_msgs/Marker |
Selected trajectory (RViz) |
ros2 launch dwa_local_planner dwa_gazebo.launch.py \
max_speed:=0.20 \
safety_margin:=0.2 \
heading_weight:=3.0Create my_config.launch.py:
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='dwa_local_planner',
executable='dwa_node',
parameters=[{
'max_speed': 0.20,
'safety_margin': 0.25,
'goal_x': 3.5,
'goal_y': 2.5,
}]
),
])Then launch: ros2 launch my_config.launch.py
| Problem | Solution |
|---|---|
| Robot not moving | Check /cmd_vel publishing: ros2 topic echo /cmd_vel |
| Collisions | Increase safety_margin or obstacle_weight |
| Too slow | Increase max_speed and velocity_weight |
| Jerky motion | Decrease max_turn or reduce num_samples |
| Can't fit through passage | Decrease safety_margin or robot_radius |
See DWA_TUNING_GUIDE.md for detailed troubleshooting.
- LAUNCH.md — Launch file configuration and usage
- USAGE_GUIDE.md — Detailed usage modes and two-layer navigation
- DWA_TUNING_GUIDE.md — Parameter tuning strategies and recipes
- .github/copilot-instructions.md — AI agent instructions
- Planning Cycle: ~10 ms (100 trajectories, 100 steps lookahead)
- Control Rate: 10 Hz (configurable via
step_time) - CPU Usage: <5% on Intel i7 with 200 samples
- Memory: ~50 MB (Python runtime + dependencies)
The Dynamic Window Approach works in three phases:
-
Dynamic Window Generation
- Compute achievable velocity ranges based on current speed and acceleration limits
- This creates a "window" of feasible (v, ω) pairs
-
Trajectory Sampling & Simulation
- Sample uniformly within the window
- Forward-simulate each trajectory using unicycle kinematic model
- Collect (x, y) waypoints along each path
-
Trajectory Evaluation & Selection
Score(trajectory) = + goal_weight × heading_to_goal + heading_weight × forward_progress + obstacle_weight × clearance_from_obstacles + smoothness_weight × velocity_stability- Select trajectory with highest score
This approach guarantees safety while maintaining responsiveness — ideal for dynamic environments!
Licensed under the Apache License, Version 2.0. See LICENSE file for details.
Found a bug? Have a feature suggestion? Open an issue or pull request!
For AI agents: See .github/copilot-instructions.md for developer guidance.
⭐ If you find this useful, please consider starring the repository!