Skip to main content
synheart-wear ships a stable adapter layer per vendor. This page is the reference for what each adapter does, what it produces, and how it interacts with cloud streaming.

Adapter matrix

VendorLocal sourceCloud / push sourceRAMEN delivery hintStatus
Apple WatchHealthKit (apple_healthkit)n/aReady
Health Connect (Android)Health Connect APIn/aReady
BLE HRM (any)GATT 0x180D Heart Rate Profilen/aReady
WHOOPBroadcast HR via BLEOAuth + webhooksstream (full payload inline)Ready
Garmin (Cloud)OAuth + Connect API webhooksping (REST pull required)Ready
Garmin Health SDK (RTS)Native real-time streamingn/aOn-demand, license-gated
FitbitOAuth via Synheart Wear APIpingReady
OuraHealthKit / Health Connect proxy (Flutter)OAuth via Synheart Wear API (Kotlin / Swift)pingReady
Samsung Watch(planned)n/aPlanned
The Dart DeviceAdapter enum lists platformHealth, fitbit, garmin, whoop, samsungHealth. The Swift and Kotlin enums additionally include bleHrm / BLE_HRM. Across all three platforms, Oura has no enum entry and is reached via OuraProvider; BLE HRM on Dart is reached via BleHrmBridge.

Apple HealthKit

Capabilities:
  • HR (instantaneous + samples).
  • HRV (RMSSD, SDNN; SDNN derived on-device when only RR is exposed).
  • Steps, calories, distance.
  • RR intervals via the HealthKit RR channel (workout-mode only on most Apple Watch models).
  • Health profile snapshot (DOB, sex, height, weight) for personalization context.
  • Workout events.
Permissions: standard HealthKit HKObjectType set, requested once via requestPermissions. source field on WearMetrics: "apple_healthkit".

Health Connect (Android)

Capabilities:
  • HR, HRV (when the recording app provides it; otherwise SDNN/RMSSD are unavailable).
  • Steps, calories, distance.
  • Sleep records (sleep stages from compatible apps).
Health Connect retention is capped per record type by the platform (typically ~30 days for HR, longer for sleep). Hosts that need older data must use the Apple XML backfill path (Apple users) or an OAuth cloud adapter. source field: "health_connect".

BLE HRM

Direct connection over BLE GATT Heart Rate Profile (0x180D). No vendor cloud, no OAuth. Compatible devices:
  • WHOOP (Broadcast HR mode).
  • Polar chest straps (H10, OH1).
  • Wahoo TICKR family.
  • Garmin HRM-Pro / HRM-Dual.
  • Any HRM advertising 0x180D Heart Rate Service.
Capabilities:
  • HR (every second per Bluetooth profile).
  • RR intervals when the device advertises the optional RR field.
The bridge runs its own permission/scan/connect/disconnect lifecycle independent of requestPermissions. See BLE Heart Rate Monitor for the detailed flow. source field: "ble_hrm".

WHOOP

Two modes:
ModePath
OAuth + webhooksCloud-side handles OAuth + webhook ingest, publishes wear.events, RAMEN delivers to client. DeliveryHint.stream (full payload inline).
BLE Broadcast HRLocal-only via BleHrmBridge. No cloud round-trip; no recovery/strain/sleep.
WhoopProvider exposes:
  • fetchRawDataForFlux(start, end) — pulls raw vendor data for HSI 1.3 transformation.
  • Real-time HR via webhooks (mode A) or BLE (mode B).
  • Workout events.
source field: "whoop" for the OAuth path. The cloud-vs-BLE distinction lives in meta.source_type ("whoop_cloud"); WHOOP via BLE Broadcast HR uses the shared BLE HRM adapter (source = "ble_hrm").

Garmin (Cloud)

Path: OAuth → Garmin Connect API webhooks → cloud → RAMEN. Delivery hint: ping — vendor only sends a notification; client must pull the full record via REST when needed. GarminProvider exposes:
  • fetchRawDataForFlux(start, end) — REST pull for Flux input.
  • Wellness, sleep, activity data classes (GarminWellnessData, GarminSleepData, GarminActivityData).
  • Connection state (GarminConnectionState) for UI.
  • Realtime data shape (GarminRealtimeData) when a session is active.
