Skip to content

mr0xf/swimfit

Repository files navigation

Pool swim FIT activity repair CLI

Important: AI-generated, obviously.

Garmin has all the data in the world, but sometimes still fails to apply even basic sanity checks.

A typical example: I am swimming in a 50 m pool. Most of my lengths are around 60 seconds and 30 strokes. Yes, I know, you are much faster than me, and I need to work on my technique. I keep that pace fairly consistently for 1.5 km. Then, halfway through one length, I catch up with two slower swimmers, pause for a couple of seconds, and try to get around them.

Garmin’s conclusion: "Good job. That was not 50 m. That was 100 m."

Suddenly the activity contains two impossible 50 m lengths: roughly 30 seconds and 15 strokes each. I am very fast. No?

What does Garmin recommend? Improve your technique. Great idea, though.

At that point I am annoyed enough to throw the Garmin away and switch to an Apple Watch. Or, probably smarter, stop caring about all these smart-device metrics and just swim for enjoyment. Unfortunately, I have the bad habit of caring about numbers.

I first found Swimming Watch Tools, which was later deprecated and handed over to swimdata.org. Since I prefer CLI tools, I decided to write my own.

This tool repairs Garmin pool-swim FIT files. It targets impossible active lengths caused by bad pool-swim detection: stroke changes, pauses, turns, exits, partial movement, or sensor glitches that Garmin turns into extra lengths.

Garmin Connect can adjust the total distance, but the original bad length records may remain inside the FIT activity and continue to distort best pace, stroke totals, SWOLF, and personal-record signals.

Warning: this tool contains bugs and may break your data. It is AI-generated, after all. Keep backups of your original activities before replacing anything.

Note: I tested this tool on macOS 15 arm64 only. It may not work correctly on other platforms.

Usage

swimfit inspect ACTIVITY.fit
swimfit dump ACTIVITY.fit
swimfit dump --unknowns-only ACTIVITY.fit
swimfit dump --json ACTIVITY.fit
swimfit detect ACTIVITY.fit
swimfit detect --min-length-seconds 10 --max-speed 3 --min-strokes 0 ACTIVITY.fit
swimfit repair --dry-run ACTIVITY.fit
swimfit repair ACTIVITY.fit --output ACTIVITY-fixed.fit
swimfit repair --merge-phantom-time ACTIVITY.fit --output ACTIVITY-fixed.fit
  • inspect, detect, and repair print human summaries as terminal tables with one-based length numbers, separate Unit columns for metric/value summaries, and parenthesized units in per-record headers.
  • dump prints every decoded FIT message and field. Use --unknowns-only to focus on unresolved/profile-gap fields, Garmin-private messages, developer fields, and invalid sentinels.
  • detect reports impossible active pool lengths, active totals, and anomaly thresholds.
  • repair converts detected impossible active lengths to idle/rest, validates length/lap indexes, removes stale Garmin best-effort messages, normalizes record distance/speed/cadence fields when present, and updates swim summary fields. Use --dry-run to preview every planned field change without writing a file.
  • --json prints structured JSON for inspect, dump, detect, and repair.
  • Options use double-dash long form only, for example --dry-run; single-dash forms are rejected.

For swimming activities with summary distance but no length messages, inspect and detect print a warning. These are often manual-like entries or minimal exports; per-length detection and repair are not available.

Phantom time merge

Use repair --merge-phantom-time when Garmin split one real length into several impossible active lengths, but the real total distance should stay the same. In that mode, contiguous detected phantom lengths donate their time and strokes to the following same-stroke active length before they are converted to idle/rest. This keeps the corrected distance while avoiding an impossible best pace from the remaining fragment.

Leave it off when the extra detection came from a real partial swim, a pool exit on the far side, or any case where dropping that active time is intentional. Those activities may correctly repair to a lower total distance.

Notes on current implementation

  • Swim Data Analyser notes that best-effort records are currently removed. For a file being corrected, any embedded best effort or personal record is very likely wrong, and deciding whether a time is a personal record is not possible from one FIT file alone.

FIT profile data

Supplemental FIT message and field names are generated from Garmin's official Profile.xlsx. To refresh them after downloading a new FIT SDK profile, run:

just update-fit-profile /path/to/Profile.xlsx
just check

Review the generated internal/fitprofile/generated.go diff before committing.

Test fixtures

Tracked FIT fixtures under internal/fitactivity/testdata/ are sanitized copies derived from local exports. To refresh or add one, use:

go run ./scripts/sanitize-fit-fixture.go --input data/private.fit --output internal/fitactivity/testdata/example.fit

Keep original activity exports under ignored local paths such as data/, out/, or docs/data/.

References and ideas

  • Swimming Watch Tools was the original inspiration. The archived PHP editor included Garmin FIT SDK based support for split, merge, delete, change-stroke, and pool-size edits, with watch-specific behavior for Garmin Swim, FR910, FR920, Fenix 2, generic Garmin, and TomTom swim files.
  • Swim Data Analyser and its source repository are a modern rewrite. Useful ideas include keeping FIT processing local, offering JSON/FIT export, showing interval summaries, best times, per-stroke summaries, heart-rate overlays, and stroke-rate/stroke-count efficiency plots.
  • Michael Stapelberg's Stamp It! All Programs Must Report Their Version is the model for the --version and --moreversion diagnostics: stamp the build, plumb it through the program, and report it clearly for debugging.

About

Inspect and fix Garmin swim FIT files

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors