A ROS 2 Nav2 navigation stack implementation for the Unitree Go2 quadruped robot, running on real hardware. This package provides a complete autonomous navigation pipeline including localization, costmap configuration, frontier exploration, and velocity bridging to the Go2's high-level sport client API.
⚠️ Work in Progress — Tested on real hardware. Active development ongoing.
This package enables fully autonomous point-to-point navigation on the Unitree Go2 using a pre-built map, as well as autonomous frontier exploration for building maps without a human driving the robot. All computation currently runs on an external laptop connected to the robot over Ethernet. The pipeline uses AMCL (or RTAB-Map SLAM, for exploration) for localization, Nav2 for path planning and obstacle avoidance, explore_lite for frontier detection, and a custom node to bridge Nav2 velocity commands to the Go2's sport client API.
/utlidar/cloud_deskewed → pointcloud_to_laserscan → /scan → AMCL
/utlidar/robot_odom → odom_tf_broadcaster → TF (odom → base_link)
Nav2 (AMCL + Costmaps + Planner + Controller) → /cmd_vel → go2_sport_bridge → Go2 Sport Client
/utlidar/robot_odom → odom_tf_broadcaster → TF (odom → base_link)
/utlidar/robot_odom, /utlidar/cloud_deskewed → restamp_node → restamped odom + lidar topics
restamped topics → RTAB-Map (SLAM) → map + global costmap
global costmap → explore_lite → frontier goal pose
Nav2 (planner) → /cmd_vel_manual → go2_sport_bridge → Go2 Sport Client
RTAB-Map builds the map and provides odometry/loop-closure entirely from LiDAR — no camera is used for loop detection. explore_lite detects frontier nodes from the occupancy grid and sends them to Nav2 as goals; Nav2 plans the path and publishes the resulting Twist to /cmd_vel_manual.
- Full Nav2 bringup with AMCL localization on real Go2 hardware
- Frontier exploration — tested and verified working on real hardware, using
explore_lite+ RTAB-Map SLAM - Custom
odom_tf_broadcasternode — converts/utlidar/robot_odomodometry topic to a TF broadcast (odom → base_link) - Custom
restamp_node— corrects a ~126s clock offset between the robot's internal clock and the laptop by restamping odometry and LiDAR data before they're used in the exploration pipeline - Custom
go2_sport_bridgenode — redirects Nav2 velocity output to the Go2 high-level sport client move API - 3D voxel layer costmaps using the Go2's native UtiLidar pointcloud
- ROS 2 Humble
- Unitree Go2 with CycloneDDS configured
- External laptop connected to Go2 over Ethernet
- The following ROS 2 packages:
nav2_bringuppointcloud_to_laserscantf2_rosrtabmap_rosexplore_lite
navigation— the original, initial navigation pipeline (AMCL + pre-built map). This branch is frozen and won't receive further changes.frontier_exploration— active development branch containing the SLAM + frontier exploration pipeline described below.
# Clone the repository
git clone <your-repo-url> ~/unitreego2nav
cd ~/unitreego2nav
# Build the package
colcon build --packages-select go2_nav
source install/setup.bashIf you're using the navigation branch with a pre-built map, you'll need a map of your environment first. This package uses RTAB-Map for mapping. Place your map files in the maps/ directory:
go2_nav/maps/
├── rtabmap.yaml # Nav2-compatible map metadata
└── rtabmap.pgm # Occupancy grid image
The rtabmap.yaml must follow Nav2 map server format:
image: rtabmap.pgm
resolution: 0.05
origin: [x, y, 0.0]
negate: 0
occupied_thresh: 0.5
free_thresh: 0.196Refer to the RTAB-Map ROS 2 documentation for mapping instructions.
On the
frontier_explorationbranch, no pre-built map is needed — SLAM and mapping happen live during exploration.
Connect your laptop to the Go2 over Ethernet and verify the robot's ROS 2 topics are visible:
ros2 topic listVerify the key sensor topics are publishing:
ros2 topic hz /utlidar/robot_odom
ros2 topic hz /utlidar/cloud_deskewedYou should see valid frequency output for both. If not, refer to the official Unitree Go2 ROS 2 setup guide to establish the connection.
In terminal 1, source the workspace and launch the navigation stack:
source install/setup.bash
ros2 launch go2_nav navigatio.launch.pyIn terminal 1, source the workspace and launch the exploration stack, which brings up RTAB-Map SLAM together with explore_lite:
source install/setup.bash
ros2 launch go2_nav explore_slam.launch.pyNote: the launch/config files are mid-rename — the
_simsuffix is being dropped from all launch and config filenames, so you may still seeexplore_slam_sim.launch.py/slam_explore_sim.launch.pyin the repo until that cleanup is finished.
In terminal 2, run the velocity bridge node:
ros2 run go2_nav go2_sport_bridgeIn terminal 3, open RViz:
rviz2Configure RViz:
- Set Fixed Frame to
map - Add
/maptopic (Global Costmap → Static Layer) - Add
/global_costmap/costmapfor obstacle visualization - Add
/planorglobal_received_pathfor path visualization - For exploration, also add the frontier markers topic to view detected frontier nodes
Pre-built map:
- Use the 2D Pose Estimate tool in RViz to set the robot's initial position on the map — place it at the robot's actual physical starting location
- Use the 2D Nav Goal tool to set a goal position
- The robot will plan a path and begin moving autonomously
Important: The robot's initial pose must match the robot's actual position in the mapped environment. The map origin is defined by where you started mapping.
Frontier exploration:
- Launch
explore_slam.launch.pyand thego2_sport_bridgeas above - The robot builds the map live via RTAB-Map while
explore_litecontinuously detects unexplored frontiers and sends them to Nav2 as goals - No manual goal-setting is required — the robot explores autonomously
go2_nav/
├── config/
│ ├── nav2_params.yaml # Nav2 stack configuration (navigation)
│ ├── slam_params.yaml # RTAB-Map SLAM configuration (exploration)
│ └── explore_params.yaml # explore_lite configuration
├── launch/
│ ├── navigatio.launch.py # Main navigation launch file (pre-built map)
│ └── explore_slam.launch.py # SLAM + frontier exploration launch file
├── maps/
│ ├── rtabmap.yaml # Map metadata (navigation branch)
│ └── rtabmap.pgm # Occupancy grid (navigation branch)
├── go2_nav/
│ ├── odomBroadcast.py # odom → base_link TF broadcaster
│ ├── restamp_node.py # corrects robot/laptop clock offset on odom + lidar data
│ └── moveapinode.py # Nav2 → Go2 sport client bridge
├── package.xml
└── setup.py
- Navigation pipeline tested on real Go2 hardware
- Custom odom TF broadcaster
- Nav2 velocity to sport client bridge
- Frontier exploration — tested and verified on real hardware
- Timestamp offset correction between robot and laptop clocks (
restamp_node) - Full indoor navigation testing
- Onboard computation — migrate from external laptop to Go2's internal computer
- Dynamic obstacle avoidance improvements
- All computation currently runs on an external laptop. Onboard deployment is planned for a future release.
- The robot's initial pose in RViz must closely match the actual physical starting position for AMCL to localize correctly (navigation branch).
- Ensure CycloneDDS is configured correctly on both the laptop and the robot before launching.
- The robot's internal clock and the laptop clock have been observed to drift ~126s out of phase;
restamp_nodecorrects this for the exploration pipeline before odometry/LiDAR data is consumed downstream.
Pull requests and issues are welcome. This is an ongoing project and contributions are appreciated.
MIT License Copyright (c) 2026 Safal