Compare commits

...

71 Commits

Author SHA1 Message Date
Roberto Viola
4e113a1b58 Update project.pbxproj 2025-06-17 14:56:47 +02:00
Roberto Viola
1f682f8748 Merge branch 'master' into healthkit-without-apple-watch 2025-06-17 14:49:33 +02:00
Roberto Viola
8baea12b26 fixing high steps
https://github.com/cagnulein/qdomyos-zwift/discussions/3277#discussioncomment-12425461
2025-03-07 12:29:55 +01:00
Roberto Viola
38a3a1e124 Update project.pbxproj 2025-03-07 11:03:25 +01:00
Roberto Viola
8896154fc1 fixed distance issue and steps 2025-03-07 10:55:27 +01:00
Roberto Viola
ff6b9bf780 fixing build on xcode 2025-03-07 09:58:01 +01:00
Roberto Viola
05da4fafc8 fixing build 2025-03-07 09:47:51 +01:00
Roberto Viola
de591bb650 Merge branch 'master' into healthkit-without-apple-watch 2025-03-07 09:34:03 +01:00
Roberto Viola
12eb8abfd0 fixing whitespaces 2025-03-06 12:15:02 +01:00
Roberto Viola
707c1d81ff NOT TESTED handling device type 2025-03-06 12:10:38 +01:00
Roberto Viola
f02228f9be adding steps for treadmills 2025-03-06 11:53:18 +01:00
Roberto Viola
db842417c4 Merge branch 'master' into healthkit-without-apple-watch 2025-03-06 11:45:34 +01:00
Roberto Viola
f0c5a41e48 BT Log share for LifeSpan-TM-2000 #3021 2025-01-31 10:45:30 +01:00
Roberto Viola
498ad13c15 Merge branch 'master' into healthkit-without-apple-watch 2025-01-31 10:34:06 +01:00
Roberto Viola
6183101e2f Update project.pbxproj 2025-01-31 07:56:43 +01:00
Roberto Viola
fbdd5bd177 Merge branch 'master' into healthkit-without-apple-watch 2025-01-31 07:41:17 +01:00
Roberto Viola
e65a209543 Update lockscreen.mm 2025-01-31 07:13:14 +01:00
Roberto Viola
458642abab Update project.pbxproj 2025-01-31 06:29:12 +01:00
Roberto Viola
045a0d0c7d Update homeform.cpp 2025-01-31 04:24:10 +01:00
Roberto Viola
93713ae19d fake treadmill simulatoion on the simulator 2025-01-30 13:50:29 +01:00
Roberto Viola
c2c581b6d0 fixing crash? 2025-01-30 13:50:05 +01:00
Roberto Viola
3711e94026 Update project.pbxproj 2025-01-30 12:13:07 +01:00
Roberto Viola
eccaa89d77 fixing build and tested 2025-01-30 12:12:34 +01:00
Roberto Viola
97a67340a6 Update homeform.cpp 2025-01-30 11:47:42 +01:00
Roberto Viola
e24b4d0a55 Merge branch 'healthkit-without-apple-watch' of https://github.com/cagnulein/qdomyos-zwift into healthkit-without-apple-watch 2025-01-30 11:45:29 +01:00
Roberto Viola
3f58080193 adding check that apple watch is available 2025-01-30 11:45:23 +01:00
Roberto Viola
deed740ac9 Merge branch 'master' into healthkit-without-apple-watch 2025-01-30 11:39:08 +01:00
Roberto Viola
d19db65cf6 Update bluetooth.cpp 2025-01-30 11:34:20 +01:00
Roberto Viola
c5942d1d93 Merge branch 'master' into healthkit-without-apple-watch 2025-01-27 15:26:20 +01:00
Roberto Viola
13851564d0 Merge branch 'master' into healthkit-without-apple-watch 2024-10-31 05:36:54 +01:00
Roberto Viola
df3620f83a Update project.pbxproj 2024-10-31 05:36:14 +01:00
Roberto Viola
614957819f improving logs 2024-10-31 05:35:02 +01:00
Roberto Viola
69d8bab7c8 Merge branch 'master' into healthkit-without-apple-watch 2024-10-31 05:28:26 +01:00
Roberto Viola
7686ca48d0 adding logs 2024-10-30 13:49:17 +01:00
Roberto Viola
9685a00b8a Merge branch 'master' into healthkit-without-apple-watch 2024-10-30 13:46:36 +01:00
Roberto Viola
c69714bfad Merge branch 'master' into healthkit-without-apple-watch 2024-10-04 10:45:51 +02:00
Roberto Viola
9da3d28995 Update project.pbxproj 2024-09-25 12:46:24 +02:00
Roberto Viola
90c8c167bc Update WorkoutTracking.swift 2024-09-25 12:42:37 +02:00
Roberto Viola
9962776f02 fixing crash and metrics 2024-09-25 12:35:37 +02:00
Roberto Viola
ef9ce3cffd Merge branch 'master' into healthkit-without-apple-watch 2024-09-25 12:02:03 +02:00
Roberto Viola
0a717865ef fake bike to test 2024-09-23 14:55:49 +02:00
Roberto Viola
329cf8beaa Merge branch 'master' into healthkit-without-apple-watch 2024-09-23 14:21:04 +02:00
Roberto Viola
a7777f4185 fix build 2024-09-20 12:22:38 +02:00
Roberto Viola
fd200f43f6 Update project.pbxproj 2024-09-20 12:17:18 +02:00
Roberto Viola
7e9024a36e fix build 2024-09-20 12:16:55 +02:00
Roberto Viola
26c261957e adding kcal 2024-09-19 15:20:16 +02:00
Roberto Viola
5d38be8a5c Merge branch 'master' into healthkit-without-apple-watch 2024-09-19 15:12:24 +02:00
Roberto Viola
a5ef8dfc21 fix build issue, forcing bike 2024-08-26 17:18:54 +02:00
Roberto Viola
39599dc7ad Update project.pbxproj 2024-08-26 17:12:05 +02:00
Roberto Viola
d765f13a13 Merge branch 'master' into healthkit-without-apple-watch 2024-08-26 17:06:35 +02:00
Roberto Viola
1173f8f8bc Merge branch 'master' into healthkit-without-apple-watch 2024-03-12 08:58:42 +01:00
Roberto Viola
6959993a40 adding device type 2024-01-22 12:18:12 +01:00
Roberto Viola
45ca7b3b5b adding metrics also when the virtualbike is not the zwift interface 2024-01-22 12:14:09 +01:00
Roberto Viola
a84bfac840 fixing speed 2024-01-22 12:01:23 +01:00
Roberto Viola
c6c83085af Merge branch 'master' into healthkit-without-apple-watch 2024-01-22 11:48:23 +01:00
Roberto Viola
f1c2414ad6 Update project.pbxproj 2024-01-20 00:09:38 +01:00
Roberto Viola
942d213216 merge 2024-01-19 23:07:22 +00:00
Roberto Viola
0ad2f62e6c Update project.pbxproj 2024-01-10 14:51:48 +01:00
Roberto Viola
37f45b8439 seems working 2024-01-10 10:00:15 +01:00
Roberto Viola
821cddb342 Merge branch 'master' into healthkit-without-apple-watch 2024-01-10 09:00:53 +01:00
Roberto Viola
f378bc3fff Update WorkoutTracking.swift 2024-01-05 12:14:54 +01:00
Roberto Viola
6ea8c002ac Merge branch 'master' into healthkit-without-apple-watch 2024-01-05 11:50:21 +01:00
Roberto Viola
a95ab376c5 Update WorkoutTracking.swift 2024-01-04 14:03:10 +01:00
Roberto Viola
7d30088c7b Update homeform.cpp 2024-01-04 12:27:14 +01:00
Roberto Viola
fb3777b7ff Update WorkoutTracking.swift 2024-01-04 12:22:08 +01:00
Roberto Viola
d28acf3e96 Update lockscreen.mm 2024-01-04 12:15:59 +01:00
Roberto Viola
553e83a075 doing 2024-01-04 12:14:53 +01:00
Roberto Viola
857839b80f Update lockscreen.mm 2024-01-04 12:03:04 +01:00
Roberto Viola
6e28f8a797 Update WorkoutTracking.swift 2024-01-04 12:00:18 +01:00
Roberto Viola
ddafafaba6 building 2024-01-04 11:16:12 +01:00
Roberto Viola
a746373894 Update WorkoutTracking.swift 2024-01-04 10:37:17 +01:00
23 changed files with 550 additions and 99 deletions

