| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
ROS 2 Humble workspace for the BRACU Duburi AUV 4.2. Controls the vehicle through a Pixhawk 2.4.8 running ArduSub via pymavlink. Perception via YOLO11 object detection with CUDA-accelerated inference, Kalman-filtered tracking, and PID-based visual servoing.
~40 Python source files · 8 packages · ~6 500 lines
| Component | Spec |
|---|---|
| Flight Controller | Pixhawk 2.4.8 |
| Firmware | ArduSub (ArduPilot) |
| Connection | /dev/ttyACM0 (serial, 115200 baud) |
| Thrusters | Blue Robotics T200 |
| Channels | Ch 1–4 lateral, Ch 5–8 depth |
| Barometer | BAR30 (depth sensing) |
| Camera | USB camera (V4L2 compatible) |
| Compute | Ubuntu 22.04, CUDA 12.8 (for YOLO inference) |
| Package | Modules | Lines | ROS Nodes | Description |
|---|---|---|---|---|
| duburi_interfaces | — | — | — | Shared ROS 2 messages: DriverCommand, TeleopCommand, VehicleState, VehicleDiagnostics, MavlinkEvent, DriverCommandFeedback, Detection, DetectionArray, AlignmentStatus, CameraStatus |
| duburi_common | 2 | ~120 | — | Shared constants (MISSION_PATHS, UNARMED_ALLOWED) and command vocabulary (aliases, direction maps, prefix resolution) |
| mavlink_inspector | 7 | ~2 250 | inspector | MAVLink ↔ ROS bridge. Owns serial port, RC override, PID controllers, command dispatch, telemetry publishing |
| mavlink_driver | 5 | ~1 040 | mission_executor, teleop_driver | High-level command API (driver_client.py), mission file execution, joystick/teleop via TeleopCommand |
| mavlink_runner | 4 | ~920 | runner | Interactive Duburi > CLI with history, status dashboard, file-based missions |
| mavlink_logger | 1 | ~230 | logger | Logs all ROS topics to session-based CSV/JSON files |
| vision | 5 | ~900 | detector_node, detector_standalone, alignment_controller | YOLO11 detection, Kalman-filtered tracking, PID-based visual servoing |
| vision_inspector | 8 | ~810 | camera_manager, camera_enum, camera_test, camera_calibrate, camera_record, camera_playback | Multi-camera management, enumeration, testing, calibration, recording/playback |
The inspector (the core MAVLink bridge) is decomposed into focused modules:
mavlink_inspector/ ├── inspector_node.py (641 lines) Thin orchestrator — timers, publishers, wiring ├── command_handler.py (440 lines) DriverCommand dispatch table → handlers ├── movement_commands.py (399 lines) MOVEMENTS registry — 30+ movement handlers ├── connection_manager.py (246 lines) Serial lifecycle, heartbeat, reconnect with backoff ├── rc_controller.py (206 lines) PWM math, channel constants, trapezoidal ramp ├── pid_controller.py (162 lines) Generic PID — deadband, anti-windup, EMA derivative └── telemetry_parser.py (159 lines) MAVLink message dispatch → vehicle state
mavlink_driver/ ├── driver_client.py (262 lines) make_command() factory + all movement functions ├── mission_parser.py (245 lines) parse_file_command() for mission files ├── mission_executor.py (318 lines) Autonomous mission runner node ├── just_commands.py (115 lines) just_* instant (no-ramp) movement variants └── teleop_driver.py (~70 lines) Twist → TeleopCommand on /driver/teleop
mavlink_runner/ ├── command_parser.py (~280 lines) parse_command() — uses duburi_common.command_vocabulary ├── runner.py (268 lines) Interactive REPL node with readline ├── constants.py (~50 lines) HELP_TEXT, re-exports from duburi_common.constants └── status_display.py (75 lines) ANSI dashboard (battery, heading, servos)
Full module map with every class, function, and import path: see analysis/12_CODE_REFERENCE.md
# ROS 2 Humble must be sourced
source /opt/ros/humble/setup.bash
# Python deps
pip install pymavlink ultralytics opencv-pythoncd /home/duburi/workspaces/duburi_ws
colcon build # builds all 8 packages (~5s clean)
source install/setup.bashrm -rf build install log && colcon build
source install/setup.bash# Terminal 1: Inspector (connects to Pixhawk) — start first, always
ros2 run mavlink_inspector inspector
# Terminal 2: Interactive CLI
ros2 run mavlink_runner runner
# Terminal 3 (optional): Logger
ros2 run mavlink_logger logger# Terminal 1: Camera streaming (multi-camera manager)
ros2 run vision_inspector camera_manager
# Terminal 2: YOLO detection (GPU)
ros2 run vision detector_node --ros-args -p enable_display:=True
# Terminal 3 (optional): Visual servoing alignment
ros2 run vision alignment_controller# Inspector + logger together
ros2 launch mavlink_inspector duburi_control.launch.py
# With custom config profile
ros2 launch mavlink_inspector duburi_control.launch.py \
params_file:=$(ros2 pkg prefix mavlink_inspector)/share/mavlink_inspector/config/pool_test.yaml
# With individual overrides
ros2 launch mavlink_inspector duburi_control.launch.py \
connection_port:=/dev/ttyACM1 enable_logger:=true
# Camera + detector together
ros2 launch vision vision.launch.py enable_display:=True confidence:=0.4Software-in-the-loop stack used for autonomous testing without the pool: Gazebo Harmonic (gz sim 8) ↔ ArduPilot ArduSub JSON ↔ mavlink_inspector over UDP (same ROS 2 graph as hardware).
Deep dive (architecture, ports, GPU lab planning, optional simulators):
analysis/17_SIMULATION_GAZEBO_ARUDSUB_SITL.md
| Layer | Requirement |
|---|---|
| OS | Ubuntu 22.04 (Jammy) |
| ROS 2 | Humble — sudo apt install ros-humble-desktop (or your metapackage) |
| Gazebo | Gazebo Harmonic — gz sim reports 8.x; metapackage gz-harmonic |
| ROS ↔ Gazebo | ros-humble-ros-gzharmonic-bridge (and related ros-humble-ros-gzharmonic* as needed) |
| ArduPilot | Clone ArduPilot/ardupilot, run Tools/environment_install/install-prereqs-ubuntu.sh, build SITL (./waf configure --board sitl then ./waf sub) |
| Gazebo world + BlueROV model | A world such as bluerov2_underwater.world from ArduPilot Gazebo (e.g. ArduPilot/ardupilot_gazebo) or your team fork. Resource paths must resolve so gz sim finds the world and models (GZ_SIM_RESOURCE_PATH / GZ_SIM_SYSTEM_PLUGIN_PATH per upstream docs). |
| Duburi workspace | This repo built with colcon build, source install/setup.bash (or .zsh) |
| Python | pymavlink (pip install pymavlink) — used by mavlink_inspector |
Verify versions:
source /opt/ros/humble/setup.bash
ros2 doctor --report | head -5
gz sim --version
dpkg -l | grep -E 'ros-humble-ros-gzharmonic|gz-sim8-cli' | head -5sim_vehicle.py --out=udp:HOST:PORT sends MAVLink UDP datagrams to that address. The inspector must listen on UDP with the same host/port:
-p connection_port:=udpin:127.0.0.1:5760Using tcp:127.0.0.1:5760 connects to TCP port 5760 (SITL’s primary MAVLink TCP listener). That is not the same socket as the UDP --out stream; you may see connect + sparse heartbeats then heartbeat loss. Always pair --out=udp:… with connection_port:=udpin:… (same host and port).
Run in separate terminals. Order matters: Gazebo first (JSON plugin), then SITL, then bridge, then Duburi.
1 — Gazebo (from a directory / env where the world resolves)
source /opt/ros/humble/setup.bash
gz sim -r bluerov2_underwater.world-r runs the simulation (unpaused). Use gz sim, not legacy ign gazebo, so the generation matches Harmonic.
2 — ArduSub SITL + MAVProxy UDP output
From your ArduPilot tree (after . ~/.profile or equivalent so sim_vehicle.py is on PATH):
cd /path/to/ardupilot
sim_vehicle.py -v ArduSub -f vectored --model JSON \
--out=udp:127.0.0.1:5760 --consoleAdjust -f vectored / frame if your Gazebo model expects another SITL frame (some stacks use vectored_6dof). Keep --out port aligned with udpin below.
3 — ROS 2 ↔ Gazebo bridge (camera + clock example)
source /opt/ros/humble/setup.bash
ros2 run ros_gz_bridge parameter_bridge \
/camera@sensor_msgs/msg/Image@gz.msgs.Image \
/clock@rosgraph_msgs/msg/Clock@gz.msgs.ClockTopic names must match your world’s sensor topics; adjust if your SDF uses different gz topic names.
4 — Duburi workspace + mavlink_inspector (UDP in)
cd /path/to/Duburi_ws
source /opt/ros/humble/setup.bash
source install/setup.bash
ros2 run mavlink_inspector inspector --ros-args \
-p connection_port:=udpin:127.0.0.1:57605 — Optional: runner, logger, vision
ros2 run mavlink_runner runner
# ros2 run mavlink_logger logger
# vision stack as in [Quick Start — Perception](#quick-start--perception)flowchart TB
subgraph Hardware["Hardware Layer"]
PIX[Pixhawk 2.4.8<br/>ArduSub Firmware]
CAM[USB Camera]
THR[T200 Thrusters]
end
subgraph Controls["Control Stack"]
INS[mavlink_inspector<br/>7 modules]
RUN[mavlink_runner<br/>Interactive CLI]
EXE[mission_executor<br/>Autonomous missions]
TEL[teleop_driver<br/>Joystick input]
LOG[mavlink_logger]
end
subgraph Perception["Vision Stack"]
CM[camera_manager<br/>Multi-camera]
DET[detector_node<br/>YOLO11 + CUDA]
ALN[alignment_controller<br/>Visual servo]
end
subgraph Shared["Shared"]
INT[duburi_interfaces<br/>ROS2 messages]
COM[duburi_common<br/>Constants + vocab]
end
PIX <-->|MAVLink| INS
INS -->|RC Override| THR
CAM --> CM
CM -->|/camera/image| DET
DET -->|/detections| ALN
ALN -->|DriverCommand| INS
RUN -->|DriverCommand| INS
EXE -->|DriverCommand| INS
TEL -->|TeleopCommand| INS
INS -->|VehicleState| LOG
flowchart LR
subgraph inspector_node["inspector_node.py (orchestrator)"]
direction TB
end
CM[connection_manager<br/>Serial I/O, reconnect] --> inspector_node
TP[telemetry_parser<br/>MAVLink → state] --> inspector_node
CH[command_handler<br/>Dispatch table] --> inspector_node
MC[movement_commands<br/>30+ handlers] --> CH
RC[rc_controller<br/>PWM ramp] --> inspector_node
PID[pid_controller ×2<br/>depth + yaw] --> inspector_node
| Branch | Status | Description |
|---|---|---|
| main | Stable | Production-ready V1 codebase |
| Control-Redesign-V1 | Stable | Decorator-based commands, modular architecture |
| Control-Redesign-V2 | Testing | Advanced control (convergence, cascade, DVL) |
Three YAML config profiles live in src/mavlink_inspector/config/:
| Profile | File | Use Case |
|---|---|---|
| defaults | defaults.yaml | Standard parameters — all values documented |
| pool_test | pool_test.yaml | Shallow pool — conservative PID, lower ramp rate |
| competition | competition.yaml | Competition-tuned — aggressive PID, full thrust |
# Use a profile at launch
ros2 launch mavlink_inspector duburi_control.launch.py \
params_file:=src/mavlink_inspector/config/pool_test.yaml
# Or with ros2 run
ros2 run mavlink_inspector inspector --ros-args \
--params-file src/mavlink_inspector/config/pool_test.yaml
# Override individual params on top of a profile
ros2 run mavlink_inspector inspector --ros-args \
--params-file src/mavlink_inspector/config/defaults.yaml \
-p depth_kp:=600.0 -p depth_tolerance:=0.1Copy defaults.yaml and modify as needed:
cp src/mavlink_inspector/config/defaults.yaml src/mavlink_inspector/config/my_pool.yaml
# Edit my_pool.yaml, then:
colcon build --packages-select mavlink_inspector
ros2 launch mavlink_inspector duburi_control.launch.py \
params_file:=src/mavlink_inspector/config/my_pool.yamlThe runner provides an interactive prompt for direct control and file-based missions.
| Prefix | Meaning | Example |
|---|---|---|
| (bare) | ArduSub firmware / standard | depth 0.5, heading 90, turn left 45 |
| ~ | Software PID (smooth, closed-loop) | ~depth 0.5, ~heading 90, ~turn left 45 |
| move | Single/compound directional thrust | move forward 50%, move forward-right 50% |
| at | Body-frame vector thrust (arbitrary angle) | at 45 60% 5s (45° from forward) |
| go | Movement + PID heading hold | go forward 90 50%, go forward-right 45 50% |
| cruise | Movement + depth PID + heading PID | cruise 0 90 0.5 60% 10s |
| just | Instant (bypass PWM ramp) variant | just forward, just go forward 90, just cruise ... |
All old command names (dive, p_dive, yaw, p_yaw, p_turn) are kept as backward-compatible aliases.
ros2 run mavlink_runner runner| Command | Example | Description |
|---|---|---|
| move forward [gain%] [Ns] | move forward 50% 7s | Move forward |
| move back [gain%] [Ns] | move back 50% 3s | Move backward |
| move left [gain%] [Ns] | move left 50% 10s | Strafe left |
| move right [gain%] [Ns] | move right 70% 5s | Strafe right |
| move up [gain%] [Ns] | move up 40% | Move up (indefinite) |
| move down [gain%] [Ns] | move down 50% 2s | Move down |
| forward [gain%] [Ns] | forward 50% 5s | Shorthand (no move) |
Move in two horizontal directions at once. Speed is automatically scaled by $1/\sqrt{2} ≈ 71%$ per axis so the resultant velocity vector matches your requested speed — the AUV doesn't go faster diagonally.
| Command | Example | Description |
|---|---|---|
| move forward-right [gain%] [Ns] | move forward-right 60% 5s | Diagonal forward-right |
| move forward-left [gain%] [Ns] | forward-left 50% 3s | Diagonal forward-left |
| move back-right [gain%] [Ns] | back-right 40% | Diagonal back-right |
| move back-left [gain%] [Ns] | move back-left 50% 2s | Diagonal back-left |
Valid combinations: Any pair of {forward, back} × {left, right}. Conflicting directions (e.g. forward-back) are rejected.
Why only horizontal? Vertical movement (up/down) should use ~depth or depth for PID-controlled depth hold. Raw throttle without PID drifts and is unreliable in water.
Two depth control strategies are available. Use bare command for ArduSub firmware, ~ prefix for software PID:
| Command | Example | Description |
|---|---|---|
| depth <m> | depth 0.5 | ArduSub ALT_HOLD firmware depth hold |
| ~depth | ~depth | Software PID — hold current depth (auto STABILIZE) |
| ~depth <m> | ~depth 0.5 | Software PID — hold specific depth (auto STABILIZE) |
| ~depth off | ~depth off | Disable software depth PID |
| surface | surface | Ascend to surface (stops everything) |
Use bare command for bang-bang (fast, snappy), ~ prefix for PID (smooth, precise):
| Command | Example | Description |
|---|---|---|
| heading <deg> [gain%] | heading 260 50% | Bang-bang yaw to heading |
| ~heading <deg> [gain%] | ~heading 260 50% | PID smooth yaw to heading |
| heading left [gain%] [Ns] | heading left 50% 5s | Open-loop rotate left |
| heading right [gain%] [Ns] | heading right 50% 5s | Open-loop rotate right |
| Command | Example | Description |
|---|---|---|
| turn left <deg> [gain%] | turn left 90 | Bang-bang relative turn left |
| turn right <deg> [gain%] | turn right 45 60% | Bang-bang relative turn right |
| ~turn left <deg> [gain%] | ~turn left 90 | PID smooth relative turn left |
| ~turn right <deg> [gain%] | ~turn right 45 | PID smooth relative turn right |
The go command is the most powerful: it moves the AUV in a direction while simultaneously PID-yawing to a target heading. This is essential for competition tasks where you need to approach a gate at a specific angle.
| Command | Example | Description |
|---|---|---|
| go forward <deg> [gain%] [Ns] | go forward 90 60% 5s | Move forward + PID yaw to 90° |
| go back <deg> [gain%] [Ns] | go back 270 50% 3s | Reverse + PID yaw to 270° |
| go left <deg> [gain%] [Ns] | go left 0 50% 5s | Strafe left + hold 0° heading |
| go right <deg> [gain%] [Ns] | go right 180 40% | Strafe right + hold 180° |
| go forward-right <deg> [gain%] [Ns] | go forward-right 45 60% 5s | Diagonal + heading |
go forward 90 60% 5s
Key insight: go doesn't "first turn, then move." Translation and rotation happen in parallel from the very start.
go only controls horizontal translation + yaw. To also hold depth:
Duburi > p_dive 0.5; go forward 90 60% 10sCan also be written with new names: Duburi > ~depth 0.5; go forward 90 60% 10s
This holds 0.5m depth (Layer 3) while moving forward toward 90° (Layers 2+4).
Move in an arbitrary direction relative to the AUV body using a bearing angle. The bearing is decomposed into forward + lateral channels using cos/sin.
| Command | Example | Description |
|---|---|---|
| at <deg> [gain%] [Ns] | at 45 60% 5s | Move at 45° (forward-right diagonal) |
| at <deg> [gain%] [Ns] | at 90 50% 3s | Move at 90° (pure right strafe) |
| at <deg> [gain%] [Ns] | at 180 50% | Move at 180° (pure backward) |
Bearing reference: 0° = forward, 90° = right, 180° = backward, 270° = left.
Why use at instead of move forward-right? Diagonals use fixed √2 scaling at 45°. at lets you move at any arbitrary angle (e.g. 30°, 60°, 120°).
The cruise command combines body-frame vector movement, depth PID, and heading PID into a single coordinated manoeuvre. This is the most comprehensive command — the AUV moves in a direction, holds a heading, and maintains depth simultaneously.
| Command | Example | Description |
|---|---|---|
| cruise <bearing°> <heading°> <depth_m> [gain%] [Ns] | cruise 0 90 0.5 60% 10s | Move forward, hold 90° heading, hold 0.5m depth |
| cruise <bearing°> <heading°> <depth_m> [gain%] [Ns] | cruise 45 45 1.0 50% | Move at 45°, hold 45° heading, hold 1.0m depth |
All movement commands accept a just prefix that bypasses PWM ramping for instant thruster response. This is useful for emergency manoeuvres or when you need raw bang-bang control.
| Command | Example | Description |
|---|---|---|
| just forward [gain%] [Ns] | just forward 50% 3s | Instant forward thrust |
| just back [gain%] [Ns] | just back 50% | Instant backward thrust |
| just surface | just surface | Instant surface |
| just at <deg> [gain%] [Ns] | just at 45 60% | Instant vector movement |
| just go forward <deg> [gain%] [Ns] | just go forward 90 60% | Instant go + heading |
| just cruise <bear> <head> <dep> [%] [s] | just cruise 0 90 0.5 60% | Instant cruise |
| just forward-right [gain%] [Ns] | just forward-right 50% | Instant diagonal |
When to use just: Emergency stops/corrections, testing thruster response, or when ramp delay is unacceptable. For normal operation, ramped commands are preferred — they're gentler on thrusters and produce smoother motion.
Every 50ms, the inspector builds a single PWM message from 4 layers:
Layer 1: Neutral (1500) on all channels ─── baseline Layer 2: Active movement (ramped) ─── sets only the channels the command owns Layer 3: Depth PID ─── OVERRIDES CH_THROTTLE (if p_dive active) Layer 4: Yaw PID ─── OVERRIDES CH_YAW (if go/p_yaw/yaw active)
Higher layers override lower ones. Movement commands (DESIGN 5) only set the channels they control:
PWM values are ramped (smooth acceleration) by default. just * variants bypass ramping for instant response.
Examples:
| Channel | What controls it | Priority |
|---|---|---|
| CH_FORWARD (5) | Movement commands | Layer 2 |
| CH_LATERAL (6) | Movement commands | Layer 2 |
| CH_THROTTLE (3) | move up/down (Layer 2), OR p_dive/dive (Layer 3) | Layer 3 overrides Layer 2 |
| CH_YAW (4) | yaw left/right (Layer 2), OR go/p_yaw (Layer 4) | Layer 4 overrides Layer 2 |
| Command | Example |
|---|---|
| mode <MODE> | mode MANUAL / mode ALT_HOLD / mode STABILIZE |
| arm | Arm motors (non-blocking) |
| disarm | Disarm motors (non-blocking) |
Arm/disarm print confirmation when events are received.
| Command | Example |
|---|---|
| stop | Stop all thrusters |
| grabber open | Open grabber |
| grabber close | Close grabber |
Execute multiple commands on one line (runner waits for duration before next):
Duburi > arm; move forward 50% 5s; move left 50% 2s; stop; disarmDuburi > run <mission_name>
Duburi > run example_gate
Duburi > list missionsMission file locations (in order):
Example mission file (missions/example_gate.txt):
# Example gate mission — approach gate at heading 90° arm mode MANUAL sleep 2 ~depth 0.5 # hold 0.5m depth (software PID) sleep 3 go forward 90 60% 8s # move forward + PID yaw to 90° sleep 9 forward-right 50% 3s # diagonal adjustment sleep 4 stop surface sleep 5 disarm
# Compound movement demo arm mode MANUAL sleep 2 ~depth 0.3 # hold depth (PID) sleep 3 go forward-right 45 50% 5s # diagonal + heading 45° sleep 6 go left 270 60% 3s # strafe left at heading 270° sleep 4 stop disarm
| Command | Description |
|---|---|
| help | Show all commands |
| status | Show vehicle status topic info |
| quit / exit / q | Exit runner |
You can plan and run missions without using the interactive runner.
Predefined missions run as a ROS 2 node.
# Start inspector first
ros2 run mavlink_inspector inspector
# Run built-in pool test mission (after ~3s delay)
ros2 run mavlink_driver mission_executor
# With custom mission name (if supported by executor)
ros2 run mavlink_driver mission_executor --ros-args -p mission:=pool_testThe executor publishes DriverCommand messages to /driver/command with delays between steps.
Control the AUV by publishing DriverCommand to /driver/command.
# Arm
ros2 topic pub --once /driver/command duburi_interfaces/msg/DriverCommand "{command: 'arm'}"
# Move forward 50% for 5 seconds
ros2 topic pub --once /driver/command duburi_interfaces/msg/DriverCommand "{command: 'move_forward', duration: 5.0, speed: 50}"
# Diagonal: forward-right for 3 seconds
ros2 topic pub --once /driver/command duburi_interfaces/msg/DriverCommand "{command: 'move_forward_right', duration: 3.0, speed: 50}"
# Software PID depth hold at 0.5m
ros2 topic pub --once /driver/command duburi_interfaces/msg/DriverCommand "{command: 'pid_depth', depth: 0.5}"
# Go forward + PID yaw to 90°, 60% for 5s
ros2 topic pub --once /driver/command duburi_interfaces/msg/DriverCommand "{command: 'go_forward', angle: 90.0, duration: 5.0, speed: 60}"
# Go diagonal forward-right + PID yaw to 45°
ros2 topic pub --once /driver/command duburi_interfaces/msg/DriverCommand "{command: 'go_forward_right', angle: 45.0, duration: 5.0, speed: 50}"
# PID yaw to heading 260°
ros2 topic pub --once /driver/command duburi_interfaces/msg/DriverCommand "{command: 'pid_yaw_to_heading', angle: 260.0, speed: 50}"
# Stop
ros2 topic pub --once /driver/command duburi_interfaces/msg/DriverCommand "{command: 'stop'}"
# Disarm
ros2 topic pub --once /driver/command duburi_interfaces/msg/DriverCommand "{command: 'disarm'}"Use mavlink_driver.driver_client to build your own mission node.
#!/usr/bin/env python3
import time
import rclpy
from rclpy.node import Node
from rclpy.qos import QoSProfile, ReliabilityPolicy
from duburi_interfaces.msg import DriverCommand
from mavlink_driver.driver_client import (
arm, disarm, move_forward, move_left, move_combo,
go_forward, go_combo, pid_depth, pid_depth_off,
set_mode, stop, surface, set_depth,
yaw_to_heading, pid_yaw,
turn_left, pid_turn_left,
)
class MyMissionNode(Node):
def __init__(self):
super().__init__('my_mission')
self._pub = self.create_publisher(
DriverCommand, '/driver/command',
QoSProfile(reliability=ReliabilityPolicy.RELIABLE, depth=10)
)
self._timer = self.create_timer(3.0, self._run_mission_once)
def _publish(self, cmd, delay=0.5):
self._pub.publish(cmd)
if delay > 0:
time.sleep(delay)
def _run_mission_once(self):
self._timer.cancel()
self._publish(set_mode('MANUAL'))
self._publish(arm(), delay=3.0)
# Hold depth at 0.5m (software PID, works in MANUAL)
self._publish(pid_depth(0.5), delay=3.0)
# Move forward at 60% for 5s while PID-yawing to 90°
self._publish(go_forward(angle=90, duration=5, speed=60))
time.sleep(6)
# Diagonal: forward-right at 50% for 3s
self._publish(move_combo('forward-right', duration=3, speed=50))
time.sleep(4)
# Diagonal + heading: forward-left while yawing to 45°
self._publish(go_combo('forward-left', angle=45, duration=4, speed=50))
time.sleep(5)
# Simple forward
self._publish(move_forward(duration=3, speed=50))
time.sleep(4)
# Clean up
self._publish(pid_depth_off())
self._publish(stop())
self._publish(surface(), delay=5.0)
self._publish(disarm())
def main():
rclpy.init()
node = MyMissionNode()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()Use missions/*.txt files as mission descriptions even when not using the runner:
Use teleop_driver to drive the AUV with Twist messages (e.g., from a joystick or nav stack). The driver converts Twist to a dedicated TeleopCommand message on /driver/teleop, with clean axis semantics (linear_x/y/z, angular_z). When the joystick returns to centre, idle=true clears movement without disrupting active depth PID or heading hold.
ros2 run mavlink_driver teleop_driver
# Publish to /cmd_vel (geometry_msgs/Twist)
# Single axis:
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.5, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"
# Multi-axis (forward + right + up + yaw simultaneously):
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.5, y: 0.5, z: 0.3}, angular: {x: 0.0, y: 0.0, z: 0.3}}"The inspector subscribes to /driver/teleop and applies RC overrides directly from TeleopCommand fields — no field overloading needed.
The logger creates session folders with rotating log files inside the workspace:
logs/
└── 2026-03-06_14-30-00/ # session timestamp
├── session.log # all events + commands (RotatingFileHandler, 5 MB max, 3 backups)
├── events.log # MAVLink events only
├── commands.log # DriverCommand only
└── state.csv # throttled telemetry (1 Hz default)
# Start logger with defaults (logs/ in workspace root)
ros2 run mavlink_logger logger
# Custom log directory
ros2 run mavlink_logger logger --ros-args -p log_directory:=/path/to/logs
# Adjust rotation (10 MB max, 5 backups)
ros2 run mavlink_logger logger --ros-args -p max_log_bytes:=10485760 -p backup_count:=5
# Throttle state logging to 0.5 Hz
ros2 run mavlink_logger logger --ros-args -p state_log_interval:=2.0The runner automatically monitors the inspector connection. If no VehicleState messages arrive for 5+ seconds, a yellow warning appears:
⚠ No telemetry for 8s — is mavlink_inspector running?
The warning auto-clears when messages resume. Also visible in the status dashboard as "Telemetry stale."
The perception stack handles camera input and object detection, publishing results as ROS 2 topics for downstream use.
# Standalone test (camera + YOLO, no ROS pipeline needed)
ros2 run vision detector_standalone --ros-args -p device_id:=0
# Full ROS pipeline
# Terminal 1: Camera streaming
ros2 run vision_inspector camera_node --ros-args -p device_id:=0
# Terminal 2: YOLO detection (GPU by default)
ros2 run vision detector_node --ros-args -p enable_display:=True
# Terminal 3: See detections
ros2 topic echo /vision/detections| Executable | Description |
|---|---|
| camera_manager | Multi-camera orchestration → /camera/<name>/image_raw per camera |
| camera_enum | Lists all V4L2 cameras (one-shot, prints table) |
| camera_test | Interactive preview with FPS overlay, snapshots (s), info (i) |
| camera_calibrate | Checkerboard calibration → YAML file (ROS CameraInfo compatible) |
| camera_record | Record camera frames to disk |
| camera_playback | Replay recorded frames as ROS topics |
camera_manager parameters:
| Parameter | Default | Description |
|---|---|---|
| device_id | 0 | V4L2 device index (/dev/videoN) |
| frame_width | 640 | Capture width |
| frame_height | 480 | Capture height |
| fps | 30 | Target framerate |
| camera_name | duburi_cam | Frame ID for image headers |
| calibration_file | "" | Path to calibration YAML |
# Enumerate cameras
ros2 run vision_inspector camera_enum
# Test a specific camera
ros2 run vision_inspector camera_test --ros-args -p device_id:=1
# Calibrate (9x6 checkerboard, 2.5cm squares)
ros2 run vision_inspector camera_calibrate --ros-args \
-p board_width:=9 -p board_height:=6 -p square_size:=0.025
# Launch camera node with calibration
ros2 launch vision_inspector camera.launch.py \
device_id:=0 calibration_file:=calibration/calibration_video0_20260306.yaml| Executable | Description |
|---|---|
| detector_node | ROS node: subscribes to image topic, runs YOLO, publishes detections |
| detector_standalone | Direct camera+YOLO test (no camera_node needed) |
detector_node parameters:
| Parameter | Default | Description |
|---|---|---|
| model | yolo11n.pt | YOLO model file |
| confidence | 0.5 | Detection confidence threshold |
| device | auto | Inference device (auto, cpu, or cuda:0) |
| image_topic | /camera/forward/image_raw | Input image topic |
| enable_display | False | Show OpenCV preview window |
| publish_annotated | True | Publish annotated image with bounding boxes |
| max_det | 50 | Max detections per frame |
| classes | "" | Comma-separated class filter (empty = all) |
| iou | 0.45 | NMS IoU threshold |
# Basic detection (GPU, prints to terminal)
ros2 run vision detector_node
# With display and custom model
ros2 run vision detector_node --ros-args \
-p enable_display:=True -p model:=yolov8s.pt -p confidence:=0.3
# Filter specific classes (e.g. only persons and cars)
ros2 run vision detector_node --ros-args -p classes:="0,2"
# Force CPU mode
ros2 run vision detector_node --ros-args -p device:=cpu
# Via launch file
ros2 launch vision vision.launch.py enable_display:=True confidence:=0.4The alignment_controller subscribes to /vision/detections and /mavlink/vehicle_state, runs PID loops (lateral, vertical, forward) to center the AUV on a detected target, and publishes DriverCommand to /driver/command. Uses simple-pid library with Kalman-filtered measurements.
| Parameter | Default | Description |
|---|---|---|
| target_class | person | YOLO class to track |
| pid_lat_kp/ki/kd | 300/5/60 | Lateral PID gains |
| pid_vert_kp/ki/kd | 300/5/60 | Vertical PID gains |
| pid_fwd_kp/ki/kd | 250/3/50 | Forward PID gains |
| max_speed | 200 | Max PWM offset from neutral |
| control_rate | 10.0 | Control loop frequency (Hz) |
| lost_timeout | 2.0 | Seconds without detection before stop |
Activation: Send alignment commands via runner or DriverCommand:
Detection.msg – single detection:
| Field | Type | Description |
|---|---|---|
| class_name | string | Class label (e.g. person, gate) |
| class_id | int32 | Numeric class ID from YOLO model |
| confidence | float32 | Detection confidence (0.0–1.0) |
| bbox_x, bbox_y | int32 | Top-left corner (pixels) |
| bbox_w, bbox_h | int32 | Bounding box size (pixels) |
| center_x, center_y | float32 | Normalized center (0.0–1.0, resolution-independent) |
DetectionArray.msg – per frame:
| Field | Type | Description |
|---|---|---|
| header | Header | Timestamp + frame_id |
| detections | Detection[] | List of detections |
| image_width | int32 | Source image width |
| image_height | int32 | Source image height |
USB Camera(s) ──► camera_manager ──► detector_node ──► /vision/detections
/dev/videoN (multi-cam) (YOLO11 GPU) (DetectionArray)
│ │ │
/camera/<name>/image_raw /vision/annotated alignment_controller
(sensor_msgs/Image) (annotated frames) (PID visual servo)
│
/driver/command
(DriverCommand)
Normalized center coordinates (center_x, center_y) are designed for easy future MAVLink integration:
center_x < 0.4 → target is left → yaw_left center_x > 0.6 → target is right → yaw_right 0.4–0.6 → centered → move_forward
| Topic | Type | Direction | Description |
|---|---|---|---|
| /driver/command | DriverCommand | → Inspector | Movement and control commands |
| /driver/feedback | DriverCommandFeedback | Inspector → | Command acknowledgement (accepted/completed/reached) |
| /mavlink/events | MavlinkEvent | Inspector → | Arm/disarm, mode, movement events |
| /mavlink/vehicle_state | VehicleState | Inspector → | Armed, mode, depth, yaw, voltage (10 Hz) |
| /mavlink/diagnostics | VehicleDiagnostics | Inspector → | Heading rate, pressure, servos, RC, CPU (2 Hz) |
| /driver/teleop | TeleopCommand | teleop_driver → | Normalized joystick axes (dedicated teleop path) |
| /cmd_vel | Twist | joystick/nav → | Teleop input (converted to TeleopCommand by teleop_driver) |
| /camera/<name>/image_raw | Image | camera_manager → | Raw camera frames (bgr8, per camera) |
| /vision/detections | DetectionArray | detector_node → | YOLO detections per frame |
| /vision/annotated_image | Image | detector_node → | Frames with bounding boxes drawn |
| /vision/alignment_status | AlignmentStatus | alignment_controller → | Visual servo state + PID errors |
DriverCommand fields:
| Field | Description |
|---|---|
| command | See command reference below |
| mode | For set_mode: flight mode name. For cruise/just_cruise: target heading (string, parsed to float). |
| depth | Target depth in meters (for set_depth, pid_depth, cruise). Teleop: throttle PWM offset. |
| angle | Target heading/bearing in degrees (for yaw_to_heading, go_*, move_at, cruise). Teleop: yaw offset. |
| duration | Duration in seconds (0 = indefinite). Teleop: lateral PWM offset. |
| speed | Gain 0–100 (percent). Teleop: forward PWM offset. |
Command reference:
| Command | Category | Description |
|---|---|---|
| move_forward | Movement | Single-axis forward |
| move_back | Movement | Single-axis backward |
| move_left | Movement | Single-axis strafe left |
| move_right | Movement | Single-axis strafe right |
| move_up | Movement | Single-axis up (prefer pid_depth) |
| move_down | Movement | Single-axis down (prefer pid_depth) |
| move_at | Vector | Body-frame vector movement at angle bearing |
| move_forward_right | Diagonal | Horizontal diagonal (√2 scaled) |
| move_forward_left | Diagonal | Horizontal diagonal (√2 scaled) |
| move_back_right | Diagonal | Horizontal diagonal (√2 scaled) |
| move_back_left | Diagonal | Horizontal diagonal (√2 scaled) |
| go_forward | Go | Move + PID yaw to angle |
| go_back | Go | Move + PID yaw to angle |
| go_left | Go | Move + PID yaw to angle |
| go_right | Go | Move + PID yaw to angle |
| go_forward_right | Go | Diagonal + PID yaw to angle |
| go_forward_left | Go | Diagonal + PID yaw to angle |
| go_back_right | Go | Diagonal + PID yaw to angle |
| go_back_left | Go | Diagonal + PID yaw to angle |
| cruise | Cruise | Vector move + depth PID + heading PID |
| yaw_angle | Heading | Legacy SET_ATTITUDE_TARGET |
| yaw_to_heading | Heading | Bang-bang yaw to angle |
| pid_yaw_to_heading | Heading | PID yaw to angle |
| yaw_left | Heading | Open-loop rotate left |
| yaw_right | Heading | Open-loop rotate right |
| set_depth | Depth | ALT_HOLD firmware depth to depth |
| pid_depth | Depth | Software PID depth to depth (0 = current) |
| pid_depth_off | Depth | Disable software depth PID |
| surface | Depth | Ascend to surface |
| arm | Control | Arm motors |
| disarm | Control | Disarm motors |
| set_mode | Control | Set flight mode (mode field) |
| stop | Control | Stop all movement + clear all PIDs |
| open_grabber | Actuator | Open grabber servo |
| close_grabber | Actuator | Close grabber servo |
| teleop | Teleop | Legacy: 4 axes via DriverCommand (prefer TeleopCommand on /driver/teleop) |
| teleop_idle | Teleop | Legacy: clear movement (prefer TeleopCommand.idle=true) |
| just_* | Instant | Any movement command with just_ prefix bypasses PWM ramp |
The inspector node accepts the following ROS parameters for tuning. All defaults are defined in src/mavlink_inspector/config/defaults.yaml — see Configuration Profiles for how to switch between pre-built profiles.
| Parameter | Default | Description |
|---|---|---|
| connection_port | /dev/ttyACM0 | Pixhawk serial port |
| baud | 115200 | Serial baud rate |
| yaw_source | attitude | Which MAVLink message updates yaw: attitude (ATTITUDE, recommended), ahrs2 (AHRS2), or both (legacy, causes jitter) |
| ramp_rate | 800 | PWM velocity ramp rate (PWM/second). 800 = 0.5s full-range ramp |
| depth_kp | 500.0 | Depth PID proportional gain |
| depth_ki | 25.0 | Depth PID integral gain |
| depth_kd | 200.0 | Depth PID derivative gain |
| depth_max_integral | 0.5 | Depth PID max integral accumulator (anti-windup cap) |
| depth_tolerance | 0.05 | Depth PID deadband tolerance in metres |
| yaw_kp | 2.0 | Yaw PID proportional gain |
| yaw_ki | 0.05 | Yaw PID integral gain |
| yaw_kd | 0.5 | Yaw PID derivative gain |
| yaw_max_integral | 50.0 | Yaw PID max integral accumulator |
| pid_max_rate | 50 | Max PID output change per tick (PWM units). Prevents thruster hunting |
| nominal_voltage | 0.0 | Battery voltage for compensation (0 = disabled). Set to full-charge voltage to maintain consistent thrust as battery depletes |
| surface_depth | 0.0 | Target depth for surface command (metres) |
| ack_timeout | 3.0 | Seconds to wait for MAVLink ACK before retrying arm/disarm |
| surface_throttle_duration | 2.0 | Seconds of upward throttle after surface depth PID completes |
# Override parameters at launch
ros2 run mavlink_inspector inspector --ros-args \
-p connection_port:=/dev/ttyACM1 \
-p yaw_source:=attitude \
-p depth_kp:=300.0 \
-p depth_ki:=15.0| Problem | Fix |
|---|---|
| Stale build artefacts | rm -rf build/ install/ log/ && colcon build |
| Symlink install permission errors | Use colcon build without --symlink-install — the workspace uses merged installs |
| "Package not found" after rebuild | Re-source: source install/setup.bash |
| CMake error on interfaces | colcon build --packages-select duburi_interfaces first, then rebuild dependent packages |
| Problem | Fix |
|---|---|
| "No telemetry for Ns" | Check Pixhawk USB cable. Run ls /dev/ttyACM* to verify port. Try connection_port:=/dev/ttyACM1 |
| Arm command times out | Pixhawk may require GPS fix or pre-arm checks. Check QGroundControl for pre-arm failures |
| PID oscillation | Lower depth_kp / yaw_kp. Pool-tuned defaults are conservative — start there |
| Depth drift | Verify BAR30 is connected. Check ~depth is active (not bare depth with ALT_HOLD) |
| Import errors after refactor | Run colcon build (not just the changed package). Circular imports were fixed in commit 87dab7e |
| Camera not found | Run ros2 run vision_inspector camera_enum to list V4L2 devices. Try device_id:=1 |
| YOLO slow / CPU-only | Verify PyTorch CUDA: python3 -c "import torch; print(torch.cuda.is_available())" |
| SITL: heartbeat lost after connect | If using --out=udp:…, use udpin:HOST:PORT on the inspector, not tcp:. TCP and UDP do not share the same socket even on the same port number. |
| No MAVLink on UDP | Start MAVProxy/SITL before or after inspector, but ensure nothing else bound the same UDP port; only one listener on udpin per port. |
| Gazebo / SITL desync | Run gz sim before sim_vehicle; unpause the world (-r); bridge /clock if nodes must use sim time. |
# Check if inspector is publishing
ros2 topic hz /mavlink/vehicle_state
# See current vehicle state
ros2 topic echo /mavlink/vehicle_state --once
# Check all active topics
ros2 topic list
# Check node health
ros2 node list
ros2 node info /mavlink_inspector
# Verify serial port
ls -la /dev/ttyACM*
dmesg | tail -20 # check USB connect/disconnectThe analysis/ folder contains detailed technical documents:
| Document | Description |
|---|---|
| 00_OVERVIEW.md | High-level system overview |
| 01_ARCHITECTURE.md | Detailed architecture and data flow |
| 02_DESIGN_DECISIONS.md | Rationale for key design choices (12 decisions) |
| 03_INSPECTOR_LINE_BY_LINE.md | Inspector node code walkthrough (pre-refactor note) |
| 04_RUNNER_LINE_BY_LINE.md | Runner CLI code walkthrough |
| 05_DRIVER_LINE_BY_LINE.md | Driver client library walkthrough |
| 06_INTERFACES.md | Message definitions and field semantics |
| 07_ARDUSUB_CONSTRAINTS.md | ArduSub firmware constraints and gotchas |
| 08_AGENT_GUIDE.md | Guide for AI agents working on this codebase |
| 09_KNOWN_ISSUES_AND_GOTCHAS.md | Known issues, edge cases, and fixes (21 entries) |
| 10_DESIGN_ISSUES.md | Post-refactor architectural analysis — 9 issues with severity/effort ratings |
| 11_DESK_TESTING_GUIDE.md | Step-by-step desk testing procedures |
| 11_REFACTORING_PLAN.md | 3-phase all-package refactoring plan (Phase 1 partially done) |
| 12_CODE_REFERENCE.md | Post-refactor module map — all packages, with line counts and descriptions |
| 12_COMMAND_REFERENCE.md | Complete command reference with examples and field encoding |
| 13_COMPETITIVE_ANALYSIS.md | Deep-dive comparison against Bumblebee (NUS) and Desert WAVE TDRs |
| 14_ISSUES_AND_RECOMMENDATIONS.md | Gap analysis, design critique, and next-step roadmap |
| VISION_PERFORMANCE_ANALYSIS.md | Vision pipeline FPS optimization (5→25 FPS) |
| 17_SIMULATION_GAZEBO_ARUDSUB_SITL.md | Gazebo Harmonic + ArduSub SITL + Duburi — stack, ports, tuning roadmap, GPU tiers, optional simulators |
# Controls
pip install pymavlink
# Perception (GPU)
pip install opencv-python ultralytics supervision simple-pid
# Verify CUDA
python3 -c "import torch; print(f'CUDA: {torch.cuda.is_available()}, Device: {torch.cuda.get_device_name(0)}')"Branch: Control-Redesign-V2 (testing)
V2 adds advanced control features for mission reliability:
graph LR
subgraph "V2 Features"
A[Convergence Gates] --> B[Wait for vehicle<br/>to stabilize]
C[Rotate-in-Place] --> D[Sharp turns<br/>no drift]
E[Cascade Control] --> F[Position→Velocity<br/>→Thrust]
G[Gain Scheduling] --> H[Speed-adaptive<br/>PID gains]
I[Multi-Source Sensors] --> J[DVL, External<br/>compass fallback]
end
New Modules:
74+ configurable parameters — all features disabled by default for safety.
See analysis/design-decisions/control-stack-v2.md for full documentation.
Apache-2.0
| Back | FazBrowse Home | New Git URL |