A modular C++/ROS 2 autonomy stack for an unmanned surface vehicle (USV), built on VRX (Virtual RobotX) — the Gazebo-based maritime robotics simulator developed by Open Robotics and the Naval Postgraduate School. The vehicle receives a set of waypoints, fuses GPS/IMU into a pose estimate, avoids obstacles detected by lidar, and enters a safe-stop state if any sensor stream goes stale.
USVs have different dynamics than wheeled robots: no fixed wheelbase, thrust differential instead of steering, wave/current drift, and (eventually) COLREGs-style right-of-way behavior. VRX simulates a real WAM-V catamaran platform with wave dynamics and twin thrusters, so the control problem this project solves is the same shape as the one a real USV autonomy stack has to solve.
┌────────────────────┐
GPS / IMU ─────► │ state_estimator │──► /vehicle_state
└────────────────────┘
GPS/IMU/Lidar ───►┌────────────────────┐
(raw topics) │ health_monitor │──► /sensor_health
└────────────────────┘
waypoints ───────►┌────────────────────┐
(action goal) │ mission_planner │──► /target_waypoint
└────────────────────┘
/target_waypoint ─►┌────────────────────┐
/vehicle_state │ obstacle_avoidance │──► /desired_heading
/scan (lidar) ────►│ │──► /desired_speed_scale
└────────────────────┘
/desired_heading ─►┌────────────────────┐
/desired_speed ───►│ vehicle_controller │──► /wamv/thrusters/{left,right}/thrust
/vehicle_state ───►│ │
/sensor_health ───►│ │
└────────────────────┘
Six independent nodes, each doing one job, communicating only through
topics/actions — no node reaches into another's internals. vehicle_controller
is the single point that actually enforces a safe stop (zero thrust) when
sensor_health reports a fault; mission_planner also aborts the mission so
the action client gets an honest result.
- Ubuntu 24.04
- ROS 2 Jazzy
- Gazebo Harmonic
- VRX (
vrx_gzpackage) — see the VRX wiki for install/build instructions
# from the workspace root (this repo)
colcon build --symlink-install
source install/setup.bashTerminal 1 — bring up VRX itself:
ros2 launch vrx_gz competition.launch.py world:=sydney_regattaTerminal 2 — bring up the autonomy stack:
source install/setup.bash
ros2 launch simulation/launch/harbor_patrol.launch.pyTerminal 3 — send a mission:
ros2 action send_goal /navigate harbor_patrol_msgs/action/Navigate \
"{waypoints: [{x: 20.0, y: 0.0, z: 0.0}, {x: 20.0, y: 20.0, z: 0.0}], \
acceptance_radius_m: 2.0}" --feedbackPure decision logic (obstacle avoidance, thrust allocation) lives in header-only functions with no ROS dependencies specifically so they can be unit tested without Gazebo:
colcon test --packages-select obstacle_avoidance vehicle_controller
colcon test-result --verboseCovered: clear-route behavior, full-stop distance threshold, steer-away direction on either side, speed scaling near the slow-distance threshold, malformed/empty scan handling, heading-error wraparound at ±π, and thrust clamping under large gains.
- Clear route
- Single obstacle, narrow passage
- GPS dropout mid-mission
- Lidar dropout mid-mission
- Unreachable waypoint (should abort cleanly, not hang)
Track mission completion time, final position error, and safe-stop count across runs after any control-logic change.
In progress — see commit history. state_estimator uses a simple
equirectangular GPS projection and raw IMU yaw, no Kalman filter (intentional
for v1). Thrust allocation is a proportional controller, not
model-predictive. Both are natural next steps once the base loop is proven
reliable in sim.
autonomous-harbor-patrol/
├── src/
│ ├── harbor_patrol_msgs/ # custom msgs + Navigate.action
│ ├── mission_planner/ # action server, waypoint sequencing
│ ├── obstacle_avoidance/ # lidar -> heading/speed commands (+ unit tests)
│ ├── vehicle_controller/ # heading/speed -> thruster commands (+ unit tests)
│ ├── health_monitor/ # sensor staleness -> safe-stop
│ └── state_estimator/ # GPS/IMU -> fused VehicleState
├── simulation/launch/ # top-level launch file
├── .github/workflows/ci.yml # colcon build + test on push/PR
└── README.md