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