Post

Replies

Boosts

Views

Activity

Core Bluetooth: Longer connection times connecting to a 5.0 peripheral vs a 4.2 peripheral
We are developing a BLE device that connects and disconnects frequently. We have noticed that our older 4.2 prototypes, that we can complete a connection in about 300ms. After moving to Bluetooth 5, we noticed that our connection time is up to about 800ms. After sniffing the Bluetooth connection, the appears to be caused by additional configuration messages that occur as part of the BLE 5 connection process. Is there any way we can configure Core Bluetooth to treat our peripheral more like a BLE 4.2 device and connect equally as fast? The longer connection times on the BLE 5.0 device are causing problems for us.
0
0
454
Apr ’21
Delay on Lap events from HKLiveWorkoutBuilder workoutBuilderDidCollectEvent
I'm having an issue with a Simple HealthKit swimming Demo on an Apple Watch I'm working on. I'm integrating with the HKLiveWorkoutBuilder and trying to simply count the number of laps a user is swimming on an indoor pool.I get up to date data from workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set<HKSampleType>). However, the workoutBuilderDidCollectEvent for the lap datatype is coming in delayed by 20+ seconds, resulting in really inaccurate data. Has anyone else run into something like this with HealthKit before?For some more detail. I receive below lap event at2020-01-23 19:52:32 +0000(lldb) po workoutBuilder.workoutEvents.last ▿ Optional - some : HKWorkoutEventTypeLap, <_NSConcreteDateInterval: 0x1569aa60> (Start Date) 2020-01-23 19:51:47 +0000 + (Duration) 22.497641 seconds = (End Date) 2020-01-23 19:52:09 +0000 { HKSwimmingStrokeStyle = 2; }Modified SpeedSloth WorkoutSession class which demonstrates the delay:/* See LICENSE folder for this sample’s licensing information. Abstract: THe workout session interface controller. */ import WatchKit import Foundation import HealthKit class WorkoutSession: WKInterfaceController, HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDelegate { @IBOutlet weak var timer: WKInterfaceTimer! @IBOutlet weak var activeCaloriesLabel: WKInterfaceLabel! @IBOutlet weak var heartRateLabel: WKInterfaceLabel! @IBOutlet weak var distanceLabel: WKInterfaceLabel! var healthStore: HKHealthStore! var configuration: HKWorkoutConfiguration! var session: HKWorkoutSession! var builder: HKLiveWorkoutBuilder! var laps = 0 override func awake(withContext context: Any?) { super.awake(withContext: context) setupWorkoutSessionInterface(with: context) // Create the session and obtain the workout builder. /// - Tag: CreateWorkout do { session = try HKWorkoutSession(healthStore: healthStore, configuration: configuration) builder = session.associatedWorkoutBuilder() } catch { dismiss() return } // Setup session and builder. session.delegate = self builder.delegate = self /// Set the workout builder's data source. /// - Tag: SetDataSource builder.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore, workoutConfiguration: configuration) // Start the workout session and begin data collection. /// - Tag: StartSession session.startActivity(with: Date()) builder.beginCollection(withStart: Date()) { (success, error) in self.setDurationTimerDate(.running) } } // Track elapsed time. func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) { // Retreive the workout event. guard let workoutEventType = workoutBuilder.workoutEvents.last?.type else { return } // Update the timer based on the event received. switch workoutEventType { case .pause: // The user paused the workout. setDurationTimerDate(.paused) case .resume: // The user resumed the workout. setDurationTimerDate(.running) case .lap: WKInterfaceDevice.current().play(.notification) print(Date()) print("Lap"); default: return } } func setDurationTimerDate(_ sessionState: HKWorkoutSessionState) { /// Obtain the elapsed time from the workout builder. /// - Tag: ObtainElapsedTime let timerDate = Date(timeInterval: -self.builder.elapsedTime, since: Date()) // Dispatch to main, because we are updating the interface. DispatchQueue.main.async { self.timer.setDate(timerDate) } // Dispatch to main, because we are updating the interface. DispatchQueue.main.async { /// Update the timer based on the state we are in. /// - Tag: UpdateTimer sessionState == .running ? self.timer.start() : self.timer.stop() } } // MARK: - HKLiveWorkoutBuilderDelegate func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set<HKSampleType>) { for type in collectedTypes { guard let quantityType = type as? HKQuantityType else { return // Nothing to do. } if(quantityType == HKQuantityType.quantityType(forIdentifier: .distanceSwimming)){ WKInterfaceDevice.current().play(.success) } /// - Tag: GetStatistics let statistics = workoutBuilder.statistics(for: quantityType) let label = labelForQuantityType(quantityType) updateLabel(label, withStatistics: statistics) } } // MARK: - State Control func pauseWorkout() { session.pause() } func resumeWorkout() { session.resume() } func endWorkout() { /// Update the timer based on the state we are in. /// - Tag: SaveWorkout session.end() builder.endCollection(withEnd: Date()) { (success, error) in self.builder.finishWorkout { (workout, error) in // Dispatch to main, because we are updating the interface. DispatchQueue.main.async() { self.dismiss() } } } } func setupWorkoutSessionInterface(with context: Any?) { guard let context = context as? WorkoutSessionContext else { dismiss() return } healthStore = context.healthStore configuration = context.configuration setupMenuItemsForWorkoutSessionState(.running) } /// Set up the contextual menu based on the workout session state. func setupMenuItemsForWorkoutSessionState(_ state: HKWorkoutSessionState) { clearAllMenuItems() if state == .running { addMenuItem(with: .pause, title: "Pause", action: #selector(pauseWorkoutAction)) } else if state == .paused { addMenuItem(with: .resume, title: "Resume", action: #selector(resumeWorkoutAction)) } addMenuItem(with: .decline, title: "End", action: #selector(endWorkoutAction)) } /// Action for the "Pause" menu item. @objc func pauseWorkoutAction() { pauseWorkout() } /// Action for the "Resume" menu item. @objc func resumeWorkoutAction() { resumeWorkout() } /// Action for the "End" menu item. @objc func endWorkoutAction() { endWorkout() } // MARK: - HKWorkoutSessionDelegate func workoutSession( _ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date ) { // Dispatch to main, because we are updating the interface. DispatchQueue.main.async { self.setupMenuItemsForWorkoutSessionState(toState) } } func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: Error) { print("Error") } // MARK: - Update the interface /// Retreive the WKInterfaceLabel object for the quantity types we are observing. func labelForQuantityType(_ type: HKQuantityType) -> WKInterfaceLabel? { switch type { case HKQuantityType.quantityType(forIdentifier: .heartRate): return heartRateLabel case HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned): return activeCaloriesLabel case HKQuantityType.quantityType(forIdentifier: .distanceSwimming): return distanceLabel default: return nil } } /// Update the WKInterfaceLabels with new data. func updateLabel(_ label: WKInterfaceLabel?, withStatistics statistics: HKStatistics?) { // Make sure we got non `nil` parameters. guard let label = label, let statistics = statistics else { return } // Dispatch to main, because we are updating the interface. DispatchQueue.main.async { switch statistics.quantityType { case HKQuantityType.quantityType(forIdentifier: .heartRate): /// - Tag: SetLabel let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute()) let value = statistics.mostRecentQuantity()?.doubleValue(for: heartRateUnit) let roundedValue = Double( round( 1 * value! ) / 1 ) label.setText("\(roundedValue) BPM") case HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned): let energyUnit = HKUnit.kilocalorie() let value = statistics.sumQuantity()?.doubleValue(for: energyUnit) let roundedValue = Double( round( 1 * value! ) / 1 ) label.setText("\(roundedValue) cal") return case HKQuantityType.quantityType(forIdentifier: .distanceSwimming): let meterUnit = HKUnit.yard() let value = statistics.sumQuantity()?.doubleValue(for: meterUnit) let roundedValue = Double( round( 1 * value! ) / 1 ) label.setText("\(roundedValue) yrd") return default: return } } } }
2
0
987
Jan ’20