mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
8 Commits
Mobi-Rower
...
build-1143
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2321838f46 | ||
|
|
37aab5ab8d | ||
|
|
fdc83d812d | ||
|
|
4284dcb6b4 | ||
|
|
542245696d | ||
|
|
97bd9f03e5 | ||
|
|
9e3b307d01 | ||
|
|
a1b313081e |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
src/qdomyos-zwift.pro.user
|
||||
|
||||
.idea/
|
||||
|
||||
src/Makefile
|
||||
|
||||
@@ -4445,7 +4445,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1142;
|
||||
CURRENT_PROJECT_VERSION = 1143;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1";
|
||||
@@ -4641,7 +4641,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1142;
|
||||
CURRENT_PROJECT_VERSION = 1143;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -4873,7 +4873,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1142;
|
||||
CURRENT_PROJECT_VERSION = 1143;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -4969,7 +4969,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1142;
|
||||
CURRENT_PROJECT_VERSION = 1143;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
@@ -5061,7 +5061,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1142;
|
||||
CURRENT_PROJECT_VERSION = 1143;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -5177,7 +5177,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1142;
|
||||
CURRENT_PROJECT_VERSION = 1143;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
|
||||
@@ -23,6 +23,7 @@ class WatchKitConnection: NSObject {
|
||||
static let shared = WatchKitConnection()
|
||||
public static var distance = 0.0
|
||||
public static var kcal = 0.0
|
||||
public static var totalKcal = 0.0
|
||||
public static var stepCadence = 0
|
||||
public static var speed = 0.0
|
||||
public static var cadence = 0.0
|
||||
@@ -70,6 +71,9 @@ extension WatchKitConnection: WatchKitConnectionProtocol {
|
||||
WatchKitConnection.distance = dDistance
|
||||
let dKcal = Double(result["kcal"] as! Double)
|
||||
WatchKitConnection.kcal = dKcal
|
||||
if let totalKcalDouble = result["totalKcal"] as? Double {
|
||||
WatchKitConnection.totalKcal = totalKcalDouble
|
||||
}
|
||||
|
||||
let dSpeed = Double(result["speed"] as! Double)
|
||||
WatchKitConnection.speed = dSpeed
|
||||
|
||||
@@ -28,6 +28,7 @@ class WorkoutTracking: NSObject {
|
||||
static let shared = WorkoutTracking()
|
||||
public static var distance = Double()
|
||||
public static var kcal = Double()
|
||||
public static var totalKcal = Double()
|
||||
public static var cadenceTimeStamp = NSDate().timeIntervalSince1970
|
||||
public static var cadenceLastSteps = Double()
|
||||
public static var cadenceSteps = 0
|
||||
@@ -166,6 +167,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceWalkingRunning)!,
|
||||
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
||||
HKSampleType.quantityType(forIdentifier: .basalEnergyBurned)!,
|
||||
HKSampleType.quantityType(forIdentifier: .cyclingPower)!,
|
||||
HKSampleType.quantityType(forIdentifier: .cyclingSpeed)!,
|
||||
HKSampleType.quantityType(forIdentifier: .cyclingCadence)!,
|
||||
@@ -185,6 +187,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceWalkingRunning)!,
|
||||
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
||||
HKSampleType.quantityType(forIdentifier: .basalEnergyBurned)!,
|
||||
HKSampleType.workoutType()
|
||||
])
|
||||
}
|
||||
@@ -223,24 +226,53 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
workoutSession.stopActivity(with: Date())
|
||||
workoutSession.end()
|
||||
|
||||
guard let quantityType = HKQuantityType.quantityType(
|
||||
// Write active calories
|
||||
guard let activeQuantityType = HKQuantityType.quantityType(
|
||||
forIdentifier: .activeEnergyBurned) else {
|
||||
return
|
||||
}
|
||||
|
||||
let unit = HKUnit.kilocalorie()
|
||||
let totalEnergyBurned = WorkoutTracking.kcal
|
||||
let quantity = HKQuantity(unit: unit,
|
||||
doubleValue: totalEnergyBurned)
|
||||
let activeEnergyBurned = WorkoutTracking.kcal
|
||||
let activeQuantity = HKQuantity(unit: unit,
|
||||
doubleValue: activeEnergyBurned)
|
||||
|
||||
let startDate = workoutSession.startDate ?? WorkoutTracking.lastDateMetric
|
||||
|
||||
let sample = HKCumulativeQuantitySeriesSample(type: quantityType,
|
||||
quantity: quantity,
|
||||
start: startDate,
|
||||
end: Date())
|
||||
let activeSample = HKCumulativeQuantitySeriesSample(type: activeQuantityType,
|
||||
quantity: activeQuantity,
|
||||
start: startDate,
|
||||
end: Date())
|
||||
|
||||
workoutBuilder.add([sample]) {(success, error) in}
|
||||
workoutBuilder.add([activeSample]) {(success, error) in
|
||||
if let error = error {
|
||||
print("WatchWorkoutTracking active calories: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
// Write total calories if available (when active calories setting is enabled)
|
||||
if WorkoutTracking.totalKcal > 0 {
|
||||
guard let basalQuantityType = HKQuantityType.quantityType(
|
||||
forIdentifier: .basalEnergyBurned) else {
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate basal calories as difference between total and active
|
||||
let basalEnergyBurned = WorkoutTracking.totalKcal - activeEnergyBurned
|
||||
let basalQuantity = HKQuantity(unit: unit,
|
||||
doubleValue: basalEnergyBurned)
|
||||
|
||||
let basalSample = HKCumulativeQuantitySeriesSample(type: basalQuantityType,
|
||||
quantity: basalQuantity,
|
||||
start: startDate,
|
||||
end: Date())
|
||||
|
||||
workoutBuilder.add([basalSample]) {(success, error) in
|
||||
if let error = error {
|
||||
print("WatchWorkoutTracking basal calories: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let unitDistance = HKUnit.mile()
|
||||
let miles = WorkoutTracking.distance
|
||||
@@ -273,6 +305,10 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,6 +370,10 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -399,6 +439,10 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,15 @@ void apexbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
emit resistanceRead(Resistance.value());
|
||||
m_pelotonResistance = Resistance.value();
|
||||
|
||||
qDebug() << QStringLiteral("Raw resistance: ") + QString::number(rawResistance) + QStringLiteral(", Inverted resistance: ") + QString::number(Resistance.value());
|
||||
// Parse cadence from 5th byte (index 4) and multiply by 2
|
||||
uint8_t rawCadence = newValue.at(4);
|
||||
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
|
||||
.toString()
|
||||
.startsWith(QStringLiteral("Disabled"))) {
|
||||
Cadence = rawCadence * 2;
|
||||
}
|
||||
|
||||
qDebug() << QStringLiteral("Raw resistance: ") + QString::number(rawResistance) + QStringLiteral(", Inverted resistance: ") + QString::number(Resistance.value()) + QStringLiteral(", Raw cadence: ") + QString::number(rawCadence) + QStringLiteral(", Final cadence: ") + QString::number(Cadence.value());
|
||||
}
|
||||
|
||||
if (newValue.length() != 10 || newValue.at(2) != 0x31) {
|
||||
@@ -162,41 +170,13 @@ void apexbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
uint16_t distanceData = (newValue.at(7) << 8) | ((uint8_t)newValue.at(8));
|
||||
double distance = ((double)distanceData);
|
||||
|
||||
if(distance != lastDistance) {
|
||||
if(lastDistance != 0) {
|
||||
double deltaDistance = distance - lastDistance;
|
||||
double deltaTime = fabs(now.msecsTo(lastTS));
|
||||
double timeHours = deltaTime / (1000.0 * 60.0 * 60.0);
|
||||
double k = 0.005333;
|
||||
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
|
||||
Speed = (deltaDistance *k) / timeHours; // Speed in distance units per hour
|
||||
} else {
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
watts(), Inclination.value(), Speed.value(),
|
||||
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
}
|
||||
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
|
||||
.toString()
|
||||
.startsWith(QStringLiteral("Disabled"))) {
|
||||
Cadence = Speed.value() / 0.37497622;
|
||||
}
|
||||
}
|
||||
lastDistance = distance;
|
||||
lastTS = now;
|
||||
qDebug() << "lastDistance" << lastDistance << "lastTS" << lastTS;
|
||||
// Calculate speed using the same method as echelon bike
|
||||
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
|
||||
Speed = 0.37497622 * ((double)Cadence.value());
|
||||
} else {
|
||||
// Check if speed and cadence should be reset due to timeout (2 seconds)
|
||||
if (lastTS.msecsTo(now) > 2000) {
|
||||
if (Speed.value() > 0) {
|
||||
Speed = 0;
|
||||
qDebug() << "Speed reset to 0 due to timeout";
|
||||
}
|
||||
if (Cadence.value() > 0) {
|
||||
Cadence = 0;
|
||||
qDebug() << "Cadence reset to 0 due to timeout";
|
||||
}
|
||||
lastTS = now;
|
||||
}
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
watts(), Inclination.value(), Speed.value(),
|
||||
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
}
|
||||
|
||||
if (watts())
|
||||
|
||||
@@ -109,7 +109,24 @@ QTime bluetoothdevice::maxPace() {
|
||||
double bluetoothdevice::odometerFromStartup() { return Distance.valueRaw(); }
|
||||
double bluetoothdevice::odometer() { return Distance.value(); }
|
||||
double bluetoothdevice::lapOdometer() { return Distance.lapValue(); }
|
||||
metric bluetoothdevice::calories() { return KCal; }
|
||||
metric bluetoothdevice::calories() {
|
||||
QSettings settings;
|
||||
bool activeOnly = settings.value(QZSettings::calories_active_only, QZSettings::default_calories_active_only).toBool();
|
||||
|
||||
if (activeOnly) {
|
||||
// Return active calories (total minus BMR)
|
||||
metric activeKCal = KCal;
|
||||
activeKCal.setValue(metric::calculateActiveKCal(KCal.value(), elapsed.value()));
|
||||
return activeKCal;
|
||||
} else {
|
||||
// Return total calories
|
||||
return KCal;
|
||||
}
|
||||
}
|
||||
|
||||
metric bluetoothdevice::totalCalories() {
|
||||
return KCal;
|
||||
}
|
||||
metric bluetoothdevice::jouls() { return m_jouls; }
|
||||
uint8_t bluetoothdevice::fanSpeed() { return FanSpeed; };
|
||||
bool bluetoothdevice::changeFanSpeed(uint8_t speed) {
|
||||
@@ -254,7 +271,17 @@ void bluetoothdevice::update_hr_from_external() {
|
||||
#ifndef IO_UNDER_QT
|
||||
lockscreen h;
|
||||
long appleWatchHeartRate = h.heartRate();
|
||||
h.setKcal(KCal.value());
|
||||
QSettings settings;
|
||||
bool activeOnly = settings.value(QZSettings::calories_active_only, QZSettings::default_calories_active_only).toBool();
|
||||
|
||||
if (activeOnly) {
|
||||
// When active calories setting is enabled, send both total and active calories
|
||||
h.setKcal(calories().value()); // This will be active calories
|
||||
h.setTotalKcal(totalCalories().value()); // This will be total calories
|
||||
} else {
|
||||
// When disabled, send total calories as before
|
||||
h.setKcal(calories().value()); // This will be total calories
|
||||
}
|
||||
h.setDistance(Distance.value());
|
||||
h.setSpeed(Speed.value());
|
||||
h.setPower(m_watt.value());
|
||||
|
||||
@@ -108,12 +108,18 @@ class bluetoothdevice : public QObject {
|
||||
|
||||
/**
|
||||
* @brief calories Gets a metric object to get and set the amount of energy expended.
|
||||
* Default implementation returns the protected KCal property. Units: kcal
|
||||
* Default implementation returns the protected KCal property, potentially adjusted for active calories. Units: kcal
|
||||
* Other implementations could have different units.
|
||||
* @return
|
||||
*/
|
||||
virtual metric calories();
|
||||
|
||||
/**
|
||||
* @brief totalCalories Gets total calories (including BMR) regardless of active calories setting.
|
||||
* @return Total calories metric
|
||||
*/
|
||||
virtual metric totalCalories();
|
||||
|
||||
/**
|
||||
* @brief jouls Gets a metric object to get and set the number of joules expended. Units: joules
|
||||
*/
|
||||
|
||||
@@ -61,6 +61,17 @@ void faketreadmill::update() {
|
||||
|
||||
Distance += ((Speed.value() / (double)3600.0) /
|
||||
((double)1000.0 / (double)(lastRefreshCharacteristicChanged.msecsTo(now))));
|
||||
|
||||
|
||||
KCal +=
|
||||
((((0.048 * ((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) +
|
||||
1.19) *
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 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
|
||||
|
||||
lastRefreshCharacteristicChanged = now;
|
||||
|
||||
// ******************************************* virtual treadmill init *************************************
|
||||
@@ -116,12 +127,6 @@ void faketreadmill::update() {
|
||||
#endif
|
||||
}
|
||||
|
||||
if (Heart.value()) {
|
||||
static double lastKcal = 0;
|
||||
if (KCal.value() < 0) // if the user pressed stop, the KCAL resets the accumulator
|
||||
lastKcal = abs(KCal.value());
|
||||
KCal = metric::calculateKCalfromHR(Heart.average(), elapsed.value()) + lastKcal;
|
||||
}
|
||||
}
|
||||
|
||||
void faketreadmill::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic,
|
||||
|
||||
@@ -7207,9 +7207,13 @@ void homeform::update() {
|
||||
|
||||
#ifndef Q_OS_IOS
|
||||
if (iphone_socket && iphone_socket->state() == QAbstractSocket::ConnectedState) {
|
||||
QSettings mdns_settings;
|
||||
bool activeOnly = mdns_settings.value(QZSettings::calories_active_only, QZSettings::default_calories_active_only).toBool();
|
||||
|
||||
QString toSend =
|
||||
"SENDER=PAD#HR=" + QString::number(bluetoothManager->device()->currentHeart().value()) +
|
||||
"#KCAL=" + QString::number(bluetoothManager->device()->calories().value()) +
|
||||
(activeOnly ? "#TOTALKCAL=" + QString::number(bluetoothManager->device()->totalCalories().value()) : "") +
|
||||
"#BCAD=" + QString::number(bluetoothManager->device()->currentCadence().value()) +
|
||||
"#SPD=" + QString::number(bluetoothManager->device()->currentSpeed().value()) +
|
||||
"#PWR=" + QString::number(bluetoothManager->device()->wattsMetric().value()) +
|
||||
|
||||
@@ -92,6 +92,18 @@ var pedometer = CMPedometer()
|
||||
Server.server?.send(createString(sender: sender))
|
||||
}
|
||||
|
||||
@objc public func setTotalKcal(totalKcal: Double) -> Void
|
||||
{
|
||||
var sender: String
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
sender = "PAD"
|
||||
} else {
|
||||
sender = "PHONE"
|
||||
}
|
||||
WatchKitConnection.totalKcal = totalKcal;
|
||||
Server.server?.send(createString(sender: sender))
|
||||
}
|
||||
|
||||
@objc public func setCadence(cadence: Double) -> Void
|
||||
{
|
||||
var sender: String
|
||||
@@ -129,7 +141,7 @@ var pedometer = CMPedometer()
|
||||
}
|
||||
|
||||
func createString(sender: String) -> String {
|
||||
return "SENDER=\(sender)#HR=\(WatchKitConnection.currentHeartRate)#KCAL=\(WatchKitConnection.kcal)#BCAD=\(WatchKitConnection.cadence)#SPD=\(WatchKitConnection.speed)#PWR=\(WatchKitConnection.power)#CAD=\(WatchKitConnection.stepCadence)#ODO=\(WatchKitConnection.distance)#";
|
||||
return "SENDER=\(sender)#HR=\(WatchKitConnection.currentHeartRate)#KCAL=\(WatchKitConnection.kcal)#TOTALKCAL=\(WatchKitConnection.totalKcal)#BCAD=\(WatchKitConnection.cadence)#SPD=\(WatchKitConnection.speed)#PWR=\(WatchKitConnection.power)#CAD=\(WatchKitConnection.stepCadence)#ODO=\(WatchKitConnection.distance)#";
|
||||
}
|
||||
|
||||
@objc func updateHeartRate() {
|
||||
|
||||
@@ -26,6 +26,7 @@ class WatchKitConnection: NSObject {
|
||||
static var distance = 0.0
|
||||
static var stepCadence = 0
|
||||
static var kcal = 0.0
|
||||
static var totalKcal = 0.0
|
||||
static var speed = 0.0
|
||||
static var power = 0.0
|
||||
static var cadence = 0.0
|
||||
@@ -55,6 +56,11 @@ class WatchKitConnection: NSObject {
|
||||
WatchKitConnection.kcal = Kcal;
|
||||
}
|
||||
|
||||
public func setTotalKCal(TotalKcal: Double) -> Void
|
||||
{
|
||||
WatchKitConnection.totalKcal = TotalKcal;
|
||||
}
|
||||
|
||||
public func setDistance(Distance: Double) -> Void
|
||||
{
|
||||
WatchKitConnection.distance = Distance;
|
||||
|
||||
@@ -31,6 +31,7 @@ protocol WorkoutTrackingProtocol {
|
||||
public static var lastDateMetric = Date()
|
||||
public static var distance = Double()
|
||||
public static var kcal = Double()
|
||||
public static var totalKcal = Double()
|
||||
public static var steps = Double()
|
||||
var sport: Int = 0
|
||||
let healthStore = HKHealthStore()
|
||||
@@ -100,6 +101,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceWalkingRunning)!,
|
||||
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
||||
HKSampleType.quantityType(forIdentifier: .basalEnergyBurned)!,
|
||||
HKSampleType.quantityType(forIdentifier: .cyclingPower)!,
|
||||
HKSampleType.quantityType(forIdentifier: .cyclingSpeed)!,
|
||||
HKSampleType.quantityType(forIdentifier: .cyclingCadence)!,
|
||||
@@ -119,6 +121,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceWalkingRunning)!,
|
||||
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
||||
HKSampleType.quantityType(forIdentifier: .basalEnergyBurned)!,
|
||||
HKSampleType.workoutType()
|
||||
])
|
||||
}
|
||||
@@ -166,22 +169,51 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
return
|
||||
}
|
||||
|
||||
guard let quantityType = HKQuantityType.quantityType(
|
||||
// Write active calories
|
||||
guard let activeQuantityType = HKQuantityType.quantityType(
|
||||
forIdentifier: .activeEnergyBurned) else {
|
||||
return
|
||||
}
|
||||
|
||||
let unit = HKUnit.kilocalorie()
|
||||
let totalEnergyBurned = WorkoutTracking.kcal
|
||||
let quantity = HKQuantity(unit: unit,
|
||||
doubleValue: totalEnergyBurned)
|
||||
let activeEnergyBurned = WorkoutTracking.kcal
|
||||
let activeQuantity = HKQuantity(unit: unit,
|
||||
doubleValue: activeEnergyBurned)
|
||||
|
||||
let sample = HKCumulativeQuantitySeriesSample(type: quantityType,
|
||||
quantity: quantity,
|
||||
start: startDate,
|
||||
end: Date())
|
||||
let activeSample = HKCumulativeQuantitySeriesSample(type: activeQuantityType,
|
||||
quantity: activeQuantity,
|
||||
start: startDate,
|
||||
end: Date())
|
||||
|
||||
workoutBuilder.add([sample]) {(success, error) in}
|
||||
workoutBuilder.add([activeSample]) {(success, error) in
|
||||
if let error = error {
|
||||
SwiftDebug.qtDebug("WorkoutTracking active calories: " + error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
// Write total calories if available (when active calories setting is enabled)
|
||||
if WorkoutTracking.totalKcal > 0 {
|
||||
guard let basalQuantityType = HKQuantityType.quantityType(
|
||||
forIdentifier: .basalEnergyBurned) else {
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate basal calories as difference between total and active
|
||||
let basalEnergyBurned = WorkoutTracking.totalKcal - activeEnergyBurned
|
||||
let basalQuantity = HKQuantity(unit: unit,
|
||||
doubleValue: basalEnergyBurned)
|
||||
|
||||
let basalSample = HKCumulativeQuantitySeriesSample(type: basalQuantityType,
|
||||
quantity: basalQuantity,
|
||||
start: startDate,
|
||||
end: Date())
|
||||
|
||||
workoutBuilder.add([basalSample]) {(success, error) in
|
||||
if let error = error {
|
||||
SwiftDebug.qtDebug("WorkoutTracking basal calories: " + error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let unitDistance = HKUnit.mile()
|
||||
let miles = WorkoutTracking.distance * 0.000621371
|
||||
@@ -215,6 +247,10 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
SwiftDebug.qtDebug("WorkoutTracking: " + error.localizedDescription)
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -270,14 +306,10 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
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
|
||||
}
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ class lockscreen {
|
||||
long heartRate();
|
||||
long stepCadence();
|
||||
void setKcal(double kcal);
|
||||
void setTotalKcal(double totalKcal);
|
||||
void setDistance(double distance);
|
||||
void setSteps(double steps);
|
||||
void setSpeed(double speed);
|
||||
|
||||
@@ -129,6 +129,11 @@ void lockscreen::setKcal(double kcal)
|
||||
[h setKcalWithKcal:kcal];
|
||||
}
|
||||
|
||||
void lockscreen::setTotalKcal(double totalKcal)
|
||||
{
|
||||
[h setTotalKcalWithTotalKcal:totalKcal];
|
||||
}
|
||||
|
||||
void lockscreen::setDistance(double distance)
|
||||
{
|
||||
[h setDistanceWithDistance:distance * 0.621371];
|
||||
|
||||
@@ -423,3 +423,44 @@ Women:
|
||||
return (T * ((0.6309 * H) + (0.1988 * W) + (0.2017 * A) - 55.0969) / 4.184);
|
||||
}
|
||||
}
|
||||
|
||||
double metric::calculateBMR() {
|
||||
// Calculate Basal Metabolic Rate using Mifflin-St Jeor equation
|
||||
// BMR (kcal/day) for males: 10 * weight(kg) + 6.25 * height(cm) - 5 * age + 5
|
||||
// BMR (kcal/day) for females: 10 * weight(kg) + 6.25 * height(cm) - 5 * age - 161
|
||||
|
||||
QSettings settings;
|
||||
QString sex = settings.value(QZSettings::sex, QZSettings::default_sex).toString();
|
||||
double weight = settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
|
||||
double age = settings.value(QZSettings::age, QZSettings::default_age).toDouble();
|
||||
double height = settings.value(QZSettings::height, QZSettings::default_height).toDouble();
|
||||
|
||||
// Full Mifflin-St Jeor equation with height
|
||||
if (sex.toLower().contains("female")) {
|
||||
return (10 * weight) + (6.25 * height) - (5 * age) - 161;
|
||||
} else {
|
||||
return (10 * weight) + (6.25 * height) - (5 * age) + 5;
|
||||
}
|
||||
}
|
||||
|
||||
double metric::calculateActiveKCal(double totalKCal, double elapsed) {
|
||||
QSettings settings;
|
||||
bool activeOnly = settings.value(QZSettings::calories_active_only, QZSettings::default_calories_active_only).toBool();
|
||||
|
||||
if (!activeOnly) {
|
||||
return totalKCal; // Return total calories if active-only mode is disabled
|
||||
}
|
||||
|
||||
// Calculate BMR in calories per second
|
||||
double bmrPerDay = calculateBMR();
|
||||
double bmrPerSecond = bmrPerDay / (24.0 * 60.0 * 60.0); // Convert from calories/day to calories/second
|
||||
|
||||
// Calculate BMR calories for the elapsed time
|
||||
double bmrForElapsed = bmrPerSecond * elapsed;
|
||||
|
||||
// Active calories = Total calories - BMR calories for the elapsed time
|
||||
double activeKCal = totalKCal - bmrForElapsed;
|
||||
|
||||
// Ensure we don't return negative calories
|
||||
return activeKCal > 0 ? activeKCal : 0;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ class metric {
|
||||
static double calculateWeightLoss(double kcal);
|
||||
static double calculateVO2Max(QList<SessionLine> *session);
|
||||
static double calculateKCalfromHR(double HR_AVG, double elapsed);
|
||||
static double calculateBMR();
|
||||
static double calculateActiveKCal(double totalKCal, double elapsed);
|
||||
|
||||
static double powerPeak(QList<SessionLine> *session, int seconds);
|
||||
|
||||
|
||||
@@ -974,9 +974,11 @@ const QString QZSettings::tile_auto_virtual_shifting_climb_enabled = QStringLite
|
||||
const QString QZSettings::tile_auto_virtual_shifting_climb_order = QStringLiteral("tile_auto_virtual_shifting_climb_order");
|
||||
const QString QZSettings::tile_auto_virtual_shifting_sprint_enabled = QStringLiteral("tile_auto_virtual_shifting_sprint_enabled");
|
||||
const QString QZSettings::tile_auto_virtual_shifting_sprint_order = QStringLiteral("tile_auto_virtual_shifting_sprint_order");
|
||||
const QString QZSettings::calories_active_only = QStringLiteral("calories_active_only");
|
||||
const QString QZSettings::height = QStringLiteral("height");
|
||||
|
||||
|
||||
const uint32_t allSettingsCount = 798;
|
||||
const uint32_t allSettingsCount = 800;
|
||||
|
||||
QVariant allSettings[allSettingsCount][2] = {
|
||||
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
|
||||
@@ -1796,6 +1798,8 @@ QVariant allSettings[allSettingsCount][2] = {
|
||||
{QZSettings::tile_auto_virtual_shifting_sprint_enabled, QZSettings::default_tile_auto_virtual_shifting_sprint_enabled},
|
||||
{QZSettings::tile_auto_virtual_shifting_sprint_order, QZSettings::default_tile_auto_virtual_shifting_sprint_order},
|
||||
{QZSettings::rogue_echo_bike, QZSettings::default_rogue_echo_bike},
|
||||
{QZSettings::calories_active_only, QZSettings::default_calories_active_only},
|
||||
{QZSettings::height, QZSettings::default_height},
|
||||
};
|
||||
|
||||
void QZSettings::qDebugAllSettings(bool showDefaults) {
|
||||
|
||||
@@ -2660,6 +2660,18 @@ class QZSettings {
|
||||
static const QString tile_auto_virtual_shifting_sprint_order;
|
||||
static constexpr int default_tile_auto_virtual_shifting_sprint_order = 57;
|
||||
|
||||
/**
|
||||
* @brief Calculate only active calories (exclude basal metabolic rate)
|
||||
*/
|
||||
static const QString calories_active_only;
|
||||
static constexpr bool default_calories_active_only = false;
|
||||
|
||||
/**
|
||||
* @brief User height in centimeters for BMR calculation
|
||||
*/
|
||||
static const QString height;
|
||||
static constexpr double default_height = 175.0;
|
||||
|
||||
/**
|
||||
* @brief Write the QSettings values using the constants from this namespace.
|
||||
* @param showDefaults Optionally indicates if the default should be shown with the key.
|
||||
|
||||
@@ -1198,6 +1198,8 @@ import Qt.labs.platform 1.1
|
||||
property int tile_auto_virtual_shifting_sprint_order: 57
|
||||
property string proform_rower_ip: ""
|
||||
property string ftms_elliptical: "Disabled"
|
||||
property bool calories_active_only: false
|
||||
property real height: 175.0
|
||||
}
|
||||
|
||||
|
||||
@@ -1317,6 +1319,62 @@ import Qt.labs.platform 1.1
|
||||
color: Material.color(Material.Lime)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
Label {
|
||||
id: labelHeight
|
||||
text: qsTr("Player Height") + "(" + (settings.miles_unit?"ft/in":"cm") + ")"
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
TextField {
|
||||
id: heightTextField
|
||||
text: settings.miles_unit ? Math.floor(settings.height / 30.48) + "'" + Math.round((settings.height % 30.48) / 2.54) + '"' : settings.height
|
||||
horizontalAlignment: Text.AlignRight
|
||||
Layout.fillHeight: false
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
//inputMethodHints: Qt.ImhFormattedNumbersOnly
|
||||
onAccepted: {
|
||||
if (settings.miles_unit) {
|
||||
var parts = text.match(/(\d+)'(\d+)"/);
|
||||
if (parts) {
|
||||
settings.height = parseInt(parts[1]) * 30.48 + parseInt(parts[2]) * 2.54;
|
||||
}
|
||||
} else {
|
||||
settings.height = text;
|
||||
}
|
||||
}
|
||||
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
|
||||
}
|
||||
Button {
|
||||
id: okHeightButton
|
||||
text: "OK"
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
onClicked: {
|
||||
if (settings.miles_unit) {
|
||||
var parts = heightTextField.text.match(/(\d+)'(\d+)"/);
|
||||
if (parts) {
|
||||
settings.height = parseInt(parts[1]) * 30.48 + parseInt(parts[2]) * 2.54;
|
||||
}
|
||||
} else {
|
||||
settings.height = heightTextField.text;
|
||||
}
|
||||
toast.show("Setting saved!");
|
||||
}
|
||||
}
|
||||
}
|
||||
Label {
|
||||
text: qsTr("Enter your height for more accurate BMR and active calories calculation. Use centimeters for metric or feet'inches\" format (e.g., 5'10\") for imperial units.")
|
||||
font.bold: true
|
||||
font.italic: true
|
||||
font.pixelSize: Qt.application.font.pixelSize - 2
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.WordWrap
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
color: Material.color(Material.Lime)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
Label {
|
||||
@@ -1721,7 +1779,35 @@ import Qt.labs.platform 1.1
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("This prevents your bike or treadmill from sending its calories-burned calculation to QZ and defaults to QZ’s more accurate calculation.")
|
||||
text: qsTr("This prevents your bike or treadmill from sending its calories-burned calculation to QZ and defaults to QZ's more accurate calculation.")
|
||||
font.bold: true
|
||||
font.italic: true
|
||||
font.pixelSize: Qt.application.font.pixelSize - 2
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.WordWrap
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
color: Material.color(Material.Lime)
|
||||
}
|
||||
|
||||
IndicatorOnlySwitch {
|
||||
id: switchActiveCaloriesOnlyDelegate
|
||||
text: qsTr("Calculate Active Calories Only")
|
||||
spacing: 0
|
||||
bottomPadding: 0
|
||||
topPadding: 0
|
||||
rightPadding: 0
|
||||
leftPadding: 0
|
||||
clip: false
|
||||
checked: settings.calories_active_only
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
onClicked: settings.calories_active_only = checked
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("Enable to calculate only active calories (excluding basal metabolic rate) similar to Apple Watch. When disabled, total calories including BMR are calculated. This affects both display and Apple Health integration.")
|
||||
font.bold: true
|
||||
font.italic: true
|
||||
font.pixelSize: Qt.application.font.pixelSize - 2
|
||||
|
||||
Reference in New Issue
Block a user