Download Latest Version ultimate2subsurface-0.5.tar.gz (649.1 kB)
Home
Name Modified Size InfoDownloads / Week
README.md < 19 hours ago 27.5 kB
ultimate2subsurface-0.5.tar.gz < 19 hours ago 649.1 kB
ultimate2subsurface-0.4.tar.gz 2026-11-04 526.7 kB
Totals: 3 Items   1.2 MB 1

Converting Huawei Dive Logs

This project reads dive data from a Huawei Health export and writes one XML file per recognized dive for Subsurface. The exports examined so far come from a Huawei Watch Ultimate 2. Huawei's format is not officially documented; the information below is based on the sample files in testdata/ and on the converter implementation. Unknown fields and event codes may have different meanings.

Converter version: 0.5

Authorship and disclaimer

Copyright (c) 2026 Hannes Krueger. The converter was developed with assistance from OpenAI's ChatGPT. Huawei and Subsurface are respective third-party trademarks and products.

This software is provided under the MIT License, without warranty of any kind. Dive-computer exports and their interpretation may contain missing, incorrect, or misunderstood data. Review imported dives and verify critical information against the original dive computer and other reliable records. Do not use this converter or its output for dive planning, decompression decisions, or other safety-critical purposes.

Usage

Obtaining the Huawei Health JSON Export

  1. Synchronize the Huawei Watch Ultimate 2 with the Huawei Health app.
  2. Allow the Huawei Health app to synchronize the watch data with Huawei's servers.
  3. In Huawei Health, use Privacy management to request a copy of your Huawei Health data.
  4. Huawei prepares the export for download. It usually becomes available about one day after the request; the dive data is therefore not available immediately after a watch-to-app sync.
  5. Download and extract the archive. The dive records used by this converter are in Motion path detail data & description/, in a file named like motion path detail data*_0.json.

The archive contains many other health and sports data files. The converter can read the motion-path JSON file directly; the whole archive is not needed.

python3 huawei_dives_json_to_subsurface.py \
  -o subsurface-export \
  'testdata-huawei-health-export/Motion path detail data & description/motion path detail data*.json'

For command-line options and supported event rules, see the converter.

The motion-path dive records contain no surface air-pressure or verified dive-site-altitude field. The broader archive can contain independent health detail records of type 200003 with key ALTITUDE, but Huawei's field description gives no unit or link to a dive. Around the 1 November dives, these readings jump between different values and include a negative sentinel, so they are not currently reliable enough to populate a dive's altitude. For altitude dives, such as dives in mountain lakes, enter the site altitude manually in Subsurface.

Huawei Format Documentation

Export File and Records

In the examined export, the file Motion path detail data & description/motion path detail data*_0.json contains a JSON array of sports records. A record includes fields such as:

Field Observed meaning
startTime, endTime Start and end times as Unix timestamps in milliseconds
timeZone Record's time-zone offset, for example +0200
type, subType Huawei record/sport identifiers; dive sport type is 291
deviceCode, recordId, dataId Device and record identifiers
summaryData A JSON object stored as JSON text in the outer JSON
detailData A time series stored as text, with one tp=... line per sample/event

The converter identifies dives by sportType == 291 in summaryData, or by certain dive fields in summaryData.mExtendTrackDataMap (for example, divingMode, divingTime, or avgDepth). The export file may also contain records for other sports or detail types. Free diving is identified by sportType == 287. Huawei's sport-type table also lists 288 as breath-hold training and 289 as a breath-hold test; neither appeared in the examined archive.

Detail Data (detailData)

Each line contains a tp type and semicolon-separated key-value pairs. The export contains the following types; current interpretation status is shown:

Type Fields / interpretation
tp=alti Dive depth profile. k is a Unix timestamp in milliseconds; v is depth in metres. Huawei's generic field sheet calls this altitude.
tp=ext Extended samples. k is an index or time value (not fully determined); timeStamp is a Unix timestamp in milliseconds. tem is water temperature in °C, po2 is PPO₂ apparently stored in hundredths of a bar (for example, 164 = 1.64 bar), cns is CNS percentage, and cel is the decompression ceiling in metres. tp=ext samples are matched to depth samples by time; differences of up to two seconds are accepted.
tp=h-r Huawei documents heart-rate samples: k is exercise time in minutes and v is heart rate in beats per minute. This tag was not present in the examined dive profiles.
tp=msg Dive events. t is a Unix timestamp in seconds. Huawei codes ty, e, id, msid, and msty are not generally documented; their meanings are used only where they have been investigated in this project.
tp=sec Summary/configuration block with fields such as sn, sdmv (maximum depth in decimetres), sdut (duration in seconds), and sdbt (meaning unknown; -1 in the freediving sample). May contain an smap object.
tp=rs Huawei's Motion path field description calls this real-time speed: k is elapsed exercise time in seconds and v is signed speed in dm/s. Negative values indicate descent and positive values ascent. The converter parses the samples and reports their range in the console, but does not export them because Subsurface has no vertical-speed sample field.
tp=i-h-r Has k and v; all observed values are -1, so no usable measurement has been identified.

