Compare commits

...

9 Commits

Author SHA1 Message Date
Roberto Viola
65c015965f Update project.pbxproj 2025-11-25 15:46:34 +01:00
Claude
54e36f9771 Add missing elevationGain parameter to addMetrics calls
Add elevationGain:0 parameter to three addMetrics calls in lockscreen.mm:
- workoutTrackingUpdate(): general workout tracking (line 211)
- virtualbike_updateFTMS(): bike workout tracking (line 269)
- virtualrower_updateFTMS(): rower workout tracking (line 279)

These devices don't have elevation gain relevant for flights climbed
calculation, so passing 0 is appropriate. The treadmill-specific call
in virtualtreadmill_updateFTMS() already correctly passes the actual
elevationGain value.
2025-11-25 14:41:04 +00:00
Claude
0ef0c4d8d2 Fix Swift static member access: use WorkoutTracking.previousDistance
Correct the static member access syntax. Since previousDistance is a static
variable, it must be accessed via the class name WorkoutTracking.previousDistance
rather than as a local variable.

Fixed in two locations:
- Line 624: cycling distance delta calculation
- Line 666: rowing distance delta calculation
2025-11-25 13:12:59 +00:00
Claude
70799d2e63 Fix compilation error: add previousDistance variable for distance delta calculations
Add missing previousDistance variable to WorkoutTracking class. This variable
is needed to calculate distance deltas for cycling and rowing metrics, which
is separate from the flights climbed calculation.

The previousDistance is used in:
- Cycling distance delta calculation (line 622)
- Rowing distance delta calculation (line 664)

Initialize previousDistance to 0 at workout start and update it at the end
of each addMetrics() call.
2025-11-25 13:11:56 +00:00
Roberto Viola
cc2ecd39f9 Merge branch 'master' into claude/add-flights-climbed-metric-01CQmFrdyxEYzQ7uoKr3HDpQ 2025-11-25 14:06:33 +01:00
Claude
d7d08dd930 Implement WatchConnectivity bridge for flights climbed on watchOS
Complete the watchOS integration by implementing WatchConnectivity bridge
to pass elevationGain from iOS to Apple Watch, enabling flights climbed
tracking on the watch.

iOS changes:
- Add elevationGain static variable to WatchKitConnection.swift
- Include elevationGain in WatchConnectivity replyHandler
- Add @objc setElevationGain() method in AppDelegate.swift
- Declare and implement setElevationGain() in lockscreen.h/mm
- Call setElevationGain() from virtualtreadmill.cpp with elevationGain value

watchOS changes:
- Add elevationGain static variable to WatchKitConnection.swift
- Extract elevationGain from iOS message in replyHandler
- Calculate flights climbed (elevationGain / 3.048) and update WorkoutTracking
- Remove unused updateMetrics() method from WatchWorkoutTracking.swift

Flow:
1. C++ treadmill.cpp calculates elevationGain from speed/inclination/deltaTime
2. virtualtreadmill.cpp passes elevationGain to iOS via lockscreen bridge
3. iOS stores elevationGain in WatchKitConnection static variable
4. When watch requests data, iOS includes elevationGain in reply message
5. Watch receives elevationGain, calculates flights climbed, updates HealthKit

Benefits:
- watchOS now receives accurate elevation data from QZ's calculations
- Flights climbed synced to Apple Health from both iOS and watchOS
- Consistent implementation across platforms
- Removes duplicate/unused code
2025-11-22 15:21:07 +00:00
Claude
b5a75b02c1 Refactor flights climbed to use QZ's existing elevationGain
Replace manual elevation calculation with QZ's built-in elevationGain metric
for improved accuracy and efficiency.

Changes:
- Add elevationGain parameter to virtualtreadmill_updateFTMS() signature
- Pass elevationGain (meters) from virtualtreadmill.cpp to iOS/watchOS
- Remove manual calculation in WorkoutTracking.swift and WatchWorkoutTracking.swift
- Simplify code by using pre-calculated metric from treadmill.cpp
- Remove unnecessary variables (previousDistance, inclination)

Benefits:
- More accurate: uses QZ's time-aware calculation with deltaTime
- More efficient: eliminates duplicate elevation calculations
- Cleaner code: reduces complexity and improves maintainability
- Consistent: aligns with QZ's existing metrics infrastructure

