I'm trying to get the instance of each heart beat from an Apple Watch. Currently I am able to get the heart rate in beats per minute but can't get the times of each heart beat. It seems to send the heart rate data at random time intervals making it not possible to use the time of each sample to get heart beat times.
It doesn't matter what the units are as long as I can get it to update when it receives a new beat rather than every few beats as it is currently doing. Or if I can get it to give me the time of each beat rather than beats per time. Is there someway to force updateHandler to be called when a heart beat comes in? I thought that was the point of using the HKAnchoredObjectQuery.
func startRecordingHeartRate() {
let heartRateSample = HKSampleType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)
let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute())
let datePredicate = HKQuery.predicateForSamples(withStart: Date(), end: nil,
options: .strictStartDate)
let anchor: HKQueryAnchor? = nil
let updateHandler: (HKAnchoredObjectQuery, [HKSample]?, [HKDeletedObject]?, HKQueryAnchor?, Error?) -> () = {
query, newResult, deleted, newAnchor, error in
if let samples = newResult as? [HKQuantitySample] {
guard samples.count > 0 else {
return
}
for sample in samples {
let doubleSample = sample.quantity.doubleValue(for: heartRateUnit)
let timeSinceStart = sample.endDate.timeIntervalSince(self.startTime)
self.hkHeartRate[0].append(doubleSample)
self.hkHeartRate[1].append(Double(timeSinceStart))
}
self.updateHeartRateLabel()
self.processData.heartRateArray = self.hkHeartRate
self.processData.checkData()
}
}
let heartRateQuery = HKAnchoredObjectQuery(type: heartRateSample!,
predicate: datePredicate,
anchor: anchor,
limit: Int(HKObjectQueryNoLimit),
resultsHandler: updateHandler)
heartRateQuery.updateHandler = updateHandler
healthStore.execute(heartRateQuery)
}
func updateHeartRateLabel() {
let endIndex = self.hkHeartRate[0].endIndex
let lastHeartRate = self.hkHeartRate[0][endIndex-1]
self.heartRateLabel.setText(self.numberFormatter.string(from:NSNumber(value: lastHeartRate)) ?? "")
}
There is no way to observe individual heart beat events on Apple Watch. The averaged heart rate quantity samples are the only representation of heart rate available to you. If you want to increase the frequency of heart rate measurements, your app should start an HKWorkoutSession.
Related
I have a limit of 40 URL Session calls to my API per minute.
I have timed the number of calls in any 60s and when 40 calls have been reached I introduced sleep(x). Where x is 60 - seconds remaining before new minute start. This works fine and the calls don’t go over 40 in any given minute. However the limit is still exceeded as there might be more calls towards the end of the minute and more at the beginning of the next 60s count. Resulting in an API error.
I could add a:
usleep(x)
Where x would be 60/40 in milliseconds. However as some large data returns take much longer than simple queries that are instant. This would increase the overall download time significantly.
Is there a way to track the actual rate to see by how much to slow the function down?
Might not be the neatest approach, but it works perfectly. Simply storing the time of each call and comparing it to see if new calls can be made and if not, the delay required.
Using previously suggested approach of delay before each API call of 60/40 = 1.5s (Minute / CallsPerMinute), as each call takes a different time to produce response, total time taken to make 500 calls was 15min 22s. Using the below approach time taken: 11min 52s as no unnecessary delay has been introduced.
Call before each API Request:
API.calls.addCall()
Call in function before executing new API task:
let limit = API.calls.isOverLimit()
if limit.isOver {
sleep(limit.waitTime)
}
Background Support Code:
var globalApiCalls: [Date] = []
public class API {
let limitePerMinute = 40 // Set API limit per minute
let margin = 2 // Margin in case you issue more than one request at a time
static let calls = API()
func addCall() {
globalApiCalls.append(Date())
}
func isOverLimit() -> (isOver: Bool, waitTime: UInt32)
{
let callInLast60s = globalApiCalls.filter({ $0 > date60sAgo() })
if callInLast60s.count > limitePerMinute - margin {
if let firstCallInSequence = callInLast60s.sorted(by: { $0 > $1 }).dropLast(2).last {
let seconds = Date().timeIntervalSince1970 - firstCallInSequence.timeIntervalSince1970
if seconds < 60 { return (true, UInt32(60 + margin) - UInt32(seconds.rounded(.up))) }
}
}
return (false, 0)
}
private func date60sAgo() -> Date
{
var dayComponent = DateComponents(); dayComponent.second = -60
return Calendar.current.date(byAdding: dayComponent, to: Date())!
}
}
Instead of using sleep have a counter. You can do this with a Semaphore (it is a counter for threads, on x amount of threads allowed at a time).
So if you only allow 40 threads at a time you will never have more. New threads will be blocked. This is much more efficient than calling sleep because it will interactively account for long calls and short calls.
The trick here is that you would call a function like this every sixty second. That would make a new semaphore every minute that would only allow 40 calls. Each semaphore would not affect one another but only it's own threads.
func uploadImages() {
let uploadQueue = DispatchQueue.global(qos: .userInitiated)
let uploadGroup = DispatchGroup()
let uploadSemaphore = DispatchSemaphore(value: 40)
uploadQueue.async(group: uploadGroup) { [weak self] in
guard let self = self else { return }
for (_, image) in images.enumerated() {
uploadGroup.enter()
uploadSemaphore.wait()
self.callAPIUploadImage(image: image) { (success, error) in
uploadGroup.leave()
uploadSemaphore.signal()
}
}
}
uploadGroup.notify(queue: .main) {
// completion
}
}
How to get data like in image below in Swift, im calculating steps per day and steps per hour but not sure how to get like below. I would like to get by device(watch, phone) steps per movement group not by a time interval?
I used HKSampleQuery
func getStepCountForTodaySegmentedByMovement( complete: #escaping ([HKCumulativeQuantitySample]) -> () ) {
let healthStore = HKHealthStore()
guard let stepsQuantityType = HKQuantityType.quantityType(forIdentifier: .stepCount) else {
fatalError("*** Unable to create a step count type ***")
}
let predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: .distantFuture, options: .strictEndDate)
let query = HKSampleQuery(sampleType: stepsQuantityType,
predicate: predicate,
limit: HKObjectQueryNoLimit,
sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: true)]) {
(query, results, error) in
complete(results as? [HKCumulativeQuantitySample] ?? [])
}
healthStore.execute(query)
}
Sample query works if you want individual samples like what the Health app displays. Related point, in case it's useful, you can also get the stats separated by source if you pass HKStatisticsOptions separateBySource
I'm using following code to get burned energy for today:
func getTodaysSummary(for type:HKQuantityType!, unit u:HKUnit!, completion: #escaping (Double) -> Void) {
let stepsQuantityType = type
let now = Date()
let startOfDay = Calendar.current.startOfDay(for: now)
let predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: now, options: .strictStartDate)
let query = HKStatisticsQuery(quantityType: stepsQuantityType!, quantitySamplePredicate: predicate, options: .cumulativeSum) { (_, result, error) in
var resultCount = 0.0
guard let result = result else {
completion(resultCount)
return
}
if let sum = result.sumQuantity() {
resultCount = sum.doubleValue(for: u)
}
DispatchQueue.main.async {
completion(resultCount)
}
}
healthStore.execute(query)
}
...
self.getTodaysSummary(for: HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned), unit: HKUnit.kilocalorie(), completion: { (energyBurned) in
print("\(energyBurned)")
})
Now I need to understand, what data does it return? In different sources like apple documentation I read this data should contain all activity including walking, swimming etc.
But in my case it returns only the number 680 (I added this value manually via + button on Activity Energy screen of Health app)
It definetly doesn't include steps I've made during the day.
So the question, do I need to calculate those calories separately?
Also there are iWatch rings that also seem like not returning by HKStatisticsQuery requests. Should I calculate them separately as well to calculate total burned energy?
The iPhone does not automatically compute Active Energy. The user must have an Apple Watch in order to get Active and Basal Energy samples automatically written to HealthKit.
I'm able to read average heart rate data from HealthKit for a period of time (duration of workout) with the below code.
However, when I look in the Apple Health app, it shows that the heart rate samples from the Apple Watch are taken very infrequently (every 15-30 min). This makes my average HR read for the period quite inaccurate.
How can I trigger HealthKit to sample (gather) Heart Rate data from the watch and write it to HealthKit?
let endDate = (calendar as NSCalendar).date(byAdding: NSCalendar.Unit.minute, value: duration, to: startDate, options: NSCalendar.Options())
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: HKQueryOptions())
let squery = HKStatisticsQuery(quantityType: heartRateType, quantitySamplePredicate: predicate, options: HKStatisticsOptions.discreteAverage, completionHandler: { (qurt, result, errval) -> Void in
guard errval == nil else { print("error"); completion(0.0, errval); return }
var averageHeartRate : Double = 0.0
if (result != nil && result!.averageQuantity() != nil) {
let quantity : HKQuantity = result!.averageQuantity()!
averageHeartRate = quantity.doubleValue(for: HKUnit.count().unitDivided(by: HKUnit.minute()))
}
})
To get more frequent heart rate data from Apple Watch, the user must be running an app on their watch that is tracking an HKWorkoutSession. If your app has a paired watchOS app that tracks workouts, you can start a workout from the iPhone by using startWatchApp(with workoutConfiguration:completion:).
I have a little problem grasping HealthKit. I want to get heart rate from HealthKit with specific time.
I have done this in the past (until I noticed that I couldn't fetch data when the phone was locked)
func retrieveMostRecentHeartRateSample(completionHandler: (sample: HKQuantitySample) -> Void) {
let sampleType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)
let predicate = HKQuery.predicateForSamplesWithStartDate(NSDate.distantPast() as! NSDate, endDate: NSDate(), options: HKQueryOptions.None)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)
let query = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: 1, sortDescriptors: [sortDescriptor])
{ (query, results, error) in
if error != nil {
println("An error has occured with the following description: \(error.localizedDescription)")
} else {
let mostRecentSample = results[0] as! HKQuantitySample
completionHandler(sample: mostRecentSample)
}
}
healthKitStore.executeQuery(query)
}
var observeQuery: HKObserverQuery!
func startObservingForHeartRateSamples() {
println("startObservingForHeartRateSamples")
let sampleType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)
if observeQuery != nil {
healthKitStore.stopQuery(observeQuery)
}
observeQuery = HKObserverQuery(sampleType: sampleType, predicate: nil) {
(query, completionHandler, error) in
if error != nil {
println("An error has occured with the following description: \(error.localizedDescription)")
} else {
self.retrieveMostRecentHeartRateSample {
(sample) in
dispatch_async(dispatch_get_main_queue()) {
let result = sample
let quantity = result.quantity
let count = quantity.doubleValueForUnit(HKUnit(fromString: "count/min"))
println("sample: \(count)")
heartChartDelegate?.updateChartWith(count)
}
}
}
}
healthKitStore.executeQuery(observeQuery)
}
This code will fetch the latest sample every time it is a change in HealthKit. But as I said earlier, it won't update when the phone is locked. I tried using:
self.healthKitStore.enableBackgroundDeliveryForType(HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate), frequency: HKUpdateFrequency.Immediate) { (success, error) in
if success{
println("success")
} else {
println("fail")
}
}
But this didn't work and as I found out there was a bug that Apple said it wasn't working as they wanted. Guess it is some security-thing.
But then I thought, maybe I can request samples between a startTime and endTime. For example i have EndTime(2015-05-31 10:34:45 +0000) and StartTime(2015-05-31 10:34:35 +0000).
So my question is how can I get heart rate samples between these two times.
I guess I must do it in the
HKQuery.predicateForSamplesWithStartDate(myStartTime, endDate: myEndTime, options: HKQueryOptions.None)
But when I tried it didn't find anything. Maybe I got this all wrong...
I am using a heart rate monitor on my chest and I know that I get some values in healthKit within the start and end time.
Edit:
Ok I tried it and it is working some times, not always. Someone has an idea?
func fetchHeartRates(endTime: NSDate, startTime: NSDate){
let sampleType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)
let predicate = HKQuery.predicateForSamplesWithStartDate(startTime, endDate: endTime, options: HKQueryOptions.None)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)
let query = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: 100, sortDescriptors: [sortDescriptor])
{ (query, results, error) in
if error != nil {
println("An error has occured with the following description: \(error.localizedDescription)")
} else {
for r in results{
let result = r as! HKQuantitySample
let quantity = result.quantity
let count = quantity.doubleValueForUnit(HKUnit(fromString: "count/min"))
println("sample: \(count) : \(result)")
}
}
}
healthKitStore.executeQuery(query)
}
Edit 2:
It was working but I couldn't call it the way I did. So I fetched it a couple of seconds later and it worked fine :)
...But as I said earlier, it won't update when the phone is locked...Guess it is some security-thing.
You are correct.
From HealthKit Framework Reference:
Because the HealthKit store is encrypted, your app cannot read data from the store when the phone is locked. This means your app may not be able to access the store when it is launched in the background. However, apps can still write data to the store, even when the phone is locked. The store temporarily caches the data and saves it to the encrypted store as soon as the phone is unlocked.
If you want your app to be alerted when there are new results, you should review Managing Background Delivery:
enableBackgroundDeliveryForType:frequency:withCompletion:
Call this method to register your app for background updates. HealthKit wakes your app whenever new samples of the specified type are saved to the store. Your app is called at most once per time period defined by the specified frequency.