figelwump
figelwump

Reputation: 827

Real time heart beat data from Apple Watch using HKHeartbeatSeriesSample

I'm trying to get access to heartbeat data recorded from the Apple Watch, in real-time. With HKWorkoutSession I can get callbacks to workoutBuilder didCollectDataOf every 5 seconds with heartrate (beats/min) data.

This isn't real-time enough for my application, so I'm looking into using the new (as of iOS 13 and watchOS 6.0) HKHeartbeatSeriesSample (and related classes) for this purpose. My understanding is that series samples for heartbeat data will record beat-by-beat measurements in series form.

I'm able to get the APIs working and get some data, but not real-time -- the series data I get is from some previous recordings (unclear what triggers these recordings).

Here's authorization:

            let allTypes: Set<HKSampleType> = Set([
                HKObjectType.workoutType(),
                HKSeriesType.heartbeat(),
                HKObjectType.quantityType(forIdentifier: .heartRate)!,
                HKQuantityType.quantityType(forIdentifier: .heartRateVariabilitySDNN)!,
            ])

            healthStore.requestAuthorization(toShare: allTypes, read: allTypes) { (success, error) in
                ///...
                let workoutSession = WorkoutSession(healthStore: self.healthStore)
                workoutSession.startHeartbeatSampleQuery()
            }

startHeartBeatSampleQuery:

            let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate,
                                                  ascending: false)

            // Query for the heartbeat samples from the specified heartbeat series.
            let heartbeatSeriesSampleQuery = HKSampleQuery(sampleType: HKSeriesType.heartbeat(),
                                                           predicate: nil,
                                                           limit: HKObjectQueryNoLimit,
                                                           sortDescriptors: [sortDescriptor]) {
                (query, results, error) in

                guard let samples = results, let sample = samples.first as? HKHeartbeatSeriesSample else {
                    print("NO SAMPLES MY FRIEND")
                    return
                }

                let heartbeatSeriesQuery = HKHeartbeatSeriesQuery(heartbeatSeries: sample) {
                    (query, timeSinceSeriesStart, precededByGap, done, error) in

                    guard error == nil else {
                        print("error in HKHeartbeatSeriesQuery: \(String(describing: error))")
                        return
                    }

                    print("timeSinceSeriesStart = \(timeSinceSeriesStart), precededByGap = \(precededByGap)")

                }

                self.healthStore.execute(heartbeatSeriesQuery)
            }

            completionHandler()

            self.healthStore.execute(heartbeatSeriesSampleQuery)
        }

The series samples I get from this query:

samples: [count=41 5D4F0C84-294D-41A7-8F64-B387A8E767A3 "Vishal’s Apple Watch" (6.1.1), "Watch3,2" (6.1.1)"Apple Watch"  (2020-01-18 12:12:06 -0800 - 2020-01-18 12:13:12 -0800), count=27 32B0B090-CDF8-48F5-BCF6-670982C426F3 "Vishal’s Apple Watch" (6.1.1), "Watch3,2" (6.1.1)"Apple Watch"  (2020-01-15 21:38:41 -0800 - 2020-01-15 21:39:46 -0800), count=67 B485328A-3EA1-49F2-8666-75B3B2143A05 "Vishal’s Apple Watch" (6.1.1), "Watch3,2" (6.1.1)"Apple Watch"  (2020-01-14 21:00:15 -0800 - 2020-01-14 21:01:17 -0800), count=48 9D550B3A-F325-4CA0-B5E7-43BA42E835ED "Vishal’s Apple Watch" (6.1.1), "Watch3,2" (6.1.1)"Apple Watch"  (2020-01-14 17:06:27 -0800 - 2020-01-14 17:07:33 -0800), count=50 278E1BF7-726B-443B-97C3-8BBA3DF06207 "Vishal’s Apple Watch" (6.1.1), "Watch3,2" (6.1.1)"Apple Watch"  (2020-01-13 12:04:28 -0800 - 2020-01-13 12:05:34 -0800), count=33 1215B2C4-F168-4EAD-9C35-5F734CC29637 "Vishal’s Apple Watch" (6.1.1), "Watch3,2" (6.1.1)"Apple Watch"  (2020-01-13 11:55:01 -0800 - 2020-01-13 11:56:06 -0800), count=48 EB2C64F9-9E81-4F5B-BA4A-62FA6816FE29 "Vishal’s Apple Watch" (6.1.1), "Watch3,2" (6.1.1)"Apple Watch"  (2020-01-11 12:20:36 -0800 - 2020-01-11 12:21:37 -0800), count=46 515D467A-51E9-490B-8082-5C8F541A0BD7 "Vishal’s Apple Watch" (6.1.1), "Watch3,2" (6.1.1)"Apple Watch"  (2020-01-11 11:01:38 -0800 - 2020-01-11 11:02:44 -0800)], sample: count=41 5D4F0C84-294D-41A7-8F64-B387A8E767A3 "Vishal’s Apple Watch" (6.1.1), "Watch3,2" (6.1.1)"Apple Watch"  (2020-01-18 12:12:06 -0800 - 2020-01-18 12:13:12 -0800)

The most recent is 1/18, and doesn't correspond to my active workout session -- and regardless of whether I start an HKWorkoutSession, or manually do a workout on my watch, that doesn't seem to change.

But the data in the series is exactly what I want; beat-by-beat data like:

timeSinceSeriesStart = 1.1875, precededByGap = false
timeSinceSeriesStart = 2.046875, precededByGap = false
timeSinceSeriesStart = 2.92578125, precededByGap = false
timeSinceSeriesStart = 3.8125, precededByGap = false
...

Am I using the API incorrectly? Or is this not a viable way to get real-time heartbeat data?

Upvotes: 4

Views: 4645

Answers (1)

Allan
Allan

Reputation: 7343

There isn't a way to record heart rate more precisely than you have already observed using the workout session API. Samples of the HKSeriesType.heartbeat() type are recorded when Apple Watch computes heart rate variability (see the sample type heartRateVariabilitySDNN). HRV is computed periodically in the background by Apple Watch. There is no API for initiating an HRV reading.

Upvotes: 5

Related Questions