source field: "garmin" for the OAuth Connect API path; meta.source_type carries the more specific "garmin_cloud".

Garmin Health SDK (Real-Time Streaming)

Native RTS path — direct device pairing without going through Garmin Connect cloud. Requires a separate Garmin license and is bundled on demand for licensed integrations only. The underlying Garmin Health SDK code is proprietary; this SDK exposes a GarminHealth provider façade that wraps it. source field: "garmin_sdk" (the adapter id from GarminSdkAdapter).

Fitbit

OAuth flow mediated by the Synheart Wear API — the device never sees Fitbit access tokens. Cloud delivers a vendor_user_id to the app via deep-link callback (?vendor=fitbit&user_id=...&status=success); pass that as the code argument to connectWithCode(...). FitbitProvider exposes:
  • fetchHrv, fetchSleep, fetchActivity — date-range record queries.
  • fetchUserProfile — Fitbit profile snapshot.
source field: "fitbit".

Oura

Two paths depending on platform:
  • Flutter: data flows through HealthKit (iOS) and Health Connect (Android) when an Oura companion app syncs there.
  • Kotlin / Swift: dedicated OuraProvider with mediated OAuth via the Synheart Wear API (same vendor_user_id-via-deep-link contract as Fitbit).
OuraProvider exposes fetchReadiness, fetchSleep, fetchHrv, fetchActivity. source field: "oura".

Streaming model

Today, cloud providers (Whoop, Garmin, Fitbit, Oura) are polled: streamHR / streamHRV run a timer (Combine Timer.publish on Swift, kotlinx.coroutines.flow.flow { delay(...) } on Kotlin) that calls readMetrics(isRealTime: true) on each tick. There is no push channel for these vendors yet. BleHrmProvider is push-based — the BLE characteristic notification feeds a SharedFlow / AsyncStream and streamHR picks up the latest sample. Future work: a RamenEventDispatcher on Kotlin / Swift (Flutter already has one) will materialize cloud-side ping events into REST pull-backs, replacing the timer-based polling for cloud providers.

Apple Health XML backfill

When a user exports export.zip from the iOS Health app:
User exports export.zip from iOS Health app


parse via AppleHealthXmlParser


batch into AppleHealthSample records


synheart_core_backfill_open(db_path, import_id)

        ▼ insert_batch (batched)
synheart_core_backfill_insert_batch(import_id, samples_json)


synheart_core_backfill_finalize(import_id)
Idempotency keys ensure repeated imports don’t duplicate samples in the runtime’s SQLite store. The high-level path through synheart-core aggregates samples per day and replays them via Synheart.srmPushWearableDaily(...) so the imported data populates the user’s runtime baselines.

RAMEN 🍜 integration

Each cloud adapter participates in the RAMEN stream when Synheart’s cloud ingestion publishes events:
vendor webhook

Synheart ingestion persists and publishes wear.events

RAMEN gRPC stream → synheart-stream-runtime client

Synheart runtime broadcasts RamenEvent

synheart-core wires WearableEventProcessor onto WearModule

WearModule routes by vendor → adapter handler

WearMetrics emitted on streamHR / streamHRV / readMetrics
Each adapter handler decides how to consume RamenEvent.payload_json:
  • DeliveryHint.stream: parse inline, emit immediately.
  • DeliveryHint.ping: schedule a REST pull (via the adapter’s provider) to fetch the body.
  • DeliveryHint.unknown: treat as stream (best-effort) and log.

Permission flow

SynheartWear.requestPermissions(permissions, reason):
  1. Maps Synheart PermissionType values to OS permission scopes.
  2. Routes to the appropriate adapter (HealthKit on iOS, Health Connect on Android, OAuth flow for cloud adapters).
  3. Returns Map<PermissionType, ConsentStatus> per type.
Adapters that don’t have OS-level permissions (BLE HRM relies on Bluetooth Manager prompts; cloud OAuth requires app navigation) handle their own UX outside this method.