The converter accepts tp=ext temperatures from -2 °C to 40 °C. It drops a single first temperature reading when it differs by more than 3 °C from the median of the next ten valid readings (with at least five valid readings available). This handles observed startup outliers while allowing plausible zero and sub-zero water temperatures.

The generic Huawei field description calls tp=alti altitude recorded every five seconds. In the examined dive records, the same tag contains the depth profile, with millisecond timestamps and typically one-second samples. The converter interprets it as depth in the dive-record context.

For tp=msg, a gas change with ty=0, id=24, and a gas=O2/He field has been confirmed (for example, 16/51 for Trimix 16/51). Values 3 and, for warnings, 2 have been observed for e. Other event codes have not been reliably interpreted and are not treated as confirmed Huawei events. For id=13 and id=14 high and low PPO₂ warnings, respectively, a present po2 field is divided by 100 and shown in bar (for example, 9 becomes 0.09 bar). The 1 November 2026 export records id=6 and id=7 with ty=0,e=1, identified as depth and time reminders. A real dive on 8 November 2026 confirmed the 15 m depth reminder (id=6) and 30-minute time reminder (id=7) in the log. The same dive also contained a second id=7 event at 20 minutes; it was not configured, and the diver does not recall a device display at that time. An unremembered display is possible, but the reason for this event is unknown.

The diver confirmed that all eight tp=msg events in the 8 November real-dive record were correctly captured. They include the depth and time reminders, an NDL reminder (id=25), deco start (id=2), decostop start (id=4), missed decostop (id=12), and a marker (id=23). This provides real-dive confirmation for these observed event labels and for the event mappings already listed above.

Two additional warnings were configured on the watch for that dive: depth 30 m and elapsed time 1:00:00. The diver confirms both warnings appeared on the watch underwater, at 30 m and exactly one hour, respectively. They used audible and vibration alerts and displayed the depth or time in red. The reminders used yellow display text. Neither warning appears in the dive's tp=msg events. The JSON has a depth profile reaching 41.7 m and continues beyond one hour, so both warning conditions occurred during the recorded profile. No corresponding warning is present in the other detailData blocks or summaryData either. This suggests that the watch did not include these warnings in this export, but the cause is unknown; unfinished watch software is only a hypothesis, not an established explanation.

Huawei event timestamps up to 15 minutes before the first depth sample are included at profile time 0:00. The watch can begin recording depth after the dive has already started. The original local timestamp and offset are retained in the dive notes. Events earlier than that allowance, or after the final depth sample, are excluded and counted in the notes; these may be stale or belong to another session. Excluded events are also listed in the converter's console output with their local timestamp, offset from the profile, and decoded label. Negative event times are avoided because Subsurface import behavior for them has not been confirmed.

For the 18 October 2026 record, 17 events are timestamped several days before the profile. One unconfirmed explanation is that they were generated while testing dive mode in simulations that were not logged, then retained by the computer and included in the next recorded dive. This has not been verified.

The 8 November 2026 dive confirms the watch displays reminders in yellow and warnings in red, with audible and vibration alerts for the configured depth and time warnings. The logged reminders have e=1. Because the two warnings were absent from the JSON, their event codes and any relationship between warning display color and e remain unknown. Earlier proposed mappings of e=3 to reminder and e=2 to warning are not confirmed.

The converter also maps the following id values to event labels. These are project observations/configured mappings, not an official Huawei code table; the rules match on id alone and retain any ty and e values in the exported bookmark for context.

id Label used by the converter
2 Deco start
4, 5 Decostop start / end
6, 7 Reminder depth / time
10, 16 CNS reminder / warning
12 Missed decostop
13 High PPO₂ warning
14 Low PPO₂ warning
17–20 Stopwatch started, paused, continued, reset
21, 22 Compass direction set / cleared
23 Marker
25 NDL reminder

msty separates communication event categories: msty=1 is an emoji, msty=2 an SOS event, and other/missing values are treated as text messages. All observed sent messages and emojis use id=26; received SOS events and ordinary received text messages use id=27. Two ordinary received messages tested during the simulated (“fake”) dive on 7 November 2026 confirmed that id=27 marks received messages. The converter therefore labels id=26 events as sent and id=27 events as received. It applies the same msid labels to received text messages as to sent text messages. Received emojis have not yet been confirmed, so applying id=27 to that category remains provisional.

