How an underwater robot writes things down

Five animated patterns that describe how autonomous underwater vehicles organize their data as ROS 2 messages and topics — and why, once you know them, a multi-terabyte mission recording is just a well-behaved dataset.

← brucekrogman.com · Bruce Krogman · researched August 2026 · sources at the bottom


An autonomous underwater vehicle (AUV) on a long mission is cut off from the world: radio doesn't travel through seawater, so for days or weeks the vehicle simply records. What it records is not a pile of text logs. Modern vehicles run ROS 2, and everything every sensor says is written into a bag — a self-describing binary archive of typed, timestamped messages organized by topic. Surveying the drivers, simulators, message standards, and public datasets of the marine robotics world turns up the same handful of structural patterns over and over. Here are the five that matter, animated.

New to the jargon? Every dotted term is clickable — each opens a short three-part explanation: the technical definition, a plain-English version, and what the thing is actually used for. A full glossary sits at the bottom of the page.

PATTERN 01

Many sensors, few message types

IMU
DVL
Depth sensor
Side-scan sonar
Camera
GPS (mast up)
Nav EKF
mission.mcap
0
messages
Seven different instruments; every one of them lands in the same file as a typed, timestamped message.

What you're watching: each sensor on the vehicle publishes its readings as messages on a named topic, and the bag recorder subscribes to everything and writes it all into one file. The message types flying by are not invented per vehicle — they come from a small shared vocabulary. Across every driver, simulator, and dataset surveyed, the same core set covers nearly everything:

Message typeWhat it carriesTypical rate
sensor_msgs/Imuattitude, angular rates, acceleration100–333 Hz
nav_msgs/Odometrythe fused navigation solution10–50 Hz
sensor_msgs/FluidPressuredepth30–60 Hz
sensor_msgs/NavSatFixGPS — only when surfacedepisodic
DVL message (see Pattern 3)velocity over the seabed, altitude2–15 Hz
sensor_msgs/Image / CompressedImagecameras and imaging sonar1–20 Hz
sensor_msgs/PointCloud2multibeam / 3-D sonar1–10 Hz
sensor_msgs/LaserScanprofiling & scanning sonar1–10 Hz
sensor_msgs/BatteryStatepower system telemetry~1 Hz
tf2 transformsthe frame tree tying it all togetherwith nav

Learn roughly a dozen message types and you can read the recordings of nearly any vehicle — regardless of who built it. The data vocabulary is small; the volume is what's big.

PATTERN 02

Every topic keeps its own clock

Blink rates are slowed ~25× so the slow topics are visible; the counters show the real divergence. One hour of mission time means millions of IMU rows and roughly 900 acoustic-modem packets.

What you're watching: eight real topics publishing at their characteristic rates. The hierarchy is remarkably consistent across vehicles — inertial sensing fastest, then depth, the fused nav solution, cameras, the Doppler velocity log, sonar frames, health telemetry, and finally the acoustic modem, which manages about thirty bytes every four seconds. The bars are on a log scale; within seconds they sort themselves into the same pyramid you'd find in any real mission bag.

This is the single most important thing to internalize before analyzing this data: a bag is not one table. It is a bundle of independent time series, each at its own rate, sharing nothing but a clock. Any cross-sensor question — what was the battery doing when the vehicle was at 40 m depth? — is a time-alignment problem: resample or as-of-join on timestamps, then analyze.

One DataFrame per topic, aligned on time. That's the entire mental model — and time-series alignment is a solved problem in the pandas ecosystem (merge_asof, resampling).

PATTERN 03

Where standards are missing, conventions grow anyway

The de facto DVL message
Five packages, five maintainers, no coordination — one shape.

What you're watching: there is no official ROS 2 message for a Doppler velocity log — the workhorse sensor of underwater navigation. Instead, five independent projects (a Swedish research fleet, two simulators, a community driver, and a multi-institution standards effort) each defined their own. Cycle through them and the field list barely moves: a header, a 3-D velocity vector, a covariance matrix, an altitude, and a per-beam array.

