My Perdix 2 records seventeen channels per sample. The open formats can carry sixteen of them. Subsurface, the best open-source dive log there is, exports four.
Divers blame the formats for that gap. I did too, in my own README. The gap is in the exporters.
One dive, seventeen channels
A823C228#13, 101.5 metres, 101 minutes, 616 samples, open-circuit trimix. Counting the columns actually populated for that dive rather than what the schema permits:
depth, temperature, GF99, deco ceiling, time to surface, next stop depth, next stop time, CNS percent, battery percent, battery voltage, average ppO2, oxygen fraction, helium fraction, an in-deco flag, a packed status byte, a solenoid fire count, and one raw oxygen sensor reading.
GF99 is the channel divers assume is trapped in vendor formats. It reads out how close your leading tissue is to its Bühlmann limit, it lives in byte 25 of Shearwater’s sample record, Shearwater has never documented it, and I spent a fortnight proving it was what I thought it was.
Filled means the format has somewhere to put that channel. The three specifications between them cover sixteen of the seventeen. The exporter column is the one your data passes through.
UDDF standardised GF99 in 2018
From uddf_3.2.3.xsd, inside waypointType, the per-sample record:
<xs:element name="gradientfactor" minOccurs="0">
<xs:complexType><xs:simpleContent>
<xs:extension base="xs:float">
<xs:attribute name="tissue" type="xs:int"/>
</xs:extension>
</xs:simpleContent></xs:complexType>
</xs:element>A per-sample gradient factor, with an attribute naming which of the sixteen compartments is leading. My dive computer does not record that attribute. On this field the XML standard beats the binary format I reverse engineered.
The prose around it is ambiguous. It calls the value “80 % of the maximum tolerated saturation”, which is not how Shearwater defines GF99, and the two quantities agree only at zero ambient pressure. The usage settles it. Subsurface ships an APD Inspiration log in its test data where <gradientfactor> appears 695 times across nineteen values, climbing from 0.50 to 0.70, while that dive’s <setgflow> is 0.50 and its <setgfhigh> is 0.70. A number that walks from the diver’s low setting to their high setting over a dive is a live GF readout.
<batterychargecondition>, <cns>, <otu>, <decostop>, <nodecotime>, <setpo2>, <measuredpo2>, <heartrate> and <tankpressure> are waypoint children too. Twelve of my seventeen.
DL7 had the ceiling in 2006
DL7 is Divers Alert Network’s format, built for Project Dive Exploration. The specification is a Word document by Petar J. Denoble dated July 2006, and it survives in the docs directory of an archived Python library. Segment attribute tables, HL7-derived delimiters, coded value tables for computer identifiers and gas codes.
Its per-sample segment is ZDP. Two of its fields:
7 | 6 | NM | O | Current Ceiling | Value
9 | | ST | O | Warning number | Warning number(s) in Hex as defined within the
specific Unit. May contain more that one Warning
Number separated by character ^A per-sample deco ceiling, and a per-sample hex warning word whose bits each computer defines for itself. The second one is a packed status byte under another name. DAN specified both eight years before my dive computer existed, and twelve years before the XML standard that has neither.
UDDF 3.3.0 was never published. Software writes it anyway.
UDDF 3.2.3, dated 15 November 2018, is the newest published release. A 3.3.0 exists too, in files people are writing today.
Subsurface’s test data contains dives/test-apd-inspiration.uddf. It opens <uddf version="3.3.0"> and its waypoints carry this:
<ppo2 ref="o2sensor_c1_1">35000.0</ppo2>
<batteryvoltage ref="battery_c1_1">5.6</batteryvoltage>
<batteryvoltage ref="battery_c1_2">5.7</batteryvoltage>Battery voltage per waypoint, per device, so a rebreather with two controllers records both. Elsewhere in the same file, <timetosurface>60.0</timetosurface>. Individual oxygen cells get their own values instead of one averaged number.
That revision was never released. The UDDF site lists 3.2.1 as current and serves a certificate that expired in July 2025 for an unrelated hostname. The schema directory stops at 3.2.2. Meanwhile commercial dive software writes 3.3.0 files and an open-source dive log keeps one as a test fixture.
The standard’s most recent useful version is a private agreement between vendors. You find it by reading files.
After all three specifications, one of my seventeen channels has nowhere to go: the CCR solenoid fire count.
Subsurface reads more UDDF than it writes
Subsurface’s internal sample structure, core/sample.h, holds time, stoptime, ndl, tts, rbt, depth, stopdepth, temperature, ten cylinder pressures, setpoint, six oxygen sensor readings plus one computer-reported ppO2, bearing, cns, heartbeat, sac, and an in-deco flag. That is enough for most of my dive.
Its UDDF exporter is xslt/uddf-export.xslt, 773 lines with two waypoint-emitting branches. Between them they write seven elements: depth, divetime, temperature, tankpressure, switchmix, alarm, heading. The file contains no xsl:element and no dynamic construction, so that list is complete.
Subsurface holds ndl, tts, cns, setpoint, oxygen sensors and heart rate in memory. UDDF has elements for all of them. The exporter writes none.
Import is a different story. It runs through a second stylesheet, xslt/uddf.xslt, 1050 lines, which handles alarm, heading, cns, otu, batteryvoltage, setgflow, setgfhigh, scrubber and divemode. It will ingest the per-cell <ppo2> values and battery voltages from a 3.3.0 file that no published schema describes.
So one project ships a reader that understands the rich half of UDDF and a writer that emits seven elements of it. Whatever a Subsurface user exports, the next program reads a thinner dive than Subsurface could have described.
For my 101 metre dive: seventeen recorded, sixteen representable, four written. Depth, time, temperature, gas.
None of this is a complaint about Subsurface, which is free, excellent, and the reason most divers have their data at all. That XSLT is old, XSLT is a miserable language to extend, and UDDF export matters far less to its users than supporting the next computer over USB. Jef Driesen, who maintains libdivecomputer, is thanked in UDDF’s own author list. These people have done far more for open dive data than I have.
UDDF has a slot for the raw bytes. Nobody fills it.
UDDF has an element called <dcdump>: the dive computer’s raw memory, bzip2-compressed and base64-encoded. The rationale:
Such file can be useful as dive computer data backup; of course the backup usually cannot be restored to original device but UDDF software can access it later, i.e. to perform another, more complete conversion into UDDF format
Somebody in 2018 had watched this happen often enough to add a place for the original bytes, so a better decoder could revisit them. It sits under <divecomputercontrol>, beside the log instead of inside it. I have never seen a tool emit one.
That is the design of my own store, which keeps original bytes content-addressed and treats the decoded database as a rebuildable view. I built it because I thought the formats had failed me. The formats had done their part. Every program that touches them implements the subset its author needed that week, that subset becomes the format in practice, and you learn which subset it was years later, when you finally have a question.
The same thing happens outside diving
Dive logs are small enough that you can count every field and read every exporter in an afternoon. That is the only unusual thing about this case. Any format where a committee wrote a large specification and a few libraries implemented the parts their authors needed works this way: healthcare records, calendars, geospatial data, most of the OpenAPI surface.
So stop grading a format by its specification. Grade it by what the tools you actually use will read and write. That intersection is smaller than the spec, smaller than any single tool’s internal model, and you can measure it in an afternoon. Then keep the original bytes, because the intersection shrinks to whatever two programs happened to agree on, and it is never the part you will want.
Sources
UDDF 3.2.3, the newest published release:
- The documentation index, dated 15 November 2018
uddf_3.2.3.xsd, the schema itself, which the documentation’s own schema chapter does not link<waypoint>,<gradientfactor>and<dcdump>
DL7:
- DL7 Standard, Divers Alert Network, Petar J. Denoble, July 2006. A Word file in the archived PyDL7 repository, which is the only copy I could find.
Subsurface, read at master:
core/sample.h, what it holds per samplexslt/uddf-export.xslt, what it writesxslt/uddf.xslt, what it readsdives/test-apd-inspiration.uddf, the UDDF 3.3.0 file
Channel counts come from my own store, one query per column across the 616 samples of A823C228#13. The decoder is bottomtime and the format work behind it is pnf.