++ ++
Project / Robotics & Autonomy

Lunar Rover Autonomy Stack

A ROS2 autonomy stack - LiDAR-based odometry, SLAM, and Nav2 path planning - built for a senior robotics project team's lunar rover, under Dr. Shiobhan Oca. My job was to take a rover that could only be driven by hand and make it capable of mapping and navigating on its own with the sole input being LiDAR scans.

RoleAutonomy Lead
TeamIndependent Project
TimelineSpring 2026
StatusWorking in simulation - hardware incomplete
ROS2LiDARRF2O OdometrySLAM ToolboxNav2URDFGazebo
[ image ] Rover URDF model rendered in RViz
The rover's URDF, modeled joint-by-joint to match the physical chassis
[ image ] Rover building a costmap of a room while navigating
The rover mapping a room in real time using the LiDAR + SLAM pipeline

The problem

This was my first exposure to ROS2. The team's rover existed as a physical platform that could already be tele-operated, but it had no way to perceive its environment or move on its own. My job was to design and build the full autonomy pipeline: take in raw LiDAR data, estimate the rover's motion without wheel encoders, build a map of its surroundings, and plan and execute paths through that map — all running on ROS2 and ultimately deployable to the rover's onboard computer.

[ video — manual/tele-op run ]
The rover being driven manually via controller — the starting point before any autonomy

Proposed pipeline

Starting from a manually controlled rover, the goal was to build a ROS2 pipeline that could map and navigate using LiDAR as its primary source of perception.

Sensing2D LiDAR
OdometryRF2O (laser-based, no encoders)
MappingSLAM Toolbox (online async)
NavigationNav2 (planner + controller servers)
SimulationCustom URDF + Gazebo

LiDAR scans fed RF2O for odometry and SLAM Toolbox for mapping, while the URDF provided the transform tree needed by the rest of the stack. Nav2 generated /cmd_vel commands, which I translated into serial motor commands with a custom cmd_vel_converter node.

urdf /tf /scan /odom /scan /map /cmd_vel serial Robot Description URDF · links, joints robot_state_publisher publishes /tf from the URDF 2D LiDAR sensor RF2O laser odometry SLAM Toolbox online async mapping Nav2 costmaps · planner · controller server cmd_vel_converter Differential Drive Calculator Twist → motor cmds Rover Base stepper motors + microcontrollers
Proposed node graph — the URDF drives /tf, LiDAR drives odometry and SLAM, and a self-written cmd_vel_converter bridges Nav2's output to the stepper motor microcontrollers

This was the proposed pipeline going in: a small, deliberately simple node graph compared to what actually ends up running once all the nodes are put together (see Section 04).

Building it in simulation first

Before touching the physical rover, I built a URDF describing its chassis, wheels, and sensor mounts so I could develop and sanity check the autonomy stack in Gazebo. This let me iterate on the LiDAR placement, tune the navigation stack's parameters, and catch obvious errors long before they cost time on the actual hardware; especially useful since I was learning ROS2 and the Nav2 stack at the same time as building the pipeline.

[ image ] Rover URDF model
URDF model of the rover chassis and wheel assemblies
[ image ] Rover mapping a room using SLAM
SLAM-generated map of a room, built live as the rover explored it

The full ROS2 graph

Once the individual pieces worked: RF2O producing odometry, SLAM Toolbox producing a map, Nav2 producing plans, the real complexity was in connecting them into a single working system: dozens of nodes talking over topics, actions, and lifecycle transitions The graph below is the full rqt_graph output for the running stack. It's not meant to be legible at this size; it's here to show just how many moving parts a "simple" navigate-to-goal command actually depends on.

Full ROS2 rqt_graph showing every node and topic in the navigation stack
Full rqt_graph of the running Nav2 + SLAM + RF2O stack — the reality behind the simplified diagram in Section 02

Did it work?

In simulation, yes — the rover could localize, build a map of a room, and navigate to a goal pose using the pipeline above. On the physical rover, it never got there.

What actually happened

The hardware itself had integration and power distribution problems that were outside my control as the software lead. The drive motors didn't reliably respond to commands; at times the wheels simply wouldn't move when the autonomy stack sent them a velocity command, which made it impossible to validate closed-loop navigation on the real rover. I could confirm that LiDAR data, odometry, and mapping were all working correctly on the physical platform; I could not get consistent enough motor response to confirm autonomous driving worked end-to-end outside of simulation.

This served as useful it's a useful lesson that autonomy software is only as reliable as the hardware it's commanding.

What I'd do differently

If I picked this project back up, I’d change two things. First, I’d push for hardware-in-the-loop testing much earlier; even just verifying that a raw /cmd_vel command reliably turned the wheels before investing heavily in SLAM and navigation tuning. Second, I’d structure the ROS2 workspace more deliberately from the start. Since I was learning ROS2 while building the system, the final setup accumulated unnecessary launch steps, manual remappings, and configuration between nodes. With what I know now, I could make the entire stack much cleaner, easier to launch, and easier to debug.