Skip to content

feat(app): save Apple Watch heart rate per race like flight battery voltage#86

Draft
Saqoosha wants to merge 1 commit into
developfrom
claude/heartbeat-apple-watch-flying-6i6htn
Draft

feat(app): save Apple Watch heart rate per race like flight battery voltage#86
Saqoosha wants to merge 1 commit into
developfrom
claude/heartbeat-apple-watch-flying-6i6htn

Conversation

@Saqoosha

Copy link
Copy Markdown
Owner

What

Saves the pilot's Apple Watch heart rate into each race record, exactly like flight-battery voltage — no firmware/OSD involvement.

New watchOS companion (app/HDZapWatch/)

  • WatchWorkoutManager runs an HKWorkoutSession + HKLiveWorkoutBuilder — the only supported way to get continuous ~1 Hz HR on watchOS (outside a workout the watch samples every few minutes) — and streams each bpm to the phone via WCSession.sendMessage(["hr": …, "ts": …]). The workout also keeps the watch app alive wrist-down for the whole flight, and the flight is saved as a real workout in Activity on finish.
  • Race START/STOP on the phone is relayed as ["race": "start"|"stop"], so once the watch app is open the workout follows the race automatically. WatchContentView shows live bpm plus a manual Start/Stop for warm-up (HR sensor lock-on takes a few seconds) and recovery.

iOS

  • WatchHeartRateManager mirrors BluetoothManager's flight-battery telemetry surface: lastHeartRateBpm / lastHeartRateReceivedAt (lockstep pair) / heartRateNotifyRevision, all mutated on the main actor.
  • TimerView ingests via the same conventions as ingestFlightBatteryTelemetry: race-start baseline capture, tRace anchored to arrival time and clamped to ≥ 0, dedupe (on arrival time — steady bpm is real series data), cleared at both START and RESET sites, sorted chronologically on save.
  • RaceRecord gains heartRateSamples with decodeIfPresent back-compat (old JSON loads fine; old app versions ignore the new key) and validator coverage (finite/clamped tRace, bpm 1–300, chronological).
  • RaceDetailView gains a heart-rate CSV share button (t_race_s,received_at,bpm) next to the battery one; RaceRecord+HeartRate mirrors RaceRecord+FlightBattery.

Project config

  • project.yml: new HDZapWatch target (watchOS 11, bundle id sh.saqoo.HDZap.watchkitapp, HealthKit entitlement, workout-processing background mode, HealthKit usage strings), embedded via dependency from the iOS target.
  • CLAUDE.md documents the pipeline.

Not included (deliberately)

  • No HR display on the goggle OSD, no firmware change, no BLE characteristic — per scope, save-only.
  • No chart in the share card / detail view yet; data is persisted and CSV-exportable, so a chart can be added later without schema changes.

Testing needed (requires Mac + devices — couldn't be done in this environment)

  1. cd app && xcodegen generaterequired, the checked-in .xcodeproj predates the new target/files.
  2. Build both targets in Xcode; run on iPhone + paired watch (HealthKit needs real hardware).
  3. Flow: open watch app → grant Health access → START a race on the phone → watch workout auto-starts → finish race → check the saved race's heart-rate CSV from race detail.

Known constraint: sendMessage requires the counterpart app to be reachable, so the auto start relay only lands when the watch app is frontmost (or already in a workout). The watch must be paired to the phone running HDZap — i.e. this targets the solo pilot-operator case.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UWHtgSAWz9zLerpAj5C9Zh


Generated by Claude Code

…oltage

Add an HDZapWatch watchOS companion target that runs an HKWorkoutSession +
HKLiveWorkoutBuilder (the only way to get continuous ~1 Hz heart rate on
watchOS) and streams bpm to the phone over WatchConnectivity. Race
START/STOP on the phone is relayed to the watch so the workout follows the
race automatically once the watch app is open; the on-watch button covers
warm-up and manual recovery.

On iOS, WatchHeartRateManager mirrors BluetoothManager's flight-battery
telemetry surface (lastHeartRateBpm / lastHeartRateReceivedAt /
heartRateNotifyRevision), and TimerView ingests samples into
RaceHeartRateSample with the same tRace anchoring/clamping, dedupe,
reset-site clearing, and chronological-sort-on-save conventions as
RaceFlightBatterySample. RaceRecord persists heartRateSamples with
decodeIfPresent back-compat and validator coverage, and RaceDetailView
gains a heart-rate CSV share button next to the battery one.

Requires 'cd app && xcodegen generate' to pick up the new target.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWHtgSAWz9zLerpAj5C9Zh
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ebaad36c-92ac-4b11-98fb-46aa47699090

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/heartbeat-apple-watch-flying-6i6htn

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants