From d8ed66e0ead110d14be135904f0ef126f535e2c2 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 2 Jan 2024 13:50:58 +0100 Subject: [PATCH 1/5] Update qzsettings.cpp --- src/qzsettings.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qzsettings.cpp b/src/qzsettings.cpp index ecbe86389..b78c31025 100644 --- a/src/qzsettings.cpp +++ b/src/qzsettings.cpp @@ -691,9 +691,9 @@ const QString QZSettings::default_proformtdf1ip = QStringLiteral(""); const QString QZSettings::proform_bike_225_csx = QStringLiteral("proform_bike_225_csx"); const QString QZSettings::proform_treadmill_l6_0s = QStringLiteral("proform_treadmill_l6_0s"); const QString QZSettings::zwift_username = QStringLiteral("zwift_username"); -const QString QZSettings::default_zwift_username = QStringLiteral("username"); +const QString QZSettings::default_zwift_username = QStringLiteral(""); const QString QZSettings::zwift_password = QStringLiteral("zwift_password"); -const QString QZSettings::default_zwift_password = QStringLiteral("password"); +const QString QZSettings::default_zwift_password = QStringLiteral(""); const uint32_t allSettingsCount = 581; From ab63a29cadce62b41eb1f37d0f6069e70c176086 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 2 Jan 2024 13:13:55 +0000 Subject: [PATCH 2/5] Garmin power sensor over bluetooth (#1347) * trying to fix it (it shows as connectable but the metrics are 0) * trying a different approach on power data * it works! * Update lockscreen.mm * handled both apple fitness on watch and garmin! --- src/ios/lockscreen.h | 2 +- src/ios/lockscreen.mm | 4 +- src/ios/virtualbike_zwift.swift | 369 +++++++++++++++++--------------- src/qzsettings.cpp | 4 +- src/qzsettings.h | 3 + src/settings.qml | 32 ++- src/virtualbike.cpp | 5 +- 7 files changed, 241 insertions(+), 178 deletions(-) diff --git a/src/ios/lockscreen.h b/src/ios/lockscreen.h index d2c1ad0ab..8b8dc9466 100644 --- a/src/ios/lockscreen.h +++ b/src/ios/lockscreen.h @@ -18,7 +18,7 @@ class lockscreen { void virtualbike_setHeartRate(unsigned char heartRate); void virtualbike_setCadence(unsigned short crankRevolutions, unsigned short lastCrankEventTime); - void virtualbike_zwift_ios(bool disable_hr); + void virtualbike_zwift_ios(bool disable_hr, bool garmin_bluetooth_compatibility); double virtualbike_getCurrentSlope(); double virtualbike_getCurrentCRR(); double virtualbike_getCurrentCW(); diff --git a/src/ios/lockscreen.mm b/src/ios/lockscreen.mm index 42edf20b0..19894b11c 100644 --- a/src/ios/lockscreen.mm +++ b/src/ios/lockscreen.mm @@ -102,9 +102,9 @@ [_virtualbike updateCadenceWithCrankRevolutions:crankRevolutions LastCrankEventTime:lastCrankEventTime]; } -void lockscreen::virtualbike_zwift_ios(bool disable_hr) +void lockscreen::virtualbike_zwift_ios(bool disable_hr, bool garmin_bluetooth_compatibility) { - _virtualbike_zwift = [[virtualbike_zwift alloc] initWithDisable_hr: disable_hr]; + _virtualbike_zwift = [[virtualbike_zwift alloc] initWithDisable_hr:disable_hr garmin_bluetooth_compatibility:garmin_bluetooth_compatibility]; } void lockscreen::virtualrower_ios() diff --git a/src/ios/virtualbike_zwift.swift b/src/ios/virtualbike_zwift.swift index 4111868d8..7315efdc6 100644 --- a/src/ios/virtualbike_zwift.swift +++ b/src/ios/virtualbike_zwift.swift @@ -11,9 +11,9 @@ let TrainingStatusUuid = CBUUID(string: "0x2AD3"); @objc public class virtualbike_zwift: NSObject { private var peripheralManager: BLEPeripheralManagerZwift! - @objc public init(disable_hr: Bool) { + @objc public init(disable_hr: Bool, garmin_bluetooth_compatibility: Bool) { super.init() - peripheralManager = BLEPeripheralManagerZwift(disable_hr: disable_hr) + peripheralManager = BLEPeripheralManagerZwift(disable_hr: disable_hr, garmin_bluetooth_compatibility: garmin_bluetooth_compatibility) } @objc public func updateHeartRate(HeartRate: UInt8) @@ -61,6 +61,7 @@ let TrainingStatusUuid = CBUUID(string: "0x2AD3"); } class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate { + private var garmin_bluetooth_compatibility: Bool = false private var disable_hr: Bool = false private var peripheralManager: CBPeripheralManager! @@ -108,9 +109,11 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate { private var notificationTimer: Timer! = nil //var delegate: BLEPeripheralManagerDelegate? - init(disable_hr: Bool) { + init(disable_hr: Bool, garmin_bluetooth_compatibility: Bool) { super.init() self.disable_hr = disable_hr + self.garmin_bluetooth_compatibility = garmin_bluetooth_compatibility + peripheralManager = CBPeripheralManager(delegate: self, queue: nil) } @@ -119,107 +122,107 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate { case .poweredOn: print("Peripheral manager is up and running") - - self.heartRateService = CBMutableService(type: heartRateServiceUUID, primary: true) - let characteristicProperties: CBCharacteristicProperties = [.notify, .read, .write] - let characteristicPermissions: CBAttributePermissions = [.readable] - self.heartRateCharacteristic = CBMutableCharacteristic(type: heartRateCharacteristicUUID, - properties: characteristicProperties, - value: nil, - permissions: characteristicPermissions) - - heartRateService.characteristics = [heartRateCharacteristic] - self.peripheralManager.add(heartRateService) - - self.FitnessMachineService = CBMutableService(type: FitnessMachineServiceUuid, primary: true) - - let FitnessMachineFeatureProperties: CBCharacteristicProperties = [.read] - let FitnessMachineFeaturePermissions: CBAttributePermissions = [.readable] - self.FitnessMachineFeatureCharacteristic = CBMutableCharacteristic(type: FitnessMachineFeatureCharacteristicUuid, - properties: FitnessMachineFeatureProperties, - value: Data (bytes: [0x83, 0x14, 0x00, 0x00, 0x0c, 0xe0, 0x00, 0x00]), - permissions: FitnessMachineFeaturePermissions) - - let supported_resistance_level_rangeProperties: CBCharacteristicProperties = [.read] - let supported_resistance_level_rangePermissions: CBAttributePermissions = [.readable] - self.supported_resistance_level_rangeCharacteristic = CBMutableCharacteristic(type: supported_resistance_level_rangeCharacteristicUuid, - properties: supported_resistance_level_rangeProperties, - value: Data (bytes: [0x0A, 0x00, 0x96, 0x00, 0x0A, 0x00]), - permissions: supported_resistance_level_rangePermissions) - - let FitnessMachineControlPointProperties: CBCharacteristicProperties = [.indicate, .notify, .write] - let FitnessMachineControlPointPermissions: CBAttributePermissions = [.writeable] - self.FitnessMachineControlPointCharacteristic = CBMutableCharacteristic(type: FitnessMachineControlPointUuid, - properties: FitnessMachineControlPointProperties, - value: nil, - permissions: FitnessMachineControlPointPermissions) - - let indoorbikeProperties: CBCharacteristicProperties = [.notify, .read] - let indoorbikePermissions: CBAttributePermissions = [.readable] - self.indoorbikeCharacteristic = CBMutableCharacteristic(type: indoorbikeUuid, - properties: indoorbikeProperties, - value: nil, - permissions: indoorbikePermissions) - - let FitnessMachinestatusProperties: CBCharacteristicProperties = [.notify] - let FitnessMachinestatusPermissions: CBAttributePermissions = [.readable] - self.FitnessMachinestatusCharacteristic = CBMutableCharacteristic(type: FitnessMachinestatusUuid, - properties: FitnessMachinestatusProperties, - value: nil, - permissions: FitnessMachinestatusPermissions) - - let TrainingStatusProperties: CBCharacteristicProperties = [.read] - let TrainingStatusPermissions: CBAttributePermissions = [.readable] - self.TrainingStatusCharacteristic = CBMutableCharacteristic(type: TrainingStatusUuid, - properties: TrainingStatusProperties, - value: Data (bytes: [0x00, 0x01]), - permissions: TrainingStatusPermissions) - - FitnessMachineService.characteristics = [FitnessMachineFeatureCharacteristic, - supported_resistance_level_rangeCharacteristic, - FitnessMachineControlPointCharacteristic, - indoorbikeCharacteristic, - FitnessMachinestatusCharacteristic, - TrainingStatusCharacteristic ] - - self.peripheralManager.add(FitnessMachineService) - - self.CSCService = CBMutableService(type: CSCServiceUUID, primary: true) - - let CSCFeatureProperties: CBCharacteristicProperties = [.read] - let CSCFeaturePermissions: CBAttributePermissions = [.readable] - self.CSCFeatureCharacteristic = CBMutableCharacteristic(type: CSCFeatureCharacteristicUUID, - properties: CSCFeatureProperties, - value: Data (bytes: [0x02, 0x00]), - permissions: CSCFeaturePermissions) - - let SensorLocationProperties: CBCharacteristicProperties = [.read] - let SensorLocationPermissions: CBAttributePermissions = [.readable] - self.SensorLocationCharacteristic = CBMutableCharacteristic(type: SensorLocationCharacteristicUUID, - properties: SensorLocationProperties, - value: Data (bytes: [0x13]), - permissions: SensorLocationPermissions) - - let CSCMeasurementProperties: CBCharacteristicProperties = [.notify, .read] - let CSCMeasurementPermissions: CBAttributePermissions = [.readable] - self.CSCMeasurementCharacteristic = CBMutableCharacteristic(type: CSCMeasurementCharacteristicUUID, - properties: CSCMeasurementProperties, + if(!self.garmin_bluetooth_compatibility) { + self.heartRateService = CBMutableService(type: heartRateServiceUUID, primary: true) + let characteristicProperties: CBCharacteristicProperties = [.notify, .read, .write] + let characteristicPermissions: CBAttributePermissions = [.readable] + self.heartRateCharacteristic = CBMutableCharacteristic(type: heartRateCharacteristicUUID, + properties: characteristicProperties, value: nil, - permissions: CSCMeasurementPermissions) - - let SCControlPointProperties: CBCharacteristicProperties = [.indicate, .write] - let SCControlPointPermissions: CBAttributePermissions = [.writeable] - self.SCControlPointCharacteristic = CBMutableCharacteristic(type: SCControlPointCharacteristicUUID, - properties: SCControlPointProperties, - value: nil, - permissions: SCControlPointPermissions) - - CSCService.characteristics = [CSCFeatureCharacteristic, - SensorLocationCharacteristic, - CSCMeasurementCharacteristic, - SCControlPointCharacteristic] - self.peripheralManager.add(CSCService) - + permissions: characteristicPermissions) + + heartRateService.characteristics = [heartRateCharacteristic] + self.peripheralManager.add(heartRateService) + + self.FitnessMachineService = CBMutableService(type: FitnessMachineServiceUuid, primary: true) + + let FitnessMachineFeatureProperties: CBCharacteristicProperties = [.read] + let FitnessMachineFeaturePermissions: CBAttributePermissions = [.readable] + self.FitnessMachineFeatureCharacteristic = CBMutableCharacteristic(type: FitnessMachineFeatureCharacteristicUuid, + properties: FitnessMachineFeatureProperties, + value: Data (bytes: [0x83, 0x14, 0x00, 0x00, 0x0c, 0xe0, 0x00, 0x00]), + permissions: FitnessMachineFeaturePermissions) + + let supported_resistance_level_rangeProperties: CBCharacteristicProperties = [.read] + let supported_resistance_level_rangePermissions: CBAttributePermissions = [.readable] + self.supported_resistance_level_rangeCharacteristic = CBMutableCharacteristic(type: supported_resistance_level_rangeCharacteristicUuid, + properties: supported_resistance_level_rangeProperties, + value: Data (bytes: [0x0A, 0x00, 0x96, 0x00, 0x0A, 0x00]), + permissions: supported_resistance_level_rangePermissions) + + let FitnessMachineControlPointProperties: CBCharacteristicProperties = [.indicate, .notify, .write] + let FitnessMachineControlPointPermissions: CBAttributePermissions = [.writeable] + self.FitnessMachineControlPointCharacteristic = CBMutableCharacteristic(type: FitnessMachineControlPointUuid, + properties: FitnessMachineControlPointProperties, + value: nil, + permissions: FitnessMachineControlPointPermissions) + + let indoorbikeProperties: CBCharacteristicProperties = [.notify, .read] + let indoorbikePermissions: CBAttributePermissions = [.readable] + self.indoorbikeCharacteristic = CBMutableCharacteristic(type: indoorbikeUuid, + properties: indoorbikeProperties, + value: nil, + permissions: indoorbikePermissions) + + let FitnessMachinestatusProperties: CBCharacteristicProperties = [.notify] + let FitnessMachinestatusPermissions: CBAttributePermissions = [.readable] + self.FitnessMachinestatusCharacteristic = CBMutableCharacteristic(type: FitnessMachinestatusUuid, + properties: FitnessMachinestatusProperties, + value: nil, + permissions: FitnessMachinestatusPermissions) + + let TrainingStatusProperties: CBCharacteristicProperties = [.read] + let TrainingStatusPermissions: CBAttributePermissions = [.readable] + self.TrainingStatusCharacteristic = CBMutableCharacteristic(type: TrainingStatusUuid, + properties: TrainingStatusProperties, + value: Data (bytes: [0x00, 0x01]), + permissions: TrainingStatusPermissions) + + FitnessMachineService.characteristics = [FitnessMachineFeatureCharacteristic, + supported_resistance_level_rangeCharacteristic, + FitnessMachineControlPointCharacteristic, + indoorbikeCharacteristic, + FitnessMachinestatusCharacteristic, + TrainingStatusCharacteristic ] + + self.peripheralManager.add(FitnessMachineService) + + self.CSCService = CBMutableService(type: CSCServiceUUID, primary: true) + + let CSCFeatureProperties: CBCharacteristicProperties = [.read] + let CSCFeaturePermissions: CBAttributePermissions = [.readable] + self.CSCFeatureCharacteristic = CBMutableCharacteristic(type: CSCFeatureCharacteristicUUID, + properties: CSCFeatureProperties, + value: Data (bytes: [0x02, 0x00]), + permissions: CSCFeaturePermissions) + + let SensorLocationProperties: CBCharacteristicProperties = [.read] + let SensorLocationPermissions: CBAttributePermissions = [.readable] + self.SensorLocationCharacteristic = CBMutableCharacteristic(type: SensorLocationCharacteristicUUID, + properties: SensorLocationProperties, + value: Data (bytes: [0x0D]), + permissions: SensorLocationPermissions) + + let CSCMeasurementProperties: CBCharacteristicProperties = [.notify, .read] + let CSCMeasurementPermissions: CBAttributePermissions = [.readable] + self.CSCMeasurementCharacteristic = CBMutableCharacteristic(type: CSCMeasurementCharacteristicUUID, + properties: CSCMeasurementProperties, + value: nil, + permissions: CSCMeasurementPermissions) + + let SCControlPointProperties: CBCharacteristicProperties = [.indicate, .write] + let SCControlPointPermissions: CBAttributePermissions = [.writeable] + self.SCControlPointCharacteristic = CBMutableCharacteristic(type: SCControlPointCharacteristicUUID, + properties: SCControlPointProperties, + value: nil, + permissions: SCControlPointPermissions) + + CSCService.characteristics = [CSCFeatureCharacteristic, + SensorLocationCharacteristic, + CSCMeasurementCharacteristic, + SCControlPointCharacteristic] + self.peripheralManager.add(CSCService) + } self.PowerService = CBMutableService(type: PowerServiceUUID, primary: true) let PowerFeatureProperties: CBCharacteristicProperties = [.read] @@ -260,8 +263,13 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate { print("Failed to add service with error: \(uwError.localizedDescription)") return } - - if(disable_hr) { + + + if(garmin_bluetooth_compatibility) { + let advertisementData = [CBAdvertisementDataLocalNameKey: "QZ", + CBAdvertisementDataServiceUUIDsKey: [PowerServiceUUID]] as [String : Any] + peripheralManager.startAdvertising(advertisementData) + } else if(disable_hr) { // useful in order to hide HR from Garmin devices let advertisementData = [CBAdvertisementDataLocalNameKey: "QZ", CBAdvertisementDataServiceUUIDsKey: [FitnessMachineServiceUuid, CSCServiceUUID, PowerServiceUUID]] as [String : Any] @@ -272,7 +280,6 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate { peripheralManager.startAdvertising(advertisementData) } - print("Successfully added service") } @@ -371,56 +378,84 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate { return cadenceData } + + private var revolutions: UInt16 = 0 + private var timestamp: UInt16 = 0 + private var lastRevolution: UInt64 = UInt64(Date().timeIntervalSince1970 * 1000) + func calculatePower() -> Data { - let flags:UInt8 = 0x30 - - /* - // set measurement - measurement[2] = power & 0xFF; - measurement[3] = (power >> 8) & 0xFF; - - measurement[4] = wheelrev & 0xFF; - measurement[5] = (wheelrev >> 8) & 0xFF; - measurement[6] = (wheelrev >> 16) & 0xFF; - measurement[7] = (wheelrev >> 24) & 0xFF; - - measurement[8] = lastwheel & 0xFF; - measurement[9] = (lastwheel >> 8) & 0xFF; - - measurement[10] = crankrev & 0xFF; - measurement[11] = (crankrev >> 8) & 0xFF; - - measurement[12] = lastcrank & 0xFF; - measurement[13] = (lastcrank >> 8) & 0xFF; - - // speed & distance - // NOTE : based on Apple Watch default wheel dimension 700c x 2.5mm - // NOTE : 3 is theoretical crank:wheel gear ratio - // NOTE : 2.13 is circumference of 700c in meters - - wheelCount = crankCount * 3; - speed = cadence * 3 * 2.13 * 60 / 1000; - distance = wheelCount * 2.13 / 1000; - - #if defined(USEPOWER) - lastWheelK = lastCrankK * 2; // 1/2048 s granularity - #else - lastWheelK = lastCrankK * 1; // 1/1024 s granularity - #endif - - */ - - //self.delegate?.BLEPeripheralManagerCSCDidSendValue(flags, crankRevolutions: self.crankRevolutions, lastCrankEventTime: self.lastCrankEventTime) - let wheelrev: UInt32 = ((UInt32)(crankRevolutions)) * 3; - let lastWheel: UInt16 = (UInt16)((((UInt32)(lastCrankEventTime)) * 2) & 0xFFFF); - var power: [UInt8] = [flags, 0x00, (UInt8)(self.CurrentWatt & 0xFF), (UInt8)((self.CurrentWatt >> 8) & 0xFF), - (UInt8)(wheelrev & 0xFF), (UInt8)((wheelrev >> 8) & 0xFF), (UInt8)((wheelrev >> 16) & 0xFF), (UInt8)((wheelrev >> 24) & 0xFF), - (UInt8)(lastWheel & 0xFF), (UInt8)((lastWheel >> 8) & 0xFF), - (UInt8)(crankRevolutions & 0xFF), (UInt8)((crankRevolutions >> 8) & 0xFF), - (UInt8)(lastCrankEventTime & 0xFF), (UInt8)((lastCrankEventTime >> 8) & 0xFF)] - let powerData = Data(bytes: &power, count: MemoryLayout.size(ofValue: power)) - return powerData - } + if(garmin_bluetooth_compatibility) { + /* + // convert RPM to timestamp + if (cadenceInstantaneous != 0 && (millis()) >= (lastRevolution + (60000 / cadenceInstantaneous))) + { + revolutions++; // One crank revolution should have passed, add one revolution + timestamp = (unsigned short)(((millis() * 1024) / 1000) % 65536); // create timestamp and format + lastRevolution = millis(); + } + */ + + let millis : UInt64 = UInt64(Date().timeIntervalSince1970 * 1000) + if CurrentCadence != 0 && (millis >= lastRevolution + (60000 / UInt64(CurrentCadence / 2))) { + revolutions = revolutions + 1 + var newT: UInt64 = ((60000 / (UInt64(CurrentCadence / 2)) * 1024) / 1000) + newT = newT + UInt64(timestamp) + newT = newT % 65536 + timestamp = UInt16(newT) + lastRevolution = millis + } + + let flags:UInt8 = 0x20 + //self.delegate?.BLEPeripheralManagerCSCDidSendValue(flags, crankRevolutions: self.crankRevolutions, lastCrankEventTime: self.lastCrankEventTime) + var power: [UInt8] = [flags, 0x00, (UInt8)(self.CurrentWatt & 0xFF), (UInt8)((self.CurrentWatt >> 8) & 0xFF), (UInt8)(revolutions & 0xFF), (UInt8)((revolutions >> 8) & 0xFF), (UInt8)(timestamp & 0xFF), (UInt8)((timestamp >> 8) & 0xFF)] + let powerData = Data(bytes: &power, count: MemoryLayout.size(ofValue: power)) + return powerData + } else { + let flags:UInt8 = 0x30 + + /* + // set measurement + measurement[2] = power & 0xFF; + measurement[3] = (power >> 8) & 0xFF; + measurement[4] = wheelrev & 0xFF; + measurement[5] = (wheelrev >> 8) & 0xFF; + measurement[6] = (wheelrev >> 16) & 0xFF; + measurement[7] = (wheelrev >> 24) & 0xFF; + measurement[8] = lastwheel & 0xFF; + measurement[9] = (lastwheel >> 8) & 0xFF; + measurement[10] = crankrev & 0xFF; + measurement[11] = (crankrev >> 8) & 0xFF; + measurement[12] = lastcrank & 0xFF; + measurement[13] = (lastcrank >> 8) & 0xFF; + + // speed & distance + // NOTE : based on Apple Watch default wheel dimension 700c x 2.5mm + // NOTE : 3 is theoretical crank:wheel gear ratio + // NOTE : 2.13 is circumference of 700c in meters + + wheelCount = crankCount * 3; + speed = cadence * 3 * 2.13 * 60 / 1000; + distance = wheelCount * 2.13 / 1000; + #if defined(USEPOWER) + lastWheelK = lastCrankK * 2; // 1/2048 s granularity + #else + lastWheelK = lastCrankK * 1; // 1/1024 s granularity + #endif + + */ + + //self.delegate?.BLEPeripheralManagerCSCDidSendValue(flags, crankRevolutions: self.crankRevolutions, lastCrankEventTime: self.lastCrankEventTime) + let wheelrev: UInt32 = ((UInt32)(crankRevolutions)) * 3; + let lastWheel: UInt16 = (UInt16)((((UInt32)(lastCrankEventTime)) * 2) & 0xFFFF); + var power: [UInt8] = [flags, 0x00, (UInt8)(self.CurrentWatt & 0xFF), (UInt8)((self.CurrentWatt >> 8) & 0xFF), + (UInt8)(wheelrev & 0xFF), (UInt8)((wheelrev >> 8) & 0xFF), (UInt8)((wheelrev >> 16) & 0xFF), (UInt8)((wheelrev >> 24) & 0xFF), + (UInt8)(lastWheel & 0xFF), (UInt8)((lastWheel >> 8) & 0xFF), + (UInt8)(crankRevolutions & 0xFF), (UInt8)((crankRevolutions >> 8) & 0xFF), + (UInt8)(lastCrankEventTime & 0xFF), (UInt8)((lastCrankEventTime >> 8) & 0xFF)] + let powerData = Data(bytes: &power, count: MemoryLayout.size(ofValue: power)) + return powerData + } + } func calculateHeartRate() -> Data { //self.delegate?.BLEPeripheralManagerDidSendValue(self.heartRate) @@ -440,35 +475,27 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate { } @objc func updateSubscribers() { - let heartRateData = self.calculateHeartRate() - let indoorBikeData = self.calculateIndoorBike() - let cadenceData = self.calculateCadence() - let powerData = self.calculatePower() - - if(self.serviceToggle == 3) + if(self.serviceToggle == 3 || garmin_bluetooth_compatibility) { + let powerData = self.calculatePower() let ok = self.peripheralManager.updateValue(powerData, for: self.PowerMeasurementCharacteristic, onSubscribedCentrals: nil) if(ok) { self.serviceToggle = 0 } - } - - if(self.serviceToggle == 2) - { + } else if(self.serviceToggle == 2) { + let cadenceData = self.calculateCadence() let ok = self.peripheralManager.updateValue(cadenceData, for: self.CSCMeasurementCharacteristic, onSubscribedCentrals: nil) if(ok) { self.serviceToggle = self.serviceToggle + 1 } - } - else if(self.serviceToggle == 1) - { + } else if(self.serviceToggle == 1) { + let heartRateData = self.calculateHeartRate() let ok = self.peripheralManager.updateValue(heartRateData, for: self.heartRateCharacteristic, onSubscribedCentrals: nil) if(ok) { self.serviceToggle = self.serviceToggle + 1 } - } - else - { + } else { + let indoorBikeData = self.calculateIndoorBike() let ok = self.peripheralManager.updateValue(indoorBikeData, for: self.indoorbikeCharacteristic, onSubscribedCentrals: nil) if(ok) { self.serviceToggle = self.serviceToggle + 1 diff --git a/src/qzsettings.cpp b/src/qzsettings.cpp index b78c31025..2d27ecac5 100644 --- a/src/qzsettings.cpp +++ b/src/qzsettings.cpp @@ -694,8 +694,9 @@ const QString QZSettings::zwift_username = QStringLiteral("zwift_username"); const QString QZSettings::default_zwift_username = QStringLiteral(""); const QString QZSettings::zwift_password = QStringLiteral("zwift_password"); const QString QZSettings::default_zwift_password = QStringLiteral(""); +const QString QZSettings::garmin_bluetooth_compatibility = QStringLiteral("garmin_bluetooth_compatibility"); -const uint32_t allSettingsCount = 581; +const uint32_t allSettingsCount = 582; QVariant allSettings[allSettingsCount][2] = { {QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles}, @@ -1283,6 +1284,7 @@ QVariant allSettings[allSettingsCount][2] = { {QZSettings::proformtdf1ip, QZSettings::default_proformtdf1ip}, {QZSettings::zwift_username, QZSettings::default_zwift_username}, {QZSettings::zwift_password, QZSettings::default_zwift_password}, + {QZSettings::garmin_bluetooth_compatibility, QZSettings::default_garmin_bluetooth_compatibility}, }; diff --git a/src/qzsettings.h b/src/qzsettings.h index 91e93ac5c..b240c9cb2 100644 --- a/src/qzsettings.h +++ b/src/qzsettings.h @@ -1944,6 +1944,9 @@ class QZSettings { static const QString zwift_password; static const QString default_zwift_password; + static const QString garmin_bluetooth_compatibility; + static constexpr bool default_garmin_bluetooth_compatibility = false; + /** * @brief Write the QSettings values using the constants from this namespace. diff --git a/src/settings.qml b/src/settings.qml index f19fad683..5f4f45a49 100644 --- a/src/settings.qml +++ b/src/settings.qml @@ -861,6 +861,9 @@ import QtQuick.Dialogs 1.0 property string proformtdf1ip: "" property string zwift_username: "" property string zwift_password: "" + + // from version 2.16.31 + property bool garmin_bluetooth_compatibility: false } function paddingZeros(text, limit) { @@ -4574,13 +4577,40 @@ import QtQuick.Dialogs 1.0 } AccordionElement { - title: qsTr("Garmin Companion Options") + "\uD83E\uDD47" + title: qsTr("Garmin Options") + "\uD83E\uDD47" indicatRectColor: Material.color(Material.Grey) textColor: Material.color(Material.Grey) color: Material.backgroundColor accordionContent: ColumnLayout { spacing: 0 + SwitchDelegate { + text: qsTr("Garmin Bluetooth Sensor") + spacing: 0 + bottomPadding: 0 + topPadding: 0 + rightPadding: 0 + leftPadding: 0 + clip: false + checked: settings.garmin_bluetooth_compatibility + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.fillWidth: true + onClicked: { settings.garmin_bluetooth_compatibility = checked; window.settings_restart_to_apply = true; } + } + + Label { + text: qsTr("If you want to send metrics to your Garmin device from your Mac, enable this. Otherwise leave it disabled.") + font.bold: true + font.italic: true + font.pixelSize: 9 + textFormat: Text.PlainText + wrapMode: Text.WordWrap + verticalAlignment: Text.AlignVCenter + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.fillWidth: true + color: Material.color(Material.Lime) + } + SwitchDelegate { text: qsTr("Enable Companion App") spacing: 0 diff --git a/src/virtualbike.cpp b/src/virtualbike.cpp index 4088e9b63..b4bd57d4b 100644 --- a/src/virtualbike.cpp +++ b/src/virtualbike.cpp @@ -28,6 +28,7 @@ virtualbike::virtualbike(bluetoothdevice *t, bool noWriteResistance, bool noHear bool echelon = settings.value(QZSettings::virtual_device_echelon, QZSettings::default_virtual_device_echelon).toBool(); bool ifit = settings.value(QZSettings::virtual_device_ifit, QZSettings::default_virtual_device_ifit).toBool(); + bool garmin_bluetooth_compatibility = settings.value(QZSettings::garmin_bluetooth_compatibility, QZSettings::default_garmin_bluetooth_compatibility).toBool(); if (settings.value(QZSettings::dircon_yes, QZSettings::default_dircon_yes).toBool()) { dirconManager = new DirconManager(Bike, bikeResistanceOffset, bikeResistanceGain, this); @@ -53,12 +54,12 @@ virtualbike::virtualbike(bluetoothdevice *t, bool noWriteResistance, bool noHear #ifndef IO_UNDER_QT bool ios_peloton_workaround = settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool(); - if (ios_peloton_workaround && !cadence && !echelon && !ifit && !heart_only && !power) { + if ((ios_peloton_workaround && !cadence && !echelon && !ifit && !heart_only) || garmin_bluetooth_compatibility) { qDebug() << "ios_zwift_workaround activated!"; h = new lockscreen(); h->virtualbike_zwift_ios( - settings.value(QZSettings::bike_heartrate_service, QZSettings::default_bike_heartrate_service).toBool()); + settings.value(QZSettings::bike_heartrate_service, QZSettings::default_bike_heartrate_service).toBool(), garmin_bluetooth_compatibility); } else #endif From fed4c0ca0f449655b56ad04c0d4ccf0cf779afb0 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 2 Jan 2024 14:17:55 +0100 Subject: [PATCH 3/5] Update project.pbxproj --- .../qdomyoszwift.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj b/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj index da5974511..d9809d755 100644 --- a/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj +++ b/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj @@ -3754,7 +3754,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 694; + CURRENT_PROJECT_VERSION = 697; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; HEADER_SEARCH_PATHS = ( @@ -3924,7 +3924,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 694; + CURRENT_PROJECT_VERSION = 697; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; @@ -4130,7 +4130,7 @@ CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 694; + CURRENT_PROJECT_VERSION = 697; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -4226,7 +4226,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 694; + CURRENT_PROJECT_VERSION = 697; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; @@ -4318,7 +4318,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 694; + CURRENT_PROJECT_VERSION = 697; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; ENABLE_PREVIEWS = YES; @@ -4432,7 +4432,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 694; + CURRENT_PROJECT_VERSION = 697; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; From 9065ef0c195436ff2e8cfc6a8c945dfab2a6a5a3 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Wed, 3 Jan 2024 09:16:28 +0100 Subject: [PATCH 4/5] Support Nordictrack S25 treadmill #1933 --- src/proformtreadmill.cpp | 147 +++++++++++++++++++++++++++++++++++++-- src/qzsettings.cpp | 5 +- src/qzsettings.h | 2 + src/settings.qml | 17 ++++- 4 files changed, 163 insertions(+), 8 deletions(-) diff --git a/src/proformtreadmill.cpp b/src/proformtreadmill.cpp index 8910856cb..71d645662 100644 --- a/src/proformtreadmill.cpp +++ b/src/proformtreadmill.cpp @@ -72,6 +72,8 @@ void proformtreadmill::forceIncline(double incline) { settings.value(QZSettings::proform_treadmill_se, QZSettings::default_proform_treadmill_se).toBool(); bool norditrack_s25i_treadmill = settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25i_treadmill).toBool(); + bool norditrack_s25_treadmill = + settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25_treadmill).toBool(); bool proform_treadmill_z1300i = settings.value(QZSettings::proform_treadmill_z1300i, QZSettings::default_proform_treadmill_z1300i).toBool(); bool nordictrack_s20_treadmill = settings.value(QZSettings::nordictrack_s20_treadmill, @@ -97,7 +99,7 @@ void proformtreadmill::forceIncline(double incline) { if (norditrack_s25i_treadmill) { write[14] = write[11] + write[12] + 0x11; - } else if (proform_treadmill_8_0 || proform_treadmill_9_0 || proform_treadmill_se || proform_treadmill_z1300i || proform_treadmill_l6_0s) { + } else if (proform_treadmill_8_0 || proform_treadmill_9_0 || proform_treadmill_se || proform_treadmill_z1300i || proform_treadmill_l6_0s || norditrack_s25_treadmill) { write[14] = write[11] + write[12] + 0x12; } else if (!nordictrack_t65s_treadmill && !nordictrack_s30_treadmill && !nordictrack_s20_treadmill && !nordictrack_t65s_83_treadmill) { for (uint8_t i = 0; i < 7; i++) { @@ -132,6 +134,8 @@ void proformtreadmill::forceSpeed(double speed) { .toBool(); bool norditrack_s25i_treadmill = settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25i_treadmill).toBool(); + bool norditrack_s25_treadmill = + settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25_treadmill).toBool(); bool proform_treadmill_z1300i = settings.value(QZSettings::proform_treadmill_z1300i, QZSettings::default_proform_treadmill_z1300i).toBool(); bool nordictrack_s20_treadmill = settings.value(QZSettings::nordictrack_s20_treadmill, @@ -148,7 +152,7 @@ void proformtreadmill::forceSpeed(double speed) { if (norditrack_s25i_treadmill) { write[14] = write[11] + write[12] + 0x11; } else if (proform_treadmill_8_0 || proform_treadmill_9_0 || proform_treadmill_se || proform_treadmill_cadence_lt || - proform_treadmill_z1300i || proform_treadmill_l6_0s) { + proform_treadmill_z1300i || proform_treadmill_l6_0s || norditrack_s25_treadmill) { write[14] = write[11] + write[12] + 0x11; } else if (!nordictrack_t65s_treadmill && !nordictrack_s30_treadmill && !nordictrack_s20_treadmill && !nordictrack_t65s_83_treadmill) { for (uint8_t i = 0; i < 7; i++) { @@ -205,6 +209,9 @@ void proformtreadmill::update() { bool norditrack_s25i_treadmill = settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25i_treadmill) .toBool(); + bool norditrack_s25_treadmill = + settings.value(QZSettings::norditrack_s25_treadmill, QZSettings::default_norditrack_s25_treadmill) + .toBool(); bool nordictrack_incline_trainer_x7i = settings .value(QZSettings::nordictrack_incline_trainer_x7i, QZSettings::default_nordictrack_incline_trainer_x7i) @@ -1268,6 +1275,68 @@ void proformtreadmill::update() { if (counterPoll > 5) { counterPoll = 0; } + } else if (norditrack_s25_treadmill) { + uint8_t noOpData1[] = {0xfe, 0x02, 0x19, 0x03}; + uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00, 0x0f, 0x80, 0x0a, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t noOpData3[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x85, 0x00, 0x10, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t noOpData4[] = {0xfe, 0x02, 0x14, 0x03}; + uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x10, 0x04, 0x10, 0x02, 0x00, 0x0a, 0x1b, 0x94, 0x30, 0x00, 0x00, 0x40, 0x50, 0x00, 0x80}; + uint8_t noOpData6[] = {0xff, 0x02, 0x18, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + switch (counterPoll) { + case 0: + writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp")); + break; + case 1: + writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("noOp")); + break; + case 2: + writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("noOp")); + break; + case 3: + writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"), false, true); + break; + case 4: + writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("noOp")); + break; + case 5: + writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("noOp"), false, true); + if (requestInclination != -100) { + if (requestInclination < 0) + requestInclination = 0; + if (requestInclination != currentInclination().value() && requestInclination >= 0 && + requestInclination <= 15) { + emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination)); + forceIncline(requestInclination); + } + requestInclination = -100; + } + if (requestSpeed != -1) { + if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) { + emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed)); + forceSpeed(requestSpeed); + } + requestSpeed = -1; + } + if (requestStart != -1) { + emit debug(QStringLiteral("starting...")); + + // btinit(); + + requestStart = -1; + emit tapeStarted(); + } + if (requestStop != -1) { + emit debug(QStringLiteral("stopping...")); + // writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape"); + requestStop = -1; + } + break; + } + counterPoll++; + if (counterPoll > 5) { + counterPoll = 0; + } } else { uint8_t noOpData1[] = {0xfe, 0x02, 0x19, 0x03}; uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x07, 0x15, 0x02, 0x00, @@ -1381,7 +1450,6 @@ void proformtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha settings.value(QZSettings::proform_treadmill_z1300i, QZSettings::default_proform_treadmill_z1300i).toBool(); bool nordictrack_s20_treadmill = settings.value(QZSettings::nordictrack_s20_treadmill, QZSettings::default_nordictrack_s20_treadmill).toBool(); - bool proform_treadmill_l6_0s = settings.value(QZSettings::proform_treadmill_l6_0s, QZSettings::default_proform_treadmill_l6_0s).toBool(); double weight = settings.value(QZSettings::weight, QZSettings::default_weight).toFloat(); @@ -1483,6 +1551,8 @@ void proformtreadmill::btinit() { .toBool(); bool norditrack_s25i_treadmill = settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25i_treadmill).toBool(); + bool norditrack_s25_treadmill = + settings.value(QZSettings::norditrack_s25_treadmill, QZSettings::default_norditrack_s25_treadmill).toBool(); bool nordictrack_t65s_83_treadmill = settings.value(QZSettings::nordictrack_t65s_83_treadmill, QZSettings::default_nordictrack_t65s_83_treadmill) .toBool(); @@ -1878,6 +1948,73 @@ void proformtreadmill::btinit() { QThread::msleep(sleepms); writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false); QThread::msleep(sleepms); + } else if (norditrack_s25_treadmill) { + uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02}; + uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t initData3[] = {0xfe, 0x02, 0x08, 0x02}; + uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x80, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t initData5[] = {0xfe, 0x02, 0x08, 0x02}; + uint8_t initData6[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x88, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t initData7[] = {0xfe, 0x02, 0x0a, 0x02}; + uint8_t initData8[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x82, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t initData9[] = {0xfe, 0x02, 0x0a, 0x02}; + uint8_t initData10[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t initData11[] = {0xfe, 0x02, 0x08, 0x02}; + uint8_t initData12[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t initData13[] = {0xfe, 0x02, 0x2c, 0x04}; + uint8_t initData14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x04, 0x28, 0x90, 0x07, 0x01, 0x38, 0xac, 0x12, 0x8e, 0xfc, 0x78, 0xee, 0x6a, 0xd0}; + uint8_t initData15[] = {0x01, 0x12, 0x54, 0xda, 0x56, 0xd4, 0x70, 0xf6, 0x62, 0xe8, 0x9c, 0x02, 0xbe, 0x2c, 0xc8, 0x7e, 0x1a, 0x80, 0x24, 0xca}; + uint8_t initData16[] = {0xff, 0x08, 0x66, 0x04, 0xe0, 0x98, 0x02, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t initData17[] = {0xfe, 0x02, 0x19, 0x03}; + uint8_t initData18[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00, 0x0f, 0x00, 0x10, 0x00, 0xd8, 0x1c, 0x48, 0x00, 0x00, 0xe0}; + uint8_t initData19[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData13, sizeof(initData13), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData14, sizeof(initData14), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData15, sizeof(initData15), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData16, sizeof(initData16), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData18, sizeof(initData18), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData19, sizeof(initData19), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); } else if (norditrack_s25i_treadmill) { uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02}; uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87, @@ -2921,14 +3058,14 @@ void proformtreadmill::stateChanged(QLowEnergyService::ServiceState state) { .toBool(); if (virtual_device_enabled) { if (!virtual_device_force_bike) { - debug("creating virtual treadmill interface..."); + emit debug("creating virtual treadmill interface..."); auto virtualTreadmill = new virtualtreadmill(this, noHeartService); connect(virtualTreadmill, &virtualtreadmill::debug, this, &proformtreadmill::debug); connect(virtualTreadmill, &virtualtreadmill::changeInclination, this, &proformtreadmill::changeInclinationRequested); this->setVirtualDevice(virtualTreadmill, VIRTUAL_DEVICE_MODE::PRIMARY); } else { - debug("creating virtual bike interface..."); + emit debug("creating virtual bike interface..."); auto virtualBike = new virtualbike(this); connect(virtualBike, &virtualbike::changeInclination, this, &proformtreadmill::changeInclinationRequested); diff --git a/src/qzsettings.cpp b/src/qzsettings.cpp index 2d27ecac5..f84b8ea48 100644 --- a/src/qzsettings.cpp +++ b/src/qzsettings.cpp @@ -695,8 +695,9 @@ const QString QZSettings::default_zwift_username = QStringLiteral(""); const QString QZSettings::zwift_password = QStringLiteral("zwift_password"); const QString QZSettings::default_zwift_password = QStringLiteral(""); const QString QZSettings::garmin_bluetooth_compatibility = QStringLiteral("garmin_bluetooth_compatibility"); +const QString QZSettings::norditrack_s25_treadmill = QStringLiteral("norditrack_s25_treadmill"); -const uint32_t allSettingsCount = 582; +const uint32_t allSettingsCount = 583; QVariant allSettings[allSettingsCount][2] = { {QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles}, @@ -1285,7 +1286,7 @@ QVariant allSettings[allSettingsCount][2] = { {QZSettings::zwift_username, QZSettings::default_zwift_username}, {QZSettings::zwift_password, QZSettings::default_zwift_password}, {QZSettings::garmin_bluetooth_compatibility, QZSettings::default_garmin_bluetooth_compatibility}, - + {QZSettings::norditrack_s25_treadmill, QZSettings::default_norditrack_s25_treadmill}, }; void QZSettings::qDebugAllSettings(bool showDefaults) { diff --git a/src/qzsettings.h b/src/qzsettings.h index b240c9cb2..4dd7af41b 100644 --- a/src/qzsettings.h +++ b/src/qzsettings.h @@ -1947,6 +1947,8 @@ class QZSettings { static const QString garmin_bluetooth_compatibility; static constexpr bool default_garmin_bluetooth_compatibility = false; + static const QString norditrack_s25_treadmill; + static constexpr int default_norditrack_s25_treadmill = false; /** * @brief Write the QSettings values using the constants from this namespace. diff --git a/src/settings.qml b/src/settings.qml index 5f4f45a49..24d9007a9 100644 --- a/src/settings.qml +++ b/src/settings.qml @@ -864,6 +864,7 @@ import QtQuick.Dialogs 1.0 // from version 2.16.31 property bool garmin_bluetooth_compatibility: false + property bool norditrack_s25_treadmill: false } function paddingZeros(text, limit) { @@ -5531,7 +5532,21 @@ import QtQuick.Dialogs 1.0 textColor: Material.color(Material.Yellow) color: Material.backgroundColor accordionContent: ColumnLayout { - spacing: 0 + spacing: 0 + SwitchDelegate { + text: qsTr("Nordictrack S25") + spacing: 0 + bottomPadding: 0 + topPadding: 0 + rightPadding: 0 + leftPadding: 0 + clip: false + checked: settings.norditrack_s25_treadmill + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.fillWidth: true + onClicked: { settings.norditrack_s25_treadmill = checked; window.settings_restart_to_apply = true; } + } + SwitchDelegate { id: nordictrackS25iDelegate text: qsTr("Nordictrack S25i") From 3dfe880b1b01f49637d7bc9c120d48e00d81d5da Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Wed, 3 Jan 2024 16:05:39 +0000 Subject: [PATCH 5/5] Sync Homeform loading with bluetooth engine (#1940) * Update python scripts to use AI.Server for OCR #1847 * Update bluetoothdevicetestsuite.cpp * Update bluetooth.cpp --- src/bluetooth.cpp | 8 ++++++++ src/bluetooth.h | 1 + src/homeform.cpp | 4 ++++ tst/Devices/bluetoothdevicetestsuite.cpp | 1 + 4 files changed, 14 insertions(+) diff --git a/src/bluetooth.cpp b/src/bluetooth.cpp index 748b59080..2e5b3cb74 100644 --- a/src/bluetooth.cpp +++ b/src/bluetooth.cpp @@ -548,6 +548,14 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) { qDebug() << device.deviceUuid(); #endif + // not required for mobile I guess +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) + if(!homeformLoaded) { + qDebug() << "homeform not yet loaded"; + return; + } +#endif + if (onlyDiscover) return; diff --git a/src/bluetooth.h b/src/bluetooth.h index 881186f42..2767f142d 100644 --- a/src/bluetooth.h +++ b/src/bluetooth.h @@ -147,6 +147,7 @@ class bluetooth : public QObject, public SignalHandler { bluetoothdevice *heartRateDevice() { return heartRateBelt; } QList devices; bool onlyDiscover = false; + volatile bool homeformLoaded = false; private: bool useDiscovery = false; diff --git a/src/homeform.cpp b/src/homeform.cpp index 634e0897f..d72941e7f 100644 --- a/src/homeform.cpp +++ b/src/homeform.cpp @@ -503,6 +503,8 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) { QObject::connect(stack, SIGNAL(floatingOpen()), this, SLOT(floatingOpen())); QObject::connect(stack, SIGNAL(openFloatingWindowBrowser()), this, SLOT(openFloatingWindowBrowser())); + qDebug() << "homeform constructor events linked"; + #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) QObject::connect(engine, &QQmlApplicationEngine::quit, &QGuiApplication::quit); #endif @@ -653,6 +655,8 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) { }); }); #endif + + bluetoothManager->homeformLoaded = true; } void homeform::setActivityDescription(QString desc) { activityDescription = desc; } diff --git a/tst/Devices/bluetoothdevicetestsuite.cpp b/tst/Devices/bluetoothdevicetestsuite.cpp index 43b9c8e27..615ff3592 100644 --- a/tst/Devices/bluetoothdevicetestsuite.cpp +++ b/tst/Devices/bluetoothdevicetestsuite.cpp @@ -12,6 +12,7 @@ void BluetoothDeviceTestSuite::tryDetectDevice(bluetooth &bt, try { // It is possible to use an EXPECT_NO_THROW here, but this // way is easier to place a breakpoint on the call to bt.deviceDiscovered. + bt.homeformLoaded = true; bt.deviceDiscovered(deviceInfo); } catch (...) { FAIL() << "Failed to perform device detection.";