mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
1 Commits
master
...
ipad_apple
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfcce41bc7 |
@@ -8,6 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
import CoreMotion
|
||||
import HealthKit
|
||||
|
||||
var pedometer = CMPedometer()
|
||||
|
||||
@@ -27,9 +28,7 @@ var pedometer = CMPedometer()
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||
Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateHeartRate), userInfo: nil, repeats: true)
|
||||
}
|
||||
Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateHeartRate), userInfo: nil, repeats: true)
|
||||
Server.server?.start()
|
||||
|
||||
LocalNotificationHelper.requestPermission()
|
||||
@@ -124,6 +123,13 @@ var pedometer = CMPedometer()
|
||||
var sender: String
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
sender = "PAD"
|
||||
if #available(iOS 17.0, *) {
|
||||
WorkoutTracking.shared.fetchLatestHeartRateSample { sample in
|
||||
WatchKitConnection.currentHeartRate = Int(sample?.quantity.doubleValue(for: HKUnit(from: "count/min")) ?? 0)
|
||||
}
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
} else {
|
||||
sender = "PHONE"
|
||||
}
|
||||
|
||||
@@ -17,14 +17,17 @@ class ViewController: UIViewController {
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
WorkoutTracking.shared.authorizeHealthKit()
|
||||
WorkoutTracking.shared.observerHeartRateSamples()
|
||||
if #available(iOS 17.0, *) {
|
||||
WorkoutTracking.authorizeHealthKit()
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
WatchKitConnection.shared.delegate = self
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewController: WatchKitConnectionDelegate {
|
||||
func didFinishedActiveSession() {
|
||||
WatchKitConnection.shared.sendMessage(message: ["username" : "nhathm" as AnyObject])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,85 @@
|
||||
//
|
||||
// WorkoutTracking.swift
|
||||
// ElecDemo
|
||||
// WatchWorkoutTracking.swift
|
||||
// ElecDemo WatchKit Extension
|
||||
//
|
||||
// Created by NhatHM on 8/12/19.
|
||||
// Copyright © 2019 GST.PID. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import HealthKit
|
||||
|
||||
protocol WorkoutTrackingProtocol {
|
||||
func authorizeHealthKit()
|
||||
func observerHeartRateSamples()
|
||||
protocol WorkoutTrackingDelegate: class {
|
||||
func didReceiveHealthKitHeartRate(_ heartRate: Double)
|
||||
func didReceiveHealthKitStepCounts(_ stepCounts: Double)
|
||||
func didReceiveHealthKitStepCadence(_ stepCadence: Double)
|
||||
func didReceiveHealthKitDistanceCycling(_ distanceCycling: Double)
|
||||
func didReceiveHealthKitActiveEnergyBurned(_ activeEnergyBurned: Double)
|
||||
}
|
||||
|
||||
class WorkoutTracking {
|
||||
protocol WorkoutTrackingProtocol {
|
||||
static func authorizeHealthKit()
|
||||
func startWorkOut(deviceType: UInt16)
|
||||
func stopWorkOut()
|
||||
}
|
||||
|
||||
@available(iOS 17.0, *)
|
||||
@objc class WorkoutTracking: NSObject {
|
||||
static let shared = WorkoutTracking()
|
||||
public static var lastDateMetric = Date()
|
||||
public static var distance = Double()
|
||||
public static var kcal = Double()
|
||||
var sport: Int = 0
|
||||
let healthStore = HKHealthStore()
|
||||
var observerQuery: HKObserverQuery!
|
||||
let configuration = HKWorkoutConfiguration()
|
||||
var workoutBuilder: HKWorkoutBuilder!
|
||||
var workoutInProgress: Bool = false
|
||||
var queryRunning = true
|
||||
|
||||
init() {
|
||||
weak var delegate: WorkoutTrackingDelegate?
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 17.0, *)
|
||||
extension WorkoutTracking {
|
||||
func setSport(_ sport: Int) {
|
||||
self.sport = sport
|
||||
}
|
||||
|
||||
private func configWorkout() {
|
||||
var activityType = HKWorkoutActivityType.cycling
|
||||
if self.sport == 1 {
|
||||
activityType = HKWorkoutActivityType.running
|
||||
} else if self.sport == 2 {
|
||||
activityType = HKWorkoutActivityType.cycling
|
||||
} else if self.sport == 3 {
|
||||
activityType = HKWorkoutActivityType.rowing
|
||||
} else if self.sport == 4 {
|
||||
activityType = HKWorkoutActivityType.elliptical
|
||||
}
|
||||
|
||||
configuration.activityType = activityType
|
||||
configuration.locationType = .indoor
|
||||
|
||||
do {
|
||||
workoutBuilder = try HKWorkoutBuilder(healthStore: healthStore, configuration: configuration, device: .local())
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 17.0, *)
|
||||
extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
func authorizeHealthKit() {
|
||||
|
||||
@objc static func requestAuth() {
|
||||
authorizeHealthKit()
|
||||
}
|
||||
|
||||
@objc public static func authorizeHealthKit() {
|
||||
if HKHealthStore.isHealthDataAvailable() {
|
||||
let infoToRead = Set([
|
||||
HKSampleType.quantityType(forIdentifier: .stepCount)!,
|
||||
@@ -33,84 +89,190 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
HKSampleType.workoutType()
|
||||
])
|
||||
|
||||
let infoToShare = Set([
|
||||
HKSampleType.quantityType(forIdentifier: .stepCount)!,
|
||||
HKSampleType.quantityType(forIdentifier: .heartRate)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
|
||||
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
||||
HKSampleType.workoutType()
|
||||
])
|
||||
var infoToShare: Set<HKSampleType> = []
|
||||
|
||||
healthStore.requestAuthorization(toShare: infoToShare, read: infoToRead) { (success, error) in
|
||||
if success {
|
||||
print("Authorization healthkit success")
|
||||
} else if let error = error {
|
||||
print(error)
|
||||
if #available(watchOSApplicationExtension 10.0, *) {
|
||||
infoToShare = Set([
|
||||
HKSampleType.quantityType(forIdentifier: .stepCount)!,
|
||||
HKSampleType.quantityType(forIdentifier: .heartRate)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceWalkingRunning)!,
|
||||
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
||||
HKSampleType.quantityType(forIdentifier: .cyclingPower)!,
|
||||
HKSampleType.quantityType(forIdentifier: .cyclingSpeed)!,
|
||||
HKSampleType.quantityType(forIdentifier: .cyclingCadence)!,
|
||||
HKSampleType.quantityType(forIdentifier: .runningPower)!,
|
||||
HKSampleType.quantityType(forIdentifier: .runningSpeed)!,
|
||||
HKSampleType.quantityType(forIdentifier: .runningStrideLength)!,
|
||||
HKSampleType.quantityType(forIdentifier: .runningVerticalOscillation)!,
|
||||
HKSampleType.quantityType(forIdentifier: .walkingSpeed)!,
|
||||
HKSampleType.quantityType(forIdentifier: .walkingStepLength)!,
|
||||
HKSampleType.workoutType()
|
||||
])
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
infoToShare = Set([
|
||||
HKSampleType.quantityType(forIdentifier: .stepCount)!,
|
||||
HKSampleType.quantityType(forIdentifier: .heartRate)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceWalkingRunning)!,
|
||||
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
||||
HKSampleType.workoutType()
|
||||
])
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
HKHealthStore().requestAuthorization(toShare: infoToShare, read: infoToRead) { (success, error) in
|
||||
if success {
|
||||
WorkoutTracking.shared.queryRunning = false
|
||||
print("Authorization healthkit success")
|
||||
} else if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
print("HealthKit not available")
|
||||
print("HealthKit not avaiable")
|
||||
}
|
||||
}
|
||||
|
||||
func observerHeartRateSamples() {
|
||||
guard let heartRateSampleType = HKObjectType.quantityType(forIdentifier: .heartRate) else {
|
||||
return
|
||||
@objc func startWorkOut(deviceType: UInt16) {
|
||||
if(workoutInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let observerQuery = observerQuery {
|
||||
healthStore.stop(observerQuery)
|
||||
}
|
||||
|
||||
observerQuery = HKObserverQuery(sampleType: heartRateSampleType, predicate: nil) { [unowned self] (_, _, error) in
|
||||
if let error = error {
|
||||
print("Error: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
|
||||
self.fetchLatestHeartRateSample { (sample) in
|
||||
guard let sample = sample else {
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute())
|
||||
let heartRate = sample.quantity.doubleValue(for: heartRateUnit)
|
||||
print("Heart Rate Sample: \(heartRate)")
|
||||
//LocalNotificationHelper.fireHeartRate(heartRate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
healthStore.execute(observerQuery)
|
||||
healthStore.enableBackgroundDelivery(for: heartRateSampleType, frequency: .immediate) { (success, error) in
|
||||
workoutInProgress = true;
|
||||
WorkoutTracking.lastDateMetric = Date()
|
||||
print("Start workout")
|
||||
setSport(Int(deviceType))
|
||||
configWorkout()
|
||||
workoutBuilder.beginCollection(withStart: Date()) { (success, error) in
|
||||
print(success)
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func stopWorkOut() {
|
||||
print("Stop workout")
|
||||
|
||||
guard let quantityType = HKQuantityType.quantityType(
|
||||
forIdentifier: .activeEnergyBurned) else {
|
||||
return
|
||||
}
|
||||
|
||||
let unit = HKUnit.kilocalorie()
|
||||
let totalEnergyBurned = WorkoutTracking.kcal
|
||||
let quantity = HKQuantity(unit: unit,
|
||||
doubleValue: totalEnergyBurned)
|
||||
|
||||
let sample = HKCumulativeQuantitySeriesSample(type: quantityType,
|
||||
quantity: quantity,
|
||||
start: workoutBuilder.startDate!,
|
||||
end: Date())
|
||||
|
||||
workoutBuilder.add([sample]) {(success, error) in}
|
||||
|
||||
let unitDistance = HKUnit.mile()
|
||||
let miles = WorkoutTracking.distance
|
||||
let quantityMiles = HKQuantity(unit: unitDistance,
|
||||
doubleValue: miles)
|
||||
|
||||
if(sport == 0) {
|
||||
|
||||
guard let quantityTypeDistance = HKQuantityType.quantityType(
|
||||
forIdentifier: .distanceCycling) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let sampleDistance = HKCumulativeQuantitySeriesSample(type: quantityTypeDistance,
|
||||
quantity: quantityMiles,
|
||||
start: workoutBuilder.startDate!,
|
||||
end: Date())
|
||||
|
||||
workoutBuilder.add([sampleDistance]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
self.workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
self.workoutBuilder.finishWorkout{ (workout, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
workout?.setValue(quantityMiles, forKey: "totalDistance")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
guard let quantityTypeDistance = HKQuantityType.quantityType(
|
||||
forIdentifier: .distanceWalkingRunning) else {
|
||||
return
|
||||
}
|
||||
|
||||
let sampleDistance = HKCumulativeQuantitySeriesSample(type: quantityTypeDistance,
|
||||
quantity: quantityMiles,
|
||||
start: workoutBuilder.startDate!,
|
||||
end: Date())
|
||||
|
||||
workoutBuilder.add([sampleDistance]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
self.workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
self.workoutBuilder.finishWorkout{ (workout, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
workout?.setValue(quantityMiles, forKey: "totalDistance")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
workoutInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@available(iOS 17.0, *)
|
||||
extension WorkoutTracking {
|
||||
private func fetchLatestHeartRateSample(completionHandler: @escaping (_ sample: HKQuantitySample?) -> Void) {
|
||||
public func fetchLatestHeartRateSample(completionHandler: @escaping (_ sample: HKQuantitySample?) -> Void) {
|
||||
if(self.queryRunning) {
|
||||
return
|
||||
}
|
||||
self.queryRunning = true
|
||||
guard let sampleType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate) else {
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
let predicate = HKQuery.predicateForSamples(withStart: Date.distantPast, end: Date(), options: .strictEndDate)
|
||||
let tenMinutesAgo = Calendar.current.date(byAdding: .minute, value: -90, to: Date())!
|
||||
let predicate = HKQuery.predicateForSamples(withStart: tenMinutesAgo, end: Date(), options: .strictEndDate)
|
||||
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)
|
||||
let query = HKSampleQuery(sampleType: sampleType,
|
||||
predicate: predicate,
|
||||
limit: Int(HKObjectQueryNoLimit),
|
||||
sortDescriptors: [sortDescriptor]) { (_, results, error) in
|
||||
self.queryRunning = false
|
||||
if let error = error {
|
||||
print("Error: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
|
||||
completionHandler(results?[0] as? HKQuantitySample)
|
||||
if(results?.count ?? 0 > 0) {
|
||||
completionHandler(results?[0] as? HKQuantitySample)
|
||||
} else {
|
||||
completionHandler(nil)
|
||||
}
|
||||
}
|
||||
|
||||
healthStore.execute(query)
|
||||
|
||||
@@ -23,6 +23,8 @@ static virtualbike_zwift* _virtualbike_zwift = nil;
|
||||
static virtualrower* _virtualrower = nil;
|
||||
static virtualtreadmill_zwift* _virtualtreadmill_zwift = nil;
|
||||
|
||||
static WorkoutTracking* workoutTracking = nil;
|
||||
|
||||
static GarminConnect* Garmin = 0;
|
||||
|
||||
static AdbClient *_adb = 0;
|
||||
@@ -47,6 +49,10 @@ void lockscreen::request()
|
||||
if (@available(iOS 17, *)) {
|
||||
_adb = [[AdbClient alloc] initWithVerbose:YES];
|
||||
}
|
||||
if (@available(iOS 17, *)) {
|
||||
workoutTracking = [[WorkoutTracking alloc] init];
|
||||
[WorkoutTracking requestAuth];
|
||||
}
|
||||
}
|
||||
|
||||
long lockscreen::heartRate()
|
||||
|
||||
Reference in New Issue
Block a user