View File

@@ -4381,7 +4381,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1100;
CURRENT_PROJECT_VERSION = 1104;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1";
@@ -4575,7 +4575,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1100;
CURRENT_PROJECT_VERSION = 1104;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4805,7 +4805,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1100;
CURRENT_PROJECT_VERSION = 1104;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4901,7 +4901,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1100;
CURRENT_PROJECT_VERSION = 1104;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -4993,7 +4993,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1100;
CURRENT_PROJECT_VERSION = 1104;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -5109,7 +5109,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1100;
CURRENT_PROJECT_VERSION = 1104;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;

View File

@@ -227,6 +227,10 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
let quantity = HKQuantity(unit: unit,
doubleValue: totalEnergyBurned)
if workoutSession.startDate == nil {
return
}
let sample = HKCumulativeQuantitySeriesSample(type: quantityType,
quantity: quantity,
start: workoutSession.startDate!,

View File

@@ -132,7 +132,7 @@ void antbike::update() {
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
}
#endif

View File

@@ -220,7 +220,7 @@ void apexbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
}
#endif

View File

@@ -36,6 +36,10 @@ bluetooth::bluetooth(bool logs, const QString &deviceName, bool noWriteResistanc
QString nordictrack_2950_ip =
settings.value(QZSettings::nordictrack_2950_ip, QZSettings::default_nordictrack_2950_ip).toString();
bool fake_bike =
settings.value(QZSettings::applewatch_fakedevice, QZSettings::default_applewatch_fakedevice).toBool();
bool fake_treadmill =
settings.value(QZSettings::fakedevice_treadmill, QZSettings::default_fakedevice_treadmill).toBool();
if (settings.value(QZSettings::peloton_bike_ocr, QZSettings::default_peloton_bike_ocr).toBool() && !pelotonBike) {
pelotonBike = new pelotonbike(noWriteResistance, noHeartService);
@@ -47,6 +51,28 @@ bluetooth::bluetooth(bool logs, const QString &deviceName, bool noWriteResistanc
}
// this signal is not associated to anything in this moment, since the homeform is not loaded yet
this->signalBluetoothDeviceConnected(pelotonBike);
} else if (fake_bike) {
fakeBike = new fakebike(noWriteResistance, noHeartService, false);
emit deviceConnected(QBluetoothDeviceInfo());
connect(fakeBike, &bluetoothdevice::connectedAndDiscovered, this, &bluetooth::connectedAndDiscovered);
connect(fakeBike, &fakebike::debug, this, &bluetooth::debug);
if (this->discoveryAgent && !this->discoveryAgent->isActive()) {
emit searchingStop();
}
// this signal is not associated to anything in this moment, since the homeform is not loaded yet
this->signalBluetoothDeviceConnected(fakeBike);
return;
} else if (fake_treadmill) {
fakeTreadmill = new faketreadmill(noWriteResistance, noHeartService, false);
emit deviceConnected(QBluetoothDeviceInfo());
connect(fakeTreadmill, &bluetoothdevice::connectedAndDiscovered, this, &bluetooth::connectedAndDiscovered);
connect(fakeTreadmill, &faketreadmill::debug, this, &bluetooth::debug);
if (this->discoveryAgent && !this->discoveryAgent->isActive()) {
emit searchingStop();
}
// this signal is not associated to anything in this moment, since the homeform is not loaded yet
this->signalBluetoothDeviceConnected(fakeBike);
return;
}
#ifdef TEST

View File

@@ -271,6 +271,12 @@ void bluetoothdevice::update_hr_from_external() {
}
#endif
}
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
lockscreen h;
h.workoutTrackingUpdate(Speed.value(), Cadence.value(), (uint16_t)m_watt.value(), calories().value(), StepCount.value(), deviceType(), odometer() * 1000.0);
#endif
#endif
}
void bluetoothdevice::clearStats() {

View File

@@ -284,7 +284,7 @@ void csafeelliptical::update() {
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
}
#endif

View File

@@ -738,7 +738,7 @@ void cycleopsphantombike::characteristicChanged(const QLowEnergyCharacteristic &
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setHeartRate((uint8_t)currentHeart().value());
}
#endif

View File

@@ -65,6 +65,14 @@ void fakebike::update() {
speedLimit());
}
double weight = settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
if (watts())
KCal +=
((((0.048 * ((double)watts()) + 1.19) * weight * 3.5) / 200.0) /
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));

