No description
  • Rust 98.8%
  • Nix 1.2%
Find a file
2026-10-02 17:00:08 +02:00
examples feat(gui): add egui desktop app for browsing and selective conversion 2026-10-02 17:00:07 +02:00
src feat(gui): add egui desktop app for browsing and selective conversion 2026-10-02 17:00:07 +02:00
tests feat(gui): add egui desktop app for browsing and selective conversion 2026-10-02 17:00:07 +02:00
.gitignore feat: convert Apple Health exports for Gadgetbridge and FOSS apps 2026-10-02 17:00:06 +02:00
Cargo.lock feat: convert Apple Health exports for Gadgetbridge and FOSS apps 2026-10-02 17:00:06 +02:00
Cargo.toml feat: convert Apple Health exports for Gadgetbridge and FOSS apps 2026-10-02 17:00:06 +02:00
flake.lock build: add Nix flake 2026-10-02 17:00:07 +02:00
flake.nix build: add Nix flake 2026-10-02 17:00:07 +02:00
README.md docs: add README with app overview and import instructions 2026-10-02 17:00:08 +02:00
rustfmt.toml feat: convert Apple Health exports for Gadgetbridge and FOSS apps 2026-10-02 17:00:06 +02:00

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
  1. iPhone: Health → profile picture → Export All Health Data → copy export.zip to your computer.
  2. Android: Gadgetbridge → side menu → Data management → Export zip → copy the zip to your computer.
  3. Convert, either in the desktop app or on the command line (both described below).
  4. Follow HOW_TO_IMPORT.txt in 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.