- Rust 98.8%
- Nix 1.2%
| examples | ||
| src | ||
| tests | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| flake.lock | ||
| flake.nix | ||
| README.md | ||
| rustfmt.toml | ||
ah2foss: Apple Health → Gadgetbridge & friends
Converts an Apple Health export into files that Gadgetbridge and other free and open-source Android apps can import. Each kind of data goes to the app that can actually show it:
| Apple Health data | Goes to | Why this app |
|---|---|---|
| Steps, heart rate, sleep stages (core/deep/REM/awake), SpO₂ | Gadgetbridge, virtual device "Apple Health history" | Your watch app. Old history and the current watch side by side |
| HRV, sleeping wrist temperature | Gadgetbridge, "Apple Health HRV & temperature" | Gadgetbridge has HRV and temperature charts |
| Weight | Gadgetbridge, "Apple Health weight" | Weight chart |
| Blood pressure | Gadgetbridge, "Apple Health blood pressure" | BP chart |
| GPS workouts (route + heart rate + activity type) | OpenTracks (or FitoTrack) | Gadgetbridge only shows workouts its own devices recorded; it can't import a workout history |
| Body fat %, lean body mass, waist | openScale | Gadgetbridge stores weight only, with no body composition |
| Menstrual flow, spotting, basal body temperature, sexual activity, cycle symptoms | drip | Gadgetbridge has no cycle tracking |
Everything (one CSV per record type) + workouts.csv |
csv/ |
Nutrition, water, medications, mindfulness, VO₂ max, resting HR, walking metrics, etc. have no FOSS app with an import path, so they're kept as CSV and nothing is lost |
All of these apps are on F-Droid.
Usage
ah2foss is a single native binary (about 26 MB, Linux/Windows/macOS). Build it with Rust:
cargo build --release
or run it with Nix:
nix run git+https://git.zoltanbalazs.com/zoltanbalazs/apple-health-to-foss # desktop app
nix run git+https://git.zoltanbalazs.com/zoltanbalazs/apple-health-to-foss -- convert export.zip -o out
nix develop # in a checkout: Rust toolchain, clippy, rust-analyzer and the GUI's runtime libraries
- iPhone: Health → profile picture → Export All Health Data → copy
export.zipto your computer. - Android: Gadgetbridge → side menu → Data management → Export zip → copy the zip to your computer.
- Convert, either in the desktop app or on the command line (both described below).
- Follow
HOW_TO_IMPORT.txtin the output folder. In short: Gadgetbridge → Import zip; OpenTracks → Settings → Import and Export → Import all; openScale → Settings → Data Management → Import measurements (CSV); drip → Settings → Data → Import data.
Don't sync your watch between steps 2 and 4. Import zip replaces Gadgetbridge's database with the one in the zip, which is your backup plus the Apple data, so anything synced in between would be lost.
Desktop app
./target/release/ah2foss
The export is read from disk; nothing is uploaded. After Scan export you get:
- Selection row: date range (all / last 12 months / last 90 days / custom) and one checkbox per data type, grouped by destination app. Each shows how many days in the range have data.
- Data coverage: one row per data type across the whole history, darker = more (quintiles within each row). Drag to select a date range; click to open a day.
- Day view: steps per hour, heart rate (workouts shaded), the previous night's sleep stages (and which source was used), SpO₂ / HRV / wrist temperature / weight / blood pressure / body composition tiles, workouts with a route map, and cycle entries. Anything outside your selection is dimmed and marked "not included". Charts have hover readouts.
- Convert: a per-app summary of what will be written, the Gadgetbridge backup and output folder, and the conversion log.
Each night's sleep is filed under the morning it ends, in both the day view and the conversion, so a date range never cuts a night in half. Paths are remembered between runs.
Command line
./target/release/ah2foss convert export.zip --gadgetbridge Gadgetbridge_backup.zip -o out
Options: --since 2019-01-01 / --until 2025-06-01 to limit the date range, --no-csv to skip
the CSV dump. On a 500 MB / 2-million-record export it takes about 42 s and 125 MB of memory.
How the Gadgetbridge part works
Gadgetbridge has no generic import, but it restores full backups. ah2foss opens the database inside your own backup, adds read-only "virtual" devices, and fills the tables those device types draw their charts from:
| Virtual device | Gadgetbridge device type | Tables |
|---|---|---|
| Apple Health history | GLORYFIT_SWISS_PEAK |
GLORY_FIT_STEPS_SAMPLE, GENERIC_HEART_RATE_SAMPLE, GENERIC_SLEEP_STAGE_SAMPLE, GENERIC_SPO2_SAMPLE |
| Apple Health HRV & temperature | ULTRAHUMAN_RING_AIR |
GENERIC_HRV_VALUE_SAMPLE, GENERIC_TEMPERATURE_SAMPLE |
| Apple Health weight | GENERIC_WEIGHT_SCALE |
GENERIC_WEIGHT_SAMPLE |
| Apple Health blood pressure | BEURER_BM_69 |
GENERIC_BLOOD_PRESSURE_SAMPLE |
These were chosen because their chart code reads the shared "generic" sample tables directly. The imported history never goes into a real watch's tables (e.g. your Garmin's), so later syncs can't overwrite it. Don't press connect on the virtual devices. Deleting one removes its data.
Because it starts from your own backup, the schema always matches your installed Gadgetbridge. Running it again on a zip it already produced replaces the earlier import instead of duplicating it.
Data handling details
- Steps: iPhone and Watch often both count the same walk. Per minute, only the source with the most steps is kept (close to what the Health app does). Rounding is cumulative, so daily totals stay exact.
- Sleep: per night, the source with real stages (Apple Watch) wins over plain "asleep" blocks from the phone or third-party apps. "In bed" is dropped.
- HRV: Apple measures SDNN. Gadgetbridge's HRV chart is normally fed RMSSD by other devices, so the absolute values aren't directly comparable with a different watch.
- Units: lb/st, °F, inches and miles are converted to kg, °C, cm and km.
- drip: cervical-mucus categories don't map onto drip's sensiplan scales, so they become notes. Future-dated entries are skipped, since drip rejects them.
Tests
cargo test
tests/gadgetbridge_schema_v147.sql is the exact DDL produced by running Gadgetbridge's own
GBDaoGenerator (schema version 147), so the tests write into the real table layout. The fake
Apple export lives in tests/fixtures/.
cargo run --example screenshot -- EXPORT DAY OUT.png [dark] renders the desktop app to a PNG
without opening a window, for visual checks.