The converter's currently known msid labels are listed separately below. For recognized events, id, ty, and e are retained in the exported bookmark, even when the label is recognized. For messages and emojis, these codes are not used to look up the label. The current export has ty=6 and e=4 for all observed text messages and emojis. These are observed values, not confirmed invariants for every Huawei export. — means that the field is not used for that mapping.

Text messages

id msty msid ty e Text message
26 0 1 6 4 Follow your buddy
26 0 2 6 4 Follow instruction
26 0 3 6 4 Check SPG
26 0 4 6 4 Do safety stop
26 0 5 6 4 Boats, watch out
26 0 6 6 4 Ascend in 1 min
26 0 7 6 4 Feeling unwell
26 0 8 6 4 Come to me
26 0 9 6 4 Take a photo
26 0 10 6 4 Avoid kicking
26 0 11 6 4 Stick close to me
26 0 12 6 4 Keep an eye on me
26 0 13 6 4 Guideline!!
26 0 14 6 4 Abort the dive
26 0 15 6 4 I'm hypothermic
26 0 16 6 4 Calm down
26 0 17 6 4 Call for help
26 0 18 6 4 Need doco gas
26 0 19 6 4 Deco done
26 0 20 6 4 Loop flooded
26 0 21 6 4 Switching to BO
26 0 22 6 4 I'm not OK
26 0 23 6 4 Watch CO2
26 0 24 6 4 I need help!
26 0 25 6 4 I'm OK now
26 0 26 6 4 Are you OK?
26 0 27 6 4 Everything's OK
26 0 28 6 4 Copy that
26 0 29 6 4 STOP
26 0 30 6 4 Head back
26 0 31 6 4 Look up
26 0 32 6 4 Look down
26 0 33 6 4 I'm stuck
26 0 34 6 4 Ascent to surface
26 0 35 6 4 Cool stuff here
26 0 36 6 4 That's cool
26 0 37 6 4 Look at me

Emojis

id msty msid ty e Emoji
26 1 1 6 4 laughing
26 1 2 6 4 hang loose, shaka
26 1 3 6 4 steam from nose
26 1 4 6 4 freezing
26 1 5 6 4 persevering face
26 1 6 6 4 unhappy
26 1 7 6 4 thinking
26 1 8 6 4 ok
26 1 9 6 4 whale
26 1 10 6 4 ray
26 1 11 6 4 dolphin
26 1 12 6 4 shark
26 1 13 6 4 turtle
26 1 14 6 4 octopus
26 1 15 6 4 jellyfish
26 1 16 6 4 prawn
26 1 17 6 4 crab
26 1 18 6 4 ocean sunfish
26 1 19 6 4 sea snail
26 1 20 6 4 seahorse
26 1 21 6 4 seastar
26 1 22 6 4 nemo
26 1 23 6 4 water snake
26 1 24 6 4 weird/unknown fish
26 1 25 6 4 need to pee

SOS events

id msty msid ty e SOS event
26 2 1 6 2 SOS sent: alert/active
26 2 2 6 3 SOS sent: ended / I'm okay
27 2 1 6 2 SOS received: alert/active
27 2 2 6 3 SOS received: ended / I'm okay

The three gauge-mode dives recorded on 23 October 2026 contain the id=27 pairs above. They confirm that e=2 marks the received SOS alert and e=3 the received "I'm okay"/SOS-ended event; msid=1 and msid=2 distinguish those two states in these samples. Two ordinary received text messages tested during a simulated (“fake”) dive on 7 November 2026 independently confirmed that id=27 marks received messages. All observed outgoing text messages, emojis, and older SOS records use id=26. Received emojis have not yet been tested. For SOS, the converter uses msty=2 to identify the category and e for the state.

The SOS tp=msg lines contain only t, ty, e, id, msid, depth, and msty. No sender code such as 4A23 or other sender-specific field was found in these events. The record-level deviceCode identifies the recording device, not the SOS sender, so the sender cannot be recovered from these motion-path records as currently understood.

The tables list all message and emoji labels currently configured in the converter; it has not been confirmed that they cover every option available on the watch. msid values are reused between message and emoji categories, so they must be interpreted together with msty. Unknown values are kept visible as unknown message/emoji labels. For id=26 and id=27, the converter includes available depth and ty/e codes in the bookmark text. The precise meaning of ty and the general meaning of e outside the observed SOS events remain uncertain.