The elevationGain is calculated by treadmill::update_metrics() as:
  elevationAcc += (speed / 3600.0) * 1000.0 * (inclination / 100.0) * deltaTime

Flights climbed = elevationGain (meters) / 3.048 (10 feet per flight)
2025-11-22 15:04:41 +00:00
Claude
6b356749df Add flights climbed metric to watchOS for treadmill workouts
Extend Apple Health flights climbed tracking to watchOS companion app
for treadmill workouts using inclination data.

Changes to watchkit Extension/WatchWorkoutTracking.swift:
- Add flightsClimbed, inclination, and previousDistance static variables
- Add HealthKit authorization for flightsClimbed quantity type
- Add updateMetrics() method to calculate flights climbed in real-time:
  * Vertical gain = distance delta * sin(atan(inclination/100))
  * Flights = vertical gain / 3.048 meters (10 feet per flight)
- Write flights climbed data to HealthKit in stopWorkOut() for walking/running
- Reset flights climbed counter at workout start and end
- Combine steps, distance, and flights into single sample array

The implementation tracks flights only for walking/running workouts (sport
types 1 and 2) when inclination is greater than 0, matching iOS behavior.
2025-11-22 14:50:22 +00:00
Claude
b750389ac4 Add flights climbed metric to Apple Health for treadmill workouts
Implement calculation and tracking of flights climbed in Apple Health for
treadmill workouts on both iOS and watchOS, using the treadmill's inclination
data.

Changes:
- Add flightsClimbed static variable to WorkoutTracking class to track accumulated flights
- Add inclination parameter to addMetrics() function with default value of 0
- Calculate flights climbed based on inclination and distance delta:
  * Vertical gain = distance * sin(atan(inclination/100))
  * Flights = vertical gain / 3.048 meters (10 feet per flight)
- Add HealthKit authorization for flightsClimbed quantity type
- Write flights climbed data to HealthKit in stopWorkOut() for walking/running workouts
- Reset flights climbed counter at workout start and end
- Pass inclination from lockscreen.mm to WorkoutTracking Swift code
- Convert inclination from centesimal int16 to percentage double

The implementation only tracks flights for treadmill workouts (sport types 0 and 1
for walking and running) when inclination is greater than 0.
2025-11-22 12:45:06 +00:00
9 changed files with 140 additions and 70 deletions

View File

@@ -4569,7 +4569,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1214;
CURRENT_PROJECT_VERSION = 1215;
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4770,7 +4770,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1214;
CURRENT_PROJECT_VERSION = 1215;
DEBUG_INFORMATION_FORMAT = dwarf;
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
DEVELOPMENT_TEAM = 6335M7T29D;
@@ -5007,7 +5007,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1214;
CURRENT_PROJECT_VERSION = 1215;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -5103,7 +5103,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1214;
CURRENT_PROJECT_VERSION = 1215;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -5195,7 +5195,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1214;
CURRENT_PROJECT_VERSION = 1215;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -5311,7 +5311,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1214;
CURRENT_PROJECT_VERSION = 1215;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
@@ -5421,7 +5421,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = QZWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1214;
CURRENT_PROJECT_VERSION = 1215;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@@ -5512,7 +5512,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = QZWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1214;
CURRENT_PROJECT_VERSION = 1215;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_NS_ASSERTIONS = NO;

View File

@@ -29,6 +29,7 @@ class WatchKitConnection: NSObject {
public static var cadence = 0.0
public static var power = 0.0
public static var steps = 0
public static var elevationGain = 0.0
weak var delegate: WatchKitConnectionDelegate?
private override init() {
@@ -85,6 +86,13 @@ extension WatchKitConnection: WatchKitConnectionProtocol {
let iSteps = Int(stepsDouble)
WatchKitConnection.steps = iSteps
}
if let elevationGainDouble = result["elevationGain"] as? Double {
WatchKitConnection.elevationGain = elevationGainDouble
// Calculate flights climbed and update WorkoutTracking
let flightsClimbed = elevationGainDouble / 3.048 // One flight = 10 feet = 3.048 meters
WorkoutTracking.flightsClimbed = flightsClimbed
print("WatchKitConnection: Received elevation gain: \(elevationGainDouble)m, flights: \(flightsClimbed)")
}
}, errorHandler: { (error) in
print(error)
})