View File

@@ -963,7 +963,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
}
#endif

View File

@@ -264,7 +264,7 @@ void kineticinroadbike::characteristicChanged(const QLowEnergyCharacteristic &ch
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
}
#endif

View File

@@ -470,7 +470,7 @@ void nordictrackifitadbelliptical::processPendingDatagrams() {
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadencep && h && firstStateChanged) {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
}
#endif

View File

@@ -240,7 +240,7 @@ void pitpatbike::characteristicChanged(const QLowEnergyCharacteristic &character
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
}
#endif

View File

@@ -424,7 +424,7 @@ void technogymbike::characteristicChanged(const QLowEnergyCharacteristic &charac
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
}
#endif

View File

@@ -762,6 +762,11 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) {
}
});
});
#else
#ifndef IO_UNDER_QT
h = new lockscreen();
h->appleWatchAppInstalled();
#endif
#endif
if (QSslSocket::supportsSsl()) {
@@ -4577,7 +4582,12 @@ void homeform::Start_inner(bool send_event_to_device) {
videoPlaybackHalfPlayer->pause();
}
} else {
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
if(h && !h->appleWatchAppInstalled())
h->startWorkout(bluetoothManager->device()->deviceType());
#endif
#endif
if (bluetoothManager->device() && send_event_to_device) {
bluetoothManager->device()->start();
}
@@ -4671,6 +4681,13 @@ void homeform::Stop() {
m_startRequested = false;
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
if(h && !h->appleWatchAppInstalled())
h->stopWorkout();
#endif
#endif
qDebug() << QStringLiteral("Stop pressed - paused") << paused << QStringLiteral("stopped") << stopped;
if (stopped) {

View File

@@ -25,8 +25,10 @@
#include <QQuickItemGrabResult>
#include <QTextToSpeech>
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
#endif
#ifdef Q_OS_ANDROID
#include <QAndroidJniEnvironment>
#include <QtAndroid>
#endif
@@ -813,6 +815,9 @@ class homeform : public QObject {
bool floating_open = false;
#endif
#ifdef Q_OS_IOS
lockscreen *h = nullptr;
#endif
bool m_locationServices = true;
#ifndef Q_OS_IOS

View File

@@ -17,8 +17,11 @@ 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
}
}

View File

@@ -1,29 +1,87 @@
//
// 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()
let SwiftDebug = swiftDebug()
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()
public static var steps = Double()
var sport: Int = 0
let healthStore = HKHealthStore()
var observerQuery: HKObserverQuery!
let configuration = HKWorkoutConfiguration()
var workoutBuilder: HKWorkoutBuilder!
var workoutInProgress: Bool = false
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,86 +91,342 @@ 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 {
SwiftDebug.qtDebug("WorkoutTracking: Authorization healthkit success")
} else if let error = error {
SwiftDebug.qtDebug("WorkoutTracking: " + error.localizedDescription)
}
}
}
} else {
print("HealthKit not available")
SwiftDebug.qtDebug("WorkoutTracking: 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
WorkoutTracking.authorizeHealthKit()
workoutInProgress = true;
WorkoutTracking.lastDateMetric = Date()
SwiftDebug.qtDebug("WorkoutTracking: Start workout")
setSport(Int(deviceType))
configWorkout()
workoutBuilder.beginCollection(withStart: Date()) { (success, error) in
SwiftDebug.qtDebug(success.description)
if let error = error {
print("Error: \(error.localizedDescription)")
return
SwiftDebug.qtDebug("WorkoutTracking: " + error.localizedDescription)
}
}
}
@objc func stopWorkOut() {
SwiftDebug.qtDebug("WorkoutTracking: 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 * 0.000621371
let quantityMiles = HKQuantity(unit: unitDistance,
doubleValue: miles)
if(sport == 2) {
guard let quantityTypeDistance = HKQuantityType.quantityType(
forIdentifier: .distanceCycling) else {
return
}
self.fetchLatestHeartRateSample { (sample) in
guard let sample = sample else {
let sampleDistance = HKCumulativeQuantitySeriesSample(type: quantityTypeDistance,
quantity: quantityMiles,
start: workoutBuilder.startDate!,
end: Date())
workoutBuilder.add([sampleDistance]) {(success, error) in
if let error = error {
SwiftDebug.qtDebug("WorkoutTracking: " + error.localizedDescription)
}
}
self.workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
if let error = error {
SwiftDebug.qtDebug("WorkoutTracking: " + error.localizedDescription)
}
self.workoutBuilder.finishWorkout{ (workout, error) in
if let error = error {
SwiftDebug.qtDebug("WorkoutTracking: " + error.localizedDescription)
}
workout?.setValue(quantityMiles, forKey: "totalDistance")
}
}
} else {
// Guard to check if steps quantity type is available
guard let quantityTypeSteps = HKQuantityType.quantityType(
forIdentifier: .stepCount) else {
return
}
let stepsQuantity = HKQuantity(unit: HKUnit.count(), doubleValue: Double(WorkoutTracking.steps))
// Create a sample for total steps
let sampleSteps = HKCumulativeQuantitySeriesSample(
type: quantityTypeSteps,
quantity: stepsQuantity, // Use your steps quantity here
start: workoutBuilder.startDate!,
end: Date())
// 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: workoutBuilder.startDate!,
end: Date())
// Add both samples to the workout builder
workoutBuilder.add([sampleSteps, sampleDistance]) { (success, error) in
if let error = error {
print(error)
SwiftDebug.qtDebug("WorkoutTracking: " + error.localizedDescription)
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)
// End the data collection - only do this once
self.workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
if let error = error {
print(error)
SwiftDebug.qtDebug("WorkoutTracking: " + error.localizedDescription)
return
}
// Finish the workout - only do this once
self.workoutBuilder.finishWorkout { (workout, error) in
if let error = error {
print(error)
SwiftDebug.qtDebug("WorkoutTracking: " + error.localizedDescription)
return
}
// No need to manually set values - the builder has added the samples
// and the workout now has steps and distance metrics built in
// You can access the data if needed:
if let workout = workout {
// Here you can use the workout as needed
// The steps and distance are now part of the workout's statistics
}
}
}
}
}
healthStore.execute(observerQuery)
healthStore.enableBackgroundDelivery(for: heartRateSampleType, frequency: .immediate) { (success, error) in
print(success)
if let error = error {
print(error)
}
workoutInProgress = false;
}
@objc func addMetrics(power: Double, cadence: Double, speed: Double, kcal: Double, steps: Double, deviceType: UInt8, distance: Double) {
SwiftDebug.qtDebug("WorkoutTracking: GET DATA: \(Date())")
if(workoutInProgress == false && power > 0) {
startWorkOut(deviceType: UInt16(deviceType))
} else if(workoutInProgress == false && power == 0) {
return;
}
let Speed = speed / 100;
WorkoutTracking.kcal = kcal
WorkoutTracking.steps = steps
WorkoutTracking.distance = distance
if(sport == 2) {
if #available(watchOSApplicationExtension 10.0, *) {
let wattPerInterval = HKQuantity(unit: HKUnit.watt(),
doubleValue: power)
if(WorkoutTracking.lastDateMetric.distance(to: Date()) < 1) {
return
}
guard let powerType = HKQuantityType.quantityType(
forIdentifier: .cyclingPower) else {
return
}
let wattPerIntervalSample = HKQuantitySample(type: powerType,
quantity: wattPerInterval,
start: WorkoutTracking.lastDateMetric,
end: Date())
workoutBuilder.add([wattPerIntervalSample]) {(success, error) in
if let error = error {
SwiftDebug.qtDebug("WorkoutTracking: " + error.localizedDescription)
}
}
let cadencePerInterval = HKQuantity(unit: HKUnit.count().unitDivided(by: HKUnit.second()),
doubleValue: cadence / 60.0)
guard let cadenceType = HKQuantityType.quantityType(
forIdentifier: .cyclingCadence) else {
return
}
let cadencePerIntervalSample = HKQuantitySample(type: cadenceType,
quantity: cadencePerInterval,
start: WorkoutTracking.lastDateMetric,
end: Date())
workoutBuilder.add([cadencePerIntervalSample]) {(success, error) in
if success {
SwiftDebug.qtDebug("WorkoutTracking: OK")
}
if let error = error {
SwiftDebug.qtDebug("WorkoutTracking: " + error.localizedDescription)
}
}
let speedPerInterval = HKQuantity(unit: HKUnit.meter().unitDivided(by: HKUnit.second()),
doubleValue: (Speed / 3.6))
guard let speedType = HKQuantityType.quantityType(
forIdentifier: .cyclingSpeed) else {
return
}
let speedPerIntervalSample = HKQuantitySample(type: speedType,
quantity: speedPerInterval,
start: WorkoutTracking.lastDateMetric,
end: Date())
workoutBuilder.add([speedPerIntervalSample]) {(success, error) in
if let error = error {
SwiftDebug.qtDebug("WorkoutTracking: " + error.localizedDescription)
}
}
} else {
// Fallback on earlier versions
}
} else if(sport == 1) {
if #available(watchOSApplicationExtension 10.0, *) {
let wattPerInterval = HKQuantity(unit: HKUnit.watt(),
doubleValue: power)
if(WorkoutTracking.lastDateMetric.distance(to: Date()) < 1) {
return
}
guard let powerType = HKQuantityType.quantityType(
forIdentifier: .runningPower) else {
return
}
let wattPerIntervalSample = HKQuantitySample(type: powerType,
quantity: wattPerInterval,
start: WorkoutTracking.lastDateMetric,
end: Date())
workoutBuilder.add([wattPerIntervalSample]) {(success, error) in
if let error = error {
SwiftDebug.qtDebug("WorkoutTracking: " + error.localizedDescription)
}
}
let speedPerInterval = HKQuantity(unit: HKUnit.meter().unitDivided(by: HKUnit.second()),
doubleValue: Speed * 0.277778)
guard let speedType = HKQuantityType.quantityType(
forIdentifier: .runningSpeed) else {
return
}
let speedPerIntervalSample = HKQuantitySample(type: speedType,
quantity: speedPerInterval,
start: WorkoutTracking.lastDateMetric,
end: Date())
workoutBuilder.add([speedPerIntervalSample]) {(success, error) in
if let error = error {
SwiftDebug.qtDebug("WorkoutTracking: " + error.localizedDescription)
}
}
} else {
// Fallback on earlier versions
}
}/* else if(sport == 2) {
if #available(watchOSApplicationExtension 10.0, *) {
let speedPerInterval = HKQuantity(unit: HKUnit.meter().unitDivided(by: HKUnit.second()),
doubleValue: Speed * 0.277778)
guard let speedType = HKQuantityType.quantityType(
forIdentifier: .walkingSpeed) else {
return
}
let speedPerIntervalSample = HKQuantitySample(type: speedType,
quantity: speedPerInterval,
start: WorkoutTracking.lastDateMetric,
end: Date())
workoutBuilder.add([speedPerIntervalSample]) {(success, error) in
if let error = error {
SwiftDebug.qtDebug("WorkoutTracking: " + error)
}
}
} else {
// Fallback on earlier versions
}
}*/
// TODO HANDLE WALKING, ROWING AND ELLIPTICAL
WorkoutTracking.lastDateMetric = Date()
}
}
extension WorkoutTracking {
private func fetchLatestHeartRateSample(completionHandler: @escaping (_ sample: HKQuantitySample?) -> Void) {
guard let sampleType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate) else {
completionHandler(nil)
return
}
let predicate = HKQuery.predicateForSamples(withStart: Date.distantPast, 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
if let error = error {
print("Error: \(error.localizedDescription)")
return
}
completionHandler(results?[0] as? HKQuantitySample)
}
healthStore.execute(query)
}
}

