From 05d598ffcfc36f94e60f54e69637a5d1a55c7c66 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 21 Oct 2024 14:06:48 +0200 Subject: [PATCH 01/45] first raw version --- .../wahookickrsnapbike/wahookickrsnapbike.cpp | 24 ++++++- .../wahookickrsnapbike/wahookickrsnapbike.h | 67 ++++++++++++++++++- 2 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp b/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp index caa489acc..312fa3285 100644 --- a/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp +++ b/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp @@ -192,10 +192,16 @@ void wahookickrsnapbike::update() { } QThread::msleep(700); + QByteArray d = setWheelCircumference(gearsToWheelDiameter(gears())); + uint8_t e[20]; + setGears(1); + memcpy(e, d.constData(), d.length()); + writeCharacteristic(e, d.length(), "setWheelCircumference", false, true); + // required to the SS2K only one time Resistance = 0; emit resistanceRead(Resistance.value()); - initRequest = false; + initRequest = false; } else if (bluetoothDevice.isValid() && m_control->state() == QLowEnergyController::DiscoveredState //&& // gattCommunicationChannelService && @@ -259,7 +265,10 @@ void wahookickrsnapbike::update() { memcpy(b, a.constData(), a.length()); writeCharacteristic(b, a.length(), "setResistance", false, true); } else if (virtualBike && virtualBike->ftmsDeviceConnected() && lastGearValue != gears()) { - inclinationChanged(lastGrade, lastGrade); + QByteArray a = setWheelCircumference(gearsToWheelDiameter(gears())); + uint8_t b[20]; + memcpy(b, a.constData(), a.length()); + writeCharacteristic(b, a.length(), "setWheelCircumference", false, true); } lastGearValue = gears(); requestResistance = -1; @@ -280,6 +289,16 @@ void wahookickrsnapbike::update() { } } +double wahookickrsnapbike::gearsToWheelDiameter(double gear) { + GearTable table; + if(gear < 1) gear = 1; + else if(gear > 12) gear = 12; + double original_ratio = crankset / rear_cog_size; + GearTable::GearInfo g = table.getGear((int)gear); + double current_ratio = g.crankset / g.rearCog; + return (wheel_size / current_ratio) * original_ratio; +} + void wahookickrsnapbike::serviceDiscovered(const QBluetoothUuid &gatt) { emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString()); } @@ -823,7 +842,6 @@ void wahookickrsnapbike::inclinationChanged(double grade, double percentage) { emit debug(QStringLiteral("writing inclination ") + QString::number(grade)); QSettings settings; double g = grade; - g += gears(); QByteArray a = setSimGrade(g); uint8_t b[20]; memcpy(b, a.constData(), a.length()); diff --git a/src/devices/wahookickrsnapbike/wahookickrsnapbike.h b/src/devices/wahookickrsnapbike/wahookickrsnapbike.h index 97357ff85..8ece29240 100644 --- a/src/devices/wahookickrsnapbike/wahookickrsnapbike.h +++ b/src/devices/wahookickrsnapbike/wahookickrsnapbike.h @@ -56,7 +56,7 @@ class wahookickrsnapbike : public bike { _setWheelCircumference = 72, }; - private: + private: QByteArray unlockCommand(); QByteArray setResistanceMode(double resistance); QByteArray setStandardMode(uint8_t level); @@ -109,6 +109,71 @@ class wahookickrsnapbike : public bike { resistance_t lastForcedResistance = -1; + // cranks & co. + double crankset = 40; + double rear_cog_size = 14; + double wheel_size = 2230; // mm 700x44 + double gearsToWheelDiameter(double gear); + + class GearTable { + public: + struct GearInfo { + int gear; + int crankset; + int rearCog; + }; + + GearTable() { + gears = { + {1, 38, 44}, + {2, 38, 38}, + {3, 38, 32}, + {4, 38, 28}, + {5, 38, 24}, + {6, 38, 21}, + {7, 38, 19}, + {8, 38, 17}, + {9, 38, 15}, + {10, 38, 13}, + {11, 38, 11}, + {12, 38, 10} + }; + } + + void addGear(int gear, int crankset, int rearCog) { + gears.push_back({gear, crankset, rearCog}); + } + + void removeGear(int gear) { + gears.erase(std::remove_if(gears.begin(), gears.end(), + [gear](const GearInfo& info) { return info.gear == gear; }), + gears.end()); + } + + void printTable() const { + qDebug() << "| Gear | Crankset | Rear Cog |\n"; + qDebug() << "|------|----------|----------|\n"; + for (const auto& gear : gears) { + qDebug() << "| " << gear.gear << " | " << gear.crankset + << " | " << gear.rearCog << " |\n"; + } + } + + GearInfo getGear(int gearNumber) const { + auto it = std::find_if(gears.begin(), gears.end(), + [gearNumber](const GearInfo& info) { return info.gear == gearNumber; }); + + if (it != gears.end()) { + return *it; + } + return GearInfo(); + } + + private: + std::vector gears; + }; + + #ifdef Q_OS_IOS lockscreen *h = 0; #endif From cc7757bfcd17a68989f48bc5a3ce4ac0d7cfc6f1 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 22 Oct 2024 15:38:44 +0200 Subject: [PATCH 02/45] 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 bf820d444..54e5bc1c7 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 @@ -4068,7 +4068,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 911; + CURRENT_PROJECT_VERSION = 912; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1"; @@ -4259,7 +4259,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 911; + CURRENT_PROJECT_VERSION = 912; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; @@ -4486,7 +4486,7 @@ CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 911; + CURRENT_PROJECT_VERSION = 912; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -4582,7 +4582,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 911; + CURRENT_PROJECT_VERSION = 912; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; @@ -4674,7 +4674,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 911; + CURRENT_PROJECT_VERSION = 912; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; ENABLE_PREVIEWS = YES; @@ -4788,7 +4788,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 911; + CURRENT_PROJECT_VERSION = 912; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; From 2d1364497ea9e94c5c4b127dcdffc20db0fc8813 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 22 Oct 2024 15:40:04 +0200 Subject: [PATCH 03/45] Update virtualbike_zwift.swift --- src/ios/virtualbike_zwift.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ios/virtualbike_zwift.swift b/src/ios/virtualbike_zwift.swift index aec05a16c..06308b98d 100644 --- a/src/ios/virtualbike_zwift.swift +++ b/src/ios/virtualbike_zwift.swift @@ -529,10 +529,10 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate { LastFTMSMessageReceived = Data([0x05, power[0], power[1]]) var response: [UInt8] = [ 0x03, 0x08, 0x82, 0x01, 0x10, 0x22, 0x18, 0x10, 0x20, 0x00, 0x28, 0x98, 0x52, 0x30, 0x86, 0xed, 0x01 ] - response[2] = self.CurrentWatt + response[2] = UInt8(self.CurrentWatt) var responseData = Data(bytes: &response, count: 17) - updateQueue.append((ZwiftPlayReadUUID, responseData)) + updateQueue.append((ZwiftPlayReadCharacteristic, responseData)) } } From d712621b7bb5b57045a59fa8f29e93f05c97899c Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Wed, 23 Oct 2024 11:47:05 +0200 Subject: [PATCH 04/45] fixing formula --- .../qdomyoszwift.xcodeproj/project.pbxproj | 12 ++++++------ .../wahookickrsnapbike/wahookickrsnapbike.cpp | 2 +- 2 files changed, 7 insertions(+), 7 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 55a423971..22c586b9f 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 @@ -4068,7 +4068,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 913; + CURRENT_PROJECT_VERSION = 914; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1"; @@ -4259,7 +4259,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 913; + CURRENT_PROJECT_VERSION = 914; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; @@ -4486,7 +4486,7 @@ CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 913; + CURRENT_PROJECT_VERSION = 914; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -4582,7 +4582,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 913; + CURRENT_PROJECT_VERSION = 914; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; @@ -4674,7 +4674,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 913; + CURRENT_PROJECT_VERSION = 914; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; ENABLE_PREVIEWS = YES; @@ -4788,7 +4788,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 913; + CURRENT_PROJECT_VERSION = 914; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; diff --git a/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp b/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp index 312fa3285..87833babf 100644 --- a/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp +++ b/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp @@ -296,7 +296,7 @@ double wahookickrsnapbike::gearsToWheelDiameter(double gear) { double original_ratio = crankset / rear_cog_size; GearTable::GearInfo g = table.getGear((int)gear); double current_ratio = g.crankset / g.rearCog; - return (wheel_size / current_ratio) * original_ratio; + return (wheel_size / original_ratio) * current_ratio; } void wahookickrsnapbike::serviceDiscovered(const QBluetoothUuid &gatt) { From 89808ae65b5df49932747fb48b7f12b0a05be0e1 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Thu, 24 Oct 2024 11:15:51 +0200 Subject: [PATCH 05/45] fixing casting to double --- .../qdomyoszwift.xcodeproj/project.pbxproj | 12 ++++++------ .../wahookickrsnapbike/wahookickrsnapbike.cpp | 6 +++--- 2 files changed, 9 insertions(+), 9 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 555e53f4f..6110153f4 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 @@ -4068,7 +4068,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 915; + CURRENT_PROJECT_VERSION = 916; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1"; @@ -4259,7 +4259,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 915; + CURRENT_PROJECT_VERSION = 916; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; @@ -4486,7 +4486,7 @@ CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 915; + CURRENT_PROJECT_VERSION = 916; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -4582,7 +4582,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 915; + CURRENT_PROJECT_VERSION = 916; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; @@ -4674,7 +4674,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 915; + CURRENT_PROJECT_VERSION = 916; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; ENABLE_PREVIEWS = YES; @@ -4788,7 +4788,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 915; + CURRENT_PROJECT_VERSION = 916; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; diff --git a/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp b/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp index 87833babf..b8946eec4 100644 --- a/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp +++ b/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp @@ -293,10 +293,10 @@ double wahookickrsnapbike::gearsToWheelDiameter(double gear) { GearTable table; if(gear < 1) gear = 1; else if(gear > 12) gear = 12; - double original_ratio = crankset / rear_cog_size; + double original_ratio = ((double)crankset) / ((double)rear_cog_size); GearTable::GearInfo g = table.getGear((int)gear); - double current_ratio = g.crankset / g.rearCog; - return (wheel_size / original_ratio) * current_ratio; + double current_ratio = ((double)g.crankset / (double)g.rearCog); + return (((double)wheel_size) / original_ratio) * ((double)current_ratio); } void wahookickrsnapbike::serviceDiscovered(const QBluetoothUuid &gatt) { From ed1599ca8e96259a5a1e12d19fe226a12c2e3c2f Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Thu, 24 Oct 2024 15:08:25 +0200 Subject: [PATCH 06/45] need to center the values in the table --- src/gears.qml | 527 ++++++++++++++++++ src/qml.qrc | 1 + ...ettings-treadmill-inclination-override.qml | 2 +- src/settings.qml | 10 +- 4 files changed, 538 insertions(+), 2 deletions(-) create mode 100644 src/gears.qml diff --git a/src/gears.qml b/src/gears.qml new file mode 100644 index 000000000..67b6103c2 --- /dev/null +++ b/src/gears.qml @@ -0,0 +1,527 @@ +import QtQuick 2.7 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.15 +import QtQuick.Controls.Material 2.0 +import Qt.labs.settings 1.0 + +ScrollView { + contentWidth: -1 + focus: true + anchors.horizontalCenter: parent.horizontalCenter + anchors.fill: parent + id: gearSettingsWindow + visible: true + clip: true + + // Properties to store the selected values + property int selectedCranksetSize: 38 + property int selectedCogSize: 44 + property string selectedWheelSize: "700 x 18C" + property real selectedCircumference: 2070 + property int initialWheelSizeIndex: 0 // indice per "700 x 18C" + + // Add these connections to your root item (ScrollView) + Connections { + target: gearSettingsWindow + function onGearConfigurationChanged() { + gearTable.updateGearListModel() + } + } + + // Initial gear data + property var gearRows: [ + { gear: 1, crankset: 38, cog: 44, active: true }, + { gear: 2, crankset: 38, cog: 38, active: true }, + { gear: 3, crankset: 38, cog: 32, active: true }, + { gear: 4, crankset: 38, cog: 28, active: true }, + { gear: 5, crankset: 38, cog: 24, active: true }, + { gear: 6, crankset: 38, cog: 21, active: true }, + { gear: 7, crankset: 38, cog: 19, active: true }, + { gear: 8, crankset: 38, cog: 17, active: true }, + { gear: 9, crankset: 38, cog: 15, active: true }, + { gear: 10, crankset: 38, cog: 13, active: true }, + { gear: 11, crankset: 38, cog: 11, active: true }, + { gear: 12, crankset: 38, cog: 10, active: true } + ] + + // Initialize components + Component.onCompleted: { + wheelSizeCombo.currentIndex = initialWheelSizeIndex + } + + function clearGearsFromIndex(startIndex) { + for (let i = startIndex; i < gearRows.length; i++) { + gearRows[i].active = false + } + // Force update + var temp = gearRows + gearRows = [] + gearRows = temp + gearConfigurationChanged(gearRows) + } + + function initializeGearRows() { + gearRows = [ + { gear: 1, crankset: 38, cog: 44, active: true }, + { gear: 2, crankset: 38, cog: 38, active: true }, + { gear: 3, crankset: 38, cog: 32, active: true }, + { gear: 4, crankset: 38, cog: 28, active: true }, + { gear: 5, crankset: 38, cog: 24, active: true }, + { gear: 6, crankset: 38, cog: 21, active: true }, + { gear: 7, crankset: 38, cog: 19, active: true }, + { gear: 8, crankset: 38, cog: 17, active: true }, + { gear: 9, crankset: 38, cog: 15, active: true }, + { gear: 10, crankset: 38, cog: 13, active: true }, + { gear: 11, crankset: 38, cog: 11, active: true }, + { gear: 12, crankset: 38, cog: 10, active: true } + ] + // Force update + var temp = gearRows + gearRows = [] + gearRows = temp + } + + // Signals to notify when values change + signal settingsChanged(int crankset, int cog, string wheelSize, real circumference) + signal gearConfigurationChanged(var gearRows) + + ColumnLayout { + anchors.fill: parent + anchors.margins: 20 + spacing: 20 + + // Crankset Size + GroupBox { + title: "Crankset Size (tooth count)" + Layout.fillWidth: true + + SpinBox { + from: 1 + to: 60 + value: selectedCranksetSize + onValueChanged: { + selectedCranksetSize = value + gearSettingsWindow.settingsChanged(selectedCranksetSize, selectedCogSize, + selectedWheelSize, selectedCircumference) + } + } + } + + // Cog Size + GroupBox { + title: "Cog Size (tooth count)" + Layout.fillWidth: true + + SpinBox { + from: 1 + to: 50 + value: selectedCogSize + onValueChanged: { + selectedCogSize = value + gearSettingsWindow.settingsChanged(selectedCranksetSize, selectedCogSize, + selectedWheelSize, selectedCircumference) + } + } + } + + // Wheel Size + GroupBox { + title: "Wheel Size" + Layout.fillWidth: true + + ComboBox { + id: wheelSizeCombo + width: parent.width + currentIndex: initialWheelSizeIndex + textRole: "text" + model: ListModel { + id: wheelSizes + ListElement { text: "700 x 18C"; circumference: 2070 } + ListElement { text: "700 x 19C"; circumference: 2080 } + ListElement { text: "700 x 20C"; circumference: 2086 } + ListElement { text: "700 x 23C"; circumference: 2096 } + ListElement { text: "700 x 25C"; circumference: 2109 } + ListElement { text: "700 x 28C"; circumference: 2127 } + ListElement { text: "700 x 30C"; circumference: 2140 } + ListElement { text: "700 x 32C"; circumference: 2152 } + ListElement { text: "700 x 35C"; circumference: 2171 } + ListElement { text: "700 x 38C"; circumference: 2190 } + ListElement { text: "700 x 40C"; circumference: 2203 } + ListElement { text: "700 x 44C"; circumference: 2230 } + ListElement { text: "700 x 45C"; circumference: 2234 } + ListElement { text: "700 x 47C"; circumference: 2247 } + ListElement { text: "700 x 50C"; circumference: 2265 } + ListElement { text: "650 x 20C"; circumference: 1938 } + ListElement { text: "650 x 23C"; circumference: 1944 } + ListElement { text: "650 x 35A"; circumference: 2090 } + ListElement { text: "650 x 38B"; circumference: 2105 } + ListElement { text: "650 x 38A"; circumference: 2125 } + ListElement { text: "12\" x 1.75\""; circumference: 935 } + ListElement { text: "12\" x 1.95\""; circumference: 940 } + ListElement { text: "14\" x 1.50\""; circumference: 1020 } + ListElement { text: "14\" x 1.75\""; circumference: 1055 } + ListElement { text: "16\" x 1.50\""; circumference: 1185 } + ListElement { text: "16\" x 1.75\""; circumference: 1195 } + ListElement { text: "16\" x 2.00\""; circumference: 1245 } + ListElement { text: "16\" x 1-1/8\""; circumference: 1290 } + ListElement { text: "16\" x 1-3/8\""; circumference: 1300 } + ListElement { text: "18\" x 1.50\""; circumference: 1340 } + ListElement { text: "18\" x 1.75\""; circumference: 1350 } + ListElement { text: "20\" x 1.25\""; circumference: 1450 } + ListElement { text: "20\" x 1.35\""; circumference: 1460 } + ListElement { text: "20\" x 1.50\""; circumference: 1490 } + ListElement { text: "20\" x 1.75\""; circumference: 1515 } + ListElement { text: "20\" x 1.95\""; circumference: 1565 } + ListElement { text: "20\" x 1-1/8\""; circumference: 1545 } + ListElement { text: "20\" x 1-3/8\""; circumference: 1615 } + ListElement { text: "22\" x 1-3/8\""; circumference: 1770 } + ListElement { text: "22\" x 1-1/2\""; circumference: 1785 } + ListElement { text: "24\" x 3/4\" Tubular"; circumference: 1785 } + ListElement { text: "24\" x 1\""; circumference: 1753 } + ListElement { text: "24\" x 1-1/8\""; circumference: 1795 } + ListElement { text: "24\" x 1-1/4\""; circumference: 1905 } + ListElement { text: "24\" x 1.75\""; circumference: 1890 } + ListElement { text: "24\" x 2.00\""; circumference: 1925 } + ListElement { text: "24\" x 2.125\""; circumference: 1965 } + ListElement { text: "26\" x 7/8\" Tubular"; circumference: 1920 } + ListElement { text: "26\" x 1.25\""; circumference: 1950 } + ListElement { text: "26\" x 1.40\""; circumference: 2005 } + ListElement { text: "26\" x 1.50\""; circumference: 2010 } + ListElement { text: "26\" x 1.75\""; circumference: 2023 } + ListElement { text: "26\" x 1.95\""; circumference: 2050 } + ListElement { text: "26\" x 2.00\""; circumference: 2055 } + ListElement { text: "26\" x 2.10\""; circumference: 2068 } + ListElement { text: "26\" x 2.125\""; circumference: 2070 } + ListElement { text: "26\" x 2.35\""; circumference: 2083 } + ListElement { text: "26\" x 3.00\""; circumference: 2170 } + ListElement { text: "26\" x 1-1.0\""; circumference: 1913 } + ListElement { text: "26\" x 1\""; circumference: 1952 } + ListElement { text: "26\" x 1-1/8\""; circumference: 1970 } + ListElement { text: "26\" x 1-3/8\""; circumference: 2068 } + ListElement { text: "26\" x 1-1/2\""; circumference: 2100 } + ListElement { text: "27\" x 1\""; circumference: 2145 } + ListElement { text: "27\" x 1-1/8\""; circumference: 2155 } + ListElement { text: "27\" x 1-1/4\""; circumference: 2161 } + ListElement { text: "27\" x 1-3/8\""; circumference: 2169 } + ListElement { text: "27.5\" / 650B x 1.50\""; circumference: 2079 } + ListElement { text: "27.5\" / 650B x 1.95\""; circumference: 2090 } + ListElement { text: "27.5\" / 650B x 2.10\""; circumference: 2148 } + ListElement { text: "27.5\" / 650B x 2.25\""; circumference: 2182 } + ListElement { text: "27.5\" / 650B x 2.3\""; circumference: 2199 } + ListElement { text: "27.5\" / 650B x 2.35\""; circumference: 2207 } + ListElement { text: "27.5\" / 650B x 2.4\""; circumference: 2213 } + ListElement { text: "27.5\" / 650B x 2.5\""; circumference: 2231 } + ListElement { text: "27.5\" / 650B x 2.6\""; circumference: 2247 } + ListElement { text: "27.5\" / 650B x 2.8\""; circumference: 2279 } + ListElement { text: "29\" x 2.1\""; circumference: 2286 } + ListElement { text: "29\" x 2.2\""; circumference: 2302 } + ListElement { text: "29\" x 2.25\""; circumference: 2310 } + ListElement { text: "29\" x 2.3\""; circumference: 2326 } + ListElement { text: "29\" x 2.35\""; circumference: 2326 } + ListElement { text: "29\" x 2.4\""; circumference: 2333 } + ListElement { text: "29\" x 2.5\""; circumference: 2350 } + ListElement { text: "29\" x 2.6\""; circumference: 2366 } + } + onCurrentIndexChanged: { + if (currentIndex >= 0) { + selectedWheelSize = model.get(currentIndex).text + selectedCircumference = model.get(currentIndex).circumference + gearSettingsWindow.settingsChanged(selectedCranksetSize, selectedCogSize, + selectedWheelSize, selectedCircumference) + } + } + } + } + + // Gear Table GroupBox + GroupBox { + title: "Gear Table" + Layout.fillWidth: true + Layout.fillHeight: true + Layout.preferredHeight: parent.height + + ColumnLayout { + anchors.fill: parent + spacing: 10 + + // Buttons (same as before) + RowLayout { + Layout.fillWidth: true + Layout.preferredHeight: 40 + spacing: 10 + + Button { + text: "Clear Selected Gear and Following" + Layout.fillWidth: true + Layout.preferredHeight: 40 + onClicked: { + if (gearTable.currentRow >= 0) { + clearGearsFromIndex(gearTable.currentRow) + } + } + } + + Button { + text: "Reset All Gears" + Layout.fillWidth: true + Layout.preferredHeight: 40 + onClicked: initializeGearRows() + } + } + + // Table Header (same as before) + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 40 + color: "#f0f0f0" + border.width: 1 + border.color: "#cccccc" + + Row { + anchors.fill: parent + + Rectangle { + width: parent.width / 3 + height: parent.height + border.width: 1 + border.color: "#cccccc" + color: "transparent" + + Text { + anchors.centerIn: parent + text: "Gear" + font.bold: true + color: "black" + } + } + + Rectangle { + width: parent.width / 3 + height: parent.height + border.width: 1 + border.color: "#cccccc" + color: "transparent" + + Text { + anchors.centerIn: parent + text: "Crankset" + font.bold: true + color: "black" + } + } + + Rectangle { + width: parent.width / 3 + height: parent.height + border.width: 1 + border.color: "#cccccc" + color: "transparent" + + Text { + anchors.centerIn: parent + text: "Rear Cog" + font.bold: true + color: "black" + } + } + } + } + + // Table Content + ListView { + id: gearTable + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + property int currentRow: -1 + model: ListModel { + id: gearListModel + } + + Component.onCompleted: { + updateGearListModel() + } + + function updateGearListModel() { + gearListModel.clear() + for (var i = 0; i < gearRows.length; i++) { + if (gearRows[i].active) { + gearListModel.append(gearRows[i]) + } + } + } + + delegate: Rectangle { + width: gearTable.width + height: 40 + color: gearTable.currentRow === index ? "#e0e0e0" : "white" + + MouseArea { + anchors.fill: parent + onClicked: gearTable.currentRow = index + } + + Row { + anchors.fill: parent + + // Gear Number (non-editable) + Rectangle { + width: parent.width / 3 + height: parent.height + border.width: 1 + border.color: "#cccccc" + color: "transparent" + + Text { + anchors.centerIn: parent + text: gear + color: "black" + } + } + + // Crankset (editable) + Rectangle { + width: parent.width / 3 + height: parent.height + border.width: 1 + border.color: "#cccccc" + color: "transparent" + + SpinBox { + id: cranksetSpinBox + anchors.centerIn: parent + width: parent.width * 0.8 + height: 30 + from: 1 + to: 60 + value: crankset + onValueModified: { + gearRows[index].crankset = value + gearConfigurationChanged(gearRows) + } + + // Style the SpinBox + contentItem: TextInput { + z: 2 + text: cranksetSpinBox.textFromValue(cranksetSpinBox.value, cranksetSpinBox.locale) + font: cranksetSpinBox.font + color: "black" + selectionColor: "#21be2b" + selectedTextColor: "#ffffff" + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + } + + up.indicator: Rectangle { + x: parent.width - width + height: parent.height + width: height + color: parent.up.pressed ? "#e4e4e4" : "#f6f6f6" + border.color: "#cccccc" + + Text { + text: "+" + color: "black" + anchors.centerIn: parent + font.pixelSize: 12 + } + } + + down.indicator: Rectangle { + x: 0 + height: parent.height + width: height + color: parent.down.pressed ? "#e4e4e4" : "#f6f6f6" + border.color: "#cccccc" + + Text { + text: "-" + color: "black" + anchors.centerIn: parent + font.pixelSize: 12 + } + } + + background: Rectangle { + color: "white" + border.color: "#cccccc" + } + } + } + + // Rear Cog (editable) + Rectangle { + width: parent.width / 3 + height: parent.height + border.width: 1 + border.color: "#cccccc" + color: "transparent" + + SpinBox { + id: cogSpinBox + anchors.centerIn: parent + width: parent.width * 0.8 + height: 30 + from: 1 + to: 50 + value: cog + onValueModified: { + gearRows[index].cog = value + gearConfigurationChanged(gearRows) + } + + // Style the SpinBox (same as cranksetSpinBox) + contentItem: TextInput { + z: 2 + text: cogSpinBox.textFromValue(cogSpinBox.value, cogSpinBox.locale) + font: cogSpinBox.font + color: "black" + selectionColor: "#21be2b" + selectedTextColor: "#ffffff" + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + } + + up.indicator: Rectangle { + x: parent.width - width + height: parent.height + width: height + color: parent.up.pressed ? "#e4e4e4" : "#f6f6f6" + border.color: "#cccccc" + + Text { + text: "+" + color: "black" + anchors.centerIn: parent + font.pixelSize: 12 + } + } + + down.indicator: Rectangle { + x: 0 + height: parent.height + width: height + color: parent.down.pressed ? "#e4e4e4" : "#f6f6f6" + border.color: "#cccccc" + + Text { + text: "-" + color: "black" + anchors.centerIn: parent + font.pixelSize: 12 + } + } + + background: Rectangle { + color: "white" + border.color: "#cccccc" + } + } + } + } + } + } + } + } + } +} diff --git a/src/qml.qrc b/src/qml.qrc index 75042363d..43f81b742 100644 --- a/src/qml.qrc +++ b/src/qml.qrc @@ -109,5 +109,6 @@ ChartFooterInnerNoJS.qml inner_templates/chartjs/dochartliveheart.js Wizard.qml + gears.qml diff --git a/src/settings-treadmill-inclination-override.qml b/src/settings-treadmill-inclination-override.qml index fc2d75938..3072d3460 100644 --- a/src/settings-treadmill-inclination-override.qml +++ b/src/settings-treadmill-inclination-override.qml @@ -11,7 +11,7 @@ ScrollView { anchors.fill: parent //anchors.bottom: footerSettings.top //anchors.bottomMargin: footerSettings.height + 10 - id: settingsTTLPane + id: settingsInclinationPane Settings { id: settings diff --git a/src/settings.qml b/src/settings.qml index 0b10a071d..7dfe5606c 100644 --- a/src/settings.qml +++ b/src/settings.qml @@ -2493,6 +2493,14 @@ import QtQuick.Dialogs 1.0 color: Material.color(Material.Lime) } + NewPageElement { + title: qsTr("Wahoo Options") + indicatRectColor: Material.color(Material.Grey) + textColor: Material.color(Material.Yellow) + color: Material.backgroundColor + accordionContent: "gears.qml" + } + AccordionElement { id: schwinnBikeAccordion title: qsTr("Schwinn Bike Options") @@ -9800,7 +9808,7 @@ import QtQuick.Dialogs 1.0 Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.fillWidth: true color: Material.color(Material.Lime) - } + } } } From 0f149448b34be7c3b6bf9300d249f6f69f4e4a4e Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Fri, 25 Oct 2024 16:28:33 +0200 Subject: [PATCH 07/45] Update gears.qml --- src/gears.qml | 196 +++++++++++++++----------------------------------- 1 file changed, 57 insertions(+), 139 deletions(-) diff --git a/src/gears.qml b/src/gears.qml index 67b6103c2..d000d8fe2 100644 --- a/src/gears.qml +++ b/src/gears.qml @@ -18,9 +18,8 @@ ScrollView { property int selectedCogSize: 44 property string selectedWheelSize: "700 x 18C" property real selectedCircumference: 2070 - property int initialWheelSizeIndex: 0 // indice per "700 x 18C" + property int initialWheelSizeIndex: 0 - // Add these connections to your root item (ScrollView) Connections { target: gearSettingsWindow function onGearConfigurationChanged() { @@ -44,20 +43,18 @@ ScrollView { { gear: 12, crankset: 38, cog: 10, active: true } ] - // Initialize components Component.onCompleted: { wheelSizeCombo.currentIndex = initialWheelSizeIndex } function clearGearsFromIndex(startIndex) { - for (let i = startIndex; i < gearRows.length; i++) { - gearRows[i].active = false - } - // Force update - var temp = gearRows - gearRows = [] - gearRows = temp - gearConfigurationChanged(gearRows) + for (let i = startIndex; i < gearRows.length; i++) { + gearRows[i].active = false + } + var temp = gearRows + gearRows = [] + gearRows = temp + gearConfigurationChanged(gearRows) } function initializeGearRows() { @@ -75,13 +72,11 @@ ScrollView { { gear: 11, crankset: 38, cog: 11, active: true }, { gear: 12, crankset: 38, cog: 10, active: true } ] - // Force update var temp = gearRows gearRows = [] gearRows = temp } - // Signals to notify when values change signal settingsChanged(int crankset, int cog, string wheelSize, real circumference) signal gearConfigurationChanged(var gearRows) @@ -95,14 +90,19 @@ ScrollView { title: "Crankset Size (tooth count)" Layout.fillWidth: true - SpinBox { - from: 1 - to: 60 - value: selectedCranksetSize - onValueChanged: { - selectedCranksetSize = value - gearSettingsWindow.settingsChanged(selectedCranksetSize, selectedCogSize, - selectedWheelSize, selectedCircumference) + TextEdit { + text: selectedCranksetSize.toString() + width: parent.width + height: 30 + verticalAlignment: TextEdit.AlignVCenter + selectByMouse: true + onTextChanged: { + var value = parseInt(text) + if (!isNaN(value) && value >= 1 && value <= 60) { + selectedCranksetSize = value + gearSettingsWindow.settingsChanged(selectedCranksetSize, selectedCogSize, + selectedWheelSize, selectedCircumference) + } } } } @@ -112,14 +112,19 @@ ScrollView { title: "Cog Size (tooth count)" Layout.fillWidth: true - SpinBox { - from: 1 - to: 50 - value: selectedCogSize - onValueChanged: { - selectedCogSize = value - gearSettingsWindow.settingsChanged(selectedCranksetSize, selectedCogSize, - selectedWheelSize, selectedCircumference) + TextEdit { + text: selectedCogSize.toString() + width: parent.width + height: 30 + verticalAlignment: TextEdit.AlignVCenter + selectByMouse: true + onTextChanged: { + var value = parseInt(text) + if (!isNaN(value) && value >= 1 && value <= 50) { + selectedCogSize = value + gearSettingsWindow.settingsChanged(selectedCranksetSize, selectedCogSize, + selectedWheelSize, selectedCircumference) + } } } } @@ -221,6 +226,7 @@ ScrollView { ListElement { text: "29\" x 2.4\""; circumference: 2333 } ListElement { text: "29\" x 2.5\""; circumference: 2350 } ListElement { text: "29\" x 2.6\""; circumference: 2366 } + } onCurrentIndexChanged: { if (currentIndex >= 0) { @@ -244,7 +250,7 @@ ScrollView { anchors.fill: parent spacing: 10 - // Buttons (same as before) + // Buttons RowLayout { Layout.fillWidth: true Layout.preferredHeight: 40 @@ -269,7 +275,7 @@ ScrollView { } } - // Table Header (same as before) + // Table Header Rectangle { Layout.fillWidth: true Layout.preferredHeight: 40 @@ -387,65 +393,21 @@ ScrollView { border.color: "#cccccc" color: "transparent" - SpinBox { - id: cranksetSpinBox + TextEdit { anchors.centerIn: parent width: parent.width * 0.8 height: 30 - from: 1 - to: 60 - value: crankset - onValueModified: { - gearRows[index].crankset = value - gearConfigurationChanged(gearRows) - } - - // Style the SpinBox - contentItem: TextInput { - z: 2 - text: cranksetSpinBox.textFromValue(cranksetSpinBox.value, cranksetSpinBox.locale) - font: cranksetSpinBox.font - color: "black" - selectionColor: "#21be2b" - selectedTextColor: "#ffffff" - horizontalAlignment: Qt.AlignHCenter - verticalAlignment: Qt.AlignVCenter - } - - up.indicator: Rectangle { - x: parent.width - width - height: parent.height - width: height - color: parent.up.pressed ? "#e4e4e4" : "#f6f6f6" - border.color: "#cccccc" - - Text { - text: "+" - color: "black" - anchors.centerIn: parent - font.pixelSize: 12 - } - } - - down.indicator: Rectangle { - x: 0 - height: parent.height - width: height - color: parent.down.pressed ? "#e4e4e4" : "#f6f6f6" - border.color: "#cccccc" - - Text { - text: "-" - color: "black" - anchors.centerIn: parent - font.pixelSize: 12 + text: crankset.toString() + verticalAlignment: TextEdit.AlignVCenter + horizontalAlignment: TextEdit.AlignHCenter + selectByMouse: true + onTextChanged: { + var value = parseInt(text) + if (!isNaN(value) && value >= 1 && value <= 60) { + gearRows[index].crankset = value + gearConfigurationChanged(gearRows) } } - - background: Rectangle { - color: "white" - border.color: "#cccccc" - } } } @@ -457,65 +419,21 @@ ScrollView { border.color: "#cccccc" color: "transparent" - SpinBox { - id: cogSpinBox + TextEdit { anchors.centerIn: parent width: parent.width * 0.8 height: 30 - from: 1 - to: 50 - value: cog - onValueModified: { - gearRows[index].cog = value - gearConfigurationChanged(gearRows) - } - - // Style the SpinBox (same as cranksetSpinBox) - contentItem: TextInput { - z: 2 - text: cogSpinBox.textFromValue(cogSpinBox.value, cogSpinBox.locale) - font: cogSpinBox.font - color: "black" - selectionColor: "#21be2b" - selectedTextColor: "#ffffff" - horizontalAlignment: Qt.AlignHCenter - verticalAlignment: Qt.AlignVCenter - } - - up.indicator: Rectangle { - x: parent.width - width - height: parent.height - width: height - color: parent.up.pressed ? "#e4e4e4" : "#f6f6f6" - border.color: "#cccccc" - - Text { - text: "+" - color: "black" - anchors.centerIn: parent - font.pixelSize: 12 - } - } - - down.indicator: Rectangle { - x: 0 - height: parent.height - width: height - color: parent.down.pressed ? "#e4e4e4" : "#f6f6f6" - border.color: "#cccccc" - - Text { - text: "-" - color: "black" - anchors.centerIn: parent - font.pixelSize: 12 + text: cog.toString() + verticalAlignment: TextEdit.AlignVCenter + horizontalAlignment: TextEdit.AlignHCenter + selectByMouse: true + onTextChanged: { + var value = parseInt(text) + if (!isNaN(value) && value >= 1 && value <= 50) { + gearRows[index].cog = value + gearConfigurationChanged(gearRows) } } - - background: Rectangle { - color: "white" - border.color: "#cccccc" - } } } } From 527396eafca8480ae46e95b21322374b0cb515ba Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 28 Oct 2024 09:18:54 +0100 Subject: [PATCH 08/45] Revert "Update gears.qml" This reverts commit 0f149448b34be7c3b6bf9300d249f6f69f4e4a4e. --- src/gears.qml | 196 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 139 insertions(+), 57 deletions(-) diff --git a/src/gears.qml b/src/gears.qml index d000d8fe2..67b6103c2 100644 --- a/src/gears.qml +++ b/src/gears.qml @@ -18,8 +18,9 @@ ScrollView { property int selectedCogSize: 44 property string selectedWheelSize: "700 x 18C" property real selectedCircumference: 2070 - property int initialWheelSizeIndex: 0 + property int initialWheelSizeIndex: 0 // indice per "700 x 18C" + // Add these connections to your root item (ScrollView) Connections { target: gearSettingsWindow function onGearConfigurationChanged() { @@ -43,18 +44,20 @@ ScrollView { { gear: 12, crankset: 38, cog: 10, active: true } ] + // Initialize components Component.onCompleted: { wheelSizeCombo.currentIndex = initialWheelSizeIndex } function clearGearsFromIndex(startIndex) { - for (let i = startIndex; i < gearRows.length; i++) { - gearRows[i].active = false - } - var temp = gearRows - gearRows = [] - gearRows = temp - gearConfigurationChanged(gearRows) + for (let i = startIndex; i < gearRows.length; i++) { + gearRows[i].active = false + } + // Force update + var temp = gearRows + gearRows = [] + gearRows = temp + gearConfigurationChanged(gearRows) } function initializeGearRows() { @@ -72,11 +75,13 @@ ScrollView { { gear: 11, crankset: 38, cog: 11, active: true }, { gear: 12, crankset: 38, cog: 10, active: true } ] + // Force update var temp = gearRows gearRows = [] gearRows = temp } + // Signals to notify when values change signal settingsChanged(int crankset, int cog, string wheelSize, real circumference) signal gearConfigurationChanged(var gearRows) @@ -90,19 +95,14 @@ ScrollView { title: "Crankset Size (tooth count)" Layout.fillWidth: true - TextEdit { - text: selectedCranksetSize.toString() - width: parent.width - height: 30 - verticalAlignment: TextEdit.AlignVCenter - selectByMouse: true - onTextChanged: { - var value = parseInt(text) - if (!isNaN(value) && value >= 1 && value <= 60) { - selectedCranksetSize = value - gearSettingsWindow.settingsChanged(selectedCranksetSize, selectedCogSize, - selectedWheelSize, selectedCircumference) - } + SpinBox { + from: 1 + to: 60 + value: selectedCranksetSize + onValueChanged: { + selectedCranksetSize = value + gearSettingsWindow.settingsChanged(selectedCranksetSize, selectedCogSize, + selectedWheelSize, selectedCircumference) } } } @@ -112,19 +112,14 @@ ScrollView { title: "Cog Size (tooth count)" Layout.fillWidth: true - TextEdit { - text: selectedCogSize.toString() - width: parent.width - height: 30 - verticalAlignment: TextEdit.AlignVCenter - selectByMouse: true - onTextChanged: { - var value = parseInt(text) - if (!isNaN(value) && value >= 1 && value <= 50) { - selectedCogSize = value - gearSettingsWindow.settingsChanged(selectedCranksetSize, selectedCogSize, - selectedWheelSize, selectedCircumference) - } + SpinBox { + from: 1 + to: 50 + value: selectedCogSize + onValueChanged: { + selectedCogSize = value + gearSettingsWindow.settingsChanged(selectedCranksetSize, selectedCogSize, + selectedWheelSize, selectedCircumference) } } } @@ -226,7 +221,6 @@ ScrollView { ListElement { text: "29\" x 2.4\""; circumference: 2333 } ListElement { text: "29\" x 2.5\""; circumference: 2350 } ListElement { text: "29\" x 2.6\""; circumference: 2366 } - } onCurrentIndexChanged: { if (currentIndex >= 0) { @@ -250,7 +244,7 @@ ScrollView { anchors.fill: parent spacing: 10 - // Buttons + // Buttons (same as before) RowLayout { Layout.fillWidth: true Layout.preferredHeight: 40 @@ -275,7 +269,7 @@ ScrollView { } } - // Table Header + // Table Header (same as before) Rectangle { Layout.fillWidth: true Layout.preferredHeight: 40 @@ -393,21 +387,65 @@ ScrollView { border.color: "#cccccc" color: "transparent" - TextEdit { + SpinBox { + id: cranksetSpinBox anchors.centerIn: parent width: parent.width * 0.8 height: 30 - text: crankset.toString() - verticalAlignment: TextEdit.AlignVCenter - horizontalAlignment: TextEdit.AlignHCenter - selectByMouse: true - onTextChanged: { - var value = parseInt(text) - if (!isNaN(value) && value >= 1 && value <= 60) { - gearRows[index].crankset = value - gearConfigurationChanged(gearRows) + from: 1 + to: 60 + value: crankset + onValueModified: { + gearRows[index].crankset = value + gearConfigurationChanged(gearRows) + } + + // Style the SpinBox + contentItem: TextInput { + z: 2 + text: cranksetSpinBox.textFromValue(cranksetSpinBox.value, cranksetSpinBox.locale) + font: cranksetSpinBox.font + color: "black" + selectionColor: "#21be2b" + selectedTextColor: "#ffffff" + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + } + + up.indicator: Rectangle { + x: parent.width - width + height: parent.height + width: height + color: parent.up.pressed ? "#e4e4e4" : "#f6f6f6" + border.color: "#cccccc" + + Text { + text: "+" + color: "black" + anchors.centerIn: parent + font.pixelSize: 12 + } + } + + down.indicator: Rectangle { + x: 0 + height: parent.height + width: height + color: parent.down.pressed ? "#e4e4e4" : "#f6f6f6" + border.color: "#cccccc" + + Text { + text: "-" + color: "black" + anchors.centerIn: parent + font.pixelSize: 12 } } + + background: Rectangle { + color: "white" + border.color: "#cccccc" + } } } @@ -419,21 +457,65 @@ ScrollView { border.color: "#cccccc" color: "transparent" - TextEdit { + SpinBox { + id: cogSpinBox anchors.centerIn: parent width: parent.width * 0.8 height: 30 - text: cog.toString() - verticalAlignment: TextEdit.AlignVCenter - horizontalAlignment: TextEdit.AlignHCenter - selectByMouse: true - onTextChanged: { - var value = parseInt(text) - if (!isNaN(value) && value >= 1 && value <= 50) { - gearRows[index].cog = value - gearConfigurationChanged(gearRows) + from: 1 + to: 50 + value: cog + onValueModified: { + gearRows[index].cog = value + gearConfigurationChanged(gearRows) + } + + // Style the SpinBox (same as cranksetSpinBox) + contentItem: TextInput { + z: 2 + text: cogSpinBox.textFromValue(cogSpinBox.value, cogSpinBox.locale) + font: cogSpinBox.font + color: "black" + selectionColor: "#21be2b" + selectedTextColor: "#ffffff" + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + } + + up.indicator: Rectangle { + x: parent.width - width + height: parent.height + width: height + color: parent.up.pressed ? "#e4e4e4" : "#f6f6f6" + border.color: "#cccccc" + + Text { + text: "+" + color: "black" + anchors.centerIn: parent + font.pixelSize: 12 + } + } + + down.indicator: Rectangle { + x: 0 + height: parent.height + width: height + color: parent.down.pressed ? "#e4e4e4" : "#f6f6f6" + border.color: "#cccccc" + + Text { + text: "-" + color: "black" + anchors.centerIn: parent + font.pixelSize: 12 } } + + background: Rectangle { + color: "white" + border.color: "#cccccc" + } } } } From 74c37f562427dea268eb39bb5d58ca813c8fcdbe Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 28 Oct 2024 09:46:58 +0100 Subject: [PATCH 09/45] Update gears.qml --- src/gears.qml | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/gears.qml b/src/gears.qml index 67b6103c2..1fe290d65 100644 --- a/src/gears.qml +++ b/src/gears.qml @@ -49,6 +49,34 @@ ScrollView { wheelSizeCombo.currentIndex = initialWheelSizeIndex } + function addNewGear() { + // Find the first inactive gear or add at the end + let newGearIndex = gearRows.findIndex(row => !row.active); + if (newGearIndex === -1) { + newGearIndex = gearRows.length; + } + + // Create new gear with default values + const newGear = { + gear: newGearIndex + 1, + crankset: selectedCranksetSize, + cog: selectedCogSize, + active: true + }; + + if (newGearIndex < gearRows.length) { + gearRows[newGearIndex] = newGear; + } else { + gearRows.push(newGear); + } + + // Force update + var temp = gearRows; + gearRows = []; + gearRows = temp; + gearConfigurationChanged(gearRows); + } + function clearGearsFromIndex(startIndex) { for (let i = startIndex; i < gearRows.length; i++) { gearRows[i].active = false @@ -244,12 +272,19 @@ ScrollView { anchors.fill: parent spacing: 10 - // Buttons (same as before) + // Updated Buttons Row RowLayout { Layout.fillWidth: true Layout.preferredHeight: 40 spacing: 10 + Button { + text: "Add Gear" + Layout.fillWidth: true + Layout.preferredHeight: 40 + onClicked: addNewGear() + } + Button { text: "Clear Selected Gear and Following" Layout.fillWidth: true From f85f7434995712799d3f05fbd788198600b04fa0 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 28 Oct 2024 11:11:05 +0100 Subject: [PATCH 10/45] i need to save the first 3 static objects and use it in the wahoo module --- src/gears.qml | 76 +++++++++++++++++++++++++++++++++++++----------- src/settings.qml | 7 +++++ 2 files changed, 66 insertions(+), 17 deletions(-) diff --git a/src/gears.qml b/src/gears.qml index 1fe290d65..3dbcc3c35 100644 --- a/src/gears.qml +++ b/src/gears.qml @@ -13,14 +13,61 @@ ScrollView { visible: true clip: true - // Properties to store the selected values - property int selectedCranksetSize: 38 - property int selectedCogSize: 44 - property string selectedWheelSize: "700 x 18C" - property real selectedCircumference: 2070 - property int initialWheelSizeIndex: 0 // indice per "700 x 18C" - - // Add these connections to your root item (ScrollView) + // Properties + Settings { + id: settings + property string gear_configuration: "1|38|44|true\n2|38|38|true\n3|38|32|true\n4|38|28|true\n5|38|24|true\n6|38|21|true\n7|38|19|true\n8|38|17|true\n9|38|15|true\n10|38|13|true\n11|38|11|true\n12|38|10|true" + property int gear_crankset_size: 38 + property int gear_cog_size: 44 + property string gear_wheel_size: "700 x 18C" + property real gear_circumference: 2070 + } + + property int selectedCranksetSize: settings.gear_crankset_size + property int selectedCogSize: settings.gear_cog_size + property string selectedWheelSize: settings.gear_wheel_size + property real selectedCircumference: settings.gear_circumference + property int initialWheelSizeIndex: 0 + + Component.onCompleted: { + if (settings.gear_configuration) { + gearRows = stringToGearRows(settings.gear_configuration) + } + } + + // Funzioni helper per la conversione delle gear + function stringToGearRows(gearString) { + if (!gearString) return [] + + return gearString.split("\n").map(function(row) { + const parts = row.split("|") + return { + gear: parseInt(parts[0]), + crankset: parseInt(parts[1]), + cog: parseInt(parts[2]), + active: parts[3] === "true" + } + }) + } + + function gearRowsToString(gearRows) { + return gearRows.map(function(row) { + return row.gear + "|" + row.crankset + "|" + row.cog + "|" + row.active + }).join("\n") + } + + // Monitora i cambiamenti nelle gear e salva automaticamente + onGearConfigurationChanged: { + settings.gear_configuration = gearRowsToString(gearRows) + } + + onSettingsChanged: { + settings.gear_crankset_size = crankset + settings.gear_cog_size = cog + settings.gear_wheel_size = wheelSize + settings.gear_circumference = circumference + } + Connections { target: gearSettingsWindow function onGearConfigurationChanged() { @@ -44,11 +91,6 @@ ScrollView { { gear: 12, crankset: 38, cog: 10, active: true } ] - // Initialize components - Component.onCompleted: { - wheelSizeCombo.currentIndex = initialWheelSizeIndex - } - function addNewGear() { // Find the first inactive gear or add at the end let newGearIndex = gearRows.findIndex(row => !row.active); @@ -128,8 +170,8 @@ ScrollView { to: 60 value: selectedCranksetSize onValueChanged: { - selectedCranksetSize = value - gearSettingsWindow.settingsChanged(selectedCranksetSize, selectedCogSize, + selectedCranksetSize = value + settingsChanged(selectedCranksetSize, selectedCogSize, selectedWheelSize, selectedCircumference) } } @@ -146,7 +188,7 @@ ScrollView { value: selectedCogSize onValueChanged: { selectedCogSize = value - gearSettingsWindow.settingsChanged(selectedCranksetSize, selectedCogSize, + settingsChanged(selectedCranksetSize, selectedCogSize, selectedWheelSize, selectedCircumference) } } @@ -254,7 +296,7 @@ ScrollView { if (currentIndex >= 0) { selectedWheelSize = model.get(currentIndex).text selectedCircumference = model.get(currentIndex).circumference - gearSettingsWindow.settingsChanged(selectedCranksetSize, selectedCogSize, + settingsChanged(selectedCranksetSize, selectedCogSize, selectedWheelSize, selectedCircumference) } } diff --git a/src/settings.qml b/src/settings.qml index 7dfe5606c..7d16e5c8c 100644 --- a/src/settings.qml +++ b/src/settings.qml @@ -987,6 +987,13 @@ import QtQuick.Dialogs 1.0 // from version 2.18.1 property bool zwift_play_emulator: false + + // from version 2.18.2 + property string gear_configuration: "1|38|44|true\n2|38|38|true\n3|38|32|true\n4|38|28|true\n5|38|24|true\n6|38|21|true\n7|38|19|true\n8|38|17|true\n9|38|15|true\n10|38|13|true\n11|38|11|true\n12|38|10|true" + property int gear_crankset_size: 38 + property int gear_cog_size: 44 + property string gear_wheel_size: "700 x 18C" + property real gear_circumference: 2070 } function paddingZeros(text, limit) { From 6fac9770bea68b0972567037b4b27e28a5da8c15 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 28 Oct 2024 15:09:13 +0100 Subject: [PATCH 11/45] qml finally saves the settings correctly --- src/gears.qml | 241 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 218 insertions(+), 23 deletions(-) diff --git a/src/gears.qml b/src/gears.qml index 3dbcc3c35..a16ed97d0 100644 --- a/src/gears.qml +++ b/src/gears.qml @@ -27,15 +27,38 @@ ScrollView { property int selectedCogSize: settings.gear_cog_size property string selectedWheelSize: settings.gear_wheel_size property real selectedCircumference: settings.gear_circumference - property int initialWheelSizeIndex: 0 + property bool inited: false + + property int initialWheelSizeIndex: { + // Trova l'indice corretto basato sul valore salvato + for (let i = 0; i < wheelSizes.count; i++) { + if (wheelSizes.get(i).text === settings.gear_wheel_size) { + return i; + } + } + return 0; // default se non trovato + } Component.onCompleted: { if (settings.gear_configuration) { gearRows = stringToGearRows(settings.gear_configuration) } + console.log("Component.onCompleted " + settings.gear_crankset_size + " " + settings.gear_cog_size + " " + settings.gear_wheel_size + " " + settings.gear_circumference) + wheelSizeCombo.currentIndex = initialWheelSizeIndex + selectedCranksetSize = settings.gear_crankset_size + selectedCogSize = settings.gear_cog_size + inited = true + } + + function updateSettings() { + if(!inited) + return; + settings.gear_crankset_size = selectedCranksetSize + settings.gear_cog_size = selectedCogSize + settings.gear_wheel_size = selectedWheelSize + settings.gear_circumference = selectedCircumference } - // Funzioni helper per la conversione delle gear function stringToGearRows(gearString) { if (!gearString) return [] @@ -62,10 +85,8 @@ ScrollView { } onSettingsChanged: { - settings.gear_crankset_size = crankset - settings.gear_cog_size = cog - settings.gear_wheel_size = wheelSize - settings.gear_circumference = circumference + console.log("onSettingsChanged") + updateSettings() } Connections { @@ -75,6 +96,150 @@ ScrollView { } } + function loadGearProfile(profileName) { + if (profileName in gearProfiles) { + const profile = gearProfiles[profileName] + + // Create new array with copied objects + var newGears = [] + for (var i = 0; i < profile.gears.length; i++) { + newGears.push({ + gear: profile.gears[i].gear, + crankset: profile.gears[i].crankset, + cog: profile.gears[i].cog, + active: profile.gears[i].active + }) + } + + gearRows = newGears + + // Update the first crankset size + if (gearRows.length > 0) { + selectedCranksetSize = gearRows[0].crankset + selectedCogSize = gearRows[0].cog + } + + // Force update + var temp = gearRows + gearRows = [] + gearRows = temp + gearConfigurationChanged(gearRows) + } + } + + property var gearProfiles: { + "Time Trial": { + name: "Time Trial (52/36, 10 - 28)", + gears: [ + { gear: 1, crankset: 36, cog: 28, active: true }, + { gear: 2, crankset: 36, cog: 24, active: true }, + { gear: 3, crankset: 36, cog: 21, active: true }, + { gear: 4, crankset: 36, cog: 19, active: true }, + { gear: 5, crankset: 36, cog: 18, active: true }, + { gear: 6, crankset: 36, cog: 17, active: true }, + { gear: 7, crankset: 36, cog: 16, active: true }, + { gear: 8, crankset: 36, cog: 15, active: true }, + { gear: 9, crankset: 36, cog: 14, active: true }, + { gear: 10, crankset: 52, cog: 19, active: true }, + { gear: 11, crankset: 52, cog: 18, active: true }, + { gear: 12, crankset: 52, cog: 17, active: true }, + { gear: 13, crankset: 52, cog: 16, active: true }, + { gear: 14, crankset: 52, cog: 15, active: true }, + { gear: 15, crankset: 52, cog: 14, active: true }, + { gear: 16, crankset: 52, cog: 13, active: true }, + { gear: 17, crankset: 52, cog: 12, active: true }, + { gear: 18, crankset: 52, cog: 11, active: true }, + { gear: 19, crankset: 52, cog: 10, active: true } + ] + }, + "Rolling Hills": { + name: "Rolling Hills (46/33, 10 - 33)", + gears: [ + { gear: 1, crankset: 33, cog: 33, active: true }, + { gear: 2, crankset: 33, cog: 28, active: true }, + { gear: 3, crankset: 33, cog: 24, active: true }, + { gear: 4, crankset: 33, cog: 21, active: true }, + { gear: 5, crankset: 33, cog: 19, active: true }, + { gear: 6, crankset: 33, cog: 17, active: true }, + { gear: 7, crankset: 33, cog: 15, active: true }, + { gear: 8, crankset: 46, cog: 19, active: true }, + { gear: 9, crankset: 46, cog: 17, active: true }, + { gear: 10, crankset: 46, cog: 15, active: true }, + { gear: 11, crankset: 46, cog: 14, active: true }, + { gear: 12, crankset: 46, cog: 13, active: true }, + { gear: 13, crankset: 46, cog: 12, active: true }, + { gear: 14, crankset: 46, cog: 11, active: true }, + { gear: 15, crankset: 46, cog: 10, active: true } + ] + }, + "Alpine": { + name: "Alpine (43/30, 10 - 36)", + gears: [ + { gear: 1, crankset: 30, cog: 36, active: true }, + { gear: 2, crankset: 30, cog: 32, active: true }, + { gear: 3, crankset: 30, cog: 28, active: true }, + { gear: 4, crankset: 30, cog: 24, active: true }, + { gear: 5, crankset: 30, cog: 21, active: true }, + { gear: 6, crankset: 30, cog: 19, active: true }, + { gear: 7, crankset: 30, cog: 17, active: true }, + { gear: 8, crankset: 30, cog: 15, active: true }, + { gear: 9, crankset: 43, cog: 19, active: true }, + { gear: 10, crankset: 43, cog: 17, active: true }, + { gear: 11, crankset: 43, cog: 15, active: true }, + { gear: 12, crankset: 43, cog: 13, active: true }, + { gear: 13, crankset: 43, cog: 12, active: true }, + { gear: 14, crankset: 43, cog: 11, active: true }, + { gear: 15, crankset: 43, cog: 10, active: true } + ] + }, + "Reality Bender": { + name: "Reality Bender (24 even spaced)", + gears: [ + { gear: 1, crankset: 30, cog: 40, active: true }, + { gear: 2, crankset: 30, cog: 36, active: true }, + { gear: 3, crankset: 30, cog: 33, active: true }, + { gear: 4, crankset: 30, cog: 30, active: true }, + { gear: 5, crankset: 30, cog: 27, active: true }, + { gear: 6, crankset: 34, cog: 28, active: true }, + { gear: 7, crankset: 34, cog: 26, active: true }, + { gear: 8, crankset: 34, cog: 24, active: true }, + { gear: 9, crankset: 34, cog: 22, active: true }, + { gear: 10, crankset: 44, cog: 26, active: true }, + { gear: 11, crankset: 44, cog: 24, active: true }, + { gear: 12, crankset: 44, cog: 22, active: true }, + { gear: 13, crankset: 44, cog: 20, active: true }, + { gear: 14, crankset: 44, cog: 18, active: true }, + { gear: 15, crankset: 56, cog: 21, active: true }, + { gear: 16, crankset: 56, cog: 19, active: true }, + { gear: 17, crankset: 58, cog: 18, active: true }, + { gear: 18, crankset: 60, cog: 17, active: true }, + { gear: 19, crankset: 62, cog: 16, active: true }, + { gear: 20, crankset: 63, cog: 15, active: true }, + { gear: 21, crankset: 64, cog: 14, active: true }, + { gear: 22, crankset: 66, cog: 13, active: true }, + { gear: 23, crankset: 67, cog: 12, active: true } + ] + }, + "Explorer": { + name: "Explorer (40, 10 - 46)", + gears: [ + { gear: 1, crankset: 40, cog: 46, active: true }, + { gear: 2, crankset: 40, cog: 38, active: true }, + { gear: 3, crankset: 40, cog: 32, active: true }, + { gear: 4, crankset: 40, cog: 28, active: true }, + { gear: 5, crankset: 40, cog: 24, active: true }, + { gear: 6, crankset: 40, cog: 21, active: true }, + { gear: 7, crankset: 40, cog: 19, active: true }, + { gear: 8, crankset: 40, cog: 17, active: true }, + { gear: 9, crankset: 40, cog: 15, active: true }, + { gear: 10, crankset: 40, cog: 13, active: true }, + { gear: 11, crankset: 40, cog: 12, active: true }, + { gear: 12, crankset: 40, cog: 11, active: true }, + { gear: 13, crankset: 40, cog: 10, active: true } + ] + } + } + // Initial gear data property var gearRows: [ { gear: 1, crankset: 38, cog: 44, active: true }, @@ -152,7 +317,7 @@ ScrollView { } // Signals to notify when values change - signal settingsChanged(int crankset, int cog, string wheelSize, real circumference) + signal settingsChanged() signal gearConfigurationChanged(var gearRows) ColumnLayout { @@ -160,6 +325,36 @@ ScrollView { anchors.margins: 20 spacing: 20 + GroupBox { + title: "Preset Gear Profiles" + Layout.fillWidth: true + + ComboBox { + id: profileCombo + width: parent.width + textRole: "text" + displayText: currentIndex < 0 ? "Select a profile..." : model.get(currentIndex).text + model: ListModel { + id: profileModel + } + + Component.onCompleted: { + for (var key in gearProfiles) { + profileModel.append({ + text: gearProfiles[key].name, + value: key + }) + } + } + + onCurrentIndexChanged: { + if (currentIndex >= 0) { + loadGearProfile(profileModel.get(currentIndex).value) + } + } + } + } + // Crankset Size GroupBox { title: "Crankset Size (tooth count)" @@ -171,8 +366,8 @@ ScrollView { value: selectedCranksetSize onValueChanged: { selectedCranksetSize = value - settingsChanged(selectedCranksetSize, selectedCogSize, - selectedWheelSize, selectedCircumference) + console.log("Crankset Size changed"); + settingsChanged() } } } @@ -188,8 +383,8 @@ ScrollView { value: selectedCogSize onValueChanged: { selectedCogSize = value - settingsChanged(selectedCranksetSize, selectedCogSize, - selectedWheelSize, selectedCircumference) + console.log("Cog Size changed"); + settingsChanged() } } } @@ -200,12 +395,12 @@ ScrollView { Layout.fillWidth: true ComboBox { - id: wheelSizeCombo - width: parent.width - currentIndex: initialWheelSizeIndex - textRole: "text" - model: ListModel { - id: wheelSizes + id: wheelSizeCombo + width: parent.width + currentIndex: initialWheelSizeIndex + textRole: "text" + model: ListModel { + id: wheelSizes ListElement { text: "700 x 18C"; circumference: 2070 } ListElement { text: "700 x 19C"; circumference: 2080 } ListElement { text: "700 x 20C"; circumference: 2086 } @@ -294,12 +489,12 @@ ScrollView { } onCurrentIndexChanged: { if (currentIndex >= 0) { - selectedWheelSize = model.get(currentIndex).text - selectedCircumference = model.get(currentIndex).circumference - settingsChanged(selectedCranksetSize, selectedCogSize, - selectedWheelSize, selectedCircumference) - } - } + selectedWheelSize = model.get(currentIndex).text + selectedCircumference = model.get(currentIndex).circumference + console.log("wheelSizeCombo changed"); + settingsChanged() + } + } } } From 2f2989f90dfe00df9dca65d99280735a5c5124a0 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 28 Oct 2024 15:40:00 +0100 Subject: [PATCH 12/45] completed? --- .../wahookickrsnapbike/wahookickrsnapbike.cpp | 7 ++- .../wahookickrsnapbike/wahookickrsnapbike.h | 60 ++++++++++++------- src/qzsettings.cpp | 14 ++++- src/qzsettings.h | 15 +++++ 4 files changed, 71 insertions(+), 25 deletions(-) diff --git a/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp b/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp index b8946eec4..394c48914 100644 --- a/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp +++ b/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp @@ -290,13 +290,14 @@ void wahookickrsnapbike::update() { } double wahookickrsnapbike::gearsToWheelDiameter(double gear) { + QSettings settings; GearTable table; if(gear < 1) gear = 1; - else if(gear > 12) gear = 12; - double original_ratio = ((double)crankset) / ((double)rear_cog_size); + else if(gear > table.maxGears) gear = table.maxGears; + double original_ratio = ((double)settings.value(QZSettings::gear_crankset_size, QZSettings::default_gear_crankset_size).toDouble()) / ((double)settings.value(QZSettings::gear_cog_size, QZSettings::default_gear_cog_size).toDouble()); GearTable::GearInfo g = table.getGear((int)gear); double current_ratio = ((double)g.crankset / (double)g.rearCog); - return (((double)wheel_size) / original_ratio) * ((double)current_ratio); + return (((double)settings.value(QZSettings::gear_wheel_size, QZSettings::default_gear_wheel_size).toDouble()) / original_ratio) * ((double)current_ratio); } void wahookickrsnapbike::serviceDiscovered(const QBluetoothUuid &gatt) { diff --git a/src/devices/wahookickrsnapbike/wahookickrsnapbike.h b/src/devices/wahookickrsnapbike/wahookickrsnapbike.h index 8ece29240..9d8c3cc99 100644 --- a/src/devices/wahookickrsnapbike/wahookickrsnapbike.h +++ b/src/devices/wahookickrsnapbike/wahookickrsnapbike.h @@ -107,37 +107,51 @@ class wahookickrsnapbike : public bike { volatile int notificationSubscribed = 0; - resistance_t lastForcedResistance = -1; + resistance_t lastForcedResistance = -1; - // cranks & co. - double crankset = 40; - double rear_cog_size = 14; - double wheel_size = 2230; // mm 700x44 double gearsToWheelDiameter(double gear); class GearTable { public: + + int maxGears = 12; + struct GearInfo { int gear; int crankset; int rearCog; }; - GearTable() { - gears = { - {1, 38, 44}, - {2, 38, 38}, - {3, 38, 32}, - {4, 38, 28}, - {5, 38, 24}, - {6, 38, 21}, - {7, 38, 19}, - {8, 38, 17}, - {9, 38, 15}, - {10, 38, 13}, - {11, 38, 11}, - {12, 38, 10} - }; + void loadGearSettings() { + QSettings settings; + + QString gearConfig = settings.value("gear_configuration").toString(); + if (gearConfig.isEmpty()) { + + gearConfig = "1|38|44|true\n2|38|38|true\n3|38|32|true\n4|38|28|true\n" + "5|38|24|true\n6|38|21|true\n7|38|19|true\n8|38|17|true\n" + "9|38|15|true\n10|38|13|true\n11|38|11|true\n12|38|10|true"; + } + + gears.clear(); + maxGears = 0; + + // Parsa la configurazione + QStringList rows = gearConfig.split('\n'); + for (const QString& row : rows) { + QStringList parts = row.split('|'); + if (parts.size() >= 4 && (parts[3] == "true")) { + GearInfo config; + config.gear = parts[0].toInt(); + config.crankset = parts[1].toInt(); + config.rearCog = parts[2].toInt(); + + gears.push_back(config); + maxGears = qMax(maxGears, config.gear); + } + } + + printTable(); } void addGear(int gear, int crankset, int rearCog) { @@ -169,9 +183,13 @@ class wahookickrsnapbike : public bike { return GearInfo(); } + GearTable() { + loadGearSettings(); + } + private: std::vector gears; - }; + }; #ifdef Q_OS_IOS diff --git a/src/qzsettings.cpp b/src/qzsettings.cpp index ac0433131..f4b33957e 100644 --- a/src/qzsettings.cpp +++ b/src/qzsettings.cpp @@ -769,8 +769,15 @@ const QString QZSettings::default_peloton_date_format = QStringLiteral("MM/dd/yy const QString QZSettings::force_resistance_instead_inclination = QStringLiteral("force_resistance_instead_inclination"); const QString QZSettings::proform_treadmill_575i = QStringLiteral("proform_treadmill_575i"); const QString QZSettings::zwift_play_emulator = QStringLiteral("zwift_play_emulator"); +const QString QZSettings::gear_configuration = QStringLiteral("gear_configuration"); +const QString QZSettings::default_gear_configuration = QStringLiteral("1|38|44|true\n2|38|38|true\n3|38|32|true\n4|38|28|true\n5|38|24|true\n6|38|21|true\n7|38|19|true\n8|38|17|true\n9|38|15|true\n10|38|13|true\n11|38|11|true\n12|38|10|true"); +const QString QZSettings::gear_crankset_size = QStringLiteral("gear_crankset_size"); +const QString QZSettings::gear_cog_size = QStringLiteral("gear_cog_size"); +const QString QZSettings::gear_wheel_size = QStringLiteral("gear_wheel_size"); +const QString QZSettings::default_gear_wheel_size = QStringLiteral("700 x 18C"); +const QString QZSettings::gear_circumference = QStringLiteral("gear_circumference"); -const uint32_t allSettingsCount = 651; +const uint32_t allSettingsCount = 656; QVariant allSettings[allSettingsCount][2] = { {QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles}, @@ -1428,6 +1435,11 @@ QVariant allSettings[allSettingsCount][2] = { {QZSettings::force_resistance_instead_inclination, QZSettings::default_force_resistance_instead_inclination}, {QZSettings::proform_treadmill_575i, QZSettings::default_proform_treadmill_575i}, {QZSettings::zwift_play_emulator, QZSettings::default_zwift_play_emulator}, + {QZSettings::gear_configuration, QZSettings::default_gear_configuration}, + {QZSettings::gear_crankset_size, QZSettings::default_gear_crankset_size}, + {QZSettings::gear_cog_size, QZSettings::default_gear_cog_size}, + {QZSettings::gear_wheel_size, QZSettings::default_gear_wheel_size}, + {QZSettings::gear_circumference, QZSettings::default_gear_circumference}, }; void QZSettings::qDebugAllSettings(bool showDefaults) { diff --git a/src/qzsettings.h b/src/qzsettings.h index 8fdb021ed..e0b40e3ab 100644 --- a/src/qzsettings.h +++ b/src/qzsettings.h @@ -2153,6 +2153,21 @@ class QZSettings { static const QString zwift_play_emulator; static constexpr bool default_zwift_play_emulator = false; + static const QString gear_configuration; + static const QString default_gear_configuration; + + static const QString gear_crankset_size; + static constexpr int default_gear_crankset_size = 38; + + static const QString gear_cog_size; + static constexpr int default_gear_cog_size = 44; + + static const QString gear_wheel_size; + static const QString default_gear_wheel_size; + + static const QString gear_circumference; + static constexpr double default_gear_circumference = 2070.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. From 379cf8d7de3a658e8b21513aa3dc0471478ecf24 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 29 Oct 2024 08:02:08 +0100 Subject: [PATCH 13/45] 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 26250a8bb..4bea73964 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 @@ -4068,7 +4068,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 919; + CURRENT_PROJECT_VERSION = 920; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1"; @@ -4259,7 +4259,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 919; + CURRENT_PROJECT_VERSION = 920; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; @@ -4486,7 +4486,7 @@ CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 919; + CURRENT_PROJECT_VERSION = 920; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -4582,7 +4582,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 919; + CURRENT_PROJECT_VERSION = 920; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; @@ -4674,7 +4674,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 919; + CURRENT_PROJECT_VERSION = 920; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; ENABLE_PREVIEWS = YES; @@ -4788,7 +4788,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 919; + CURRENT_PROJECT_VERSION = 920; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; From 64b9ec9d72685afc1d08a56f07dc5d108f2f4756 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 29 Oct 2024 08:17:38 +0100 Subject: [PATCH 14/45] fixing gear conversion --- src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp | 4 +++- src/devices/wahookickrsnapbike/wahookickrsnapbike.h | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp b/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp index 394c48914..92f21ace5 100644 --- a/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp +++ b/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp @@ -31,6 +31,8 @@ wahookickrsnapbike::wahookickrsnapbike(bool noWriteResistance, bool noHeartServi initDone = false; connect(refresh, &QTimer::timeout, this, &wahookickrsnapbike::update); refresh->start(200ms); + GearTable g; + g.printTable(); } bool wahookickrsnapbike::writeCharacteristic(uint8_t *data, uint8_t data_len, QString info, bool disable_log, @@ -297,7 +299,7 @@ double wahookickrsnapbike::gearsToWheelDiameter(double gear) { double original_ratio = ((double)settings.value(QZSettings::gear_crankset_size, QZSettings::default_gear_crankset_size).toDouble()) / ((double)settings.value(QZSettings::gear_cog_size, QZSettings::default_gear_cog_size).toDouble()); GearTable::GearInfo g = table.getGear((int)gear); double current_ratio = ((double)g.crankset / (double)g.rearCog); - return (((double)settings.value(QZSettings::gear_wheel_size, QZSettings::default_gear_wheel_size).toDouble()) / original_ratio) * ((double)current_ratio); + return (((double)settings.value(QZSettings::gear_circumference, QZSettings::default_gear_circumference).toDouble()) / original_ratio) * ((double)current_ratio); } void wahookickrsnapbike::serviceDiscovered(const QBluetoothUuid &gatt) { diff --git a/src/devices/wahookickrsnapbike/wahookickrsnapbike.h b/src/devices/wahookickrsnapbike/wahookickrsnapbike.h index 9d8c3cc99..27fac2416 100644 --- a/src/devices/wahookickrsnapbike/wahookickrsnapbike.h +++ b/src/devices/wahookickrsnapbike/wahookickrsnapbike.h @@ -150,8 +150,6 @@ class wahookickrsnapbike : public bike { maxGears = qMax(maxGears, config.gear); } } - - printTable(); } void addGear(int gear, int crankset, int rearCog) { From d7c3a84adb456b7fbee32bba24c60b72e4cc6d28 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 29 Oct 2024 08:21:59 +0100 Subject: [PATCH 15/45] adding max and minGears --- .../qdomyoszwift.xcodeproj/project.pbxproj | 12 ++++++------ src/devices/bike.cpp | 8 ++++++++ src/devices/bike.h | 2 ++ .../wahookickrsnapbike/wahookickrsnapbike.cpp | 9 +++++++++ src/devices/wahookickrsnapbike/wahookickrsnapbike.h | 2 ++ 5 files changed, 27 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 4bea73964..93a6fc936 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 @@ -4068,7 +4068,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 920; + CURRENT_PROJECT_VERSION = 921; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1"; @@ -4259,7 +4259,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 920; + CURRENT_PROJECT_VERSION = 921; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; @@ -4486,7 +4486,7 @@ CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 920; + CURRENT_PROJECT_VERSION = 921; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -4582,7 +4582,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 920; + CURRENT_PROJECT_VERSION = 921; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; @@ -4674,7 +4674,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 920; + CURRENT_PROJECT_VERSION = 921; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; ENABLE_PREVIEWS = YES; @@ -4788,7 +4788,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 920; + CURRENT_PROJECT_VERSION = 921; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; diff --git a/src/devices/bike.cpp b/src/devices/bike.cpp index b64d715d5..597bf2151 100644 --- a/src/devices/bike.cpp +++ b/src/devices/bike.cpp @@ -106,6 +106,14 @@ void bike::setGears(double gears) { qDebug() << "new gear value ignored because of gears_zwift_ratio setting!"; return; } + if(gears > maxGears()) { + qDebug() << "new gear value ignored because of maxGears" << maxGears(); + return; + } + if(gears < minGears()) { + qDebug() << "new gear value ignored because of minGears" << minGears(); + return; + } m_gears = gears; if(homeform::singleton()) { homeform::singleton()->updateGearsValue(); diff --git a/src/devices/bike.h b/src/devices/bike.h index c296a9138..f075bd784 100644 --- a/src/devices/bike.h +++ b/src/devices/bike.h @@ -23,6 +23,8 @@ class bike : public bluetoothdevice { double currentCrankRevolutions() override; uint16_t lastCrankEventTime() override; bool connected() override; + virtual double maxGears() { return 9999.0; } + virtual double minGears() { return -9999.0; } virtual uint16_t watts(); virtual resistance_t pelotonToBikeResistance(int pelotonResistance); virtual resistance_t resistanceFromPowerRequest(uint16_t power); diff --git a/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp b/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp index 92f21ace5..16f116434 100644 --- a/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp +++ b/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp @@ -854,3 +854,12 @@ void wahookickrsnapbike::inclinationChanged(double grade, double percentage) { bool wahookickrsnapbike::inclinationAvailableByHardware() { return KICKR_BIKE; } + +double wahookickrsnapbike::maxGears() { + GearTable g; + return g.maxGears; +} + +double wahookickrsnapbike::minGears() { + return 1; +} diff --git a/src/devices/wahookickrsnapbike/wahookickrsnapbike.h b/src/devices/wahookickrsnapbike/wahookickrsnapbike.h index 27fac2416..b77f8322f 100644 --- a/src/devices/wahookickrsnapbike/wahookickrsnapbike.h +++ b/src/devices/wahookickrsnapbike/wahookickrsnapbike.h @@ -42,6 +42,8 @@ class wahookickrsnapbike : public bike { bool connected() override; resistance_t maxResistance() override { return 100; } bool inclinationAvailableByHardware() override; + double maxGears() override; + double minGears() override; enum OperationCode : uint8_t { _unlock = 32, From a7b8e6323526961868f272132e38bd844258bd05 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 29 Oct 2024 08:50:33 +0100 Subject: [PATCH 16/45] fixing UI and settings --- src/gears.qml | 136 +++++++++++++++++++++++++++-------------------- src/qzsettings.h | 4 +- src/settings.qml | 4 +- 3 files changed, 81 insertions(+), 63 deletions(-) diff --git a/src/gears.qml b/src/gears.qml index a16ed97d0..6f1d6ea0f 100644 --- a/src/gears.qml +++ b/src/gears.qml @@ -17,8 +17,8 @@ ScrollView { Settings { id: settings property string gear_configuration: "1|38|44|true\n2|38|38|true\n3|38|32|true\n4|38|28|true\n5|38|24|true\n6|38|21|true\n7|38|19|true\n8|38|17|true\n9|38|15|true\n10|38|13|true\n11|38|11|true\n12|38|10|true" - property int gear_crankset_size: 38 - property int gear_cog_size: 44 + property int gear_crankset_size: 42 + property int gear_cog_size: 14 property string gear_wheel_size: "700 x 18C" property real gear_circumference: 2070 } @@ -113,12 +113,6 @@ ScrollView { gearRows = newGears - // Update the first crankset size - if (gearRows.length > 0) { - selectedCranksetSize = gearRows[0].crankset - selectedCogSize = gearRows[0].cog - } - // Force update var temp = gearRows gearRows = [] @@ -324,74 +318,62 @@ ScrollView { anchors.fill: parent anchors.margins: 20 spacing: 20 - - GroupBox { - title: "Preset Gear Profiles" - Layout.fillWidth: true - - ComboBox { - id: profileCombo - width: parent.width - textRole: "text" - displayText: currentIndex < 0 ? "Select a profile..." : model.get(currentIndex).text - model: ListModel { - id: profileModel - } - - Component.onCompleted: { - for (var key in gearProfiles) { - profileModel.append({ - text: gearProfiles[key].name, - value: key - }) - } - } - - onCurrentIndexChanged: { - if (currentIndex >= 0) { - loadGearProfile(profileModel.get(currentIndex).value) - } - } - } - } + id: chainringColumn // Crankset Size GroupBox { - title: "Crankset Size (tooth count)" + title: "Chainring Size" Layout.fillWidth: true - SpinBox { - from: 1 - to: 60 - value: selectedCranksetSize - onValueChanged: { - selectedCranksetSize = value - console.log("Crankset Size changed"); - settingsChanged() + ColumnLayout { + Label { + text: "Tooth count of your chainring on the bike you are currently riding on your trainer - enter 42 for Zwift Ride" + wrapMode: Text.WordWrap + Layout.fillWidth: true + Layout.maximumWidth: chainringColumn.width - 20 + } + + SpinBox { + from: 1 + to: 60 + value: selectedCranksetSize + onValueChanged: { + selectedCranksetSize = value + console.log("Crankset Size changed"); + settingsChanged() + } } } } // Cog Size GroupBox { - title: "Cog Size (tooth count)" + title: "Cog Size" Layout.fillWidth: true - SpinBox { - from: 1 - to: 50 - value: selectedCogSize - onValueChanged: { - selectedCogSize = value - console.log("Cog Size changed"); - settingsChanged() + ColumnLayout { + Label { + text: "Tooth count of your rear cog on your trainer - enter 14 if you have the Zwift Cog" + wrapMode: Text.WordWrap + Layout.fillWidth: true + Layout.maximumWidth: chainringColumn.width - 20 + } + SpinBox { + from: 1 + to: 50 + value: selectedCogSize + onValueChanged: { + selectedCogSize = value + console.log("Cog Size changed"); + settingsChanged() + } } } } // Wheel Size GroupBox { - title: "Wheel Size" + title: "Virtual Wheel Size" Layout.fillWidth: true ComboBox { @@ -498,9 +480,40 @@ ScrollView { } } + GroupBox { + title: "Preset Gear Profiles" + Layout.fillWidth: true + + ComboBox { + id: profileCombo + width: parent.width + textRole: "text" + displayText: currentIndex < 0 ? "Select a profile..." : model.get(currentIndex).text + model: ListModel { + id: profileModel + } + + Component.onCompleted: { + for (var key in gearProfiles) { + profileModel.append({ + text: gearProfiles[key].name, + value: key + }) + } + } + + onCurrentIndexChanged: { + if (currentIndex >= 0) { + loadGearProfile(profileModel.get(currentIndex).value) + } + } + } + } + + // Gear Table GroupBox GroupBox { - title: "Gear Table" + title: "Virtual Gear Table" Layout.fillWidth: true Layout.fillHeight: true Layout.preferredHeight: parent.height @@ -576,7 +589,7 @@ ScrollView { Text { anchors.centerIn: parent - text: "Crankset" + text: "Chainring" font.bold: true color: "black" } @@ -610,6 +623,11 @@ ScrollView { id: gearListModel } + ScrollBar.vertical: ScrollBar { + policy: ScrollBar.AlwaysOff + } + + Component.onCompleted: { updateGearListModel() } diff --git a/src/qzsettings.h b/src/qzsettings.h index e0b40e3ab..eb2ebf1b1 100644 --- a/src/qzsettings.h +++ b/src/qzsettings.h @@ -2157,10 +2157,10 @@ class QZSettings { static const QString default_gear_configuration; static const QString gear_crankset_size; - static constexpr int default_gear_crankset_size = 38; + static constexpr int default_gear_crankset_size = 42; static const QString gear_cog_size; - static constexpr int default_gear_cog_size = 44; + static constexpr int default_gear_cog_size = 14; static const QString gear_wheel_size; static const QString default_gear_wheel_size; diff --git a/src/settings.qml b/src/settings.qml index 7d16e5c8c..6a872938e 100644 --- a/src/settings.qml +++ b/src/settings.qml @@ -990,8 +990,8 @@ import QtQuick.Dialogs 1.0 // from version 2.18.2 property string gear_configuration: "1|38|44|true\n2|38|38|true\n3|38|32|true\n4|38|28|true\n5|38|24|true\n6|38|21|true\n7|38|19|true\n8|38|17|true\n9|38|15|true\n10|38|13|true\n11|38|11|true\n12|38|10|true" - property int gear_crankset_size: 38 - property int gear_cog_size: 44 + property int gear_crankset_size: 42 + property int gear_cog_size: 14 property string gear_wheel_size: "700 x 18C" property real gear_circumference: 2070 } From 82a6afe6b6ab5432771ba959ce460d4326dfea43 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 29 Oct 2024 12:28:13 +0100 Subject: [PATCH 17/45] kickr core to wahookickr class --- .../qdomyoszwift.xcodeproj/project.pbxproj | 12 ++++++------ src/devices/bluetooth.cpp | 2 +- 2 files changed, 7 insertions(+), 7 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 93a6fc936..fa6126769 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 @@ -4068,7 +4068,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 921; + CURRENT_PROJECT_VERSION = 922; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1"; @@ -4259,7 +4259,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 921; + CURRENT_PROJECT_VERSION = 922; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; @@ -4486,7 +4486,7 @@ CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 921; + CURRENT_PROJECT_VERSION = 922; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -4582,7 +4582,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 921; + CURRENT_PROJECT_VERSION = 922; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; @@ -4674,7 +4674,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 921; + CURRENT_PROJECT_VERSION = 922; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; ENABLE_PREVIEWS = YES; @@ -4788,7 +4788,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 921; + CURRENT_PROJECT_VERSION = 922; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; diff --git a/src/devices/bluetooth.cpp b/src/devices/bluetooth.cpp index 4690b16b8..f1472dad4 100644 --- a/src/devices/bluetooth.cpp +++ b/src/devices/bluetooth.cpp @@ -1511,7 +1511,6 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) { (b.name().toUpper().startsWith(ftmsAccessoryName.toUpper()) && settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton) .toBool()) || // ss2k on a peloton bike - ((b.name().toUpper().startsWith("KICKR CORE")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || (b.name().toUpper().startsWith("MERACH-MR667-")) || (b.name().toUpper().startsWith("DS60-")) || (b.name().toUpper().startsWith("BIKE-")) || @@ -1562,6 +1561,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) { this->signalBluetoothDeviceConnected(ftmsBike); } else if ((b.name().toUpper().startsWith("KICKR SNAP") || b.name().toUpper().startsWith("KICKR BIKE") || b.name().toUpper().startsWith("KICKR ROLLR") || + b.name().toUpper().startsWith("KICKR CORE") || (b.name().toUpper().startsWith("HAMMER ") && saris_trainer) || (b.name().toUpper().startsWith("WAHOO KICKR"))) && !wahooKickrSnapBike && !ftmsBike && filter) { From 9cc02dede925eb33b4b9462ffbe1d4e542b433b7 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Wed, 30 Oct 2024 15:57:31 +0100 Subject: [PATCH 18/45] ftms wheel circumference for gears --- src/devices/ftmsbike/ftmsbike.cpp | 29 +++-- src/devices/ftmsbike/ftmsbike.h | 2 + .../wahookickrsnapbike/wahookickrsnapbike.cpp | 19 +-- .../wahookickrsnapbike/wahookickrsnapbike.h | 82 +----------- src/qdomyos-zwift.pri | 1 + src/wheelcircumference.h | 120 ++++++++++++++++++ 6 files changed, 148 insertions(+), 105 deletions(-) create mode 100644 src/wheelcircumference.h diff --git a/src/devices/ftmsbike/ftmsbike.cpp b/src/devices/ftmsbike/ftmsbike.cpp index a118cb1f9..58f85b776 100644 --- a/src/devices/ftmsbike/ftmsbike.cpp +++ b/src/devices/ftmsbike/ftmsbike.cpp @@ -35,6 +35,8 @@ ftmsbike::ftmsbike(bool noWriteResistance, bool noHeartService, int8_t bikeResis initDone = false; connect(refresh, &QTimer::timeout, this, &ftmsbike::update); refresh->start(settings.value(QZSettings::poll_device_time, QZSettings::default_poll_device_time).toInt()); + wheelCircumference::GearTable g; + g.printTable(); } void ftmsbike::writeCharacteristicZwiftPlay(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log, @@ -170,6 +172,18 @@ void ftmsbike::zwiftPlayInit() { } } +void ftmsbike::setWheelDiameter(double diameter) { + uint8_t write[] = {FTMS_SET_WHEEL_CIRCUMFERENCE, 0x00, 0x00}; + + diameter = diameter * 10.0; + + write[1] = ((uint16_t)diameter) & 0xFF; + write[2] = ((uint16_t)diameter) >> 8; + + writeCharacteristic(write, sizeof(write), QStringLiteral("setWheelCircumference ") + QString::number(diameter)); +} + + void ftmsbike::forcePower(int16_t requestPower) { if(resistance_lvl_mode) { forceResistance(resistanceFromPowerRequest(requestPower)); @@ -285,14 +299,16 @@ void ftmsbike::update() { if (((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike) && (requestPower == 0 || requestPower == -1)) { init(); - forceResistance(requestResistance + (gears() * 5)); + if(requestResistance != - 1) + forceResistance(requestResistance + (gears() * 5)); + else + setWheelDiameter(wheelCircumference::gearsToWheelDiameter(gears())); } } requestResistance = -1; } if((virtualBike && virtualBike->ftmsDeviceConnected()) && lastGearValue != gears() && lastRawRequestedInclinationValue != -100 && lastPacketFromFTMS.length() >= 7) { - qDebug() << "injecting fake ftms frame in order to send the new gear value ASAP" << lastPacketFromFTMS.toHex(' '); - ftmsCharacteristicChanged(QLowEnergyCharacteristic(), lastPacketFromFTMS); + setWheelDiameter(wheelCircumference::gearsToWheelDiameter(gears())); } QSettings settings; @@ -1065,13 +1081,8 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact lastPacketFromFTMS.append(b.at(i)); qDebug() << "lastPacketFromFTMS" << lastPacketFromFTMS.toHex(' '); int16_t slope = (((uint8_t)b.at(3)) + (b.at(4) << 8)); - if (gears() != 0) { - slope += (gears() * 50); - } b[3] = slope & 0xFF; - b[4] = slope >> 8; - - qDebug() << "applying gears mod" << gears() << slope; + b[4] = slope >> 8; /*} else if(b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS && zwiftPlayService != nullptr && gears_zwift_ratio) { int16_t slope = (((uint8_t)b.at(3)) + (b.at(4) << 8)); uint8_t gear2[] = {0x04, 0x22, 0x02, 0x10, 0x00}; diff --git a/src/devices/ftmsbike/ftmsbike.h b/src/devices/ftmsbike/ftmsbike.h index fe21e284b..fa4fd4e06 100644 --- a/src/devices/ftmsbike/ftmsbike.h +++ b/src/devices/ftmsbike/ftmsbike.h @@ -26,6 +26,7 @@ #include #include +#include "wheelcircumference.h" #include "devices/bike.h" #ifdef Q_OS_IOS @@ -85,6 +86,7 @@ class ftmsbike : public bike { void init(); void forceResistance(resistance_t requestResistance); void forcePower(int16_t requestPower); + void setWheelDiameter(double diameter); uint16_t wattsFromResistance(double resistance); QTimer *refresh; diff --git a/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp b/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp index 16f116434..f15abc55c 100644 --- a/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp +++ b/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp @@ -31,7 +31,7 @@ wahookickrsnapbike::wahookickrsnapbike(bool noWriteResistance, bool noHeartServi initDone = false; connect(refresh, &QTimer::timeout, this, &wahookickrsnapbike::update); refresh->start(200ms); - GearTable g; + wheelCircumference::GearTable g; g.printTable(); } @@ -194,7 +194,7 @@ void wahookickrsnapbike::update() { } QThread::msleep(700); - QByteArray d = setWheelCircumference(gearsToWheelDiameter(gears())); + QByteArray d = setWheelCircumference(wheelCircumference::gearsToWheelDiameter(gears())); uint8_t e[20]; setGears(1); memcpy(e, d.constData(), d.length()); @@ -267,7 +267,7 @@ void wahookickrsnapbike::update() { memcpy(b, a.constData(), a.length()); writeCharacteristic(b, a.length(), "setResistance", false, true); } else if (virtualBike && virtualBike->ftmsDeviceConnected() && lastGearValue != gears()) { - QByteArray a = setWheelCircumference(gearsToWheelDiameter(gears())); + QByteArray a = setWheelCircumference(wheelCircumference::gearsToWheelDiameter(gears())); uint8_t b[20]; memcpy(b, a.constData(), a.length()); writeCharacteristic(b, a.length(), "setWheelCircumference", false, true); @@ -291,17 +291,6 @@ void wahookickrsnapbike::update() { } } -double wahookickrsnapbike::gearsToWheelDiameter(double gear) { - QSettings settings; - GearTable table; - if(gear < 1) gear = 1; - else if(gear > table.maxGears) gear = table.maxGears; - double original_ratio = ((double)settings.value(QZSettings::gear_crankset_size, QZSettings::default_gear_crankset_size).toDouble()) / ((double)settings.value(QZSettings::gear_cog_size, QZSettings::default_gear_cog_size).toDouble()); - GearTable::GearInfo g = table.getGear((int)gear); - double current_ratio = ((double)g.crankset / (double)g.rearCog); - return (((double)settings.value(QZSettings::gear_circumference, QZSettings::default_gear_circumference).toDouble()) / original_ratio) * ((double)current_ratio); -} - void wahookickrsnapbike::serviceDiscovered(const QBluetoothUuid &gatt) { emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString()); } @@ -856,7 +845,7 @@ bool wahookickrsnapbike::inclinationAvailableByHardware() { } double wahookickrsnapbike::maxGears() { - GearTable g; + wheelCircumference::GearTable g; return g.maxGears; } diff --git a/src/devices/wahookickrsnapbike/wahookickrsnapbike.h b/src/devices/wahookickrsnapbike/wahookickrsnapbike.h index b77f8322f..16522fa4c 100644 --- a/src/devices/wahookickrsnapbike/wahookickrsnapbike.h +++ b/src/devices/wahookickrsnapbike/wahookickrsnapbike.h @@ -26,6 +26,7 @@ #include #include +#include "wheelcircumference.h" #include "devices/bike.h" #include "virtualdevices/virtualbike.h" @@ -111,87 +112,6 @@ class wahookickrsnapbike : public bike { resistance_t lastForcedResistance = -1; - double gearsToWheelDiameter(double gear); - - class GearTable { - public: - - int maxGears = 12; - - struct GearInfo { - int gear; - int crankset; - int rearCog; - }; - - void loadGearSettings() { - QSettings settings; - - QString gearConfig = settings.value("gear_configuration").toString(); - if (gearConfig.isEmpty()) { - - gearConfig = "1|38|44|true\n2|38|38|true\n3|38|32|true\n4|38|28|true\n" - "5|38|24|true\n6|38|21|true\n7|38|19|true\n8|38|17|true\n" - "9|38|15|true\n10|38|13|true\n11|38|11|true\n12|38|10|true"; - } - - gears.clear(); - maxGears = 0; - - // Parsa la configurazione - QStringList rows = gearConfig.split('\n'); - for (const QString& row : rows) { - QStringList parts = row.split('|'); - if (parts.size() >= 4 && (parts[3] == "true")) { - GearInfo config; - config.gear = parts[0].toInt(); - config.crankset = parts[1].toInt(); - config.rearCog = parts[2].toInt(); - - gears.push_back(config); - maxGears = qMax(maxGears, config.gear); - } - } - } - - void addGear(int gear, int crankset, int rearCog) { - gears.push_back({gear, crankset, rearCog}); - } - - void removeGear(int gear) { - gears.erase(std::remove_if(gears.begin(), gears.end(), - [gear](const GearInfo& info) { return info.gear == gear; }), - gears.end()); - } - - void printTable() const { - qDebug() << "| Gear | Crankset | Rear Cog |\n"; - qDebug() << "|------|----------|----------|\n"; - for (const auto& gear : gears) { - qDebug() << "| " << gear.gear << " | " << gear.crankset - << " | " << gear.rearCog << " |\n"; - } - } - - GearInfo getGear(int gearNumber) const { - auto it = std::find_if(gears.begin(), gears.end(), - [gearNumber](const GearInfo& info) { return info.gear == gearNumber; }); - - if (it != gears.end()) { - return *it; - } - return GearInfo(); - } - - GearTable() { - loadGearSettings(); - } - - private: - std::vector gears; - }; - - #ifdef Q_OS_IOS lockscreen *h = 0; #endif diff --git a/src/qdomyos-zwift.pri b/src/qdomyos-zwift.pri index fa8d97c41..47108ef8a 100644 --- a/src/qdomyos-zwift.pri +++ b/src/qdomyos-zwift.pri @@ -313,6 +313,7 @@ HEADERS += \ $$PWD/devices/trxappgateusbelliptical/trxappgateusbelliptical.h \ $$PWD/ergtable.h \ $$PWD/treadmillErgTable.h \ + $$PWD/wheelcircumference.h \ QTelnet.h \ devices/bkoolbike/bkoolbike.h \ devices/csaferower/csafe.h \ diff --git a/src/wheelcircumference.h b/src/wheelcircumference.h new file mode 100644 index 000000000..a43c4d4e0 --- /dev/null +++ b/src/wheelcircumference.h @@ -0,0 +1,120 @@ +#ifndef WHEELCIRCUMFERENCE_H +#define WHEELCIRCUMFERENCE_H + +#include + +#ifndef Q_OS_ANDROID +#include +#else +#include +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "qzsettings.h" + +class wheelCircumference : public QObject { + + Q_OBJECT + + public: + static double gearsToWheelDiameter(double gear) { + QSettings settings; + GearTable table; + if(gear < 1) gear = 1; + else if(gear > table.maxGears) gear = table.maxGears; + double original_ratio = ((double)settings.value(QZSettings::gear_crankset_size, QZSettings::default_gear_crankset_size).toDouble()) / ((double)settings.value(QZSettings::gear_cog_size, QZSettings::default_gear_cog_size).toDouble()); + GearTable::GearInfo g = table.getGear((int)gear); + double current_ratio = ((double)g.crankset / (double)g.rearCog); + return (((double)settings.value(QZSettings::gear_circumference, QZSettings::default_gear_circumference).toDouble()) / original_ratio) * ((double)current_ratio); + } + + + class GearTable { + public: + + int maxGears = 12; + + struct GearInfo { + int gear; + int crankset; + int rearCog; + }; + + void loadGearSettings() { + QSettings settings; + + QString gearConfig = settings.value("gear_configuration").toString(); + if (gearConfig.isEmpty()) { + + gearConfig = "1|38|44|true\n2|38|38|true\n3|38|32|true\n4|38|28|true\n" + "5|38|24|true\n6|38|21|true\n7|38|19|true\n8|38|17|true\n" + "9|38|15|true\n10|38|13|true\n11|38|11|true\n12|38|10|true"; + } + + gears.clear(); + maxGears = 0; + + // Parsa la configurazione + QStringList rows = gearConfig.split('\n'); + for (const QString& row : rows) { + QStringList parts = row.split('|'); + if (parts.size() >= 4 && (parts[3] == "true")) { + GearInfo config; + config.gear = parts[0].toInt(); + config.crankset = parts[1].toInt(); + config.rearCog = parts[2].toInt(); + + gears.push_back(config); + maxGears = qMax(maxGears, config.gear); + } + } + } + + void addGear(int gear, int crankset, int rearCog) { + gears.push_back({gear, crankset, rearCog}); + } + + void removeGear(int gear) { + gears.erase(std::remove_if(gears.begin(), gears.end(), + [gear](const GearInfo& info) { return info.gear == gear; }), + gears.end()); + } + + void printTable() const { + qDebug() << "| Gear | Crankset | Rear Cog |\n"; + qDebug() << "|------|----------|----------|\n"; + for (const auto& gear : gears) { + qDebug() << "| " << gear.gear << " | " << gear.crankset + << " | " << gear.rearCog << " |\n"; + } + } + + GearInfo getGear(int gearNumber) const { + auto it = std::find_if(gears.begin(), gears.end(), + [gearNumber](const GearInfo& info) { return info.gear == gearNumber; }); + + if (it != gears.end()) { + return *it; + } + return GearInfo(); + } + + GearTable() { + loadGearSettings(); + } + + private: + std::vector gears; + }; +}; + +#endif // WHEELCIRCUMFERENCE_H From dbb9ba3510eca40c982b0fc755fb9f70263c5528 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 4 Nov 2024 14:31:58 +0100 Subject: [PATCH 19/45] implementing --- src/devices/tacxneo2/tacxneo2.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/devices/tacxneo2/tacxneo2.cpp b/src/devices/tacxneo2/tacxneo2.cpp index 0d2d35b56..6f7c9fe07 100644 --- a/src/devices/tacxneo2/tacxneo2.cpp +++ b/src/devices/tacxneo2/tacxneo2.cpp @@ -7,6 +7,7 @@ #include #include #include +#include "wheelcircumference.h" #ifdef Q_OS_ANDROID #include "keepawakehelper.h" #include @@ -123,7 +124,7 @@ void tacxneo2::update() { auto virtualBike = this->VirtualBike(); if (requestResistance != -1) { - if (requestResistance != currentResistance().value() || lastGearValue != gears()) { + if (requestResistance != currentResistance().value()) { emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance)); if (((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike) && (requestPower == 0 || requestPower == -1)) { @@ -135,15 +136,18 @@ void tacxneo2::update() { } if (requestInclination != -100) { emit debug(QStringLiteral("writing inclination ") + QString::number(requestInclination)); - forceInclination(requestInclination + gears()); // since this bike doesn't have the concept of resistance, + forceInclination(requestInclination); // since this bike doesn't have the concept of resistance, // i'm using the gears in the inclination requestInclination = -100; - } else if((virtualBike && virtualBike->ftmsDeviceConnected()) && lastGearValue != gears() && lastRawRequestedInclinationValue != -100) { + } else if((virtualBike && virtualBike->ftmsDeviceConnected()) && lastRawRequestedInclinationValue != -100) { // in order to send the new gear value ASAP - forceInclination(lastRawRequestedInclinationValue + gears()); // since this bike doesn't have the concept of resistance, + forceInclination(lastRawRequestedInclinationValue); // since this bike doesn't have the concept of resistance, // i'm using the gears in the inclination } + if(lastGearValue != gears()) + setUserConfiguration(wheelCircumference::gearsToWheelDiameter(gears()), 1); + lastGearValue = gears(); if (requestPower != -1) { From 06253998ee3c8ea6b50b2cd1d790007cbcf5f086 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 4 Nov 2024 15:08:26 +0100 Subject: [PATCH 20/45] Update wahookickrsnapbike.cpp --- src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp b/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp index a52643eeb..6ae46d505 100644 --- a/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp +++ b/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp @@ -268,7 +268,7 @@ void wahookickrsnapbike::update() { } if (lastGearValue != gears()) { - QByteArray a = setWheelCircumference(gearsToWheelDiameter(gears())); + QByteArray a = setWheelCircumference(wheelCircumference::gearsToWheelDiameter(gears())); uint8_t b[20]; memcpy(b, a.constData(), a.length()); writeCharacteristic(b, a.length(), "setWheelCircumference", false, true); From 8b60b56dc2b686492f4671c892e82e71378a83e7 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Sat, 9 Nov 2024 15:43:06 +0100 Subject: [PATCH 21/45] Update ftmsbike.cpp --- src/devices/ftmsbike/ftmsbike.cpp | 137 +++++++++--------------------- 1 file changed, 38 insertions(+), 99 deletions(-) diff --git a/src/devices/ftmsbike/ftmsbike.cpp b/src/devices/ftmsbike/ftmsbike.cpp index df9d34de6..4b8cebf80 100644 --- a/src/devices/ftmsbike/ftmsbike.cpp +++ b/src/devices/ftmsbike/ftmsbike.cpp @@ -15,6 +15,7 @@ #include "keepawakehelper.h" #endif #include +#include "wheelcircumference.h" #ifdef Q_OS_IOS extern quint8 QZ_EnableDiscoveryCharsAndDescripttors; @@ -132,9 +133,8 @@ void ftmsbike::init() { void ftmsbike::zwiftPlayInit() { QSettings settings; - bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool(); - if(zwiftPlayService && gears_zwift_ratio) { + if(zwiftPlayService) { uint8_t rideOn[] = {0x52, 0x69, 0x64, 0x65, 0x4f, 0x6e, 0x02, 0x01}; writeCharacteristicZwiftPlay(rideOn, sizeof(rideOn), "rideOn", false, true); @@ -312,102 +312,42 @@ void ftmsbike::update() { } QSettings settings; - bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool(); - if(zwiftPlayService && gears_zwift_ratio && lastGearValue != gears()) { - uint8_t gear1[] = {0x04, 0x2a, 0x03, 0x10, 0xdc, 0xec}; - uint8_t gear2[] = {0x04, 0x2a, 0x04, 0x10, 0xdc, 0xec, 0x01}; - uint32_t gear_value = 0; - - switch((int)gears()) { - case 1: - gear_value = 0x3acc; - break; - case 2: - gear_value = 0x43fc; - break; - case 3: - gear_value = 0x4dac; - break; - case 4: - gear_value = 0x56d5; - break; - case 5: - gear_value = 0x608c; - break; - case 6: - gear_value = 0x6be8; - break; - case 7: - gear_value = 0x77c4; - break; - case 8: - gear_value = 0x183a0; - break; - case 9: - gear_value = 0x191a8; - break; - case 10: - gear_value = 0x19fb0; - break; - case 11: - gear_value = 0x1adb8; - break; - case 12: - gear_value = 0x1bbc0; - break; - case 13: - gear_value = 0x1cbf3; - break; - case 14: - gear_value = 0x1dca8; - break; - case 15: - gear_value = 0x1ecdc; - break; - case 16: - gear_value = 0x1fd90; - break; - case 17: - gear_value = 0x290d4; - break; - case 18: - gear_value = 0x2a498; - break; - case 19: - gear_value = 0x2b7dc; - break; - case 20: - gear_value = 0x2cb9f; - break; - case 21: - gear_value = 0x2e2d8; - break; - case 22: - gear_value = 0x2fa90; - break; - case 23: - gear_value = 0x391c8; - break; - case 24: - gear_value = 0x3acf3; - break; - default: - // Gestione del caso di default - break; - } - - gear_value = gear_value * settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble(); - - if(gear_value < 0x10000) { - gear1[4] = gear_value & 0xFF; - gear1[5] = ((gear_value & 0xFF00) >> 8) & 0xFF; - writeCharacteristicZwiftPlay(gear1, sizeof(gear1), "gear", false, true); - } else { - gear2[4] = gear_value & 0xFF; - gear2[5] = ((gear_value & 0xFF00) >> 8) & 0xFF; - gear2[6] = ((gear_value & 0xFF0000) >> 16) & 0xFF; - writeCharacteristicZwiftPlay(gear2, sizeof(gear2), "gear", false, true); + if(zwiftPlayService && lastGearValue != gears()) { + QSettings settings; + wheelCircumference::GearTable table; + wheelCircumference::GearTable::GearInfo g = table.getGear((int)gears()); + double original_ratio = ((double)settings.value(QZSettings::gear_crankset_size, QZSettings::default_gear_crankset_size).toDouble()) / + ((double)settings.value(QZSettings::gear_cog_size, QZSettings::default_gear_cog_size).toDouble()); + + double current_ratio = ((double)g.crankset / (double)g.rearCog); + + uint32_t gear_value = static_cast(10000.0 * (current_ratio/original_ratio) * (42.0/14.0)); + + QByteArray proto; + proto.append(0x04); // Length prefix + proto.append(0x2a); // Field number/wire type + + // Calculate varint size inline + uint32_t temp = gear_value; + int size = 0; + do { + size++; + temp >>= 7; + } while (temp > 0); + + // Use 0x03 for 2-byte values, 0x04 for 3-byte values + proto.append(size <= 2 ? 0x03 : 0x04); + + proto.append(0x10); // Field marker + + // Encode value as varint + temp = gear_value; + while (temp > 0x7F) { + proto.append((temp & 0x7F) | 0x80); + temp >>= 7; } + proto.append(temp & 0x7F); + writeCharacteristicZwiftPlay((uint8_t*)proto.data(), sizeof(proto), "gear", false, true); uint8_t gearApply[] = {0x00, 0x08, 0x88, 0x04}; writeCharacteristicZwiftPlay(gearApply, sizeof(gearApply), "gearApply", false, true); @@ -1069,13 +1009,12 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact QByteArray b = newValue; QSettings settings; - bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool(); if (gattWriteCharControlPointId.isValid()) { qDebug() << "routing FTMS packet to the bike from virtualbike" << characteristic.uuid() << newValue.toHex(' '); // handling gears - if (b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS && ((zwiftPlayService == nullptr && gears_zwift_ratio) || !gears_zwift_ratio)) { + if (b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS && zwiftPlayService == nullptr) { double min_inclination = settings.value(QZSettings::min_inclination, QZSettings::default_min_inclination).toDouble(); lastPacketFromFTMS.clear(); for(int i=0; i Date: Sat, 9 Nov 2024 15:48:01 +0100 Subject: [PATCH 22/45] first custom gear test --- .../qdomyoszwift.xcodeproj/project.pbxproj | 12 ++++++------ src/devices/ftmsbike/ftmsbike.cpp | 6 ++++++ 2 files changed, 12 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 0926d91c8..2dc425187 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 @@ -4068,7 +4068,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 931; + CURRENT_PROJECT_VERSION = 932; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1"; @@ -4259,7 +4259,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 931; + CURRENT_PROJECT_VERSION = 932; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; @@ -4486,7 +4486,7 @@ CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 931; + CURRENT_PROJECT_VERSION = 932; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -4582,7 +4582,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 931; + CURRENT_PROJECT_VERSION = 932; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; @@ -4674,7 +4674,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 931; + CURRENT_PROJECT_VERSION = 932; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; ENABLE_PREVIEWS = YES; @@ -4788,7 +4788,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 931; + CURRENT_PROJECT_VERSION = 932; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; diff --git a/src/devices/ftmsbike/ftmsbike.cpp b/src/devices/ftmsbike/ftmsbike.cpp index 4b8cebf80..30d86f4f8 100644 --- a/src/devices/ftmsbike/ftmsbike.cpp +++ b/src/devices/ftmsbike/ftmsbike.cpp @@ -323,6 +323,8 @@ void ftmsbike::update() { uint32_t gear_value = static_cast(10000.0 * (current_ratio/original_ratio) * (42.0/14.0)); + qDebug() << "zwift hub gear current ratio" << current_ratio << g.crankset << g.rearCog << "gear_value" << gear_value << "original_ratio" << original_ratio; + QByteArray proto; proto.append(0x04); // Length prefix proto.append(0x2a); // Field number/wire type @@ -333,6 +335,10 @@ void ftmsbike::update() { do { size++; temp >>= 7; + if(size > 3) { + qDebug() << "ERROR! on while"; + break; + } } while (temp > 0); // Use 0x03 for 2-byte values, 0x04 for 3-byte values From 07606facbaf47680e6aaa418b63b827d37926c71 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Sat, 9 Nov 2024 18:49:48 +0100 Subject: [PATCH 23/45] adding inclination custom message too --- .../qdomyoszwift.xcodeproj/project.pbxproj | 12 ++--- src/devices/ftmsbike/ftmsbike.cpp | 48 ++++++++++++++++--- 2 files changed, 47 insertions(+), 13 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 aedf95d83..bc46a0158 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 @@ -4068,7 +4068,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 933; + CURRENT_PROJECT_VERSION = 935; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1"; @@ -4259,7 +4259,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 933; + CURRENT_PROJECT_VERSION = 935; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; @@ -4486,7 +4486,7 @@ CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 933; + CURRENT_PROJECT_VERSION = 935; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -4582,7 +4582,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 933; + CURRENT_PROJECT_VERSION = 935; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; @@ -4674,7 +4674,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 933; + CURRENT_PROJECT_VERSION = 935; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; ENABLE_PREVIEWS = YES; @@ -4788,7 +4788,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 933; + CURRENT_PROJECT_VERSION = 935; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; diff --git a/src/devices/ftmsbike/ftmsbike.cpp b/src/devices/ftmsbike/ftmsbike.cpp index 30d86f4f8..3af5f1460 100644 --- a/src/devices/ftmsbike/ftmsbike.cpp +++ b/src/devices/ftmsbike/ftmsbike.cpp @@ -1038,15 +1038,49 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact b[3] = slope & 0xFF; b[4] = slope >> 8; - /*} else if(b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS && zwiftPlayService != nullptr && gears_zwift_ratio) { + } else if(b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS && zwiftPlayService != nullptr) { int16_t slope = (((uint8_t)b.at(3)) + (b.at(4) << 8)); - uint8_t gear2[] = {0x04, 0x22, 0x02, 0x10, 0x00}; - int g = (int)(((double)slope / 100.0) + settings.value(QZSettings::gears_offset, QZSettings::default_gears_offset).toDouble()); - if(g < 0) { - g = 0; + + QByteArray message; + + // Command code 0x04 + message.append(0x04); + + // SimulationParam tag (field 4 << 3 | wire_type 2 = 34) + message.append(0x22); + + // Lunghezza payload (11 bytes) + message.append(0x0b); + + // Wind (field 1) - fisso a 0 + message.append(0x08); // tag + message.append((uint8_t)0x00); // value = 0 + + // Incline (field 2) + message.append(0x10); // tag + qint32 inclineX100 = static_cast(slope); + // varint inclination + while (inclineX100 >= 0x80) { + message.append((inclineX100 & 0x7F) | 0x80); + inclineX100 >>= 7; } - gear2[4] = g; - writeCharacteristicZwiftPlay(gear2, sizeof(gear2), "gearInclination", false, false);*/ + message.append(inclineX100 & 0x7F); + + // CWa (field 3) - fix a 10220 (1.022 / 2) + message.append(0x18); // tag + message.append(0xec); // 236 + message.append(0x27); // 39 + + // Crr (field 4) - fix a 400 (0.004) + message.append(0x20); // tag + message.append(0x90); // 144 + message.append(0x03); // 3 + + writeCharacteristicZwiftPlay((uint8_t*)message.data(), message.length(), "gearInclination", false, false); + return; + } else if(b.at(0) == FTMS_SET_TARGET_POWER && zwiftPlayService != nullptr) { + qDebug() << "discarding"; + return; } else if(b.at(0) == FTMS_SET_TARGET_POWER && b.length() > 2) { lastPacketFromFTMS.clear(); for(int i=0; i Date: Sat, 9 Nov 2024 20:27:18 +0100 Subject: [PATCH 24/45] Update ftmsbike.cpp --- src/devices/ftmsbike/ftmsbike.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/devices/ftmsbike/ftmsbike.cpp b/src/devices/ftmsbike/ftmsbike.cpp index 3af5f1460..6c910a9d3 100644 --- a/src/devices/ftmsbike/ftmsbike.cpp +++ b/src/devices/ftmsbike/ftmsbike.cpp @@ -1054,7 +1054,7 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact // Wind (field 1) - fisso a 0 message.append(0x08); // tag - message.append((uint8_t)0x00); // value = 0 + message.append((char)0x00); // value = 0 // Incline (field 2) message.append(0x10); // tag From 9ca3fca817b9071de56868663498c68e0713a9b5 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 11 Nov 2024 09:04:48 +0100 Subject: [PATCH 25/45] implemented protobuf --- .../qdomyoszwift.xcodeproj/project.pbxproj | 20 +- src/devices/ftmsbike/ftmsbike.cpp | 45 +- src/devices/zwifthubbike/Zwift hub.pb.swift | 1838 +++++++++++++++++ src/devices/zwifthubbike/Zwift hub.proto | 165 ++ src/devices/zwifthubbike/zwifthubbike.swift | 33 + src/ios/lockscreen.h | 5 + src/ios/lockscreen.mm | 14 + 7 files changed, 2079 insertions(+), 41 deletions(-) create mode 100644 src/devices/zwifthubbike/Zwift hub.pb.swift create mode 100644 src/devices/zwifthubbike/Zwift hub.proto create mode 100644 src/devices/zwifthubbike/zwifthubbike.swift 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 bc46a0158..9906018fd 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 @@ -439,6 +439,8 @@ 87B617F225F260150094A1CB /* moc_fitshowtreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B617EF25F260140094A1CB /* moc_fitshowtreadmill.cpp */; }; 87B617F325F260150094A1CB /* moc_snodebike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B617F025F260140094A1CB /* moc_snodebike.cpp */; }; 87B617F425F260150094A1CB /* moc_screencapture.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B617F125F260150094A1CB /* moc_screencapture.cpp */; }; + 87B871902CE1E837009B06CA /* Zwift hub.pb.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B8718F2CE1E837009B06CA /* Zwift hub.pb.swift */; }; + 87B871932CE1E94D009B06CA /* zwifthubbike.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B871922CE1E94D009B06CA /* zwifthubbike.swift */; }; 87BAC3BF2BA497160003E925 /* PrivacyInfo.xcprivacy in Copy Bundle Resources */ = {isa = PBXBuildFile; fileRef = 87BAC3BE2BA497160003E925 /* PrivacyInfo.xcprivacy */; }; 87BAC3C12BA497350003E925 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 87BAC3C02BA497350003E925 /* PrivacyInfo.xcprivacy */; }; 87BAFE482B8CA7AA00065FCD /* moc_focustreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87BAFE472B8CA7AA00065FCD /* moc_focustreadmill.cpp */; }; @@ -1362,6 +1364,8 @@ 87B617EF25F260140094A1CB /* moc_fitshowtreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_fitshowtreadmill.cpp; sourceTree = ""; }; 87B617F025F260140094A1CB /* moc_snodebike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_snodebike.cpp; sourceTree = ""; }; 87B617F125F260150094A1CB /* moc_screencapture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_screencapture.cpp; sourceTree = ""; }; + 87B8718F2CE1E837009B06CA /* Zwift hub.pb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Zwift hub.pb.swift"; path = "../src/devices/zwifthubbike/Zwift hub.pb.swift"; sourceTree = SOURCE_ROOT; }; + 87B871922CE1E94D009B06CA /* zwifthubbike.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = zwifthubbike.swift; path = ../src/devices/zwifthubbike/zwifthubbike.swift; sourceTree = SOURCE_ROOT; }; 87BAC3BE2BA497160003E925 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = ../src/ios/PrivacyInfo.xcprivacy; sourceTree = ""; }; 87BAC3C02BA497350003E925 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 87BAFE472B8CA7AA00065FCD /* moc_focustreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_focustreadmill.cpp; sourceTree = ""; }; @@ -2079,6 +2083,7 @@ 2EB56BE3C2D93CDAB0C52E67 /* Sources */ = { isa = PBXGroup; children = ( + 87B8718F2CE1E837009B06CA /* Zwift hub.pb.swift */, 87A083062C73361C00567A4E /* characteristicnotifier2ad9.h */, 8772B7F92CB5603A004AB8E9 /* deerruntreadmill.h */, 8772B7F62CB55E98004AB8E9 /* deerruntreadmill.cpp */, @@ -2494,6 +2499,7 @@ 8710707229C4A5E70094D0F3 /* GarminConnect.swift */, 87A2E0202B2B024200E6168F /* swiftDebug.h */, 8730A3912B404159007E336D /* zwift_protobuf_layer.swift */, + 87B871922CE1E94D009B06CA /* zwifthubbike.swift */, ); name = Sources; sourceTree = ""; @@ -3442,6 +3448,7 @@ 87D269A325F535340076AA48 /* moc_skandikawiribike.cpp in Compile Sources */, 87F4FB5C29D550E00061BB4A /* moc_schwinn170bike.cpp in Compile Sources */, 87C5F0C226285E5F0067A1B5 /* emailaddress.cpp in Compile Sources */, + 87B871902CE1E837009B06CA /* Zwift hub.pb.swift in Compile Sources */, 873824AF27E64706004F1B46 /* moc_characteristicwriteprocessor2ad9.cpp in Compile Sources */, 25F2400F80DAFBD41FE5CC75 /* fit_field.cpp in Compile Sources */, 873824E227E647A8004F1B46 /* dns.cpp in Compile Sources */, @@ -3474,6 +3481,7 @@ 87DF68B825E2673B00FCDA46 /* eslinkertreadmill.cpp in Compile Sources */, 87BCE6BD29F28F72001F70EB /* ypooelliptical.cpp in Compile Sources */, 87C7074327E4CF5900E79C46 /* keepbike.cpp in Compile Sources */, + 87B871932CE1E94D009B06CA /* zwifthubbike.swift in Compile Sources */, 878C9E6928B77E7C00669129 /* nordictrackifitadbbike.cpp in Compile Sources */, 8798C8892733E10E003148B3 /* moc_strydrunpowersensor.cpp in Compile Sources */, 8781907E2615089D0085E656 /* peloton.cpp in Compile Sources */, @@ -4068,7 +4076,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 935; + CURRENT_PROJECT_VERSION = 936; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1"; @@ -4259,7 +4267,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 935; + CURRENT_PROJECT_VERSION = 936; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; @@ -4486,7 +4494,7 @@ CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 935; + CURRENT_PROJECT_VERSION = 936; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -4582,7 +4590,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 935; + CURRENT_PROJECT_VERSION = 936; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; @@ -4674,7 +4682,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 935; + CURRENT_PROJECT_VERSION = 936; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; ENABLE_PREVIEWS = YES; @@ -4788,7 +4796,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 935; + CURRENT_PROJECT_VERSION = 936; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; diff --git a/src/devices/ftmsbike/ftmsbike.cpp b/src/devices/ftmsbike/ftmsbike.cpp index 6c910a9d3..4fb1e4b6d 100644 --- a/src/devices/ftmsbike/ftmsbike.cpp +++ b/src/devices/ftmsbike/ftmsbike.cpp @@ -86,6 +86,11 @@ bool ftmsbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QStrin qDebug() << QStringLiteral("gattFTMSService is null!"); return false; } + + if(zwiftPlayService) { + qDebug() << QStringLiteral("zwiftPlayService is present!"); + return false; + } if (wait_for_response) { connect(gattFTMSService, &QLowEnergyService::characteristicChanged, &loop, &QEventLoop::quit); @@ -1041,41 +1046,11 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact } else if(b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS && zwiftPlayService != nullptr) { int16_t slope = (((uint8_t)b.at(3)) + (b.at(4) << 8)); - QByteArray message; - - // Command code 0x04 - message.append(0x04); - - // SimulationParam tag (field 4 << 3 | wire_type 2 = 34) - message.append(0x22); - - // Lunghezza payload (11 bytes) - message.append(0x0b); - - // Wind (field 1) - fisso a 0 - message.append(0x08); // tag - message.append((char)0x00); // value = 0 - - // Incline (field 2) - message.append(0x10); // tag - qint32 inclineX100 = static_cast(slope); - // varint inclination - while (inclineX100 >= 0x80) { - message.append((inclineX100 & 0x7F) | 0x80); - inclineX100 >>= 7; - } - message.append(inclineX100 & 0x7F); - - // CWa (field 3) - fix a 10220 (1.022 / 2) - message.append(0x18); // tag - message.append(0xec); // 236 - message.append(0x27); // 39 - - // Crr (field 4) - fix a 400 (0.004) - message.append(0x20); // tag - message.append(0x90); // 144 - message.append(0x03); // 3 - +#ifdef Q_OS_IOS + QByteArray message = lockscreen::zwift_hub_inclinationCommand(((double)slope) / 100.0); +#else + qDebug() << "implement zwift hub protobuf!"; +#endif writeCharacteristicZwiftPlay((uint8_t*)message.data(), message.length(), "gearInclination", false, false); return; } else if(b.at(0) == FTMS_SET_TARGET_POWER && zwiftPlayService != nullptr) { diff --git a/src/devices/zwifthubbike/Zwift hub.pb.swift b/src/devices/zwifthubbike/Zwift hub.pb.swift new file mode 100644 index 000000000..67cfa71e1 --- /dev/null +++ b/src/devices/zwifthubbike/Zwift hub.pb.swift @@ -0,0 +1,1838 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: Zwift hub.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +enum BLEReceiver_Zwift_PlayButtonStatus: SwiftProtobuf.Enum { + typealias RawValue = Int + case on // = 0 + case off // = 1 + + init() { + self = .on + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .on + case 1: self = .off + default: return nil + } + } + + var rawValue: Int { + switch self { + case .on: return 0 + case .off: return 1 + } + } + +} + +#if swift(>=4.2) + +extension BLEReceiver_Zwift_PlayButtonStatus: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +///----------------- Zwift Ride messages +enum BLEReceiver_Zwift_RideButtonMask: SwiftProtobuf.Enum { + typealias RawValue = Int + case leftBtn // = 1 + case upBtn // = 2 + case rightBtn // = 4 + case downBtn // = 8 + case aBtn // = 16 + case bBtn // = 32 + case yBtn // = 64 + case zBtn // = 256 + case shftUpLBtn // = 512 + case shftDnLBtn // = 1024 + case powerupLBtn // = 2048 + case onoffLBtn // = 4096 + case shftUpRBtn // = 8192 + case shftDnRBtn // = 16384 + case powerupRBtn // = 65536 + case onoffRBtn // = 131072 + + init() { + self = .leftBtn + } + + init?(rawValue: Int) { + switch rawValue { + case 1: self = .leftBtn + case 2: self = .upBtn + case 4: self = .rightBtn + case 8: self = .downBtn + case 16: self = .aBtn + case 32: self = .bBtn + case 64: self = .yBtn + case 256: self = .zBtn + case 512: self = .shftUpLBtn + case 1024: self = .shftDnLBtn + case 2048: self = .powerupLBtn + case 4096: self = .onoffLBtn + case 8192: self = .shftUpRBtn + case 16384: self = .shftDnRBtn + case 65536: self = .powerupRBtn + case 131072: self = .onoffRBtn + default: return nil + } + } + + var rawValue: Int { + switch self { + case .leftBtn: return 1 + case .upBtn: return 2 + case .rightBtn: return 4 + case .downBtn: return 8 + case .aBtn: return 16 + case .bBtn: return 32 + case .yBtn: return 64 + case .zBtn: return 256 + case .shftUpLBtn: return 512 + case .shftDnLBtn: return 1024 + case .powerupLBtn: return 2048 + case .onoffLBtn: return 4096 + case .shftUpRBtn: return 8192 + case .shftDnRBtn: return 16384 + case .powerupRBtn: return 65536 + case .onoffRBtn: return 131072 + } + } + +} + +#if swift(>=4.2) + +extension BLEReceiver_Zwift_RideButtonMask: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +enum BLEReceiver_Zwift_RideAnalogLocation: SwiftProtobuf.Enum { + typealias RawValue = Int + case left // = 0 + case right // = 1 + case up // = 2 + case down // = 3 + + init() { + self = .left + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .left + case 1: self = .right + case 2: self = .up + case 3: self = .down + default: return nil + } + } + + var rawValue: Int { + switch self { + case .left: return 0 + case .right: return 1 + case .up: return 2 + case .down: return 3 + } + } + +} + +#if swift(>=4.2) + +extension BLEReceiver_Zwift_RideAnalogLocation: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +///-------------- Zwift Hub messages +/// The command code prepending this message is 0x00 +/// This message is sent always following the change of the gear ratio probably to verify it was received properly +struct BLEReceiver_Zwift_HubRequest { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// Value observed 520 and 0, 0 requests general info, 1-7 are the fields# in DeviceInformationContent, 520 requests the gear ratio + var dataID: UInt32 { + get {return _dataID ?? 0} + set {_dataID = newValue} + } + /// Returns true if `dataID` has been explicitly set. + var hasDataID: Bool {return self._dataID != nil} + /// Clears the value of `dataID`. Subsequent reads from it will return its default value. + mutating func clearDataID() {self._dataID = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _dataID: UInt32? = nil +} + +/// The command code prepending this message is 0x03 +struct BLEReceiver_Zwift_HubRidingData { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var power: UInt32 { + get {return _power ?? 0} + set {_power = newValue} + } + /// Returns true if `power` has been explicitly set. + var hasPower: Bool {return self._power != nil} + /// Clears the value of `power`. Subsequent reads from it will return its default value. + mutating func clearPower() {self._power = nil} + + var cadence: UInt32 { + get {return _cadence ?? 0} + set {_cadence = newValue} + } + /// Returns true if `cadence` has been explicitly set. + var hasCadence: Bool {return self._cadence != nil} + /// Clears the value of `cadence`. Subsequent reads from it will return its default value. + mutating func clearCadence() {self._cadence = nil} + + var speedX100: UInt32 { + get {return _speedX100 ?? 0} + set {_speedX100 = newValue} + } + /// Returns true if `speedX100` has been explicitly set. + var hasSpeedX100: Bool {return self._speedX100 != nil} + /// Clears the value of `speedX100`. Subsequent reads from it will return its default value. + mutating func clearSpeedX100() {self._speedX100 = nil} + + var hr: UInt32 { + get {return _hr ?? 0} + set {_hr = newValue} + } + /// Returns true if `hr` has been explicitly set. + var hasHr: Bool {return self._hr != nil} + /// Clears the value of `hr`. Subsequent reads from it will return its default value. + mutating func clearHr() {self._hr = nil} + + /// Values observed 0 when stopped, 2864, 4060, 4636, 6803 + var unknown1: UInt32 { + get {return _unknown1 ?? 0} + set {_unknown1 = newValue} + } + /// Returns true if `unknown1` has been explicitly set. + var hasUnknown1: Bool {return self._unknown1 != nil} + /// Clears the value of `unknown1`. Subsequent reads from it will return its default value. + mutating func clearUnknown1() {self._unknown1 = nil} + + /// Values observed 25714, 30091 (constant during session) + var unknown2: UInt32 { + get {return _unknown2 ?? 0} + set {_unknown2 = newValue} + } + /// Returns true if `unknown2` has been explicitly set. + var hasUnknown2: Bool {return self._unknown2 != nil} + /// Clears the value of `unknown2`. Subsequent reads from it will return its default value. + mutating func clearUnknown2() {self._unknown2 = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _power: UInt32? = nil + fileprivate var _cadence: UInt32? = nil + fileprivate var _speedX100: UInt32? = nil + fileprivate var _hr: UInt32? = nil + fileprivate var _unknown1: UInt32? = nil + fileprivate var _unknown2: UInt32? = nil +} + +struct BLEReceiver_Zwift_SimulationParam { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// Wind in m/s * 100. In zwift there is no wind (0). Negative is backwind + var wind: Int32 { + get {return _wind ?? 0} + set {_wind = newValue} + } + /// Returns true if `wind` has been explicitly set. + var hasWind: Bool {return self._wind != nil} + /// Clears the value of `wind`. Subsequent reads from it will return its default value. + mutating func clearWind() {self._wind = nil} + + /// Incline value * 100 + var inclineX100: Int32 { + get {return _inclineX100 ?? 0} + set {_inclineX100 = newValue} + } + /// Returns true if `inclineX100` has been explicitly set. + var hasInclineX100: Bool {return self._inclineX100 != nil} + /// Clears the value of `inclineX100`. Subsequent reads from it will return its default value. + mutating func clearInclineX100() {self._inclineX100 = nil} + + /// Wind coefficient CW * a * 10000. In zwift this is constant 0.51 (5100) + var cwa: UInt32 { + get {return _cwa ?? 0} + set {_cwa = newValue} + } + /// Returns true if `cwa` has been explicitly set. + var hasCwa: Bool {return self._cwa != nil} + /// Clears the value of `cwa`. Subsequent reads from it will return its default value. + mutating func clearCwa() {self._cwa = nil} + + /// Rolling resistance Crr * 100000. In zwift this is constant 0.004 (400) + var crr: UInt32 { + get {return _crr ?? 0} + set {_crr = newValue} + } + /// Returns true if `crr` has been explicitly set. + var hasCrr: Bool {return self._crr != nil} + /// Clears the value of `crr`. Subsequent reads from it will return its default value. + mutating func clearCrr() {self._crr = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _wind: Int32? = nil + fileprivate var _inclineX100: Int32? = nil + fileprivate var _cwa: UInt32? = nil + fileprivate var _crr: UInt32? = nil +} + +struct BLEReceiver_Zwift_PhysicalParam { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var gearRatioX10000: UInt32 { + get {return _gearRatioX10000 ?? 0} + set {_gearRatioX10000 = newValue} + } + /// Returns true if `gearRatioX10000` has been explicitly set. + var hasGearRatioX10000: Bool {return self._gearRatioX10000 != nil} + /// Clears the value of `gearRatioX10000`. Subsequent reads from it will return its default value. + mutating func clearGearRatioX10000() {self._gearRatioX10000 = nil} + + var bikeWeightx100: UInt32 { + get {return _bikeWeightx100 ?? 0} + set {_bikeWeightx100 = newValue} + } + /// Returns true if `bikeWeightx100` has been explicitly set. + var hasBikeWeightx100: Bool {return self._bikeWeightx100 != nil} + /// Clears the value of `bikeWeightx100`. Subsequent reads from it will return its default value. + mutating func clearBikeWeightx100() {self._bikeWeightx100 = nil} + + var riderWeightx100: UInt32 { + get {return _riderWeightx100 ?? 0} + set {_riderWeightx100 = newValue} + } + /// Returns true if `riderWeightx100` has been explicitly set. + var hasRiderWeightx100: Bool {return self._riderWeightx100 != nil} + /// Clears the value of `riderWeightx100`. Subsequent reads from it will return its default value. + mutating func clearRiderWeightx100() {self._riderWeightx100 = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _gearRatioX10000: UInt32? = nil + fileprivate var _bikeWeightx100: UInt32? = nil + fileprivate var _riderWeightx100: UInt32? = nil +} + +/// The command code prepending this message is 0x04 +struct BLEReceiver_Zwift_HubCommand { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var powerTarget: UInt32 { + get {return _powerTarget ?? 0} + set {_powerTarget = newValue} + } + /// Returns true if `powerTarget` has been explicitly set. + var hasPowerTarget: Bool {return self._powerTarget != nil} + /// Clears the value of `powerTarget`. Subsequent reads from it will return its default value. + mutating func clearPowerTarget() {self._powerTarget = nil} + + var simulation: BLEReceiver_Zwift_SimulationParam { + get {return _simulation ?? BLEReceiver_Zwift_SimulationParam()} + set {_simulation = newValue} + } + /// Returns true if `simulation` has been explicitly set. + var hasSimulation: Bool {return self._simulation != nil} + /// Clears the value of `simulation`. Subsequent reads from it will return its default value. + mutating func clearSimulation() {self._simulation = nil} + + var physical: BLEReceiver_Zwift_PhysicalParam { + get {return _physical ?? BLEReceiver_Zwift_PhysicalParam()} + set {_physical = newValue} + } + /// Returns true if `physical` has been explicitly set. + var hasPhysical: Bool {return self._physical != nil} + /// Clears the value of `physical`. Subsequent reads from it will return its default value. + mutating func clearPhysical() {self._physical = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _powerTarget: UInt32? = nil + fileprivate var _simulation: BLEReceiver_Zwift_SimulationParam? = nil + fileprivate var _physical: BLEReceiver_Zwift_PhysicalParam? = nil +} + +/// The command code prepending this message is 0x07 +struct BLEReceiver_Zwift_PlayKeyPadStatus { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var rightPad: BLEReceiver_Zwift_PlayButtonStatus { + get {return _rightPad ?? .on} + set {_rightPad = newValue} + } + /// Returns true if `rightPad` has been explicitly set. + var hasRightPad: Bool {return self._rightPad != nil} + /// Clears the value of `rightPad`. Subsequent reads from it will return its default value. + mutating func clearRightPad() {self._rightPad = nil} + + var buttonYUp: BLEReceiver_Zwift_PlayButtonStatus { + get {return _buttonYUp ?? .on} + set {_buttonYUp = newValue} + } + /// Returns true if `buttonYUp` has been explicitly set. + var hasButtonYUp: Bool {return self._buttonYUp != nil} + /// Clears the value of `buttonYUp`. Subsequent reads from it will return its default value. + mutating func clearButtonYUp() {self._buttonYUp = nil} + + var buttonZLeft: BLEReceiver_Zwift_PlayButtonStatus { + get {return _buttonZLeft ?? .on} + set {_buttonZLeft = newValue} + } + /// Returns true if `buttonZLeft` has been explicitly set. + var hasButtonZLeft: Bool {return self._buttonZLeft != nil} + /// Clears the value of `buttonZLeft`. Subsequent reads from it will return its default value. + mutating func clearButtonZLeft() {self._buttonZLeft = nil} + + var buttonARight: BLEReceiver_Zwift_PlayButtonStatus { + get {return _buttonARight ?? .on} + set {_buttonARight = newValue} + } + /// Returns true if `buttonARight` has been explicitly set. + var hasButtonARight: Bool {return self._buttonARight != nil} + /// Clears the value of `buttonARight`. Subsequent reads from it will return its default value. + mutating func clearButtonARight() {self._buttonARight = nil} + + var buttonBDown: BLEReceiver_Zwift_PlayButtonStatus { + get {return _buttonBDown ?? .on} + set {_buttonBDown = newValue} + } + /// Returns true if `buttonBDown` has been explicitly set. + var hasButtonBDown: Bool {return self._buttonBDown != nil} + /// Clears the value of `buttonBDown`. Subsequent reads from it will return its default value. + mutating func clearButtonBDown() {self._buttonBDown = nil} + + var buttonOn: BLEReceiver_Zwift_PlayButtonStatus { + get {return _buttonOn ?? .on} + set {_buttonOn = newValue} + } + /// Returns true if `buttonOn` has been explicitly set. + var hasButtonOn: Bool {return self._buttonOn != nil} + /// Clears the value of `buttonOn`. Subsequent reads from it will return its default value. + mutating func clearButtonOn() {self._buttonOn = nil} + + var buttonShift: BLEReceiver_Zwift_PlayButtonStatus { + get {return _buttonShift ?? .on} + set {_buttonShift = newValue} + } + /// Returns true if `buttonShift` has been explicitly set. + var hasButtonShift: Bool {return self._buttonShift != nil} + /// Clears the value of `buttonShift`. Subsequent reads from it will return its default value. + mutating func clearButtonShift() {self._buttonShift = nil} + + var analogLr: Int32 { + get {return _analogLr ?? 0} + set {_analogLr = newValue} + } + /// Returns true if `analogLr` has been explicitly set. + var hasAnalogLr: Bool {return self._analogLr != nil} + /// Clears the value of `analogLr`. Subsequent reads from it will return its default value. + mutating func clearAnalogLr() {self._analogLr = nil} + + var analogUd: Int32 { + get {return _analogUd ?? 0} + set {_analogUd = newValue} + } + /// Returns true if `analogUd` has been explicitly set. + var hasAnalogUd: Bool {return self._analogUd != nil} + /// Clears the value of `analogUd`. Subsequent reads from it will return its default value. + mutating func clearAnalogUd() {self._analogUd = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _rightPad: BLEReceiver_Zwift_PlayButtonStatus? = nil + fileprivate var _buttonYUp: BLEReceiver_Zwift_PlayButtonStatus? = nil + fileprivate var _buttonZLeft: BLEReceiver_Zwift_PlayButtonStatus? = nil + fileprivate var _buttonARight: BLEReceiver_Zwift_PlayButtonStatus? = nil + fileprivate var _buttonBDown: BLEReceiver_Zwift_PlayButtonStatus? = nil + fileprivate var _buttonOn: BLEReceiver_Zwift_PlayButtonStatus? = nil + fileprivate var _buttonShift: BLEReceiver_Zwift_PlayButtonStatus? = nil + fileprivate var _analogLr: Int32? = nil + fileprivate var _analogUd: Int32? = nil +} + +struct BLEReceiver_Zwift_PlayCommandParameters { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var param1: UInt32 { + get {return _param1 ?? 0} + set {_param1 = newValue} + } + /// Returns true if `param1` has been explicitly set. + var hasParam1: Bool {return self._param1 != nil} + /// Clears the value of `param1`. Subsequent reads from it will return its default value. + mutating func clearParam1() {self._param1 = nil} + + var param2: UInt32 { + get {return _param2 ?? 0} + set {_param2 = newValue} + } + /// Returns true if `param2` has been explicitly set. + var hasParam2: Bool {return self._param2 != nil} + /// Clears the value of `param2`. Subsequent reads from it will return its default value. + mutating func clearParam2() {self._param2 = nil} + + var hapticPattern: UInt32 { + get {return _hapticPattern ?? 0} + set {_hapticPattern = newValue} + } + /// Returns true if `hapticPattern` has been explicitly set. + var hasHapticPattern: Bool {return self._hapticPattern != nil} + /// Clears the value of `hapticPattern`. Subsequent reads from it will return its default value. + mutating func clearHapticPattern() {self._hapticPattern = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _param1: UInt32? = nil + fileprivate var _param2: UInt32? = nil + fileprivate var _hapticPattern: UInt32? = nil +} + +struct BLEReceiver_Zwift_PlayCommandContents { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var commandParameters: BLEReceiver_Zwift_PlayCommandParameters { + get {return _commandParameters ?? BLEReceiver_Zwift_PlayCommandParameters()} + set {_commandParameters = newValue} + } + /// Returns true if `commandParameters` has been explicitly set. + var hasCommandParameters: Bool {return self._commandParameters != nil} + /// Clears the value of `commandParameters`. Subsequent reads from it will return its default value. + mutating func clearCommandParameters() {self._commandParameters = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _commandParameters: BLEReceiver_Zwift_PlayCommandParameters? = nil +} + +/// The command code prepending this message is 0x12 +/// This is sent to the control point to configure and make the controller vibrate +struct BLEReceiver_Zwift_PlayCommand { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var commandContents: BLEReceiver_Zwift_PlayCommandContents { + get {return _commandContents ?? BLEReceiver_Zwift_PlayCommandContents()} + set {_commandContents = newValue} + } + /// Returns true if `commandContents` has been explicitly set. + var hasCommandContents: Bool {return self._commandContents != nil} + /// Clears the value of `commandContents`. Subsequent reads from it will return its default value. + mutating func clearCommandContents() {self._commandContents = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _commandContents: BLEReceiver_Zwift_PlayCommandContents? = nil +} + +/// The command code prepending this message is 0x19 +/// This is sent periodically when there are no button presses +struct BLEReceiver_Zwift_Idle { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var unknown2: UInt32 { + get {return _unknown2 ?? 0} + set {_unknown2 = newValue} + } + /// Returns true if `unknown2` has been explicitly set. + var hasUnknown2: Bool {return self._unknown2 != nil} + /// Clears the value of `unknown2`. Subsequent reads from it will return its default value. + mutating func clearUnknown2() {self._unknown2 = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _unknown2: UInt32? = nil +} + +struct BLEReceiver_Zwift_RideAnalogKeyPress { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var location: BLEReceiver_Zwift_RideAnalogLocation { + get {return _location ?? .left} + set {_location = newValue} + } + /// Returns true if `location` has been explicitly set. + var hasLocation: Bool {return self._location != nil} + /// Clears the value of `location`. Subsequent reads from it will return its default value. + mutating func clearLocation() {self._location = nil} + + var analogValue: Int32 { + get {return _analogValue ?? 0} + set {_analogValue = newValue} + } + /// Returns true if `analogValue` has been explicitly set. + var hasAnalogValue: Bool {return self._analogValue != nil} + /// Clears the value of `analogValue`. Subsequent reads from it will return its default value. + mutating func clearAnalogValue() {self._analogValue = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _location: BLEReceiver_Zwift_RideAnalogLocation? = nil + fileprivate var _analogValue: Int32? = nil +} + +struct BLEReceiver_Zwift_RideAnalogKeyGroup { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var groupStatus: [BLEReceiver_Zwift_RideAnalogKeyPress] = [] + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +/// The command code prepending this message is 0x23 +struct BLEReceiver_Zwift_RideKeyPadStatus { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var buttonMap: UInt32 { + get {return _buttonMap ?? 0} + set {_buttonMap = newValue} + } + /// Returns true if `buttonMap` has been explicitly set. + var hasButtonMap: Bool {return self._buttonMap != nil} + /// Clears the value of `buttonMap`. Subsequent reads from it will return its default value. + mutating func clearButtonMap() {self._buttonMap = nil} + + var analogButtons: BLEReceiver_Zwift_RideAnalogKeyGroup { + get {return _analogButtons ?? BLEReceiver_Zwift_RideAnalogKeyGroup()} + set {_analogButtons = newValue} + } + /// Returns true if `analogButtons` has been explicitly set. + var hasAnalogButtons: Bool {return self._analogButtons != nil} + /// Clears the value of `analogButtons`. Subsequent reads from it will return its default value. + mutating func clearAnalogButtons() {self._analogButtons = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _buttonMap: UInt32? = nil + fileprivate var _analogButtons: BLEReceiver_Zwift_RideAnalogKeyGroup? = nil +} + +///------------------ Zwift Click messages +/// The command code prepending this message is 0x37 +struct BLEReceiver_Zwift_ClickKeyPadStatus { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var buttonPlus: BLEReceiver_Zwift_PlayButtonStatus { + get {return _buttonPlus ?? .on} + set {_buttonPlus = newValue} + } + /// Returns true if `buttonPlus` has been explicitly set. + var hasButtonPlus: Bool {return self._buttonPlus != nil} + /// Clears the value of `buttonPlus`. Subsequent reads from it will return its default value. + mutating func clearButtonPlus() {self._buttonPlus = nil} + + var buttonMinus: BLEReceiver_Zwift_PlayButtonStatus { + get {return _buttonMinus ?? .on} + set {_buttonMinus = newValue} + } + /// Returns true if `buttonMinus` has been explicitly set. + var hasButtonMinus: Bool {return self._buttonMinus != nil} + /// Clears the value of `buttonMinus`. Subsequent reads from it will return its default value. + mutating func clearButtonMinus() {self._buttonMinus = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _buttonPlus: BLEReceiver_Zwift_PlayButtonStatus? = nil + fileprivate var _buttonMinus: BLEReceiver_Zwift_PlayButtonStatus? = nil +} + +///------------------ Device Information requested after connection +/// The command code prepending this message is 0x3c +struct BLEReceiver_Zwift_DeviceInformationContent { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var unknown1: UInt32 { + get {return _unknown1 ?? 0} + set {_unknown1 = newValue} + } + /// Returns true if `unknown1` has been explicitly set. + var hasUnknown1: Bool {return self._unknown1 != nil} + /// Clears the value of `unknown1`. Subsequent reads from it will return its default value. + mutating func clearUnknown1() {self._unknown1 = nil} + + var softwareVersion: [UInt32] = [] + + var deviceName: String { + get {return _deviceName ?? String()} + set {_deviceName = newValue} + } + /// Returns true if `deviceName` has been explicitly set. + var hasDeviceName: Bool {return self._deviceName != nil} + /// Clears the value of `deviceName`. Subsequent reads from it will return its default value. + mutating func clearDeviceName() {self._deviceName = nil} + + var unknown4: UInt32 { + get {return _unknown4 ?? 0} + set {_unknown4 = newValue} + } + /// Returns true if `unknown4` has been explicitly set. + var hasUnknown4: Bool {return self._unknown4 != nil} + /// Clears the value of `unknown4`. Subsequent reads from it will return its default value. + mutating func clearUnknown4() {self._unknown4 = nil} + + var unknown5: UInt32 { + get {return _unknown5 ?? 0} + set {_unknown5 = newValue} + } + /// Returns true if `unknown5` has been explicitly set. + var hasUnknown5: Bool {return self._unknown5 != nil} + /// Clears the value of `unknown5`. Subsequent reads from it will return its default value. + mutating func clearUnknown5() {self._unknown5 = nil} + + var serialNumber: String { + get {return _serialNumber ?? String()} + set {_serialNumber = newValue} + } + /// Returns true if `serialNumber` has been explicitly set. + var hasSerialNumber: Bool {return self._serialNumber != nil} + /// Clears the value of `serialNumber`. Subsequent reads from it will return its default value. + mutating func clearSerialNumber() {self._serialNumber = nil} + + var hardwareVersion: String { + get {return _hardwareVersion ?? String()} + set {_hardwareVersion = newValue} + } + /// Returns true if `hardwareVersion` has been explicitly set. + var hasHardwareVersion: Bool {return self._hardwareVersion != nil} + /// Clears the value of `hardwareVersion`. Subsequent reads from it will return its default value. + mutating func clearHardwareVersion() {self._hardwareVersion = nil} + + var replyData: [UInt32] = [] + + var unknown9: UInt32 { + get {return _unknown9 ?? 0} + set {_unknown9 = newValue} + } + /// Returns true if `unknown9` has been explicitly set. + var hasUnknown9: Bool {return self._unknown9 != nil} + /// Clears the value of `unknown9`. Subsequent reads from it will return its default value. + mutating func clearUnknown9() {self._unknown9 = nil} + + var unknown10: UInt32 { + get {return _unknown10 ?? 0} + set {_unknown10 = newValue} + } + /// Returns true if `unknown10` has been explicitly set. + var hasUnknown10: Bool {return self._unknown10 != nil} + /// Clears the value of `unknown10`. Subsequent reads from it will return its default value. + mutating func clearUnknown10() {self._unknown10 = nil} + + var unknown13: UInt32 { + get {return _unknown13 ?? 0} + set {_unknown13 = newValue} + } + /// Returns true if `unknown13` has been explicitly set. + var hasUnknown13: Bool {return self._unknown13 != nil} + /// Clears the value of `unknown13`. Subsequent reads from it will return its default value. + mutating func clearUnknown13() {self._unknown13 = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _unknown1: UInt32? = nil + fileprivate var _deviceName: String? = nil + fileprivate var _unknown4: UInt32? = nil + fileprivate var _unknown5: UInt32? = nil + fileprivate var _serialNumber: String? = nil + fileprivate var _hardwareVersion: String? = nil + fileprivate var _unknown9: UInt32? = nil + fileprivate var _unknown10: UInt32? = nil + fileprivate var _unknown13: UInt32? = nil +} + +struct BLEReceiver_Zwift_SubContent { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var content: BLEReceiver_Zwift_DeviceInformationContent { + get {return _content ?? BLEReceiver_Zwift_DeviceInformationContent()} + set {_content = newValue} + } + /// Returns true if `content` has been explicitly set. + var hasContent: Bool {return self._content != nil} + /// Clears the value of `content`. Subsequent reads from it will return its default value. + mutating func clearContent() {self._content = nil} + + var unknown2: UInt32 { + get {return _unknown2 ?? 0} + set {_unknown2 = newValue} + } + /// Returns true if `unknown2` has been explicitly set. + var hasUnknown2: Bool {return self._unknown2 != nil} + /// Clears the value of `unknown2`. Subsequent reads from it will return its default value. + mutating func clearUnknown2() {self._unknown2 = nil} + + var unknown4: UInt32 { + get {return _unknown4 ?? 0} + set {_unknown4 = newValue} + } + /// Returns true if `unknown4` has been explicitly set. + var hasUnknown4: Bool {return self._unknown4 != nil} + /// Clears the value of `unknown4`. Subsequent reads from it will return its default value. + mutating func clearUnknown4() {self._unknown4 = nil} + + var unknown5: UInt32 { + get {return _unknown5 ?? 0} + set {_unknown5 = newValue} + } + /// Returns true if `unknown5` has been explicitly set. + var hasUnknown5: Bool {return self._unknown5 != nil} + /// Clears the value of `unknown5`. Subsequent reads from it will return its default value. + mutating func clearUnknown5() {self._unknown5 = nil} + + var unknown6: UInt32 { + get {return _unknown6 ?? 0} + set {_unknown6 = newValue} + } + /// Returns true if `unknown6` has been explicitly set. + var hasUnknown6: Bool {return self._unknown6 != nil} + /// Clears the value of `unknown6`. Subsequent reads from it will return its default value. + mutating func clearUnknown6() {self._unknown6 = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _content: BLEReceiver_Zwift_DeviceInformationContent? = nil + fileprivate var _unknown2: UInt32? = nil + fileprivate var _unknown4: UInt32? = nil + fileprivate var _unknown5: UInt32? = nil + fileprivate var _unknown6: UInt32? = nil +} + +struct BLEReceiver_Zwift_DeviceInformation { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var informationID: UInt32 { + get {return _informationID ?? 0} + set {_informationID = newValue} + } + /// Returns true if `informationID` has been explicitly set. + var hasInformationID: Bool {return self._informationID != nil} + /// Clears the value of `informationID`. Subsequent reads from it will return its default value. + mutating func clearInformationID() {self._informationID = nil} + + var subContent: BLEReceiver_Zwift_SubContent { + get {return _subContent ?? BLEReceiver_Zwift_SubContent()} + set {_subContent = newValue} + } + /// Returns true if `subContent` has been explicitly set. + var hasSubContent: Bool {return self._subContent != nil} + /// Clears the value of `subContent`. Subsequent reads from it will return its default value. + mutating func clearSubContent() {self._subContent = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _informationID: UInt32? = nil + fileprivate var _subContent: BLEReceiver_Zwift_SubContent? = nil +} + +#if swift(>=5.5) && canImport(_Concurrency) +extension BLEReceiver_Zwift_PlayButtonStatus: @unchecked Sendable {} +extension BLEReceiver_Zwift_RideButtonMask: @unchecked Sendable {} +extension BLEReceiver_Zwift_RideAnalogLocation: @unchecked Sendable {} +extension BLEReceiver_Zwift_HubRequest: @unchecked Sendable {} +extension BLEReceiver_Zwift_HubRidingData: @unchecked Sendable {} +extension BLEReceiver_Zwift_SimulationParam: @unchecked Sendable {} +extension BLEReceiver_Zwift_PhysicalParam: @unchecked Sendable {} +extension BLEReceiver_Zwift_HubCommand: @unchecked Sendable {} +extension BLEReceiver_Zwift_PlayKeyPadStatus: @unchecked Sendable {} +extension BLEReceiver_Zwift_PlayCommandParameters: @unchecked Sendable {} +extension BLEReceiver_Zwift_PlayCommandContents: @unchecked Sendable {} +extension BLEReceiver_Zwift_PlayCommand: @unchecked Sendable {} +extension BLEReceiver_Zwift_Idle: @unchecked Sendable {} +extension BLEReceiver_Zwift_RideAnalogKeyPress: @unchecked Sendable {} +extension BLEReceiver_Zwift_RideAnalogKeyGroup: @unchecked Sendable {} +extension BLEReceiver_Zwift_RideKeyPadStatus: @unchecked Sendable {} +extension BLEReceiver_Zwift_ClickKeyPadStatus: @unchecked Sendable {} +extension BLEReceiver_Zwift_DeviceInformationContent: @unchecked Sendable {} +extension BLEReceiver_Zwift_SubContent: @unchecked Sendable {} +extension BLEReceiver_Zwift_DeviceInformation: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +fileprivate let _protobuf_package = "BLEReceiver.Zwift" + +extension BLEReceiver_Zwift_PlayButtonStatus: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "ON"), + 1: .same(proto: "OFF"), + ] +} + +extension BLEReceiver_Zwift_RideButtonMask: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "LEFT_BTN"), + 2: .same(proto: "UP_BTN"), + 4: .same(proto: "RIGHT_BTN"), + 8: .same(proto: "DOWN_BTN"), + 16: .same(proto: "A_BTN"), + 32: .same(proto: "B_BTN"), + 64: .same(proto: "Y_BTN"), + 256: .same(proto: "Z_BTN"), + 512: .same(proto: "SHFT_UP_L_BTN"), + 1024: .same(proto: "SHFT_DN_L_BTN"), + 2048: .same(proto: "POWERUP_L_BTN"), + 4096: .same(proto: "ONOFF_L_BTN"), + 8192: .same(proto: "SHFT_UP_R_BTN"), + 16384: .same(proto: "SHFT_DN_R_BTN"), + 65536: .same(proto: "POWERUP_R_BTN"), + 131072: .same(proto: "ONOFF_R_BTN"), + ] +} + +extension BLEReceiver_Zwift_RideAnalogLocation: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "LEFT"), + 1: .same(proto: "RIGHT"), + 2: .same(proto: "UP"), + 3: .same(proto: "DOWN"), + ] +} + +extension BLEReceiver_Zwift_HubRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".HubRequest" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "DataId"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self._dataID) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._dataID { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: BLEReceiver_Zwift_HubRequest, rhs: BLEReceiver_Zwift_HubRequest) -> Bool { + if lhs._dataID != rhs._dataID {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension BLEReceiver_Zwift_HubRidingData: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".HubRidingData" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "Power"), + 2: .same(proto: "Cadence"), + 3: .same(proto: "SpeedX100"), + 4: .same(proto: "HR"), + 5: .same(proto: "Unknown1"), + 6: .same(proto: "Unknown2"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self._power) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self._cadence) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self._speedX100) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &self._hr) }() + case 5: try { try decoder.decodeSingularUInt32Field(value: &self._unknown1) }() + case 6: try { try decoder.decodeSingularUInt32Field(value: &self._unknown2) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._power { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 1) + } }() + try { if let v = self._cadence { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2) + } }() + try { if let v = self._speedX100 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 3) + } }() + try { if let v = self._hr { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 4) + } }() + try { if let v = self._unknown1 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 5) + } }() + try { if let v = self._unknown2 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 6) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: BLEReceiver_Zwift_HubRidingData, rhs: BLEReceiver_Zwift_HubRidingData) -> Bool { + if lhs._power != rhs._power {return false} + if lhs._cadence != rhs._cadence {return false} + if lhs._speedX100 != rhs._speedX100 {return false} + if lhs._hr != rhs._hr {return false} + if lhs._unknown1 != rhs._unknown1 {return false} + if lhs._unknown2 != rhs._unknown2 {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension BLEReceiver_Zwift_SimulationParam: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".SimulationParam" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "Wind"), + 2: .same(proto: "InclineX100"), + 3: .same(proto: "CWa"), + 4: .same(proto: "Crr"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularSInt32Field(value: &self._wind) }() + case 2: try { try decoder.decodeSingularSInt32Field(value: &self._inclineX100) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self._cwa) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &self._crr) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._wind { + try visitor.visitSingularSInt32Field(value: v, fieldNumber: 1) + } }() + try { if let v = self._inclineX100 { + try visitor.visitSingularSInt32Field(value: v, fieldNumber: 2) + } }() + try { if let v = self._cwa { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 3) + } }() + try { if let v = self._crr { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 4) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: BLEReceiver_Zwift_SimulationParam, rhs: BLEReceiver_Zwift_SimulationParam) -> Bool { + if lhs._wind != rhs._wind {return false} + if lhs._inclineX100 != rhs._inclineX100 {return false} + if lhs._cwa != rhs._cwa {return false} + if lhs._crr != rhs._crr {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension BLEReceiver_Zwift_PhysicalParam: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".PhysicalParam" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 2: .same(proto: "GearRatioX10000"), + 4: .same(proto: "BikeWeightx100"), + 5: .same(proto: "RiderWeightx100"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 2: try { try decoder.decodeSingularUInt32Field(value: &self._gearRatioX10000) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &self._bikeWeightx100) }() + case 5: try { try decoder.decodeSingularUInt32Field(value: &self._riderWeightx100) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._gearRatioX10000 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2) + } }() + try { if let v = self._bikeWeightx100 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 4) + } }() + try { if let v = self._riderWeightx100 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 5) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: BLEReceiver_Zwift_PhysicalParam, rhs: BLEReceiver_Zwift_PhysicalParam) -> Bool { + if lhs._gearRatioX10000 != rhs._gearRatioX10000 {return false} + if lhs._bikeWeightx100 != rhs._bikeWeightx100 {return false} + if lhs._riderWeightx100 != rhs._riderWeightx100 {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension BLEReceiver_Zwift_HubCommand: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".HubCommand" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 3: .same(proto: "PowerTarget"), + 4: .same(proto: "Simulation"), + 5: .same(proto: "Physical"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 3: try { try decoder.decodeSingularUInt32Field(value: &self._powerTarget) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._simulation) }() + case 5: try { try decoder.decodeSingularMessageField(value: &self._physical) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._powerTarget { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 3) + } }() + try { if let v = self._simulation { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + try { if let v = self._physical { + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: BLEReceiver_Zwift_HubCommand, rhs: BLEReceiver_Zwift_HubCommand) -> Bool { + if lhs._powerTarget != rhs._powerTarget {return false} + if lhs._simulation != rhs._simulation {return false} + if lhs._physical != rhs._physical {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension BLEReceiver_Zwift_PlayKeyPadStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".PlayKeyPadStatus" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "RightPad"), + 2: .standard(proto: "Button_Y_Up"), + 3: .standard(proto: "Button_Z_Left"), + 4: .standard(proto: "Button_A_Right"), + 5: .standard(proto: "Button_B_Down"), + 6: .standard(proto: "Button_On"), + 7: .standard(proto: "Button_Shift"), + 8: .standard(proto: "Analog_LR"), + 9: .standard(proto: "Analog_UD"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self._rightPad) }() + case 2: try { try decoder.decodeSingularEnumField(value: &self._buttonYUp) }() + case 3: try { try decoder.decodeSingularEnumField(value: &self._buttonZLeft) }() + case 4: try { try decoder.decodeSingularEnumField(value: &self._buttonARight) }() + case 5: try { try decoder.decodeSingularEnumField(value: &self._buttonBDown) }() + case 6: try { try decoder.decodeSingularEnumField(value: &self._buttonOn) }() + case 7: try { try decoder.decodeSingularEnumField(value: &self._buttonShift) }() + case 8: try { try decoder.decodeSingularSInt32Field(value: &self._analogLr) }() + case 9: try { try decoder.decodeSingularSInt32Field(value: &self._analogUd) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._rightPad { + try visitor.visitSingularEnumField(value: v, fieldNumber: 1) + } }() + try { if let v = self._buttonYUp { + try visitor.visitSingularEnumField(value: v, fieldNumber: 2) + } }() + try { if let v = self._buttonZLeft { + try visitor.visitSingularEnumField(value: v, fieldNumber: 3) + } }() + try { if let v = self._buttonARight { + try visitor.visitSingularEnumField(value: v, fieldNumber: 4) + } }() + try { if let v = self._buttonBDown { + try visitor.visitSingularEnumField(value: v, fieldNumber: 5) + } }() + try { if let v = self._buttonOn { + try visitor.visitSingularEnumField(value: v, fieldNumber: 6) + } }() + try { if let v = self._buttonShift { + try visitor.visitSingularEnumField(value: v, fieldNumber: 7) + } }() + try { if let v = self._analogLr { + try visitor.visitSingularSInt32Field(value: v, fieldNumber: 8) + } }() + try { if let v = self._analogUd { + try visitor.visitSingularSInt32Field(value: v, fieldNumber: 9) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: BLEReceiver_Zwift_PlayKeyPadStatus, rhs: BLEReceiver_Zwift_PlayKeyPadStatus) -> Bool { + if lhs._rightPad != rhs._rightPad {return false} + if lhs._buttonYUp != rhs._buttonYUp {return false} + if lhs._buttonZLeft != rhs._buttonZLeft {return false} + if lhs._buttonARight != rhs._buttonARight {return false} + if lhs._buttonBDown != rhs._buttonBDown {return false} + if lhs._buttonOn != rhs._buttonOn {return false} + if lhs._buttonShift != rhs._buttonShift {return false} + if lhs._analogLr != rhs._analogLr {return false} + if lhs._analogUd != rhs._analogUd {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension BLEReceiver_Zwift_PlayCommandParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".PlayCommandParameters" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "param1"), + 2: .same(proto: "param2"), + 3: .same(proto: "HapticPattern"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self._param1) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self._param2) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self._hapticPattern) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._param1 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 1) + } }() + try { if let v = self._param2 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2) + } }() + try { if let v = self._hapticPattern { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 3) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: BLEReceiver_Zwift_PlayCommandParameters, rhs: BLEReceiver_Zwift_PlayCommandParameters) -> Bool { + if lhs._param1 != rhs._param1 {return false} + if lhs._param2 != rhs._param2 {return false} + if lhs._hapticPattern != rhs._hapticPattern {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension BLEReceiver_Zwift_PlayCommandContents: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".PlayCommandContents" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "CommandParameters"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._commandParameters) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._commandParameters { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: BLEReceiver_Zwift_PlayCommandContents, rhs: BLEReceiver_Zwift_PlayCommandContents) -> Bool { + if lhs._commandParameters != rhs._commandParameters {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension BLEReceiver_Zwift_PlayCommand: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".PlayCommand" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 2: .same(proto: "CommandContents"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 2: try { try decoder.decodeSingularMessageField(value: &self._commandContents) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._commandContents { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: BLEReceiver_Zwift_PlayCommand, rhs: BLEReceiver_Zwift_PlayCommand) -> Bool { + if lhs._commandContents != rhs._commandContents {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension BLEReceiver_Zwift_Idle: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".Idle" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 2: .same(proto: "Unknown2"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 2: try { try decoder.decodeSingularUInt32Field(value: &self._unknown2) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._unknown2 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: BLEReceiver_Zwift_Idle, rhs: BLEReceiver_Zwift_Idle) -> Bool { + if lhs._unknown2 != rhs._unknown2 {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension BLEReceiver_Zwift_RideAnalogKeyPress: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".RideAnalogKeyPress" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "Location"), + 2: .same(proto: "AnalogValue"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self._location) }() + case 2: try { try decoder.decodeSingularSInt32Field(value: &self._analogValue) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._location { + try visitor.visitSingularEnumField(value: v, fieldNumber: 1) + } }() + try { if let v = self._analogValue { + try visitor.visitSingularSInt32Field(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: BLEReceiver_Zwift_RideAnalogKeyPress, rhs: BLEReceiver_Zwift_RideAnalogKeyPress) -> Bool { + if lhs._location != rhs._location {return false} + if lhs._analogValue != rhs._analogValue {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension BLEReceiver_Zwift_RideAnalogKeyGroup: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".RideAnalogKeyGroup" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "GroupStatus"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeRepeatedMessageField(value: &self.groupStatus) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.groupStatus.isEmpty { + try visitor.visitRepeatedMessageField(value: self.groupStatus, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: BLEReceiver_Zwift_RideAnalogKeyGroup, rhs: BLEReceiver_Zwift_RideAnalogKeyGroup) -> Bool { + if lhs.groupStatus != rhs.groupStatus {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension BLEReceiver_Zwift_RideKeyPadStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".RideKeyPadStatus" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "ButtonMap"), + 2: .same(proto: "AnalogButtons"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self._buttonMap) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._analogButtons) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._buttonMap { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 1) + } }() + try { if let v = self._analogButtons { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: BLEReceiver_Zwift_RideKeyPadStatus, rhs: BLEReceiver_Zwift_RideKeyPadStatus) -> Bool { + if lhs._buttonMap != rhs._buttonMap {return false} + if lhs._analogButtons != rhs._analogButtons {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension BLEReceiver_Zwift_ClickKeyPadStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".ClickKeyPadStatus" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "Button_Plus"), + 2: .standard(proto: "Button_Minus"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self._buttonPlus) }() + case 2: try { try decoder.decodeSingularEnumField(value: &self._buttonMinus) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._buttonPlus { + try visitor.visitSingularEnumField(value: v, fieldNumber: 1) + } }() + try { if let v = self._buttonMinus { + try visitor.visitSingularEnumField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: BLEReceiver_Zwift_ClickKeyPadStatus, rhs: BLEReceiver_Zwift_ClickKeyPadStatus) -> Bool { + if lhs._buttonPlus != rhs._buttonPlus {return false} + if lhs._buttonMinus != rhs._buttonMinus {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension BLEReceiver_Zwift_DeviceInformationContent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".DeviceInformationContent" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "Unknown1"), + 2: .same(proto: "SoftwareVersion"), + 3: .same(proto: "DeviceName"), + 4: .same(proto: "Unknown4"), + 5: .same(proto: "Unknown5"), + 6: .same(proto: "SerialNumber"), + 7: .same(proto: "HardwareVersion"), + 8: .same(proto: "ReplyData"), + 9: .same(proto: "Unknown9"), + 10: .same(proto: "Unknown10"), + 13: .same(proto: "Unknown13"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self._unknown1) }() + case 2: try { try decoder.decodeRepeatedUInt32Field(value: &self.softwareVersion) }() + case 3: try { try decoder.decodeSingularStringField(value: &self._deviceName) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &self._unknown4) }() + case 5: try { try decoder.decodeSingularUInt32Field(value: &self._unknown5) }() + case 6: try { try decoder.decodeSingularStringField(value: &self._serialNumber) }() + case 7: try { try decoder.decodeSingularStringField(value: &self._hardwareVersion) }() + case 8: try { try decoder.decodeRepeatedUInt32Field(value: &self.replyData) }() + case 9: try { try decoder.decodeSingularUInt32Field(value: &self._unknown9) }() + case 10: try { try decoder.decodeSingularUInt32Field(value: &self._unknown10) }() + case 13: try { try decoder.decodeSingularUInt32Field(value: &self._unknown13) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._unknown1 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 1) + } }() + if !self.softwareVersion.isEmpty { + try visitor.visitRepeatedUInt32Field(value: self.softwareVersion, fieldNumber: 2) + } + try { if let v = self._deviceName { + try visitor.visitSingularStringField(value: v, fieldNumber: 3) + } }() + try { if let v = self._unknown4 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 4) + } }() + try { if let v = self._unknown5 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 5) + } }() + try { if let v = self._serialNumber { + try visitor.visitSingularStringField(value: v, fieldNumber: 6) + } }() + try { if let v = self._hardwareVersion { + try visitor.visitSingularStringField(value: v, fieldNumber: 7) + } }() + if !self.replyData.isEmpty { + try visitor.visitRepeatedUInt32Field(value: self.replyData, fieldNumber: 8) + } + try { if let v = self._unknown9 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 9) + } }() + try { if let v = self._unknown10 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 10) + } }() + try { if let v = self._unknown13 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 13) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: BLEReceiver_Zwift_DeviceInformationContent, rhs: BLEReceiver_Zwift_DeviceInformationContent) -> Bool { + if lhs._unknown1 != rhs._unknown1 {return false} + if lhs.softwareVersion != rhs.softwareVersion {return false} + if lhs._deviceName != rhs._deviceName {return false} + if lhs._unknown4 != rhs._unknown4 {return false} + if lhs._unknown5 != rhs._unknown5 {return false} + if lhs._serialNumber != rhs._serialNumber {return false} + if lhs._hardwareVersion != rhs._hardwareVersion {return false} + if lhs.replyData != rhs.replyData {return false} + if lhs._unknown9 != rhs._unknown9 {return false} + if lhs._unknown10 != rhs._unknown10 {return false} + if lhs._unknown13 != rhs._unknown13 {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension BLEReceiver_Zwift_SubContent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".SubContent" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "Content"), + 2: .same(proto: "Unknown2"), + 4: .same(proto: "Unknown4"), + 5: .same(proto: "Unknown5"), + 6: .same(proto: "Unknown6"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._content) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self._unknown2) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &self._unknown4) }() + case 5: try { try decoder.decodeSingularUInt32Field(value: &self._unknown5) }() + case 6: try { try decoder.decodeSingularUInt32Field(value: &self._unknown6) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._content { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._unknown2 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2) + } }() + try { if let v = self._unknown4 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 4) + } }() + try { if let v = self._unknown5 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 5) + } }() + try { if let v = self._unknown6 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 6) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: BLEReceiver_Zwift_SubContent, rhs: BLEReceiver_Zwift_SubContent) -> Bool { + if lhs._content != rhs._content {return false} + if lhs._unknown2 != rhs._unknown2 {return false} + if lhs._unknown4 != rhs._unknown4 {return false} + if lhs._unknown5 != rhs._unknown5 {return false} + if lhs._unknown6 != rhs._unknown6 {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension BLEReceiver_Zwift_DeviceInformation: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".DeviceInformation" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "InformationId"), + 2: .same(proto: "SubContent"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self._informationID) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._subContent) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._informationID { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 1) + } }() + try { if let v = self._subContent { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: BLEReceiver_Zwift_DeviceInformation, rhs: BLEReceiver_Zwift_DeviceInformation) -> Bool { + if lhs._informationID != rhs._informationID {return false} + if lhs._subContent != rhs._subContent {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/src/devices/zwifthubbike/Zwift hub.proto b/src/devices/zwifthubbike/Zwift hub.proto new file mode 100644 index 000000000..0cb15163b --- /dev/null +++ b/src/devices/zwifthubbike/Zwift hub.proto @@ -0,0 +1,165 @@ +syntax = "proto2"; +package BLEReceiver.Zwift; + +option csharp_namespace = "BLEReceiver.Ble.Devices.Services.Protobuf.Zwift"; + + +//-------------- Zwift Hub messages +// The command code prepending this message is 0x00 +// This message is sent always following the change of the gear ratio probably to verify it was received properly +message HubRequest { + optional uint32 DataId = 1; // Value observed 520 and 0, 0 requests general info, 1-7 are the fields# in DeviceInformationContent, 520 requests the gear ratio + // 512 to 534 responds unidentifiable data +} + +// The command code prepending this message is 0x03 +message HubRidingData { + optional uint32 Power = 1; + optional uint32 Cadence = 2; + optional uint32 SpeedX100 = 3; + optional uint32 HR = 4; + optional uint32 Unknown1 = 5; // Values observed 0 when stopped, 2864, 4060, 4636, 6803 + optional uint32 Unknown2 = 6; // Values observed 25714, 30091 (constant during session) +} + +message SimulationParam { + optional sint32 Wind = 1; // Wind in m/s * 100. In zwift there is no wind (0). Negative is backwind + optional sint32 InclineX100 = 2; // Incline value * 100 + optional uint32 CWa = 3; // Wind coefficient CW * a * 10000. In zwift this is constant 0.51 (5100) + optional uint32 Crr = 4; // Rolling resistance Crr * 100000. In zwift this is constant 0.004 (400) +} + +message PhysicalParam { + optional uint32 GearRatioX10000 = 2; + optional uint32 BikeWeightx100 = 4; + optional uint32 RiderWeightx100 = 5; +} + +// The command code prepending this message is 0x04 +message HubCommand { + optional uint32 PowerTarget = 3; + optional SimulationParam Simulation = 4; + optional PhysicalParam Physical = 5; +} + +//---------------- Zwift Play messages + +enum PlayButtonStatus { + ON = 0; + OFF = 1; +} +// The command code prepending this message is 0x07 +message PlayKeyPadStatus { + optional PlayButtonStatus RightPad = 1; + optional PlayButtonStatus Button_Y_Up = 2; + optional PlayButtonStatus Button_Z_Left = 3; + optional PlayButtonStatus Button_A_Right = 4; + optional PlayButtonStatus Button_B_Down = 5; + optional PlayButtonStatus Button_On = 6; + optional PlayButtonStatus Button_Shift = 7; + optional sint32 Analog_LR = 8; + optional sint32 Analog_UD = 9; +} + + +message PlayCommandParameters { + optional uint32 param1 = 1; + optional uint32 param2 = 2; + optional uint32 HapticPattern = 3; +} + +message PlayCommandContents { + optional PlayCommandParameters CommandParameters = 1; +} + +// The command code prepending this message is 0x12 +// This is sent to the control point to configure and make the controller vibrate +message PlayCommand { + optional PlayCommandContents CommandContents = 2; +} + +// The command code prepending this message is 0x19 +// This is sent periodically when there are no button presses +message Idle { + optional uint32 Unknown2 = 2; +} + +//----------------- Zwift Ride messages +enum RideButtonMask { + LEFT_BTN = 0x00001; + UP_BTN = 0x00002; + RIGHT_BTN = 0x00004; + DOWN_BTN = 0x00008; + A_BTN = 0x00010; + B_BTN = 0x00020; + Y_BTN = 0x00040; + + Z_BTN = 0x00100; + SHFT_UP_L_BTN = 0x00200; + SHFT_DN_L_BTN = 0x00400; + POWERUP_L_BTN = 0x00800; + ONOFF_L_BTN = 0x01000; + SHFT_UP_R_BTN = 0x02000; + SHFT_DN_R_BTN = 0x04000; + + POWERUP_R_BTN = 0x10000; + ONOFF_R_BTN = 0x20000; +} + +enum RideAnalogLocation { + LEFT = 0; + RIGHT = 1; + UP = 2; + DOWN = 3; +} + +message RideAnalogKeyPress { + optional RideAnalogLocation Location = 1; + optional sint32 AnalogValue = 2; +} + +message RideAnalogKeyGroup { + repeated RideAnalogKeyPress GroupStatus = 1; +} + +// The command code prepending this message is 0x23 +message RideKeyPadStatus { + optional uint32 ButtonMap = 1; + optional RideAnalogKeyGroup AnalogButtons = 2; +} + +//------------------ Zwift Click messages +// The command code prepending this message is 0x37 +message ClickKeyPadStatus { + optional PlayButtonStatus Button_Plus = 1; + optional PlayButtonStatus Button_Minus = 2; +} + +//------------------ Device Information requested after connection +// The command code prepending this message is 0x3c +message DeviceInformationContent { + optional uint32 Unknown1 = 1; + repeated uint32 SoftwareVersion = 2; + optional string DeviceName = 3; + optional uint32 Unknown4 = 4; + optional uint32 Unknown5 =5; + optional string SerialNumber = 6; + optional string HardwareVersion = 7; + repeated uint32 ReplyData = 8; + optional uint32 Unknown9 = 9; + optional uint32 Unknown10 = 10; + optional uint32 Unknown13 = 13; +} + +message SubContent { + optional DeviceInformationContent Content = 1; + optional uint32 Unknown2 = 2; + optional uint32 Unknown4 = 4; + optional uint32 Unknown5 = 5; + optional uint32 Unknown6 = 6; +} + +message DeviceInformation { + optional uint32 InformationId = 1; + optional SubContent SubContent = 2; +} \ No newline at end of file diff --git a/src/devices/zwifthubbike/zwifthubbike.swift b/src/devices/zwifthubbike/zwifthubbike.swift new file mode 100644 index 000000000..781cded1c --- /dev/null +++ b/src/devices/zwifthubbike/zwifthubbike.swift @@ -0,0 +1,33 @@ +// +// zwifthubbike.swift +// qdomyoszwift +// +// Created by Roberto Viola on 11/11/24. +// + +import Foundation + +@objc public class ZwiftHubBike : NSObject { + @objc public static func inclinationCommand(inclination: Double) throws -> Data { + // Creiamo i parametri di simulazione con solo pendenza + var simulation = BLEReceiver_Zwift_SimulationParam() + simulation.inclineX100 = Int32(inclination * 100.0) + + // Creiamo il comando principale solo con simulation + var command = BLEReceiver_Zwift_HubCommand() + command.simulation = simulation + + // Serializziamo in dati binari + let data = try command.serializedData() + + // Se vuoi vedere l'hex string: + let hexString = data.map { String(format: "%02x", $0) }.joined() + print("Hex data: \(hexString)") + + // Aggiungi il command code 0x04 + var fullData = Data([0x04]) + fullData.append(data) + + return fullData + } +} diff --git a/src/ios/lockscreen.h b/src/ios/lockscreen.h index cd9d075b1..586e11203 100644 --- a/src/ios/lockscreen.h +++ b/src/ios/lockscreen.h @@ -1,6 +1,8 @@ #ifndef LOCKSCREEN_H #define LOCKSCREEN_H +#include + class lockscreen { public: void setTimerDisabled(); @@ -78,6 +80,9 @@ class lockscreen { float zwift_api_getlatitude(); float zwift_api_getlongitude(); + // Zwift Hub Protobuf + static QByteArray zwift_hub_inclinationCommand(double inclination); + // quick actions static void set_action_profile(const char* profile); static const char* get_action_profile(); diff --git a/src/ios/lockscreen.mm b/src/ios/lockscreen.mm index fc6cbb44f..14c0aec86 100644 --- a/src/ios/lockscreen.mm +++ b/src/ios/lockscreen.mm @@ -341,4 +341,18 @@ float lockscreen::zwift_api_getlongitude() { return [zwiftProtobufLayer getLongitude]; } + +QByteArray lockscreen::zwift_hub_inclinationCommand(double inclination) { + NSError *error = nil; + NSData *command = [ZwiftHubBike inclinationCommandWithInclination:inclination error:&error]; + + if (error) { + qDebug() << "Errore nella generazione del comando: " << error; + return QByteArray(); + } else { + const char* bytes = static_cast([command bytes]); + NSUInteger length = [command length]; + return QByteArray(bytes, length); + } +} #endif From e349ca9bad8b78b592ffd9b276157c742550f261 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 11 Nov 2024 11:10:11 +0100 Subject: [PATCH 26/45] protobuf also for the gears --- .../qdomyoszwift.xcodeproj/project.pbxproj | 12 ++++---- src/devices/ftmsbike/ftmsbike.cpp | 29 +------------------ src/devices/zwifthubbike/zwifthubbike.swift | 21 +++++++++----- src/ios/lockscreen.h | 1 + src/ios/lockscreen.mm | 16 +++++++++- 5 files changed, 36 insertions(+), 43 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 9906018fd..1dfdf401a 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 @@ -4076,7 +4076,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 936; + CURRENT_PROJECT_VERSION = 937; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1"; @@ -4267,7 +4267,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 936; + CURRENT_PROJECT_VERSION = 937; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; @@ -4494,7 +4494,7 @@ CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 936; + CURRENT_PROJECT_VERSION = 937; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -4590,7 +4590,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 936; + CURRENT_PROJECT_VERSION = 937; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; @@ -4682,7 +4682,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 936; + CURRENT_PROJECT_VERSION = 937; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; ENABLE_PREVIEWS = YES; @@ -4796,7 +4796,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 936; + CURRENT_PROJECT_VERSION = 937; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; diff --git a/src/devices/ftmsbike/ftmsbike.cpp b/src/devices/ftmsbike/ftmsbike.cpp index 4fb1e4b6d..e0d1a8aea 100644 --- a/src/devices/ftmsbike/ftmsbike.cpp +++ b/src/devices/ftmsbike/ftmsbike.cpp @@ -330,34 +330,7 @@ void ftmsbike::update() { qDebug() << "zwift hub gear current ratio" << current_ratio << g.crankset << g.rearCog << "gear_value" << gear_value << "original_ratio" << original_ratio; - QByteArray proto; - proto.append(0x04); // Length prefix - proto.append(0x2a); // Field number/wire type - - // Calculate varint size inline - uint32_t temp = gear_value; - int size = 0; - do { - size++; - temp >>= 7; - if(size > 3) { - qDebug() << "ERROR! on while"; - break; - } - } while (temp > 0); - - // Use 0x03 for 2-byte values, 0x04 for 3-byte values - proto.append(size <= 2 ? 0x03 : 0x04); - - proto.append(0x10); // Field marker - - // Encode value as varint - temp = gear_value; - while (temp > 0x7F) { - proto.append((temp & 0x7F) | 0x80); - temp >>= 7; - } - proto.append(temp & 0x7F); + QByteArray proto = lockscreen::zwift_hub_setGearsCommand(gear_value); writeCharacteristicZwiftPlay((uint8_t*)proto.data(), sizeof(proto), "gear", false, true); uint8_t gearApply[] = {0x00, 0x08, 0x88, 0x04}; diff --git a/src/devices/zwifthubbike/zwifthubbike.swift b/src/devices/zwifthubbike/zwifthubbike.swift index 781cded1c..4057c0ff8 100644 --- a/src/devices/zwifthubbike/zwifthubbike.swift +++ b/src/devices/zwifthubbike/zwifthubbike.swift @@ -9,22 +9,27 @@ import Foundation @objc public class ZwiftHubBike : NSObject { @objc public static func inclinationCommand(inclination: Double) throws -> Data { - // Creiamo i parametri di simulazione con solo pendenza var simulation = BLEReceiver_Zwift_SimulationParam() simulation.inclineX100 = Int32(inclination * 100.0) - // Creiamo il comando principale solo con simulation var command = BLEReceiver_Zwift_HubCommand() command.simulation = simulation - // Serializziamo in dati binari - let data = try command.serializedData() + let data = try command.serializedData() + var fullData = Data([0x04]) + fullData.append(data) + + return fullData + } + + @objc public static func setGearCommand(gears: UInt32) throws -> Data { + var physical = BLEReceiver_Zwift_PhysicalParam() + physical.gearRatioX10000 = gears - // Se vuoi vedere l'hex string: - let hexString = data.map { String(format: "%02x", $0) }.joined() - print("Hex data: \(hexString)") + var command = BLEReceiver_Zwift_HubCommand() + command.physical = physical - // Aggiungi il command code 0x04 + let data = try command.serializedData() var fullData = Data([0x04]) fullData.append(data) diff --git a/src/ios/lockscreen.h b/src/ios/lockscreen.h index 586e11203..590e922f8 100644 --- a/src/ios/lockscreen.h +++ b/src/ios/lockscreen.h @@ -82,6 +82,7 @@ class lockscreen { // Zwift Hub Protobuf static QByteArray zwift_hub_inclinationCommand(double inclination); + static QByteArray zwift_hub_setGearsCommand(unsigned int gears); // quick actions static void set_action_profile(const char* profile); diff --git a/src/ios/lockscreen.mm b/src/ios/lockscreen.mm index 14c0aec86..4830561ac 100644 --- a/src/ios/lockscreen.mm +++ b/src/ios/lockscreen.mm @@ -347,7 +347,21 @@ NSData *command = [ZwiftHubBike inclinationCommandWithInclination:inclination error:&error]; if (error) { - qDebug() << "Errore nella generazione del comando: " << error; + qDebug() << "error zwift_hub_inclinationCommand: " << error; + return QByteArray(); + } else { + const char* bytes = static_cast([command bytes]); + NSUInteger length = [command length]; + return QByteArray(bytes, length); + } +} + +QByteArray lockscreen::zwift_hub_setGearsCommand(unsigned int gears) { + NSError *error = nil; + NSData *command = [ZwiftHubBike setGearCommandWithGears:gears error:&error]; + + if (error) { + qDebug() << "error zwift_hub_setGearsCommand: " << error; return QByteArray(); } else { const char* bytes = static_cast([command bytes]); From 839213e91763d13bd4719deb5eee53b1fee85f49 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 11 Nov 2024 13:03:32 +0100 Subject: [PATCH 27/45] Update ftmsbike.cpp --- src/devices/ftmsbike/ftmsbike.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/devices/ftmsbike/ftmsbike.cpp b/src/devices/ftmsbike/ftmsbike.cpp index e0d1a8aea..f4489de01 100644 --- a/src/devices/ftmsbike/ftmsbike.cpp +++ b/src/devices/ftmsbike/ftmsbike.cpp @@ -331,7 +331,7 @@ void ftmsbike::update() { qDebug() << "zwift hub gear current ratio" << current_ratio << g.crankset << g.rearCog << "gear_value" << gear_value << "original_ratio" << original_ratio; QByteArray proto = lockscreen::zwift_hub_setGearsCommand(gear_value); - writeCharacteristicZwiftPlay((uint8_t*)proto.data(), sizeof(proto), "gear", false, true); + writeCharacteristicZwiftPlay((uint8_t*)proto.data(), proto.length(), "gear", false, true); uint8_t gearApply[] = {0x00, 0x08, 0x88, 0x04}; writeCharacteristicZwiftPlay(gearApply, sizeof(gearApply), "gearApply", false, true); From 159f96dd8da31bf237d16cdeb06115ae9bd19810 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 11 Nov 2024 13:04:35 +0100 Subject: [PATCH 28/45] 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 1dfdf401a..4804bd8b3 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 @@ -4076,7 +4076,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 937; + CURRENT_PROJECT_VERSION = 938; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1"; @@ -4267,7 +4267,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 937; + CURRENT_PROJECT_VERSION = 938; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; @@ -4494,7 +4494,7 @@ CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 937; + CURRENT_PROJECT_VERSION = 938; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -4590,7 +4590,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 937; + CURRENT_PROJECT_VERSION = 938; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; @@ -4682,7 +4682,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 937; + CURRENT_PROJECT_VERSION = 938; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; ENABLE_PREVIEWS = YES; @@ -4796,7 +4796,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 937; + CURRENT_PROJECT_VERSION = 938; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; From 190011a3653b84ee59b9855d4d37248040248c9a Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 12 Nov 2024 09:07:02 +0100 Subject: [PATCH 29/45] reverting tacxneo wheel diameter and ftms standard wheel diamater in order to merge it --- src/devices/ftmsbike/ftmsbike.cpp | 22 +++++----------------- src/devices/ftmsbike/ftmsbike.h | 1 - src/devices/tacxneo2/tacxneo2.cpp | 12 ++++-------- 3 files changed, 9 insertions(+), 26 deletions(-) diff --git a/src/devices/ftmsbike/ftmsbike.cpp b/src/devices/ftmsbike/ftmsbike.cpp index f4489de01..05030aa50 100644 --- a/src/devices/ftmsbike/ftmsbike.cpp +++ b/src/devices/ftmsbike/ftmsbike.cpp @@ -177,18 +177,6 @@ void ftmsbike::zwiftPlayInit() { } } -void ftmsbike::setWheelDiameter(double diameter) { - uint8_t write[] = {FTMS_SET_WHEEL_CIRCUMFERENCE, 0x00, 0x00}; - - diameter = diameter * 10.0; - - write[1] = ((uint16_t)diameter) & 0xFF; - write[2] = ((uint16_t)diameter) >> 8; - - writeCharacteristic(write, sizeof(write), QStringLiteral("setWheelCircumference ") + QString::number(diameter)); -} - - void ftmsbike::forcePower(int16_t requestPower) { if(resistance_lvl_mode) { forceResistance(resistanceFromPowerRequest(requestPower)); @@ -304,16 +292,14 @@ void ftmsbike::update() { if (((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike || resistance_lvl_mode) && (requestPower == 0 || requestPower == -1)) { init(); - if(requestResistance != - 1) - forceResistance(requestResistance + (gears() * 5)); - else - setWheelDiameter(wheelCircumference::gearsToWheelDiameter(gears())); + forceResistance(requestResistance + (gears() * 5)); } } requestResistance = -1; } if((virtualBike && virtualBike->ftmsDeviceConnected()) && lastGearValue != gears() && lastRawRequestedInclinationValue != -100 && lastPacketFromFTMS.length() >= 7) { - setWheelDiameter(wheelCircumference::gearsToWheelDiameter(gears())); + qDebug() << "injecting fake ftms frame in order to send the new gear value ASAP" << lastPacketFromFTMS.toHex(' '); + ftmsCharacteristicChanged(QLowEnergyCharacteristic(), lastPacketFromFTMS); } QSettings settings; @@ -1016,6 +1002,8 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact b[3] = slope & 0xFF; b[4] = slope >> 8; + + qDebug() << "applying gears mod" << gears() << slope; } else if(b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS && zwiftPlayService != nullptr) { int16_t slope = (((uint8_t)b.at(3)) + (b.at(4) << 8)); diff --git a/src/devices/ftmsbike/ftmsbike.h b/src/devices/ftmsbike/ftmsbike.h index fa4fd4e06..214059920 100644 --- a/src/devices/ftmsbike/ftmsbike.h +++ b/src/devices/ftmsbike/ftmsbike.h @@ -86,7 +86,6 @@ class ftmsbike : public bike { void init(); void forceResistance(resistance_t requestResistance); void forcePower(int16_t requestPower); - void setWheelDiameter(double diameter); uint16_t wattsFromResistance(double resistance); QTimer *refresh; diff --git a/src/devices/tacxneo2/tacxneo2.cpp b/src/devices/tacxneo2/tacxneo2.cpp index 6f7c9fe07..0d2d35b56 100644 --- a/src/devices/tacxneo2/tacxneo2.cpp +++ b/src/devices/tacxneo2/tacxneo2.cpp @@ -7,7 +7,6 @@ #include #include #include -#include "wheelcircumference.h" #ifdef Q_OS_ANDROID #include "keepawakehelper.h" #include @@ -124,7 +123,7 @@ void tacxneo2::update() { auto virtualBike = this->VirtualBike(); if (requestResistance != -1) { - if (requestResistance != currentResistance().value()) { + if (requestResistance != currentResistance().value() || lastGearValue != gears()) { emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance)); if (((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike) && (requestPower == 0 || requestPower == -1)) { @@ -136,18 +135,15 @@ void tacxneo2::update() { } if (requestInclination != -100) { emit debug(QStringLiteral("writing inclination ") + QString::number(requestInclination)); - forceInclination(requestInclination); // since this bike doesn't have the concept of resistance, + forceInclination(requestInclination + gears()); // since this bike doesn't have the concept of resistance, // i'm using the gears in the inclination requestInclination = -100; - } else if((virtualBike && virtualBike->ftmsDeviceConnected()) && lastRawRequestedInclinationValue != -100) { + } else if((virtualBike && virtualBike->ftmsDeviceConnected()) && lastGearValue != gears() && lastRawRequestedInclinationValue != -100) { // in order to send the new gear value ASAP - forceInclination(lastRawRequestedInclinationValue); // since this bike doesn't have the concept of resistance, + forceInclination(lastRawRequestedInclinationValue + gears()); // since this bike doesn't have the concept of resistance, // i'm using the gears in the inclination } - if(lastGearValue != gears()) - setUserConfiguration(wheelCircumference::gearsToWheelDiameter(gears()), 1); - lastGearValue = gears(); if (requestPower != -1) { From 797ae5f1025a838e21d3f040420692c7a44b172b Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 12 Nov 2024 09:10:14 +0100 Subject: [PATCH 30/45] fixing mingears and maxgears --- src/devices/ftmsbike/ftmsbike.cpp | 16 ++++++++++++++++ src/devices/ftmsbike/ftmsbike.h | 2 ++ 2 files changed, 18 insertions(+) diff --git a/src/devices/ftmsbike/ftmsbike.cpp b/src/devices/ftmsbike/ftmsbike.cpp index 05030aa50..0086307a8 100644 --- a/src/devices/ftmsbike/ftmsbike.cpp +++ b/src/devices/ftmsbike/ftmsbike.cpp @@ -1186,3 +1186,19 @@ void ftmsbike::controllerStateChanged(QLowEnergyController::ControllerState stat m_control->connectToDevice(); } } + +double ftmsbike::maxGears() { + if(zwiftPlayService != nullptr) { + wheelCircumference::GearTable g; + return g.maxGears; + } else { + return 9999.0; + } +} + +double ftmsbike::minGears() { + if(zwiftPlayService != nullptr) + return 1; + else + return -9999.0; +} diff --git a/src/devices/ftmsbike/ftmsbike.h b/src/devices/ftmsbike/ftmsbike.h index 214059920..06c2b876b 100644 --- a/src/devices/ftmsbike/ftmsbike.h +++ b/src/devices/ftmsbike/ftmsbike.h @@ -74,6 +74,8 @@ class ftmsbike : public bike { resistance_t pelotonToBikeResistance(int pelotonResistance) override; resistance_t maxResistance() override { return max_resistance; } resistance_t resistanceFromPowerRequest(uint16_t power) override; + double maxGears() override; + double minGears() override; private: bool writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false, From 9bb016f4b9a4d4f224aa123d2a54e910225fd1ed Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 12 Nov 2024 09:33:53 +0100 Subject: [PATCH 31/45] adding android part --- src/android/src/ZwiftHub.java | 74 ++++++++++ src/android/src/main/proto/zwift_hub.proto | 162 +++++++++++++++++++++ src/devices/ftmsbike/ftmsbike.cpp | 54 +++++++ src/main.cpp | 44 ++++++ src/qdomyos-zwift.pri | 2 + 5 files changed, 336 insertions(+) create mode 100644 src/android/src/ZwiftHub.java create mode 100644 src/android/src/main/proto/zwift_hub.proto diff --git a/src/android/src/ZwiftHub.java b/src/android/src/ZwiftHub.java new file mode 100644 index 000000000..7fc9aca40 --- /dev/null +++ b/src/android/src/ZwiftHub.java @@ -0,0 +1,74 @@ +package org.cagnulen.qdomyoszwift; + +import android.app.ActivityManager; +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; +import android.os.Looper; +import android.os.Handler; +import android.util.Log; +import com.garmin.android.connectiq.ConnectIQ; +import com.garmin.android.connectiq.ConnectIQAdbStrategy; +import com.garmin.android.connectiq.IQApp; +import com.garmin.android.connectiq.IQDevice; +import com.garmin.android.connectiq.exception.InvalidStateException; +import com.garmin.android.connectiq.exception.ServiceUnavailableException; +import android.content.BroadcastReceiver; +import android.content.ContextWrapper; +import android.content.IntentFilter; +import android.widget.Toast; + +import org.jetbrains.annotations.Nullable; + +import com.google.protobuf.InvalidProtocolBufferException; + +import java.util.HashMap; +import java.util.List; + +public class ZwiftHub { + + private static Context context; + + private static final String TAG = "ZwiftHub: "; + + public static byte[] inclinationCommand(double inclination) throws InvalidProtocolBufferException { + SimulationParam.Builder simulation = SimulationParam.newBuilder(); + simulation.setInclineX100((int)(inclination * 100.0)); + + HubCommand.Builder command = HubCommand.newBuilder(); + command.setSimulation(simulation.build()); + + byte[] data = command.build().toByteArray(); + byte[] fullData = new byte[data.length + 1]; + fullData[0] = 0x04; + System.arraycopy(data, 0, fullData, 1, data.length); + + return fullData; + } + + public static byte[] setGearCommand(int gears) throws InvalidProtocolBufferException { + PhysicalParam.Builder physical = PhysicalParam.newBuilder(); + physical.setGearRatioX10000(gears); + + HubCommand.Builder command = HubCommand.newBuilder(); + command.setPhysical(physical.build()); + + byte[] data = command.build().toByteArray(); + byte[] fullData = new byte[data.length + 1]; + fullData[0] = 0x04; + System.arraycopy(data, 0, fullData, 1, data.length); + + return fullData; + } +} diff --git a/src/android/src/main/proto/zwift_hub.proto b/src/android/src/main/proto/zwift_hub.proto new file mode 100644 index 000000000..3ab67edc1 --- /dev/null +++ b/src/android/src/main/proto/zwift_hub.proto @@ -0,0 +1,162 @@ +syntax = "proto2"; +package org.cagnulen.qdomyoszwift; + +//-------------- Zwift Hub messages +// The command code prepending this message is 0x00 +// This message is sent always following the change of the gear ratio probably to verify it was received properly +message HubRequest { + optional uint32 DataId = 1; // Value observed 520 and 0, 0 requests general info, 1-7 are the fields# in DeviceInformationContent, 520 requests the gear ratio + // 512 to 534 responds unidentifiable data +} + +// The command code prepending this message is 0x03 +message HubRidingData { + optional uint32 Power = 1; + optional uint32 Cadence = 2; + optional uint32 SpeedX100 = 3; + optional uint32 HR = 4; + optional uint32 Unknown1 = 5; // Values observed 0 when stopped, 2864, 4060, 4636, 6803 + optional uint32 Unknown2 = 6; // Values observed 25714, 30091 (constant during session) +} + +message SimulationParam { + optional sint32 Wind = 1; // Wind in m/s * 100. In zwift there is no wind (0). Negative is backwind + optional sint32 InclineX100 = 2; // Incline value * 100 + optional uint32 CWa = 3; // Wind coefficient CW * a * 10000. In zwift this is constant 0.51 (5100) + optional uint32 Crr = 4; // Rolling resistance Crr * 100000. In zwift this is constant 0.004 (400) +} + +message PhysicalParam { + optional uint32 GearRatioX10000 = 2; + optional uint32 BikeWeightx100 = 4; + optional uint32 RiderWeightx100 = 5; +} + +// The command code prepending this message is 0x04 +message HubCommand { + optional uint32 PowerTarget = 3; + optional SimulationParam Simulation = 4; + optional PhysicalParam Physical = 5; +} + +//---------------- Zwift Play messages + +enum PlayButtonStatus { + ON = 0; + OFF = 1; +} +// The command code prepending this message is 0x07 +message PlayKeyPadStatus { + optional PlayButtonStatus RightPad = 1; + optional PlayButtonStatus Button_Y_Up = 2; + optional PlayButtonStatus Button_Z_Left = 3; + optional PlayButtonStatus Button_A_Right = 4; + optional PlayButtonStatus Button_B_Down = 5; + optional PlayButtonStatus Button_On = 6; + optional PlayButtonStatus Button_Shift = 7; + optional sint32 Analog_LR = 8; + optional sint32 Analog_UD = 9; +} + + +message PlayCommandParameters { + optional uint32 param1 = 1; + optional uint32 param2 = 2; + optional uint32 HapticPattern = 3; +} + +message PlayCommandContents { + optional PlayCommandParameters CommandParameters = 1; +} + +// The command code prepending this message is 0x12 +// This is sent to the control point to configure and make the controller vibrate +message PlayCommand { + optional PlayCommandContents CommandContents = 2; +} + +// The command code prepending this message is 0x19 +// This is sent periodically when there are no button presses +message Idle { + optional uint32 Unknown2 = 2; +} + +//----------------- Zwift Ride messages +enum RideButtonMask { + LEFT_BTN = 0x00001; + UP_BTN = 0x00002; + RIGHT_BTN = 0x00004; + DOWN_BTN = 0x00008; + A_BTN = 0x00010; + B_BTN = 0x00020; + Y_BTN = 0x00040; + + Z_BTN = 0x00100; + SHFT_UP_L_BTN = 0x00200; + SHFT_DN_L_BTN = 0x00400; + POWERUP_L_BTN = 0x00800; + ONOFF_L_BTN = 0x01000; + SHFT_UP_R_BTN = 0x02000; + SHFT_DN_R_BTN = 0x04000; + + POWERUP_R_BTN = 0x10000; + ONOFF_R_BTN = 0x20000; +} + +enum RideAnalogLocation { + LEFT = 0; + RIGHT = 1; + UP = 2; + DOWN = 3; +} + +message RideAnalogKeyPress { + optional RideAnalogLocation Location = 1; + optional sint32 AnalogValue = 2; +} + +message RideAnalogKeyGroup { + repeated RideAnalogKeyPress GroupStatus = 1; +} + +// The command code prepending this message is 0x23 +message RideKeyPadStatus { + optional uint32 ButtonMap = 1; + optional RideAnalogKeyGroup AnalogButtons = 2; +} + +//------------------ Zwift Click messages +// The command code prepending this message is 0x37 +message ClickKeyPadStatus { + optional PlayButtonStatus Button_Plus = 1; + optional PlayButtonStatus Button_Minus = 2; +} + +//------------------ Device Information requested after connection +// The command code prepending this message is 0x3c +message DeviceInformationContent { + optional uint32 Unknown1 = 1; + repeated uint32 SoftwareVersion = 2; + optional string DeviceName = 3; + optional uint32 Unknown4 = 4; + optional uint32 Unknown5 =5; + optional string SerialNumber = 6; + optional string HardwareVersion = 7; + repeated uint32 ReplyData = 8; + optional uint32 Unknown9 = 9; + optional uint32 Unknown10 = 10; + optional uint32 Unknown13 = 13; +} + +message SubContent { + optional DeviceInformationContent Content = 1; + optional uint32 Unknown2 = 2; + optional uint32 Unknown4 = 4; + optional uint32 Unknown5 = 5; + optional uint32 Unknown6 = 6; +} + +message DeviceInformation { + optional uint32 InformationId = 1; + optional SubContent SubContent = 2; +} diff --git a/src/devices/ftmsbike/ftmsbike.cpp b/src/devices/ftmsbike/ftmsbike.cpp index 0086307a8..b357bd087 100644 --- a/src/devices/ftmsbike/ftmsbike.cpp +++ b/src/devices/ftmsbike/ftmsbike.cpp @@ -316,7 +316,37 @@ void ftmsbike::update() { qDebug() << "zwift hub gear current ratio" << current_ratio << g.crankset << g.rearCog << "gear_value" << gear_value << "original_ratio" << original_ratio; +#ifdef Q_OS_IOS +#ifndef IO_UNDER_QT QByteArray proto = lockscreen::zwift_hub_setGearsCommand(gear_value); +#else + QByteArray proto; +#endif +#elif defined Q_OS_ANDROID + QAndroidJniObject result = QAndroidJniObject::callStaticObjectMethod( + "org/cagnulen/qdomyoszwift/ZwiftHub", + "setGearCommand", + "(I)[B", + gear_value); + + if (!result.isValid()) { + qDebug() << "setGearCommand returned invalid value"; + return; + } + + jbyteArray array = result.object(); + QAndroidJniEnvironment env; + jbyte* bytes = env->GetByteArrayElements(array, nullptr); + jsize length = env->GetArrayLength(array); + + QByteArray proto((char*)bytes, length); + + env->ReleaseByteArrayElements(array, bytes, JNI_ABORT); +#else + QByteArray proto; + qDebug() << "ERROR: gear message not handled!"; + return; +#endif writeCharacteristicZwiftPlay((uint8_t*)proto.data(), proto.length(), "gear", false, true); uint8_t gearApply[] = {0x00, 0x08, 0x88, 0x04}; @@ -1008,9 +1038,33 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact int16_t slope = (((uint8_t)b.at(3)) + (b.at(4) << 8)); #ifdef Q_OS_IOS +#ifndef IO_UNDER_QT QByteArray message = lockscreen::zwift_hub_inclinationCommand(((double)slope) / 100.0); +#else + QByteArray message; +#endif +#elif defined(Q_OS_ANDROID) + QAndroidJniObject result = QAndroidJniObject::callStaticObjectMethod( + "org/cagnulen/qdomyoszwift/ZwiftHub", + "inclinationCommand", + "(D)[B", + slope); + + if(!result.isValid()) { + qDebug() << "inclinationCommand returned invalid value"; + } + + jbyteArray array = result.object(); + QAndroidJniEnvironment env; + jbyte* bytes = env->GetByteArrayElements(array, nullptr); + jsize length = env->GetArrayLength(array); + + QByteArray message((char*)bytes, length); + + env->ReleaseByteArrayElements(array, bytes, JNI_ABORT); #else qDebug() << "implement zwift hub protobuf!"; + return; #endif writeCharacteristicZwiftPlay((uint8_t*)message.data(), message.length(), "gearInclination", false, false); return; diff --git a/src/main.cpp b/src/main.cpp index a81b7ba2f..34782ea7e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -331,6 +331,50 @@ int main(int argc, char *argv[]) { app->setOrganizationDomain(QStringLiteral("robertoviola.cloud")); app->setApplicationName(QStringLiteral("qDomyos-Zwift")); + /* TEST ZWIFT HUB */ +#ifdef Q_OS_ANDROID + QAndroidJniObject result = QAndroidJniObject::callStaticObjectMethod( + "org/cagnulen/qdomyoszwift/ZwiftHub", + "inclinationCommand", + "(D)[B", + 8.0); + + if(!result.isValid()) { + qDebug() << "inclinationCommand returned invalid value"; + } + + jbyteArray array = result.object(); + QAndroidJniEnvironment env; + jbyte* bytes = env->GetByteArrayElements(array, nullptr); + jsize length = env->GetArrayLength(array); + + QByteArray message((char*)bytes, length); + + env->ReleaseByteArrayElements(array, bytes, JNI_ABORT); + qDebug() << "inclination command" << message.toHex(' '); + + QAndroidJniObject result = QAndroidJniObject::callStaticObjectMethod( + "org/cagnulen/qdomyoszwift/ZwiftHub", + "setGearCommand", + "(I)[B", + 32608); + + if (!result.isValid()) { + qDebug() << "setGearCommand returned invalid value"; + return; + } + + jbyteArray array = result.object(); + QAndroidJniEnvironment env; + jbyte* bytes = env->GetByteArrayElements(array, nullptr); + jsize length = env->GetArrayLength(array); + + QByteArray proto((char*)bytes, length); + + env->ReleaseByteArrayElements(array, bytes, JNI_ABORT); + qDebug() << "gear command" << proto.toHex(' '); +#endif + QSettings settings; #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) diff --git a/src/qdomyos-zwift.pri b/src/qdomyos-zwift.pri index 09de57948..3e28958bc 100644 --- a/src/qdomyos-zwift.pri +++ b/src/qdomyos-zwift.pri @@ -773,6 +773,8 @@ DISTFILES += \ $$PWD/android/src/WearableMessageListenerService.java \ $$PWD/android/src/ZapClickLayer.java \ $$PWD/android/src/ZwiftAPI.java \ + $$PWD/android/src/ZwiftHub.java \ + $$PWD/android/src/main/proto/zwift_hub.proto \ $$PWD/android/src/main/proto/zwift_messages.proto \ .clang-format \ AppxManifest.xml \ From 7c52e9263f9836bf4369d2c463ea0cf53698ff5b Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 12 Nov 2024 09:58:34 +0100 Subject: [PATCH 32/45] Update main.cpp --- src/main.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 34782ea7e..d218ba54f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -333,17 +333,17 @@ int main(int argc, char *argv[]) { /* TEST ZWIFT HUB */ #ifdef Q_OS_ANDROID - QAndroidJniObject result = QAndroidJniObject::callStaticObjectMethod( + QAndroidJniObject r = QAndroidJniObject::callStaticObjectMethod( "org/cagnulen/qdomyoszwift/ZwiftHub", "inclinationCommand", "(D)[B", 8.0); - if(!result.isValid()) { + if(!r.isValid()) { qDebug() << "inclinationCommand returned invalid value"; } - jbyteArray array = result.object(); + jbyteArray array = r.object(); QAndroidJniEnvironment env; jbyte* bytes = env->GetByteArrayElements(array, nullptr); jsize length = env->GetArrayLength(array); @@ -353,18 +353,18 @@ int main(int argc, char *argv[]) { env->ReleaseByteArrayElements(array, bytes, JNI_ABORT); qDebug() << "inclination command" << message.toHex(' '); - QAndroidJniObject result = QAndroidJniObject::callStaticObjectMethod( + QAndroidJniObject rr = QAndroidJniObject::callStaticObjectMethod( "org/cagnulen/qdomyoszwift/ZwiftHub", "setGearCommand", "(I)[B", 32608); - if (!result.isValid()) { + if (!rr.isValid()) { qDebug() << "setGearCommand returned invalid value"; return; } - jbyteArray array = result.object(); + jbyteArray array = rr.object(); QAndroidJniEnvironment env; jbyte* bytes = env->GetByteArrayElements(array, nullptr); jsize length = env->GetArrayLength(array); From eafe75e65f0f45dd6d5e2f236b677561c25b109a Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 12 Nov 2024 10:24:46 +0100 Subject: [PATCH 33/45] Update main.cpp --- src/main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index d218ba54f..7c6ab767b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -333,17 +333,17 @@ int main(int argc, char *argv[]) { /* TEST ZWIFT HUB */ #ifdef Q_OS_ANDROID - QAndroidJniObject r = QAndroidJniObject::callStaticObjectMethod( + QAndroidJniObject rrr = QAndroidJniObject::callStaticObjectMethod( "org/cagnulen/qdomyoszwift/ZwiftHub", "inclinationCommand", "(D)[B", 8.0); - if(!r.isValid()) { + if(!rrr.isValid()) { qDebug() << "inclinationCommand returned invalid value"; } - jbyteArray array = r.object(); + jbyteArray array = rrr.object(); QAndroidJniEnvironment env; jbyte* bytes = env->GetByteArrayElements(array, nullptr); jsize length = env->GetArrayLength(array); From a2e721812fe8a1b82ec9769cc630fb18b6a22554 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 12 Nov 2024 10:39:49 +0100 Subject: [PATCH 34/45] Update main.cpp --- src/main.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 7c6ab767b..0d0655f46 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -333,6 +333,7 @@ int main(int argc, char *argv[]) { /* TEST ZWIFT HUB */ #ifdef Q_OS_ANDROID +{ QAndroidJniObject rrr = QAndroidJniObject::callStaticObjectMethod( "org/cagnulen/qdomyoszwift/ZwiftHub", "inclinationCommand", @@ -364,15 +365,15 @@ int main(int argc, char *argv[]) { return; } - jbyteArray array = rr.object(); - QAndroidJniEnvironment env; - jbyte* bytes = env->GetByteArrayElements(array, nullptr); - jsize length = env->GetArrayLength(array); + array = rr.object(); + bytes = env->GetByteArrayElements(array, nullptr); + length = env->GetArrayLength(array); QByteArray proto((char*)bytes, length); env->ReleaseByteArrayElements(array, bytes, JNI_ABORT); qDebug() << "gear command" << proto.toHex(' '); +} #endif QSettings settings; From c5cfb1c93697bf247c8652d0167023e840ad07a6 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 12 Nov 2024 10:46:30 +0100 Subject: [PATCH 35/45] 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 4804bd8b3..534e984b8 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 @@ -4076,7 +4076,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 938; + CURRENT_PROJECT_VERSION = 939; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1"; @@ -4267,7 +4267,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 938; + CURRENT_PROJECT_VERSION = 939; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; @@ -4494,7 +4494,7 @@ CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 938; + CURRENT_PROJECT_VERSION = 939; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -4590,7 +4590,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 938; + CURRENT_PROJECT_VERSION = 939; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; @@ -4682,7 +4682,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 938; + CURRENT_PROJECT_VERSION = 939; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; ENABLE_PREVIEWS = YES; @@ -4796,7 +4796,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 938; + CURRENT_PROJECT_VERSION = 939; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; From f602563789e8b301488c2235cfe2d28a7a5d030a Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 12 Nov 2024 11:00:51 +0100 Subject: [PATCH 36/45] fixing android build --- src/devices/ftmsbike/ftmsbike.cpp | 1 + src/main.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/devices/ftmsbike/ftmsbike.cpp b/src/devices/ftmsbike/ftmsbike.cpp index b357bd087..8cb34a450 100644 --- a/src/devices/ftmsbike/ftmsbike.cpp +++ b/src/devices/ftmsbike/ftmsbike.cpp @@ -1052,6 +1052,7 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact if(!result.isValid()) { qDebug() << "inclinationCommand returned invalid value"; + return; } jbyteArray array = result.object(); diff --git a/src/main.cpp b/src/main.cpp index 0d0655f46..728874582 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -362,7 +362,6 @@ int main(int argc, char *argv[]) { if (!rr.isValid()) { qDebug() << "setGearCommand returned invalid value"; - return; } array = rr.object(); From 79ba8ea8744b54833e95b731101796fba9213875 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 12 Nov 2024 11:11:11 +0100 Subject: [PATCH 37/45] Update horizontreadmill.cpp --- src/devices/horizontreadmill/horizontreadmill.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/devices/horizontreadmill/horizontreadmill.cpp b/src/devices/horizontreadmill/horizontreadmill.cpp index 0ec6d5fe8..ec919d21f 100644 --- a/src/devices/horizontreadmill/horizontreadmill.cpp +++ b/src/devices/horizontreadmill/horizontreadmill.cpp @@ -2317,13 +2317,25 @@ void horizontreadmill::serviceScanDone(void) { initRequest = false; firstStateChanged = 0; auto services_list = m_control->services(); + QBluetoothUuid ftmsService((quint16)0x1826); + QBluetoothUuid CustomService((quint16)0xFFF0); for (const QBluetoothUuid &s : qAsConst(services_list)) { +#ifdef Q_OS_WIN + if (s == ftmsService || s == CustomService) +#endif + { qDebug() << s << "discovering..."; gattCommunicationChannelService.append(m_control->createServiceObject(s)); connect(gattCommunicationChannelService.constLast(), &QLowEnergyService::stateChanged, this, &horizontreadmill::stateChanged); gattCommunicationChannelService.constLast()->discoverDetails(); + } +#ifdef Q_OS_WIN + else { + qDebug() << s << "NOT discovering!"; + } +#endif } } From c76195a27154cd335c7760b6433072e5e6582b2e Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 12 Nov 2024 11:20:14 +0100 Subject: [PATCH 38/45] Update ftmsbike.cpp --- src/devices/ftmsbike/ftmsbike.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/devices/ftmsbike/ftmsbike.cpp b/src/devices/ftmsbike/ftmsbike.cpp index 8cb34a450..e0785df85 100644 --- a/src/devices/ftmsbike/ftmsbike.cpp +++ b/src/devices/ftmsbike/ftmsbike.cpp @@ -1064,6 +1064,7 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact env->ReleaseByteArrayElements(array, bytes, JNI_ABORT); #else + QByteArray message; qDebug() << "implement zwift hub protobuf!"; return; #endif From 99394713f89b885c665ef569d231c9cf033137e6 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 12 Nov 2024 12:06:05 +0100 Subject: [PATCH 39/45] Update homeform.cpp --- src/homeform.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/homeform.cpp b/src/homeform.cpp index 842f12f72..b642fd258 100644 --- a/src/homeform.cpp +++ b/src/homeform.cpp @@ -990,14 +990,15 @@ void homeform::backup() { static uint8_t index = 0; qDebug() << QStringLiteral("saving fit file backup..."); - QString path = getWritableAppDir(); - bluetoothdevice *dev = bluetoothManager->device(); - if (dev) { - - QString filename = path + QString::number(index) + backupFitFileName; - QFile::remove(filename); - qfit::save(filename, Session, dev->deviceType(), + qDebug() << QStringLiteral("writable dir") << path; + bluetoothdevice *dev = bluetoothManager->device(); + if (dev) { + QString filename = path + QString::number(index) + backupFitFileName; + qDebug() << QStringLiteral("filename") << filename; + QFile::remove(filename); + qDebug() << QStringLiteral("filename removed") << filename; + qfit::save(filename, Session, dev->deviceType(), qobject_cast(dev) ? QFIT_PROCESS_DISTANCENOISE : QFIT_PROCESS_NONE, stravaPelotonWorkoutType, dev->bluetoothDevice.name()); From e8faa3e67163d513b1380082cc9613f52750c1cd Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Wed, 13 Nov 2024 12:14:37 +0100 Subject: [PATCH 40/45] Update horizontreadmill.cpp --- src/devices/horizontreadmill/horizontreadmill.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/devices/horizontreadmill/horizontreadmill.cpp b/src/devices/horizontreadmill/horizontreadmill.cpp index ec919d21f..3273315eb 100644 --- a/src/devices/horizontreadmill/horizontreadmill.cpp +++ b/src/devices/horizontreadmill/horizontreadmill.cpp @@ -2329,7 +2329,9 @@ void horizontreadmill::serviceScanDone(void) { gattCommunicationChannelService.append(m_control->createServiceObject(s)); connect(gattCommunicationChannelService.constLast(), &QLowEnergyService::stateChanged, this, &horizontreadmill::stateChanged); - gattCommunicationChannelService.constLast()->discoverDetails(); + QTimer::singleShot(0, [=] () { + gattCommunicationChannelService.constLast()->discoverDetails(); + }); } #ifdef Q_OS_WIN else { From df3e64e57dff5d396179d7b67b3f53a991a81159 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Thu, 14 Nov 2024 09:04:42 +0100 Subject: [PATCH 41/45] Revert "Update horizontreadmill.cpp" This reverts commit e8faa3e67163d513b1380082cc9613f52750c1cd. --- src/devices/horizontreadmill/horizontreadmill.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/devices/horizontreadmill/horizontreadmill.cpp b/src/devices/horizontreadmill/horizontreadmill.cpp index 3273315eb..ec919d21f 100644 --- a/src/devices/horizontreadmill/horizontreadmill.cpp +++ b/src/devices/horizontreadmill/horizontreadmill.cpp @@ -2329,9 +2329,7 @@ void horizontreadmill::serviceScanDone(void) { gattCommunicationChannelService.append(m_control->createServiceObject(s)); connect(gattCommunicationChannelService.constLast(), &QLowEnergyService::stateChanged, this, &horizontreadmill::stateChanged); - QTimer::singleShot(0, [=] () { - gattCommunicationChannelService.constLast()->discoverDetails(); - }); + gattCommunicationChannelService.constLast()->discoverDetails(); } #ifdef Q_OS_WIN else { From e6efe0f074493b55b3ecb4a3ab8b25613dce550f Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Thu, 21 Nov 2024 12:09:32 +0100 Subject: [PATCH 42/45] trying to use win11 api for discovery --- .../horizontreadmill/horizontreadmill.cpp | 99 ++++++++++++++++--- .../horizontreadmill/horizontreadmill.h | 12 +++ 2 files changed, 95 insertions(+), 16 deletions(-) diff --git a/src/devices/horizontreadmill/horizontreadmill.cpp b/src/devices/horizontreadmill/horizontreadmill.cpp index ec919d21f..38df2db9b 100644 --- a/src/devices/horizontreadmill/horizontreadmill.cpp +++ b/src/devices/horizontreadmill/horizontreadmill.cpp @@ -2311,31 +2311,98 @@ void horizontreadmill::characteristicRead(const QLowEnergyCharacteristic &charac } } +//#ifdef Q_OS_WIN +bool horizontreadmill::discoverServicesWin11(const QBluetoothAddress& address) { + BLUETOOTH_DEVICE_INFO deviceInfo = { sizeof(BLUETOOTH_DEVICE_INFO) }; + deviceInfo.Address.ullLong = address.toUInt64(); + + if (BluetoothGetDeviceInfo(nullptr, &deviceInfo) != ERROR_SUCCESS) { + qDebug() << "Failed to get device info"; + return false; + } + + + DWORD serviceCount = 0; + GUID* serviceGuids = nullptr; + + // Prima chiamata per ottenere il numero di servizi + DWORD result = BluetoothEnumerateInstalledServices( + nullptr, + &deviceInfo, + &serviceCount, + nullptr); + + if (result != ERROR_SUCCESS) { + qDebug() << "Failed to enumerate services count"; + return false; + } + + if (serviceCount == 0) { + qDebug() << "No services found"; + return false; + } + + // Allocare memoria per i GUID dei servizi + serviceGuids = new GUID[serviceCount]; + + // Seconda chiamata per ottenere i GUID effettivi + result = BluetoothEnumerateInstalledServices( + nullptr, + &deviceInfo, + &serviceCount, + serviceGuids); + + if (result != ERROR_SUCCESS) { + delete[] serviceGuids; + qDebug() << "Failed to enumerate services"; + return false; + } + + // Processare ogni servizio trovato + for (DWORD i = 0; i < serviceCount; i++) { + handleWin11Service(serviceGuids[i]); + } + + delete[] serviceGuids; + return true; +} + +void horizontreadmill::handleWin11Service(const GUID& serviceGuid) { + wchar_t guidString[39]; + StringFromGUID2(serviceGuid, guidString, sizeof(guidString)/sizeof(wchar_t)); + + QString qtGuidString = QString::fromWCharArray(guidString); + QBluetoothUuid qtUuid(qtGuidString); + + QLowEnergyService* service = m_control->createServiceObject(qtUuid); + if (service) { + gattCommunicationChannelService.append(service); + connect(service, &QLowEnergyService::stateChanged, this, &horizontreadmill::stateChanged); + service->discoverDetails(); + qDebug() << "Discovered service:" << qtUuid; + } +} +//#endif + void horizontreadmill::serviceScanDone(void) { emit debug(QStringLiteral("serviceScanDone")); initRequest = false; firstStateChanged = 0; auto services_list = m_control->services(); - QBluetoothUuid ftmsService((quint16)0x1826); - QBluetoothUuid CustomService((quint16)0xFFF0); - for (const QBluetoothUuid &s : qAsConst(services_list)) { #ifdef Q_OS_WIN - if (s == ftmsService || s == CustomService) -#endif - { - qDebug() << s << "discovering..."; - gattCommunicationChannelService.append(m_control->createServiceObject(s)); - connect(gattCommunicationChannelService.constLast(), &QLowEnergyService::stateChanged, this, - &horizontreadmill::stateChanged); - gattCommunicationChannelService.constLast()->discoverDetails(); - } -#ifdef Q_OS_WIN - else { - qDebug() << s << "NOT discovering!"; - } + if (discoverServicesWin11(m_control->remoteAddress())) { + return; + } #endif + + for (const QBluetoothUuid &s : qAsConst(services_list)) { + qDebug() << s << "discovering..."; + gattCommunicationChannelService.append(m_control->createServiceObject(s)); + connect(gattCommunicationChannelService.constLast(), &QLowEnergyService::stateChanged, this, + &horizontreadmill::stateChanged); + gattCommunicationChannelService.constLast()->discoverDetails(); } } diff --git a/src/devices/horizontreadmill/horizontreadmill.h b/src/devices/horizontreadmill/horizontreadmill.h index 66d8685fe..469d82373 100644 --- a/src/devices/horizontreadmill/horizontreadmill.h +++ b/src/devices/horizontreadmill/horizontreadmill.h @@ -34,6 +34,13 @@ #include "ios/lockscreen.h" #endif +#ifdef Q_OS_WIN +#include +#include +#include +#include +#endif + class horizontreadmill : public treadmill { Q_OBJECT public: @@ -48,6 +55,11 @@ class horizontreadmill : public treadmill { bool autoStartWhenSpeedIsGreaterThenZero() override; private: +#ifdef Q_OS_WIN + bool discoverServicesWin11(const QBluetoothAddress& address); + void handleWin11Service(const GUID& serviceGuid); +#endif + void writeCharacteristic(QLowEnergyService *service, QLowEnergyCharacteristic characteristic, uint8_t *data, uint8_t data_len, QString info, bool disable_log = false, bool wait_for_response = false); void waitForAPacket(); From 721f6d460e10f1e73fea307b380f80100bef7a1b Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Thu, 21 Nov 2024 12:14:25 +0100 Subject: [PATCH 43/45] Update horizontreadmill.cpp --- src/devices/horizontreadmill/horizontreadmill.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/devices/horizontreadmill/horizontreadmill.cpp b/src/devices/horizontreadmill/horizontreadmill.cpp index 38df2db9b..3ec2af283 100644 --- a/src/devices/horizontreadmill/horizontreadmill.cpp +++ b/src/devices/horizontreadmill/horizontreadmill.cpp @@ -2311,7 +2311,7 @@ void horizontreadmill::characteristicRead(const QLowEnergyCharacteristic &charac } } -//#ifdef Q_OS_WIN +#ifdef Q_OS_WIN bool horizontreadmill::discoverServicesWin11(const QBluetoothAddress& address) { BLUETOOTH_DEVICE_INFO deviceInfo = { sizeof(BLUETOOTH_DEVICE_INFO) }; deviceInfo.Address.ullLong = address.toUInt64(); @@ -2382,7 +2382,7 @@ void horizontreadmill::handleWin11Service(const GUID& serviceGuid) { qDebug() << "Discovered service:" << qtUuid; } } -//#endif +#endif void horizontreadmill::serviceScanDone(void) { emit debug(QStringLiteral("serviceScanDone")); From b573442800e1a45bbf452b0acaf18b3e052e3b98 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Thu, 21 Nov 2024 13:00:17 +0100 Subject: [PATCH 44/45] Update qdomyos-zwift.pri --- src/qdomyos-zwift.pri | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/qdomyos-zwift.pri b/src/qdomyos-zwift.pri index 06b154d72..617b56b5c 100644 --- a/src/qdomyos-zwift.pri +++ b/src/qdomyos-zwift.pri @@ -31,7 +31,14 @@ CONFIG += qmltypes #unix:!android: CONFIG += webengine win32:DEFINES += _ITERATOR_DEBUG_LEVEL=0 -win32:!mingw:LIBS += -llibprotobuf -llibprotoc -labseil_dll -llibprotobuf-lite -L$$PWD +win32:!mingw:LIBS += -llibprotobuf -llibprotoc -labseil_dll -llibprotobuf-lite -lBthprops -lVersion -lRpcrt4 -lole32 -L$$PWD + +win32 { + LIBS += -lBthprops + LIBS += -lVersion + LIBS += -lRpcrt4 # Per StringFromGUID2 + LIBS += -lole32 # Altra possibile dipendenza per GUID +} QML_IMPORT_NAME = org.cagnulein.qdomyoszwift QML_IMPORT_MAJOR_VERSION = 1 From f8aa63d0d8f0f410df84fab22885253843e47fa8 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 10 Dec 2024 15:01:55 +0100 Subject: [PATCH 45/45] Update horizontreadmill.cpp --- src/devices/horizontreadmill/horizontreadmill.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/devices/horizontreadmill/horizontreadmill.cpp b/src/devices/horizontreadmill/horizontreadmill.cpp index 3ec2af283..390a560bb 100644 --- a/src/devices/horizontreadmill/horizontreadmill.cpp +++ b/src/devices/horizontreadmill/horizontreadmill.cpp @@ -2316,8 +2316,11 @@ bool horizontreadmill::discoverServicesWin11(const QBluetoothAddress& address) { BLUETOOTH_DEVICE_INFO deviceInfo = { sizeof(BLUETOOTH_DEVICE_INFO) }; deviceInfo.Address.ullLong = address.toUInt64(); - if (BluetoothGetDeviceInfo(nullptr, &deviceInfo) != ERROR_SUCCESS) { - qDebug() << "Failed to get device info"; + DWORD error = BluetoothGetDeviceInfo(nullptr, &deviceInfo); + if (error != ERROR_SUCCESS) { + qDebug() << "Failed to get device info. Error code:" << error; + DWORD lastError = GetLastError(); + qDebug() << "Last error:" << lastError; return false; }