|
1 | | -# Apple Health to Garmin Converter |
| 1 | +# Apple Health to Garmin |
2 | 2 |
|
3 | | -Convert Apple Watch workouts from Apple Health export to **FIT** or **TCX** format for importing to Garmin Connect, Strava, TrainingPeaks, and other fitness platforms. |
| 3 | +Convert Apple Watch workouts to Garmin's FIT format with full-resolution heart rate, running power, stride length, vertical oscillation, and ground contact time. |
4 | 4 |
|
5 | | -## Features |
| 5 | +Apple's "Export All Health Data" aggregates workout heart rate into low-resolution chunks. This tool bypasses that by reading HealthKit directly on the iPhone via a small sideloaded app, then converting the full-resolution data to FIT files for Garmin Connect. |
6 | 6 |
|
7 | | -- Converts to FIT (recommended) or TCX format |
8 | | -- Preserves per-second heart rate, GPS routes, and workout statistics |
9 | | -- FIT output includes running power, stride length, vertical oscillation, and ground contact time |
10 | | -- Organises workouts by year/month |
11 | | -- Supports running, walking, cycling, hiking, swimming, and other workout types |
| 7 | +## How it works |
12 | 8 |
|
13 | | -## Requirements |
14 | | - |
15 | | -- Python 3.11+ |
16 | | -- [uv](https://docs.astral.sh/uv/) (for FIT format — manages dependencies automatically) |
17 | | -- Apple Health export data |
18 | | - |
19 | | -## How to Export Apple Health Data |
| 9 | +1. **iOS app** serves workout data from HealthKit over a local HTTP server |
| 10 | +2. **Fetch script** pulls all workouts (metrics + GPS) from the phone to your Mac |
| 11 | +3. **Converter** produces FIT files ready for Garmin Connect |
20 | 12 |
|
21 | | -1. Open the **Apple Health** app on your iPhone |
22 | | -2. Tap your **profile picture** (top right corner) |
23 | | -3. Scroll down and tap **"Export All Health Data"** |
24 | | -4. Tap **"Export"** to confirm |
25 | | -5. **AirDrop or email** the ZIP to your Mac |
26 | | -6. Extract the ZIP — you'll get a folder containing: |
27 | | - - `export.xml` — main health data including workout statistics and per-second metrics |
28 | | - - `workout-routes/` — GPX files with GPS trackpoints for each workout |
29 | | - |
30 | | -## Usage |
| 13 | +## Requirements |
31 | 14 |
|
32 | | -### FIT format (recommended) |
| 15 | +- iPhone with Apple Health data |
| 16 | +- Mac with [Xcode](https://apps.apple.com/app/xcode/id497799835) (for sideloading the iOS app) |
| 17 | +- [uv](https://docs.astral.sh/uv/) (Python package manager) |
33 | 18 |
|
34 | | -FIT is Garmin's native binary format and preserves the most data — heart rate, running power, stride length, vertical oscillation, and ground contact time. |
| 19 | +## Quick start |
35 | 20 |
|
36 | 21 | ```bash |
37 | 22 | git clone https://github.com/brtkwr/apple-to-garmin.git |
38 | 23 | cd apple-to-garmin |
39 | | -uv run convert_to_fit.py /path/to/apple_health_export |
40 | 24 | ``` |
41 | 25 |
|
42 | | -Filter by activity type: |
43 | | -```bash |
44 | | -uv run convert_to_fit.py /path/to/export --activity running |
45 | | -``` |
| 26 | +### 1. Install the iOS app |
| 27 | + |
| 28 | +1. Open `HealthKitExporter.xcodeproj` in Xcode |
| 29 | +2. Set your development team in **Signing & Capabilities** |
| 30 | +3. Connect your iPhone via USB and hit **Run** |
| 31 | +4. On your iPhone: **Settings > General > VPN & Device Management** → trust the developer certificate |
| 32 | + |
| 33 | +### 2. Fetch workouts from the phone |
| 34 | + |
| 35 | +Open the app on your iPhone, tap **Start**, then from your Mac: |
46 | 36 |
|
47 | | -Specify output directory: |
48 | 37 | ```bash |
49 | | -uv run convert_to_fit.py /path/to/export --output /path/to/fit/files |
| 38 | +python3 fetch_healthkit.py <iphone-ip> |
50 | 39 | ``` |
51 | 40 |
|
52 | | -### TCX format (zero dependencies) |
| 41 | +This pulls all Apple Watch workouts with full-resolution metrics and GPS routes into `healthkit_export/`. Keep the phone screen on while fetching — HealthKit data is inaccessible when the screen is locked. |
53 | 42 |
|
54 | | -TCX is an XML-based format that works everywhere but only supports heart rate, GPS, altitude, distance, and calories. No running dynamics. |
| 43 | +### 3. Convert to FIT |
55 | 44 |
|
56 | 45 | ```bash |
57 | | -python3 convert_to_tcx.py /path/to/apple_health_export |
| 46 | +uv run convert_healthkit_to_fit.py healthkit_export |
58 | 47 | ``` |
59 | 48 |
|
60 | | -## Output Structure |
61 | | - |
62 | | -```text |
63 | | -fit_files/ # or tcx_files/ |
64 | | -├── 2024/ |
65 | | -│ ├── 01/ |
66 | | -│ │ ├── 2024-01-02_183050_Running.fit |
67 | | -│ │ └── 2024-01-06_090528_Running.fit |
68 | | -│ └── 02/ |
69 | | -│ └── 2024-02-20_182954_Running.fit |
70 | | -└── 2025/ |
71 | | - └── ... |
72 | | -``` |
| 49 | +FIT files are written to `fit_files/`, organised by year and month. |
73 | 50 |
|
74 | | -TCX output also includes a `no_heart_rate/` subfolder for workouts without HR data. |
| 51 | +Filter by activity type: |
75 | 52 |
|
76 | | -## What Gets Converted |
| 53 | +```bash |
| 54 | +uv run convert_healthkit_to_fit.py healthkit_export --activity running |
| 55 | +``` |
77 | 56 |
|
78 | | -| Data | FIT | TCX | |
79 | | -|------|-----|-----| |
80 | | -| GPS coordinates | ✅ | ✅ | |
81 | | -| Heart rate (per-second) | ✅ | ✅ | |
82 | | -| Distance | ✅ | ✅ | |
83 | | -| Calories | ✅ | ✅ | |
84 | | -| Altitude | ✅ | ✅ | |
85 | | -| Running power | ✅ | ❌ | |
86 | | -| Stride length | ✅ | ❌ | |
87 | | -| Vertical oscillation | ✅ | ❌ | |
88 | | -| Ground contact time | ✅ | ❌ | |
89 | | -| Running speed | ✅ | ❌ | |
| 57 | +### 4. Import to Garmin Connect |
90 | 58 |
|
91 | | -## Heart Rate Data |
| 59 | +1. Go to [Garmin Connect](https://connect.garmin.com) |
| 60 | +2. Click **"+"** → Import Data |
| 61 | +3. Upload your FIT files (can select multiple) |
92 | 62 |
|
93 | | -### From the XML export |
| 63 | +## What gets exported |
94 | 64 |
|
95 | | -Apple Health exports contain `HKQuantityTypeIdentifierHeartRate` records in `export.xml`. The FIT converter emits each HR reading at its original timestamp as a separate record alongside the GPS stream — no interpolation, highest resolution available from the export. |
| 65 | +| Data | Included | |
| 66 | +| -------------------- | -------- | |
| 67 | +| GPS coordinates | Yes | |
| 68 | +| Heart rate | Yes | |
| 69 | +| Distance | Yes | |
| 70 | +| Calories | Yes | |
| 71 | +| Altitude | Yes | |
| 72 | +| Running power | Yes | |
| 73 | +| Stride length | Yes | |
| 74 | +| Vertical oscillation | Yes | |
| 75 | +| Ground contact time | Yes | |
| 76 | +| Running speed | Yes | |
96 | 77 |
|
97 | | -### Known limitation: low-resolution HR during workouts |
| 78 | +## Why not use Apple's XML export? |
98 | 79 |
|
99 | | -Apple's "Export All Health Data" feature aggregates workout heart rate into ~15 minute chunks. A 65-minute run might only have 23 HR records in the export, while apps like Strava (which sync via HealthKit's API directly) show a smooth per-second curve. This is a [known issue](https://discussions.apple.com/thread/253843222). |
| 80 | +Apple's "Export All Health Data" stores workout heart rate as aggregated records spanning ~15 minute windows. A 65-minute run might only have 23 HR data points in the export. The same workout viewed on Strava (which reads HealthKit directly) shows 513 data points. |
100 | 81 |
|
101 | | -The per-second data exists on the iPhone via `HKQuantitySeriesSampleQuery`, but Apple doesn't include it in the XML export. |
| 82 | +This is a [known limitation](https://discussions.apple.com/thread/253843222) of Apple's XML export. The full-resolution data exists on the phone via `HKQuantitySeriesSampleQuery` — this tool accesses it. |
102 | 83 |
|
103 | | -### HealthKit Exporter iOS app |
| 84 | +## API endpoints |
104 | 85 |
|
105 | | -The `HealthKitExporter/` directory contains a SwiftUI iOS app that reads full-resolution HR and running dynamics directly from HealthKit and serves them as JSON over a local HTTP server. This bypasses the XML export limitation. |
| 86 | +The iOS app serves JSON on port 8080: |
106 | 87 |
|
107 | | -To use it: |
| 88 | +| Endpoint | Description | |
| 89 | +| ------------------------------- | ------------------------------------- | |
| 90 | +| `GET /workouts` | List all workouts with metadata | |
| 91 | +| `GET /workouts/{index}/metrics` | All metrics + GPS route for a workout | |
108 | 92 |
|
109 | | -1. Open `HealthKitExporter.xcodeproj` in Xcode |
110 | | -2. Set your development team in Signing & Capabilities |
111 | | -3. Build and run on your iPhone |
112 | | -4. The app shows the device IP and port — hit the endpoints from your Mac: |
| 93 | +## Development |
113 | 94 |
|
114 | | -```bash |
115 | | -# List all workouts |
116 | | -curl http://<iphone-ip>:8080/workouts |
| 95 | +### Mock server |
117 | 96 |
|
118 | | -# Get per-second HR for a specific workout |
119 | | -curl http://<iphone-ip>:8080/workouts/0/heart_rate |
| 97 | +For local development and testing without a phone: |
120 | 98 |
|
121 | | -# Get all metrics (HR, power, speed, stride, VO, GCT) |
122 | | -curl http://<iphone-ip>:8080/workouts/0/metrics |
| 99 | +```bash |
| 100 | +python3 mock_server.py |
123 | 101 | ``` |
124 | 102 |
|
125 | | -Requires Xcode and a free Apple Developer account (app expires after 7 days, re-install from Xcode as needed). |
126 | | - |
127 | | -## Importing to Garmin Connect |
| 103 | +Serves sample workouts on `http://localhost:8080` with the same API as the iOS app. |
128 | 104 |
|
129 | | -1. Go to [Garmin Connect](https://connect.garmin.com) |
130 | | -2. Click the **"+"** button → Import Data |
131 | | -3. Upload your FIT or TCX files (can select multiple) |
132 | | -4. Wait for processing — workouts will appear in your timeline |
133 | | - |
134 | | -## Testing |
| 105 | +### Tests |
135 | 106 |
|
136 | 107 | ```bash |
137 | 108 | uv run pytest -v |
138 | 109 | ``` |
139 | 110 |
|
140 | | -Tests run automatically on GitHub Actions for Python 3.11-3.13. |
141 | | - |
142 | | -## Troubleshooting |
| 111 | +44 tests, 91% coverage. Tests use the mock server — no phone required. |
143 | 112 |
|
144 | | -**"Found 0 Apple Watch workouts"** |
145 | | -- Make sure you've exported from the Apple Health app (not Apple Watch app) |
146 | | -- Verify the export folder contains `export.xml` and `workout-routes/` |
| 113 | +### CI |
147 | 114 |
|
148 | | -**"No heart rate data"** |
149 | | -- Early Apple Watch workouts may not have recorded heart rate |
150 | | -- TCX converter saves these separately in `no_heart_rate/` |
| 115 | +Tests run on GitHub Actions for Python 3.11–3.14. Coverage is reported on PRs. iOS build can be triggered manually via workflow dispatch. |
151 | 116 |
|
152 | 117 | ## License |
153 | 118 |
|
|
0 commit comments