GSSNS

AI-assisted, human-edited

I used AI as a sparring partner for this post: to explore the topic, check my thinking and draft some sections. I edited everything, and the ideas, opinions and final words are mine. I never publish posts that are fully AI-generated.

Exploring Garmin watch data over USB (MTP)

TL;DR: Over USB, a Garmin watch exposes its storage as folders of FIT files. On a Fenix 6 these hold about 13 months of full activities, summary-only activities going back to 2022, about 24 hours of all-day health data (heart rate, stress, respiration, sleep stages, overnight HRV), 100 nights of sleep scores, a 28-day HRV trend, personal records and lifetime totals. 66 of the 126 message types in these files are not in the public FIT profile.

This post maps the data that a Garmin watch exposes over USB: which folders exist, what’s in them, how far back they go and how to read them. It’s meant as a reference for developers who want to read data straight from the device instead of through an API. In the previous post I compared the activity file on the watch with the copies Garmin hands out. This post covers everything else on the watch.

Scope: one Garmin Fenix 6 Sapphire, firmware 28.02. Other models and firmware versions will differ in the details. The folder layout looks similar on Garmin’s other recent watches, but I haven’t checked that.

Disclaimer: this was a technical exercise to see which data I could read from my own Garmin device over USB. It is not meant as a workaround for the undocumented and potentially proprietary data that Garmin removes from FIT files delivered through the Garmin Connect API.

Access over MTP

Older Garmin devices mount as a USB drive. Recent watches use MTP (Media Transfer Protocol), the same protocol Android phones use. MTP is not a file system: the client asks the device for folder listings and then downloads files one at a time.

Tools: mtp-rs for MTP; pyroparse for parsing, with fitdecode for the files pyroparse can’t read yet.

Folder overview

All data lives under /GARMIN. Besides the folders below, the watch holds maps (about 11 GB), language files and Connect IQ app storage.

FolderContentsRetention
Activity/Full activity recordings~13 months
SUMMARY/Older activities, summaries onlyback to 2022
Monitor/All-day HR, steps, stress, respiration~24 hours
Sleep/Sleep stages and sleep scorelast night
HRVStatus/Overnight HRVlast night
Metrics/, Debug/MetricsBackup/VO2max and other daily metrics~1–5 days
FbtBackup/Daily Firstbeat history: sleep score, bedtimes, HRV trend100 nights
FbtPtdBackup/Firstbeat metrics per activitylast 150 activities
Totals/, Records/, Goals/Lifetime totals, personal records, goalslifetime
Settings/, Sports/User profile, zones, sport profilescurrent
Courses/, Workouts/, Schedule/Routes and planned workouts—

Nearly everything is FIT.

Activities (Activity/, SUMMARY/)

Activity/ held 191 files going back 13 months. These files are byte-identical to Garmin Connect’s “Export Original”. Besides the per-second record data (heart rate, power, cadence, GPS, altitude, temperature), they contain:

DataWhereNotes
Beat-to-beat RR intervalshrvOnly with a chest strap that has HRV logging enabled
Connect IQ developer fieldsdeveloper dataFor example Stryd form power, Concept2 drag factor
Wrist (optical) HRrecord field 136, undocumentedRecorded even when a chest strap is the HR source
Strap HRrecord field 144, undocumentedCopy of heart_rate
Training effect, recovery timesession; message 140, undocumentedMessage 140 stores training effect ×10
Watch batterymessage 104, undocumentedVoltage, percentage and temperature during the activity

The undocumented fields are the ones that Garmin’s API removes (previous post).

SUMMARY/ held 100 older activities going back to February 2022, for which the full files are gone. They have the session, laps and time in zone, but no time series. pyroparse fails on most of these files (yes, I will work on supporting them). fitdecode reads all of them with CRC checks disabled and errors ignored.

Health data (Monitor/, Sleep/, HRVStatus/, Metrics/)

The watch keeps only about 24 hours of detailed health data. The files appear to be removed once the watch has synced with Garmin Connect, so building a history from USB requires a sync at least once a day.

MetricFile → messageResolution
All-day heart rateMonitor/ → monitoring.heart_rate~1/min
Steps, distance, caloriesMonitor/ → monitoringcumulative per day
StressMonitor/ → stress_level1/min
RespirationMonitor/ → respiration_rate1/min
Resting HR (today, 7-day average)Monitor/ → monitoring_hr_dataper day
Sleep stagesSleep/ → sleep_levelper stage change
Sleep score and sub-scoresSleep/ → sleep_assessmentper night
Overnight HRVHRVStatus/ → hrv_value, hrv_status_summary5 min, per night
VO2maxDebug/MetricsBackup/ → max_met_dataper update

All of these are documented FIT messages. Parsing notes:

Firstbeat backups (FbtBackup/, FbtPtdBackup/)

“Fbt” refers to Firstbeat, the company behind Garmin’s training status, load and recovery metrics. FbtBackup/lha_bkup.fit contains only undocumented messages. Comparing them with the documented Sleep/ and HRVStatus/ files identifies these fields:

Message, fieldContentsHistory
384, field 2Sleep score100 nights
384, fields 9 / 8Bedtime (UTC) / wake time (local)100 nights
401, fields 2 / 3 / 4HRV 7-day average, baseline lower / upper, per day28 days

The sleep scores match sleep_assessment and the HRV values match hrv_status_summary exactly. This makes FbtBackup/ the only multi-week health history on the watch. The watch rewrites this file in place, so keep every downloaded version.

FbtPtdBackup/ptd_bkup.fit holds Firstbeat’s per-activity metrics (message 140, as in the activity files) for the last 150 activities.

Lifetime data (Totals/, Records/, Goals/)

FileContentsDocumented
Totals/Totals.fitSessions, time, distance and calories per sportyes
Records/Records.fitPersonal records in message 114: field 1 = sport, field 2 = distance (cm), field 5 = time (ms)no
Goals/Goals.fitStep, active minutes and ascent goalsyes

Record distances are exact (100,000 = 1 km, 4,219,500 = a marathon), which makes them easy to identify.

Undocumented messages

Of the 126 message types across all files, 66 are not in the public FIT profile. Parsers show them as numbers (384) with fields named unknown_field_N. The ones identified so far:

WhereContentsConfidence
record field 136Wrist HRchecked
record field 144Strap HR (copy of heart_rate)checked
record field 90Performance conditionlikely
Message 140Per-activity Firstbeat metrics: training effect ×10 (fields 4, 20), recovery time (field 9)checked / likely
Message 104Watch battery during activity (mV, %, °C)likely
Message 79User metrics snapshot: height, weight, max HR, LTHRlikely
Message 384Nightly summary: sleep score, bedtime, wake timechecked
Message 40128-day HRV trendchecked
Message 114Personal recordschecked

Checked means the values match a documented field exactly. Likely means the identification rests on the range and shape of the values.

Not located: Body Battery, SpO2 and training status. They are probably in one of the undocumented messages in Monitor/ or Metrics/.

Pitfalls

Unstrap

This exploration was part of building Unstrap, a tool that copies every file off a Garmin watch, from the browser or the command line, and keeps every version. It’s in beta. Everything above also works with the open source tools listed earlier and a USB cable.

If you’ve read data from other Garmin models and found differences, or decoded one of the remaining messages, let me know and I’ll update this post.