The same story repeats elsewhere. Imaging sonar has no standard message either — in practice it travels as sensor_msgs/Image, or as a vendor message published alongside a standard-type twin topic. And the whole marine domain is permanently bilingual between ROS's east-north-up coordinate convention and the maritime world's north-east-down, resolved by suffixing frames (odom_ned).

Missing standards are not chaos — they're convergent evolution. For an analyst the practical consequence is even better: bag files embed the schema of every custom message inside the file, so even a message you've never seen deserializes cleanly. The adapters you have to write are thin.

PATTERN 04

The public data landscape has an empty bin

A survey of public marine robotics datasets, sorted by distribution format. The bin for the format that current-generation ROS 2 vehicles actually log — rosbag2/MCAP — is empty.

What you're watching: the marine datasets that exist in public, sorting themselves into bins. Academic navigation datasets ship as legacy rosbag1 files. Industrial and institutional missions (Kongsberg HUGIN surveys, WHOI's Sentry, MBARI, NOAA archives) publish in vendor formats.kmall, XTF, SEG-Y, netCDF. Sonar machine-learning datasets are distributed as plain image files, stripped of their context. And the modern format, rosbag2/MCAP, has essentially no established public data at all.

That empty bin is an opportunity. Anyone who can bridge the gap — convert legacy bags forward, parse vendor formats, and build first-look tooling for MCAP mission data — is working in territory the public tooling hasn't reached yet.

PATTERN 05

The bandwidth pyramid: terabytes on board, kilobytes ashore

AUV — onboard
day 0 of 38
0.0 TB recorded
satellite / acoustic — ~340 B per burst
dock offload — full recordings
Shore
live ops · post-mission analysis
operator has: 0.0 kB
analysts have: 0.0 TB
Underway: the operator steers a 22 TB mission through a soda straw.

What you're watching: an entire long-endurance sortie compressed into a loop. Underwater, side-scan and synthetic-aperture sonar accumulate on the order of 60–250 GB per surveying hour, so the onboard disk climbs toward tens of terabytes. Meanwhile the only paths to shore mid-mission are an acoustic modem at a few kilobits per second and satellite bursts of a few hundred bytes — roughly nine orders of magnitude narrower than the disk. Only when the vehicle is recovered does the flood arrive.

This single constraint shapes the entire software stack: onboard edge processing must reduce raw sensor data to tiny contact reports and health summaries the operator can act on now, and post-mission analysis must digest the full terabytes fast once they land — which questions did the mission answer, what did the detectors find, how is the vehicle aging.

The scarce resource isn't data — it's the time of whoever makes sense of it. That's a data engineering problem, and it's the one I find most interesting.


SO WHAT

From bag to DataFrame in fifteen lines

The payoff of these five patterns: mission data that looks exotic from the outside is, structurally, a well-organized multi-rate time-series database. Because bags are self-describing, they open with an ordinary pure-Python library — no robot, no ROS installation — and each topic falls out as a tidy table:

from pathlib import Path
import pandas as pd
from rosbags.highlevel import AnyReader

rows = []
with AnyReader([Path("mission_bag")]) as reader:
    conns = [c for c in reader.connections if c.topic == "/fuel_cell"]
    for conn, timestamp, raw in reader.messages(connections=conns):
        msg = reader.deserialize(raw, conn.msgtype)
        rows.append({"t": timestamp / 1e9,
                     "voltage": msg.voltage,
                     "current": msg.current,
                     "temp": msg.temperature})

df = pd.DataFrame(rows).set_index("t")   # and now it's just data science

From here it's resampling, as-of joins, anomaly detection, dashboards, and model-building — the standard toolkit, pointed at an unusually interesting ocean.

GLOSSARY

Every term, three ways

All 34 terms used on this page now live on their own page, each defined three ways — technical, plain English, and what it is actually used for — and each with a full infographic panel. The dotted terms above still explain themselves in place; the glossary is there when you want to read or search the whole vocabulary at once.

Open the glossary →