The following smap keys have been observed. Meanings inferred from the single freediving sample are marked as provisional:

Key Observed meaning
2910001 Dive start time as a Unix timestamp in seconds
2910002 Maximum CNS percentage
2910003 OTU
2910004 Gas definitions; technical mode uses O₂/He/N₂ triplets, while sport mode stores the selected O₂ percentage as one value (for example, 3200 = 32% O₂)
2910005, 2910006 Minimum and maximum water temperature in °C
2910012 Average depth in decimetres
2910015, 2910016 Longitude and latitude
2870001 Freediving segment start time as a Unix timestamp in seconds; displayed in the segment's local time in XML notes
2870005, 2870006 Minimum and maximum water temperature for the freediving segment in °C, confirmed against Huawei Health
2870008 Unknown; value 0 in all three freediving segments
2870009 Unknown; values 2, 2, and 4 in the three segments
2870012 Freediving segment average depth in decimetres, confirmed against Huawei Health
2870013–2870016 Unknown; all are 360.000000 in the sample, probably unavailable/sentinel values; possibly position-related

In the two simulated dives from 20 October 2026, both coordinate fields contain 360.000000, which is outside the valid latitude/longitude ranges and appears to be Huawei's unavailable-coordinate sentinel. In Subsurface notes, invalid coordinate values are shown as no valid data rather than as coordinates.

The code documents divingMode=0 as free diving, 1 as sport/recreational diving, 2 as technical diving, and 3 as gauge mode. Observed water settings in mExtendTrackDataMap are:

Field Meaning / Subsurface output
waterType=0 Custom water. Read waterDensity in kg/L and convert it to Subsurface salinity in g/L (multiply by 1000); for example, waterDensity="1.130000" becomes salinity="1130 g/l".
waterType=1 Fresh water; exported as salinity="1000 g/l".
waterType=2 Salt water; exported using the conventional value salinity="1040 g/l".

These mappings were checked against two simulated dives in the export from 20 October 2026. The 15:54 dive, configured with custom density 1.13 kg/L, contains waterType="0" and waterDensity="1.130000" in summaryData.mExtendTrackDataMap. The 16:02 dive, configured for seawater, contains waterType="2" and no explicit waterDensity. The converter therefore uses the recorded density for custom water and the conventional 1.04 kg/L value for seawater. Unknown or missing custom densities are not guessed, so no salinity value is emitted for waterType=0 unless a valid positive density is present.

For sport mode, a single smap[2910004] value is treated as the selected oxygen percentage. For sport and tech diving, Air (21% O₂) is used when Huawei provides no gas definition. Gauge and free-diving modes have no cylinder output.

For technical dives, smap[2910004] appears to list gases used during the dive, not every gas configured on the watch. In the 1 November 2026 technical dive, it contains Oxygen, 17/34, and 50/19, but not Air (21/00), which the diver believes was configured but not used. The JSON contains no explicit field for the starting gas: the first gas in this list is not necessarily the one used at the start, and gas-change events record later switches. A recorded PPO₂ may be consistent with a particular gas, but it does not identify that gas reliably. The converter uses a cautious workaround: if exactly one listed gas has no matching gas-change event during the dive (including the accepted pre-profile window), it moves that gas to the first cylinder position. The XML notes mark this as an inference. If the data leaves multiple candidates, Huawei's order is retained. This can still be wrong if Huawei includes a configured but unused gas in the list.

During this dive the computer repeatedly displayed a “better gas: 21/00” recommendation, including at around 40 m. The motion-path JSON contains neither that gas in smap[2910004] nor a matching tp=msg event, so the recommendation cannot be reconstructed from the examined record.

Freediving session sample