View File

@@ -5,6 +5,7 @@
class lockscreen {
public:
bool appleWatchAppInstalled();
void setTimerDisabled();
void request();
long heartRate();
@@ -15,6 +16,10 @@ class lockscreen {
void setSpeed(double speed);
void setPower(double power);
void setCadence(double cadence);
void startWorkout(unsigned short deviceType);
void stopWorkout();
void workoutTrackingUpdate(double speed, unsigned short cadence, unsigned short watt, unsigned short currentCalories, unsigned long long currentSteps, unsigned char deviceType, double distance);
// virtualbike
void virtualbike_ios();
@@ -28,7 +33,7 @@ class lockscreen {
double virtualbike_getPowerRequested();
bool virtualbike_updateFTMS(unsigned short normalizeSpeed, unsigned char currentResistance,
unsigned short currentCadence, unsigned short currentWatt,
unsigned short CrankRevolutions, unsigned short LastCrankEventTime, signed short Gears);
unsigned short CrankRevolutions, unsigned short LastCrankEventTime, signed short Gears, unsigned short currentCalories, unsigned int Distance);
int virtualbike_getLastFTMSMessage(unsigned char *message);
// virtualrower
@@ -38,7 +43,7 @@ class lockscreen {
unsigned short currentCadence, unsigned short currentWatt,
unsigned short CrankRevolutions, unsigned short LastCrankEventTime,
unsigned short StrokesCount, unsigned int Distance, unsigned short KCal,
unsigned short Pace);
unsigned short Pace, unsigned short currentCalories);
int virtualrower_getLastFTMSMessage(unsigned char *message);
// virtualtreadmill
@@ -49,7 +54,8 @@ class lockscreen {
double virtualtreadmill_getPowerRequested();
bool virtualtreadmill_updateFTMS(unsigned short normalizeSpeed, unsigned char currentResistance,
unsigned short currentCadence, unsigned short currentWatt,
unsigned short currentInclination, unsigned long long currentDistance);
unsigned short currentInclination, unsigned long long currentDistance, unsigned short currentCalories,
qint32 currentSteps);
// volume
double getVolume();

View File

@@ -7,6 +7,7 @@
#import <ConnectIQ/ConnectIQ.h>
#import "qdomyoszwift-Swift2.h"
#include "ios/lockscreen.h"
#include "devices/bluetoothdevice.h"
#include <QDebug>
#include "ios/AdbClient.h"
#include "ios/ios_eliteariafan.h"
@@ -25,6 +26,7 @@ static virtualbike_ios_swift* _virtualbike = nil;
static virtualbike_zwift* _virtualbike_zwift = nil;
static virtualrower_zwift* _virtualrower = nil;
static virtualtreadmill_zwift* _virtualtreadmill_zwift = nil;
static WorkoutTracking* workoutTracking = nil;
static GarminConnect* Garmin = 0;
@@ -37,6 +39,36 @@ static zwift_protobuf_layer* zwiftProtobufLayer = nil;
static NSString* profile_selected;
bool lockscreen::appleWatchAppInstalled() {
if ([WCSession isSupported]) {
// Get the default session
WCSession *session = [WCSession defaultSession];
// Activate the session
[session activateSession];
// Check if a watch is paired and the app is installed
if (session.isPaired && session.isWatchAppInstalled) {
// An Apple Watch is paired and has the companion app installed
qDebug() << "Apple Watch is paired and app is installed";
return true;
} else if (session.isPaired) {
// An Apple Watch is paired but doesn't have the companion app
qDebug() << "Apple Watch is paired but app is not installed";
return false;
} else {
// No Apple Watch is paired
qDebug() << "No Apple Watch is paired";
return false;
}
} else {
// This device doesn't support Watch connectivity
qDebug() << "Watch connectivity is not supported on this device";
return false;
}
return false;
}
void lockscreen::setTimerDisabled() {
[[UIApplication sharedApplication] setIdleTimerDisabled: YES];
}
@@ -49,7 +81,22 @@ void lockscreen::request()
if (@available(iOS 13, *)) {
Garmin = [[GarminConnect alloc] init];
}
_adb = [[AdbClient alloc] initWithVerbose:YES];
// just to be sure, I built the library for iOS17 only but theorically we can use any iOS version
if (@available(iOS 17, *)) {
workoutTracking = [[WorkoutTracking alloc] init];
[WorkoutTracking requestAuth];
_adb = [[AdbClient alloc] initWithVerbose:YES];
}
}
void lockscreen::startWorkout(unsigned short deviceType) {
if(workoutTracking != nil && !appleWatchAppInstalled())
[workoutTracking startWorkOutWithDeviceType:deviceType];
}
void lockscreen::stopWorkout() {
if(workoutTracking != nil && !appleWatchAppInstalled())
[workoutTracking stopWorkOut];
}
long lockscreen::heartRate()
@@ -110,6 +157,11 @@ void lockscreen::virtualbike_setCadence(unsigned short crankRevolutions, unsigne
[_virtualbike updateCadenceWithCrankRevolutions:crankRevolutions LastCrankEventTime:lastCrankEventTime];
}
void lockscreen::workoutTrackingUpdate(double speed, unsigned short cadence, unsigned short watt, unsigned short currentCalories, unsigned long long currentSteps, unsigned char deviceType, double currentDistance) {
if(workoutTracking != nil && !appleWatchAppInstalled())
[workoutTracking addMetricsWithPower:watt cadence:cadence*2 speed:speed * 100 kcal:currentCalories steps:currentSteps deviceType:deviceType distance:currentDistance];
}
void lockscreen::virtualbike_zwift_ios(bool disable_hr, bool garmin_bluetooth_compatibility, bool zwift_play_emulator, bool watt_bike_emulator)
{
_virtualbike_zwift = [[virtualbike_zwift alloc] initWithDisable_hr:disable_hr garmin_bluetooth_compatibility:garmin_bluetooth_compatibility zwift_play_emulator:zwift_play_emulator watt_bike_emulator:watt_bike_emulator];
@@ -156,15 +208,21 @@ double lockscreen::virtualbike_getPowerRequested()
return 0;
}
bool lockscreen::virtualbike_updateFTMS(UInt16 normalizeSpeed, UInt8 currentResistance, UInt16 currentCadence, UInt16 currentWatt, UInt16 CrankRevolutions, UInt16 LastCrankEventTime, signed short Gears)
bool lockscreen::virtualbike_updateFTMS(UInt16 normalizeSpeed, UInt8 currentResistance, UInt16 currentCadence, UInt16 currentWatt, UInt16 CrankRevolutions, UInt16 LastCrankEventTime, signed short Gears, UInt16 currentCalories, UInt32 Distance)
{
if(workoutTracking != nil && !appleWatchAppInstalled())
[workoutTracking addMetricsWithPower:currentWatt cadence:currentCadence speed:normalizeSpeed kcal:currentCalories steps:0 deviceType: bluetoothdevice::BLUETOOTH_TYPE::BIKE distance:Distance];
if(_virtualbike_zwift != nil)
return [_virtualbike_zwift updateFTMSWithNormalizeSpeed:normalizeSpeed currentCadence:currentCadence currentResistance:currentResistance currentWatt:currentWatt CrankRevolutions:CrankRevolutions LastCrankEventTime:LastCrankEventTime Gears:Gears];
return 0;
}
bool lockscreen::virtualrower_updateFTMS(UInt16 normalizeSpeed, UInt8 currentResistance, UInt16 currentCadence, UInt16 currentWatt, UInt16 CrankRevolutions, UInt16 LastCrankEventTime, UInt16 StrokesCount, UInt32 Distance, UInt16 KCal, UInt16 Pace)
bool lockscreen::virtualrower_updateFTMS(UInt16 normalizeSpeed, UInt8 currentResistance, UInt16 currentCadence, UInt16 currentWatt, UInt16 CrankRevolutions, UInt16 LastCrankEventTime, UInt16 StrokesCount, UInt32 Distance, UInt16 KCal, UInt16 Pace, UInt16 currentCalories)
{
if(workoutTracking != nil && !appleWatchAppInstalled())
[workoutTracking addMetricsWithPower:currentWatt cadence:currentCadence speed:normalizeSpeed kcal:currentCalories steps:0 deviceType: bluetoothdevice::BLUETOOTH_TYPE::ROWING distance:Distance];
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];
return 0;
@@ -216,8 +274,11 @@ double lockscreen::virtualtreadmill_getPowerRequested()
return 0;
}
bool lockscreen::virtualtreadmill_updateFTMS(UInt16 normalizeSpeed, UInt8 currentResistance, UInt16 currentCadence, UInt16 currentWatt, UInt16 currentInclination, UInt64 currentDistance)
bool lockscreen::virtualtreadmill_updateFTMS(UInt16 normalizeSpeed, UInt8 currentResistance, UInt16 currentCadence, UInt16 currentWatt, UInt16 currentInclination, UInt64 currentDistance, UInt16 currentCalories, qint32 currentSteps)
{
if(workoutTracking != nil && !appleWatchAppInstalled())
[workoutTracking addMetricsWithPower:currentWatt cadence:currentCadence speed:normalizeSpeed kcal:currentCalories steps:currentSteps deviceType:bluetoothdevice::BLUETOOTH_TYPE::TREADMILL distance:currentDistance];
if(_virtualtreadmill_zwift != nil)
return [_virtualtreadmill_zwift updateFTMSWithNormalizeSpeed:normalizeSpeed currentCadence:currentCadence currentResistance:currentResistance currentWatt:currentWatt currentInclination:currentInclination currentDistance:currentDistance];
return 0;

View File

@@ -1341,7 +1341,7 @@ void virtualbike::bikeProvider() {
// really connected to a device
if (h->virtualbike_updateFTMS(normalizeSpeed, (char)Bike->currentResistance().value(),
(uint16_t)Bike->currentCadence().value() * 2, (uint16_t)normalizeWattage,
Bike->currentCrankRevolutions(), Bike->lastCrankEventTime(), ((bike*)Bike)->gears())) {
Bike->currentCrankRevolutions(), Bike->lastCrankEventTime(), ((bike*)Bike)->gears(), Bike->calories().value(), Bike->odometer() * 1000.0)) {
h->virtualbike_setHeartRate(Bike->currentHeart().value());
uint8_t ftms_message[255];

View File

@@ -347,7 +347,7 @@ void virtualrower::rowerProvider() {
normalizeSpeed, (char)Rower->currentResistance().value(), (uint16_t)Rower->currentCadence().value() * 2,
(uint16_t)normalizeWattage, Rower->currentCrankRevolutions(), Rower->lastCrankEventTime(),
((rower *)Rower)->currentStrokesCount().value(), Rower->odometer() * 1000, Rower->calories().value(),
QTime(0, 0, 0).secsTo(((rower *)Rower)->currentPace()))) {
QTime(0, 0, 0).secsTo(((rower *)Rower)->currentPace()), Rower->calories().value())) {
h->virtualrower_setHeartRate(Rower->currentHeart().value());
uint8_t ftms_message[255];

View File

@@ -546,7 +546,8 @@ void virtualtreadmill::treadmillProvider() {
if (h->virtualtreadmill_updateFTMS(
normalizeSpeed, 0, (uint16_t)((treadmill *)treadMill)->currentCadence().value() * cadence_multiplier,
(uint16_t)((treadmill *)treadMill)->wattsMetric().value(),
inclination * 10, (uint64_t)(((treadmill *)treadMill)->odometer() * 1000.0))) {
inclination * 10, (uint64_t)(((treadmill *)treadMill)->odometer() * 1000.0), ((treadmill *)treadMill)->calories().value(),
((treadmill *)treadMill)->currentStepCount().value())) {
h->virtualtreadmill_setHeartRate(((treadmill *)treadMill)->currentHeart().value());
lastSlopeChanged = h->virtualtreadmill_lastChangeCurrentSlope();
if ((uint64_t)QDateTime::currentSecsSinceEpoch() < lastSlopeChanged + slopeTimeoutSecs)