Skip to main content

bottomtime

A lossless dive-log store for divers who log with more than one computer.

Tec divers often wear two dive computers (say, a Garmin Descent on one wrist and a Shearwater on the other). Each records the same dive differently: the Garmin logs at 1 Hz with heart rate, GPS, CNS and tissue loading; the Shearwater logs its native record with GF99, deco ceiling, ppO2 sensors and per-sample battery. No existing interchange format (UDDF, DL7, Subsurface) preserves the union, so exporting to any of them throws data away.

bottomtime keeps everything:

  • Ingests Garmin FIT files (official garmin-fit-sdk). Known dive channels become typed columns; unknown fields and Garmin's undocumented message types are preserved raw, per sample and per message.
  • Decodes Shearwater's Petrel Native Format directly from a Shearwater Cloud database (dive_data.db, the "Export Database" output) with a pure Python decoder, including per-sample GF99, deco ceiling, CNS and battery voltage that Shearwater's own XML/CSV exports omit. Unknown record types are preserved raw.
  • Archives every source file verbatim, content-addressed by SHA-256. The database is a decoded view; the archive is the source of truth.
  • Reconciles dual-computer dives: interval overlap plus depth-profile cross-correlation links the two logs of the same physical dive (storing clock offset and residual skew) without ever merging or resampling the original series.
  • Verifies itself: the decoder is cross-checked per-sample against Shearwater XML exports, against Shearwater Cloud's own computed values (EndGF99), and matched dives are checked for depth agreement.

Everything lands in a single SQLite file with a stable schema, ready for SQL, pandas, or whatever you analyze with.

Install

pip install bottomtime

Usage

# create a store
bottomtime init

# ingest a directory of Garmin FIT files
# (optionally with a Garmin Connect index JSON for true UTC offsets and metadata)
bottomtime ingest garmin ~/dives/garmin-fits

# ingest a Shearwater Cloud database export
bottomtime ingest shearwater ~/dives/dive_data.db

# link dual-computer dives and build the canonical dive list
bottomtime match

# run the verification suite (XML dir optional but recommended)
bottomtime verify --xml-dir ~/dives/shearwater-xml

bottomtime status

Looking at your dives

Once the store is built, interrogate it from the CLI:

bottomtime list                 # canonical dive table (--all includes test dives)
bottomtime show 291             # one dive: computers, channels, gases, match quality
bottomtime export 291 -o d.csv  # per-source sample series (also --format json)
bottomtime plot 291 -o d.png    # aligned dual-computer profile + GF99/ppO2 panels

plot needs matplotlib: pip install 'bottomtime[plot]'. Alignment uses the matcher's clock offset and residual skew, so both computers' profiles sit on one time axis without resampling either.

Or from Python:

import bottomtime
import pandas as pd

for d in bottomtime.list_dives("data/dives.db"):
    print(d["dive_number"], d["start_time_utc"], d["max_depth_m"], d["sources"])

dive = bottomtime.load_dive("data/dives.db", 291)
sw = pd.DataFrame(dive["sources"]["shearwater"]["samples"])
sw.plot(x="t_s", y=["depth_m", "ceiling_m"])

load_dive returns everything about one dive: per-source samples (column-oriented, ready for DataFrames), gases, events, the verbatim decoded headers, computer model/firmware/serial, and the match metadata.

And it's just SQLite; the views cover the common queries directly:

sqlite3 data/dives.db "SELECT dive_number, max_depth_m FROM v_dive_summary
                       WHERE is_test=0 ORDER BY max_depth_m DESC LIMIT 10"
datasette data/dives.db   # instant web UI + JSON API, if you have datasette

All commands are idempotent: re-running an ingest skips already-stored dives, so syncing after a dive trip only adds what's new.

Schema in one breath

raw_artifacts (archived files) → source_dives (one row per computer log, with the verbatim header and, for Shearwater, the raw PNF blob) → garmin_samples / shearwater_samples (wide, per-source, never resampled) + gases, gas_segments, events, undecoded_payloads (raw bytes of anything not yet understood) → matches (dual-computer links) → dives + dive_members (canonical dives). Views v_dive_summary and v_samples_unified cover the common queries.

Notes on the PNF decoder

The decoder follows libdivecomputer's shearwater_predator_parser.c for the documented fields and adds empirically verified mappings for GF99 (byte 25), deco ceiling (byte 24), battery voltage (byte 18) and @+5 TTS (bytes 26-27), validated against Shearwater Cloud's displayed values across hundreds of dives. Bytes without a known meaning are preserved per sample. If your dives disagree, bottomtime verify will say so loudly; issue reports with a failing blob are very welcome.

PNF is shared across the Predator/Petrel firmware family. The empirical mappings above have so far been validated on Perdix 2 and Petrel 3 logs (the latter integrated with a Choptima CCR). Other models, and standalone vs. CCR-integrated units, may populate fields differently; unknown bytes are always preserved, and bottomtime verify cross-checks every decoded value, so drift on other hardware is detected rather than silently mis-decoded.

License

MIT

Metadata

Release files for bottomtime 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bottomtime 0.4.0
File Size Uploaded
bottomtime-0.4.0.tar.gz 29.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for bottomtime 0.4.0
File Interpreter ABI Platform
bottomtime-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 63.8 kB

Release files / bottomtime-0.4.0.tar.gz

Download URL bottomtime-0.4.0.tar.gz
Size 29.4 kB
Tags Source
SHA-256 checksum
How to use checksums
0c6c2a351c0505d8a47c5b30aeff07ee6a83f8a995aa13d1a4fc1cb2df676800
BLAKE2b-256 checksum
How to use checksums
e1065e0d4456696f5e23d8d3eee717fdab404e4110cb54568d5c0f9b6481a169
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.5

Release files / bottomtime-0.4.0-py3-none-any.whl

Download URL bottomtime-0.4.0-py3-none-any.whl
Size 34.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2527f951f34ab1e6beedd98a1eabe724066c230ff35ca74d065881a35aa0ee0a
BLAKE2b-256 checksum
How to use checksums
f8e74edaf1fa0a2a298dc9b212b748c835d387b457db1b5ebfb425fb96e50e18
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.5

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page