The 23 October 2026 export contains a record with sportType=287 (Huawei's field-description sheet names this "Free diving") and divingMode=0. It is a single logbook session containing three dives. The summary reports divingCount=3, segmentNum=3, divingTime=15, underwaterTime=15, and maxUnderwaterTime=6. The converter now names mode 0 as Free diving, but does not yet interpret these session or segment metrics.

The record has 88 one-second depth, temperature, tp=rs, and tp=i-h-r samples, plus three tp=sec blocks (sn=1..3). The blocks contain separate maximum depth, duration, temperature, and probably average-depth values:

Segment sdmv maximum depth sdut duration 2870001 timestamp 2870012 average depth 2870005..6 temperature
1 1.3 m 6 s 22:38:22 1.1 m 29–29 °C
2 1.4 m 6 s 22:39:05 1.2 m 29–30 °C
3 1.7 m 3 s 22:39:29 1.5 m 30–30 °C

The 2870001 timestamps are the start times of the respective segments and are now rendered as local date/time in XML notes, alongside the raw Unix value. Huawei Health confirms 2870005 and 2870006 as each segment's minimum and maximum water temperature. 2870012 is the segment average depth in decimetres, confirmed against Huawei Health; XML notes display the value in metres and retain the raw value. The session-level avgDepth=13 also appears consistent with 1.3 m. The values 2870008 and 2870009 are not understood. All 2870013–2870016 values are 360.000000; these may be unavailable position-related values, but that is unconfirmed.

The session summary reports maxDescentSpeed=5 and maxAscentSpeed=1; their relationship to the individual tp=rs samples is not fully understood. tp=rs is zero in all examined scuba samples.

Freediving profile from 1 November 2026

The first dive in the 1 November export is a freedive (sportType=287, divingMode=0) with 149 one-second depth samples and 149 tp=rs samples. Here, tp=rs values match the vertical-speed plot in Huawei Health: at profile time 00:05, v=-2 matches -0.2 m/s while descending; at 00:18, v=2 matches +0.2 m/s while ascending. This confirms that v is signed vertical speed in decimetres per second (dm/s). The converter parses these values and reports the converted range in its console output, but does not add them to the XML because Subsurface has no field for vertical speed.

The session summary reports maxDescentSpeed=2 and maxAscentSpeed=1, while the profile contains an ascent sample of v=2. The summary fields' exact relationship to the sampled values remains unclear.

tp=i-h-r is -1 throughout the freediving record. The summary's avgHeartRate=0, maxHeartRate=6, minHeartRate=0, and mAverageHangTime=-1 also do not provide plausible usable readings; their sentinel/default meanings are unconfirmed. tp=ext contains temperature only, and there are no tp=msg events. The current XML notes retain raw tp=sec values, but do not yet decode the freediving session or segment summary fields.

Huawei's generic tag description defines tp=h-r as heart-rate data, but it does not occur in the examined dive profiles. Separate Health detail records also contain heart-rate measurements; none overlap either of the 1 November dive intervals.

The sample exports also contain other detail-data types such as tp=i-h-r; their meanings remain under investigation. Missing values are not replaced with estimated measurements.

Output and Limitations

The converter writes the depth profile from tp=alti, temperature, PPO₂, and decompression ceiling from tp=ext, gas definitions from smap, and confirmed gas changes from tp=msg to Subsurface XML. Unknown Huawei events may be exported as bookmarks; Huawei tp=sec and smap information is also included in the notes. Free diving is identified, and the first multi-dive session sample is documented above; dedicated freediving summary/segment handling and other Huawei record types still need investigation.

Subsurface stores the dive mode on the <divecomputer> element using dctype="Freedive"; the converter emits this attribute for Huawei divingMode=0.

Every depth sample is written. Temperature and computer-supplied PPO₂ use Subsurface's change-only sample attributes (temp and dc_supplied_ppo2). A positive Huawei ceiling sets in_deco and stopdepth; when the ceiling returns to zero, the converter clears the deco state and stop depth. PPO₂ is exported as a dive-computer measurement because it is supplied by the watch. The explanatory note about Huawei cel is included only when a positive ceiling occurs in sport or technical mode; it is omitted for free diving, gauge mode, and dives without deco. The XML dive duration spans the full dive-mode profile, including the configured surface interval before the computer closes the dive. Huawei sdut is underwater time and is expected to be shorter. Per-sample CNS values are parsed but are not currently exported as sample attributes; the summary CNS value from smap is retained in the notes.

Decompression data: limits and observations

Huawei's JSON includes the recorded ceiling (cel) but not the Ultimate 2's decompression model or settings, so those calculations cannot be independently verified from the export. During the 8 November 2026 dive, the displayed ceiling matched cel; the JSON records whole metres while the watch displayed decimal values.

On that dive, the Ultimate 2 and OSTC2 were configured with one TMX 18/45 gas, GF 40/80, and a 6 m final stop. At about 119 minutes, the OSTC2 showed TTS 257 minutes with one 6 m stop; the Ultimate 2 showed TTS 137 minutes and a 6 m stop lasting 24 minutes. The shorter TTS may mean the Ultimate 2 included an unshown 3 m stop despite the 6 m setting. This is not confirmed. Check on further dives and report it to Huawei if confirmed. The setup had no deco gases and is not a realistic dive plan.

Surface-GF values also differed: near peak saturation, Ultimate 2 195% and OSTC2 220%; maximum recorded values were 179% and 208%, respectively. These observations do not establish which calculation is correct. Maybe this should be investigated in comaparison other implementations.

Source: README.md, updated 2026-11-09