View File

@@ -37,17 +37,18 @@ class WorkoutTracking: NSObject {
public static var steps = Int()
public static var cadence = Double()
public static var lastDateMetric = Date()
public static var flightsClimbed = Double()
var sport: Int = 0
let healthStore = HKHealthStore()
let configuration = HKWorkoutConfiguration()
var workoutSession: HKWorkoutSession!
var workoutBuilder: HKLiveWorkoutBuilder!
weak var delegate: WorkoutTrackingDelegate?
override init() {
super.init()
}
}
}
extension WorkoutTracking {
@@ -177,6 +178,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
HKSampleType.quantityType(forIdentifier: .runningVerticalOscillation)!,
HKSampleType.quantityType(forIdentifier: .walkingSpeed)!,
HKSampleType.quantityType(forIdentifier: .walkingStepLength)!,
HKSampleType.quantityType(forIdentifier: .flightsClimbed)!,
HKSampleType.workoutType()
])
} else {
@@ -188,6 +190,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
HKSampleType.quantityType(forIdentifier: .distanceWalkingRunning)!,
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
HKSampleType.quantityType(forIdentifier: .basalEnergyBurned)!,
HKSampleType.quantityType(forIdentifier: .flightsClimbed)!,
HKSampleType.workoutType()
])
}
@@ -206,6 +209,8 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
func startWorkOut() {
WorkoutTracking.lastDateMetric = Date()
// Reset flights climbed for new workout
WorkoutTracking.flightsClimbed = 0
print("Start workout")
configWorkout()
workoutSession.startActivity(with: Date())
@@ -354,7 +359,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
}
}
} else {
// Guard to check if steps quantity type is available
guard let quantityTypeSteps = HKQuantityType.quantityType(
forIdentifier: .stepCount) else {
@@ -362,7 +367,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
}
let stepsQuantity = HKQuantity(unit: HKUnit.count(), doubleValue: Double(WorkoutTracking.steps))
// Create a sample for total steps
let sampleSteps = HKCumulativeQuantitySeriesSample(
type: quantityTypeSteps,
@@ -370,55 +375,59 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
start: startDate,
end: Date())
// Add the steps sample to workout builder
workoutBuilder.add([sampleSteps]) { (success, error) in
// Guard to check if distance quantity type is available
guard let quantityTypeDistance = HKQuantityType.quantityType(
forIdentifier: .distanceWalkingRunning) else {
return
}
let sampleDistance = HKCumulativeQuantitySeriesSample(type: quantityTypeDistance,
quantity: quantityMiles,
start: startDate,
end: Date())
// Create flights climbed sample if available
var samplesToAdd: [HKCumulativeQuantitySeriesSample] = [sampleSteps, sampleDistance]
if WorkoutTracking.flightsClimbed > 0 {
if let quantityTypeFlights = HKQuantityType.quantityType(forIdentifier: .flightsClimbed) {
let flightsQuantity = HKQuantity(unit: HKUnit.count(), doubleValue: WorkoutTracking.flightsClimbed)
let sampleFlights = HKCumulativeQuantitySeriesSample(
type: quantityTypeFlights,
quantity: flightsQuantity,
start: startDate,
end: Date())
samplesToAdd.append(sampleFlights)
print("WatchWorkoutTracking: Adding flights climbed to workout: \(WorkoutTracking.flightsClimbed)")
}
}
// Add all samples to the workout builder
workoutBuilder.add(samplesToAdd) { (success, error) in
if let error = error {
print(error)
}
// End the data collection
self.workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
if let error = error {
print(error)
}
// Finish the workout and save total steps
// Finish the workout and save metrics
self.workoutBuilder.finishWorkout { (workout, error) in
if let error = error {
print(error)
}
workout?.setValue(stepsQuantity, forKey: "totalSteps")
}
}
}
guard let quantityTypeDistance = HKQuantityType.quantityType(
forIdentifier: .distanceWalkingRunning) else {
return
}
let sampleDistance = HKCumulativeQuantitySeriesSample(type: quantityTypeDistance,
quantity: quantityMiles,
start: 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")
// Set total energy burned on the workout
let totalEnergy = WorkoutTracking.totalKcal > 0 ? WorkoutTracking.totalKcal : activeEnergyBurned
let totalEnergyQuantity = HKQuantity(unit: unit, doubleValue: totalEnergy)
workout?.setValue(totalEnergyQuantity, forKey: "totalEnergyBurned")
// Reset flights climbed for next workout
WorkoutTracking.flightsClimbed = 0
}
}
}
@@ -433,7 +442,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
}
let startOfDay = Calendar.current.startOfDay(for: Date())
let predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: Date(), options: .strictStartDate)
let query = HKStatisticsQuery(quantityType: stepCounts, quantitySamplePredicate: predicate, options: .cumulativeSum) { [weak self] (_, result, error) in
guard let weakSelf = self else {
return
@@ -443,7 +452,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
print("Failed to fetch steps rate")
return
}
if let sum = result.sumQuantity() {
resultCount = sum.doubleValue(for: HKUnit.count())
weakSelf.delegate?.didReceiveHealthKitStepCounts(resultCount)

View File

@@ -139,6 +139,11 @@ var pedometer = CMPedometer()
Server.server?.send(createString(sender: sender))
}
@objc public func setElevationGain(elevationGain: Double) -> Void
{
WatchKitConnection.elevationGain = elevationGain;
}
@objc public func setHeartRate(heartRate: Int) -> Void
{
if #available(iOS 17.0, *) {

View File

@@ -31,6 +31,7 @@ class WatchKitConnection: NSObject {
static var power = 0.0
static var cadence = 0.0
static var steps = 0
static var elevationGain = 0.0
private override init() {
super.init()
@@ -150,9 +151,10 @@ extension WatchKitConnection: WCSessionDelegate {
replyValues["power"] = WatchKitConnection.power
replyValues["speed"] = WatchKitConnection.speed
replyValues["steps"] = Double(WatchKitConnection.steps)
replyValues["elevationGain"] = WatchKitConnection.elevationGain
SwiftDebug.qtDebug(replyValues.debugDescription)
replyHandler(replyValues)
//LocalNotificationHelper.fireHeartRate(heartReateDouble)

View File

@@ -30,9 +30,11 @@ protocol WorkoutTrackingProtocol {
static let shared = WorkoutTracking()
public static var lastDateMetric = Date()
public static var distance = Double()
public static var previousDistance = Double()
public static var kcal = Double()
public static var totalKcal = Double()
public static var steps = Double()
public static var flightsClimbed = Double()
static var sport: Int = 0
static let healthStore = HKHealthStore()
static let configuration = HKWorkoutConfiguration()
@@ -156,7 +158,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
])
var infoToShare: Set<HKSampleType> = []
if #available(watchOSApplicationExtension 10.0, *) {
if #available(iOS 18.0, *) {
infoToShare = Set([
@@ -177,6 +179,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
HKSampleType.quantityType(forIdentifier: .walkingStepLength)!,
HKSampleType.quantityType(forIdentifier: .distanceRowing)!,
HKSampleType.quantityType(forIdentifier: .rowingSpeed)!,
HKSampleType.quantityType(forIdentifier: .flightsClimbed)!,
HKSampleType.workoutType()
])
} else {
@@ -195,6 +198,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
HKSampleType.quantityType(forIdentifier: .runningSpeed)!,
HKSampleType.quantityType(forIdentifier: .distanceRowing)!,
HKSampleType.quantityType(forIdentifier: .rowingSpeed)!,
HKSampleType.quantityType(forIdentifier: .flightsClimbed)!,
HKSampleType.workoutType()
])
}
@@ -221,6 +225,9 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
WorkoutTracking.authorizeHealthKit()
WorkoutTracking.workoutInProgress = true;
WorkoutTracking.lastDateMetric = Date()
// Reset flights climbed and previous distance for new workout
WorkoutTracking.flightsClimbed = 0
WorkoutTracking.previousDistance = 0
SwiftDebug.qtDebug("WorkoutTracking: Start workout")
setSport(Int(deviceType))
configWorkout()
@@ -338,14 +345,30 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
start: startDate,
end: Date())
// Add both samples to the workout builder
workoutBuilder.add([sampleSteps, sampleDistance]) { (success, error) in
// Create flights climbed sample
var samplesToAdd: [HKCumulativeQuantitySeriesSample] = [sampleSteps, sampleDistance]
if WorkoutTracking.flightsClimbed > 0 {
if let quantityTypeFlights = HKQuantityType.quantityType(forIdentifier: .flightsClimbed) {
let flightsQuantity = HKQuantity(unit: HKUnit.count(), doubleValue: WorkoutTracking.flightsClimbed)
let sampleFlights = HKCumulativeQuantitySeriesSample(
type: quantityTypeFlights,
quantity: flightsQuantity,
start: startDate,
end: Date())
samplesToAdd.append(sampleFlights)
SwiftDebug.qtDebug("WorkoutTracking: Adding flights climbed to workout: \(WorkoutTracking.flightsClimbed)")
}
}
// Add all samples to the workout builder
workoutBuilder.add(samplesToAdd) { (success, error) in
if let error = error {
print(error)
SwiftDebug.qtDebug("WorkoutTracking: " + error.localizedDescription)
return
}
// End the data collection - only do this once
WorkoutTracking.workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
if let error = error {
@@ -353,7 +376,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
SwiftDebug.qtDebug("WorkoutTracking: " + error.localizedDescription)
return
}
// Finish the workout - only do this once
WorkoutTracking.workoutBuilder.finishWorkout { (workout, error) in
if let error = error {
@@ -361,11 +384,14 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
SwiftDebug.qtDebug("WorkoutTracking: " + error.localizedDescription)
return
}
// Set total energy burned on the workout
let totalEnergy = WorkoutTracking.totalKcal > 0 ? WorkoutTracking.totalKcal : activeEnergyBurned
let totalEnergyQuantity = HKQuantity(unit: unit, doubleValue: totalEnergy)
workout?.setValue(totalEnergyQuantity, forKey: "totalEnergyBurned")
// Reset flights climbed for next workout
WorkoutTracking.flightsClimbed = 0
}
}
}
@@ -374,9 +400,9 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
WorkoutTracking.workoutInProgress = false;
}
@objc func addMetrics(power: Double, cadence: Double, speed: Double, kcal: Double, steps: Double, deviceType: UInt8, distance: Double, totalKcal: Double) {
@objc func addMetrics(power: Double, cadence: Double, speed: Double, kcal: Double, steps: Double, deviceType: UInt8, distance: Double, totalKcal: Double, elevationGain: Double = 0) {
SwiftDebug.qtDebug("WorkoutTracking: GET DATA: \(Date())")
if(WorkoutTracking.workoutInProgress == false && power > 0 && WorkoutTracking.firstWorkout) {
WorkoutTracking.firstWorkout = false
startWorkOut(deviceType: UInt16(deviceType))
@@ -386,13 +412,20 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
let Speed = speed / 100
let previousSteps = WorkoutTracking.steps
let previousDistance = WorkoutTracking.distance
WorkoutTracking.kcal = kcal
WorkoutTracking.totalKcal = totalKcal
WorkoutTracking.steps = steps
WorkoutTracking.distance = distance
// Calculate flights climbed from elevation gain for treadmill (sport == 0 or 1)
// elevationGain is already calculated by QZ in meters
if (WorkoutTracking.sport == 0 || WorkoutTracking.sport == 1) && elevationGain > 0 {
// One flight = 10 feet = 3.048 meters
WorkoutTracking.flightsClimbed = elevationGain / 3.048
SwiftDebug.qtDebug("WorkoutTracking: Flights climbed: \(WorkoutTracking.flightsClimbed) from elevation: \(elevationGain)m")
}
if(WorkoutTracking.sport == 2) {
if #available(watchOSApplicationExtension 10.0, *) {
let wattPerInterval = HKQuantity(unit: HKUnit.watt(),
@@ -588,7 +621,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
}
}
let distanceDelta = max(0, distance - previousDistance)
let distanceDelta = max(0, distance - WorkoutTracking.previousDistance)
if distanceDelta > 0,
let distanceType = HKQuantityType.quantityType(forIdentifier: .distanceCycling) {
let distanceQuantity = HKQuantity(unit: HKUnit.meter(), doubleValue: distanceDelta)
@@ -630,7 +663,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
// Fallback on earlier versions
}
let distanceDelta = max(0, distance - previousDistance)
let distanceDelta = max(0, distance - WorkoutTracking.previousDistance)
if #available(iOS 18.0, *) {
if distanceDelta > 0,
let distanceType = HKQuantityType.quantityType(forIdentifier: .distanceRowing) {
@@ -653,6 +686,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
}
}
WorkoutTracking.previousDistance = distance
WorkoutTracking.lastDateMetric = Date()
}

View File

@@ -16,6 +16,7 @@ class lockscreen {
void setSpeed(double speed);
void setPower(double power);
void setCadence(double cadence);
void setElevationGain(double elevationGain);
void setHeartRate(unsigned char heartRate);
void startWorkout(unsigned short deviceType);
void stopWorkout();
@@ -54,8 +55,9 @@ class lockscreen {
double virtualtreadmill_getRequestedSpeed();
bool virtualtreadmill_updateFTMS(unsigned short normalizeSpeed, unsigned char currentResistance,
unsigned short currentCadence, unsigned short currentWatt,
unsigned short currentInclination, unsigned long long currentDistance, unsigned short currentCalories,
qint32 currentSteps, unsigned short elapsedSeconds, unsigned char deviceType);
unsigned short currentInclination, unsigned long long currentDistance, double elevationGain,
unsigned short currentCalories, qint32 currentSteps, unsigned short elapsedSeconds,
unsigned char deviceType);
// volume
double getVolume();

View File

@@ -165,6 +165,12 @@ void lockscreen::setPower(double power)
{
[h setPowerWithPower:power];
}
void lockscreen::setElevationGain(double elevationGain)
{
[h setElevationGainWithElevationGain:elevationGain];
}
void lockscreen::setCadence(double cadence)
{
[h setCadenceWithCadence:cadence];
@@ -202,7 +208,7 @@ void lockscreen::virtualbike_setCadence(unsigned short crankRevolutions, unsigne
void lockscreen::workoutTrackingUpdate(double speed, unsigned short cadence, unsigned short watt, unsigned short currentCalories, unsigned long long currentSteps, unsigned char deviceType, double currentDistance, double totalKcal, bool useMiles) {
if(workoutTracking != nil && !appleWatchAppInstalled())
[workoutTracking addMetricsWithPower:watt cadence:cadence*2 speed:speed * 100 kcal:currentCalories steps:currentSteps deviceType:deviceType distance:currentDistance totalKcal:totalKcal];
[workoutTracking addMetricsWithPower:watt cadence:cadence*2 speed:speed * 100 kcal:currentCalories steps:currentSteps deviceType:deviceType distance:currentDistance totalKcal:totalKcal elevationGain:0];
// Start Live Activity on first update, then keep updating
if (!ios_liveactivity::isLiveActivityRunning()) {
@@ -260,7 +266,7 @@ double lockscreen::virtualbike_getPowerRequested()
bool lockscreen::virtualbike_updateFTMS(UInt16 normalizeSpeed, UInt8 currentResistance, UInt16 currentCadence, UInt16 currentWatt, UInt16 CrankRevolutions, UInt16 LastCrankEventTime, signed short Gears, UInt16 currentCalories, UInt32 Distance, UInt8 deviceType)
{
if(workoutTracking != nil && !appleWatchAppInstalled())
[workoutTracking addMetricsWithPower:currentWatt cadence:currentCadence speed:normalizeSpeed kcal:currentCalories steps:0 deviceType: deviceType distance:Distance totalKcal:0];
[workoutTracking addMetricsWithPower:currentWatt cadence:currentCadence speed:normalizeSpeed kcal:currentCalories steps:0 deviceType: deviceType distance:Distance totalKcal:0 elevationGain:0];
if(_virtualbike_zwift != nil)
return [_virtualbike_zwift updateFTMSWithNormalizeSpeed:normalizeSpeed currentCadence:currentCadence currentResistance:currentResistance currentWatt:currentWatt CrankRevolutions:CrankRevolutions LastCrankEventTime:LastCrankEventTime Gears:Gears];
@@ -270,7 +276,7 @@ bool lockscreen::virtualbike_updateFTMS(UInt16 normalizeSpeed, UInt8 currentResi
bool lockscreen::virtualrower_updateFTMS(UInt16 normalizeSpeed, UInt8 currentResistance, UInt16 currentCadence, UInt16 currentWatt, UInt16 CrankRevolutions, UInt16 LastCrankEventTime, UInt16 StrokesCount, UInt32 Distance, UInt16 KCal, UInt16 Pace, UInt8 deviceType)
{
if(workoutTracking != nil && !appleWatchAppInstalled())
[workoutTracking addMetricsWithPower:currentWatt cadence:currentCadence speed:normalizeSpeed kcal:KCal steps:0 deviceType: deviceType distance:Distance totalKcal:0];
[workoutTracking addMetricsWithPower:currentWatt cadence:currentCadence speed:normalizeSpeed kcal:KCal steps:0 deviceType: deviceType distance:Distance totalKcal:0 elevationGain:0];
if(_virtualrower != nil)
return [_virtualrower updateFTMSWithNormalizeSpeed:normalizeSpeed currentCadence:currentCadence currentResistance:currentResistance currentWatt:currentWatt CrankRevolutions:CrankRevolutions LastCrankEventTime:LastCrankEventTime StrokesCount:StrokesCount Distance:Distance KCal:KCal Pace:Pace];
@@ -332,11 +338,13 @@ double lockscreen::virtualtreadmill_getRequestedSpeed()
return 0;
}
bool lockscreen::virtualtreadmill_updateFTMS(UInt16 normalizeSpeed, UInt8 currentResistance, UInt16 currentCadence, UInt16 currentWatt, UInt16 currentInclination, UInt64 currentDistance, unsigned short currentCalories, qint32 currentSteps, unsigned short elapsedSeconds, UInt8 deviceType)
bool lockscreen::virtualtreadmill_updateFTMS(UInt16 normalizeSpeed, UInt8 currentResistance, UInt16 currentCadence, UInt16 currentWatt, UInt16 currentInclination, UInt64 currentDistance, double elevationGain, unsigned short currentCalories, qint32 currentSteps, unsigned short elapsedSeconds, UInt8 deviceType)
{
if(workoutTracking != nil && !appleWatchAppInstalled())
[workoutTracking addMetricsWithPower:currentWatt cadence:currentCadence speed:normalizeSpeed kcal:currentCalories steps:currentSteps deviceType:deviceType distance:currentDistance totalKcal:0];
if(workoutTracking != nil && !appleWatchAppInstalled()) {
// Use elevationGain directly from QZ instead of recalculating
[workoutTracking addMetricsWithPower:currentWatt cadence:currentCadence speed:normalizeSpeed kcal:currentCalories steps:currentSteps deviceType:deviceType distance:currentDistance totalKcal:0 elevationGain:elevationGain];
}
if(_virtualtreadmill_zwift != nil)
return [_virtualtreadmill_zwift updateFTMSWithNormalizeSpeed:normalizeSpeed currentCadence:currentCadence currentResistance:currentResistance currentWatt:currentWatt currentInclination:currentInclination currentDistance:currentDistance elapsedTimeSeconds:elapsedSeconds];
return 0;

View File

@@ -574,13 +574,15 @@ void virtualtreadmill::treadmillProvider() {
swiftWatt,
swiftInclination,
swiftDistance,
((treadmill *)treadMill)->elevationGain().value(), // Use QZ's calculated elevation gain
swiftCalories,
swiftSteps,
swiftElapsedTimeSeconds,
static_cast<uint8_t>(treadMill->deviceType())
)) {
h->virtualtreadmill_setHeartRate(((treadmill *)treadMill)->currentHeart().value());
h->setElevationGain(((treadmill *)treadMill)->elevationGain().value());
lastSlopeChanged = h->virtualtreadmill_lastChangeCurrentSlope();
if ((uint64_t)QDateTime::currentSecsSinceEpoch() < lastSlopeChanged + slopeTimeoutSecs)