Compare commits

...

42 Commits

Author SHA1 Message Date
Roberto Viola
c5cfb1c936 Update project.pbxproj 2024-11-12 10:46:30 +01:00
Roberto Viola
f737e1be83 Merge branch 'master' into zwift-hub-gear-custom 2024-11-12 10:37:20 +01:00
Roberto Viola
eafe75e65f Update main.cpp 2024-11-12 10:24:46 +01:00
Roberto Viola
7c52e9263f Update main.cpp 2024-11-12 09:58:34 +01:00
Roberto Viola
9bb016f4b9 adding android part 2024-11-12 09:33:53 +01:00
Roberto Viola
797ae5f102 fixing mingears and maxgears 2024-11-12 09:10:14 +01:00
Roberto Viola
190011a365 reverting tacxneo wheel diameter and ftms standard wheel diamater in order to merge it 2024-11-12 09:07:02 +01:00
Roberto Viola
159f96dd8d Update project.pbxproj 2024-11-11 13:04:35 +01:00
Roberto Viola
839213e917 Update ftmsbike.cpp 2024-11-11 13:03:32 +01:00
Roberto Viola
e349ca9bad protobuf also for the gears 2024-11-11 11:10:11 +01:00
Roberto Viola
9ca3fca817 implemented protobuf 2024-11-11 09:04:48 +01:00
Roberto Viola
095d8bc1a3 Update ftmsbike.cpp 2024-11-09 20:27:18 +01:00
Roberto Viola
07606facba adding inclination custom message too 2024-11-09 18:49:48 +01:00
Roberto Viola
bd0e7033c2 Merge branch 'master' into zwift-hub-gear-custom 2024-11-09 18:42:47 +01:00
Roberto Viola
0337c08252 first custom gear test 2024-11-09 15:48:01 +01:00
Roberto Viola
8b60b56dc2 Update ftmsbike.cpp 2024-11-09 15:43:06 +01:00
Roberto Viola
f3d8b3964c Merge branch 'master' into tacx_wheel_diameter 2024-11-09 15:37:11 +01:00
Roberto Viola
06253998ee Update wahookickrsnapbike.cpp 2024-11-04 15:08:26 +01:00
Roberto Viola
dbb9ba3510 implementing 2024-11-04 14:31:58 +01:00
Roberto Viola
ad802f7d2d Merge branch 'master' into tacx_wheel_diameter 2024-11-04 14:28:42 +01:00
Roberto Viola
9cc02dede9 ftms wheel circumference for gears 2024-10-30 15:57:31 +01:00
Roberto Viola
82a6afe6b6 kickr core to wahookickr class 2024-10-29 12:28:13 +01:00
Roberto Viola
a7b8e63235 fixing UI and settings 2024-10-29 08:50:33 +01:00
Roberto Viola
d7c3a84adb adding max and minGears 2024-10-29 08:21:59 +01:00
Roberto Viola
64b9ec9d72 fixing gear conversion 2024-10-29 08:17:38 +01:00
Roberto Viola
379cf8d7de Update project.pbxproj 2024-10-29 08:02:08 +01:00
Roberto Viola
2f2989f90d completed? 2024-10-28 15:40:00 +01:00
Roberto Viola
6fac9770be qml finally saves the settings correctly 2024-10-28 15:09:13 +01:00
Roberto Viola
8d07d7c3f7 Merge branch 'master' into Custom-gearing-ranges/ratios-(Discussion-#2671) 2024-10-28 13:44:31 +01:00
Roberto Viola
f85f743499 i need to save the first 3 static objects and use it in the wahoo module 2024-10-28 11:11:05 +01:00
Roberto Viola
74c37f5624 Update gears.qml 2024-10-28 09:46:58 +01:00
Roberto Viola
527396eafc Revert "Update gears.qml"
This reverts commit 0f149448b3.
2024-10-28 09:18:54 +01:00
Roberto Viola
0f149448b3 Update gears.qml 2024-10-25 16:28:33 +02:00
Roberto Viola
ed1599ca8e need to center the values in the table 2024-10-24 15:08:25 +02:00
Roberto Viola
89808ae65b fixing casting to double 2024-10-24 11:15:51 +02:00
Roberto Viola
2da194f073 Merge branch 'master' into Custom-gearing-ranges/ratios-(Discussion-#2671) 2024-10-24 11:00:46 +02:00
Roberto Viola
d712621b7b fixing formula 2024-10-23 11:47:05 +02:00
Roberto Viola
1c06260036 Merge branch 'master' into Custom-gearing-ranges/ratios-(Discussion-#2671) 2024-10-23 11:44:26 +02:00
Roberto Viola
2d1364497e Update virtualbike_zwift.swift 2024-10-22 15:40:04 +02:00
Roberto Viola
cc7757bfcd Update project.pbxproj 2024-10-22 15:38:44 +02:00
Roberto Viola
49c7a96c81 Merge branch 'master' into Custom-gearing-ranges/ratios-(Discussion-#2671) 2024-10-22 15:32:50 +02:00
Roberto Viola
05d598ffcf first raw version 2024-10-21 14:06:48 +02:00
15 changed files with 2604 additions and 207 deletions

View File

@@ -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 = "<group>"; };
87B617F025F260140094A1CB /* moc_snodebike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_snodebike.cpp; sourceTree = "<group>"; };
87B617F125F260150094A1CB /* moc_screencapture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_screencapture.cpp; sourceTree = "<group>"; };
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 = "<group>"; };
87BAC3C02BA497350003E925 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
87BAFE472B8CA7AA00065FCD /* moc_focustreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_focustreadmill.cpp; sourceTree = "<group>"; };
@@ -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 = "<group>";
@@ -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 = 933;
CURRENT_PROJECT_VERSION = 939;
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 = 933;
CURRENT_PROJECT_VERSION = 939;
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 = 933;
CURRENT_PROJECT_VERSION = 939;
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 = 933;
CURRENT_PROJECT_VERSION = 939;
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 = 933;
CURRENT_PROJECT_VERSION = 939;
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 = 933;
CURRENT_PROJECT_VERSION = 939;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -15,6 +15,7 @@
#include "keepawakehelper.h"
#endif
#include <chrono>
#include "wheelcircumference.h"
#ifdef Q_OS_IOS
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
@@ -35,6 +36,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,
@@ -83,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);
@@ -130,9 +138,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);
@@ -296,102 +303,51 @@ 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;
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<uint32_t>(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;
#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);
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;
if (!result.isValid()) {
qDebug() << "setGearCommand returned invalid value";
return;
}
gear_value = gear_value * settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble();
jbyteArray array = result.object<jbyteArray>();
QAndroidJniEnvironment env;
jbyte* bytes = env->GetByteArrayElements(array, nullptr);
jsize length = env->GetArrayLength(array);
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);
}
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};
writeCharacteristicZwiftPlay(gearApply, sizeof(gearApply), "gearApply", false, true);
@@ -1053,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<b.length(); i++)
@@ -1076,18 +1031,46 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact
}
b[3] = slope & 0xFF;
b[4] = slope >> 8;
b[4] = slope >> 8;
qDebug() << "applying gears mod" << gears() << slope;
/*} 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;
#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";
}
gear2[4] = g;
writeCharacteristicZwiftPlay(gear2, sizeof(gear2), "gearInclination", false, false);*/
jbyteArray array = result.object<jbyteArray>();
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;
} 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<b.length(); i++)
@@ -1257,3 +1240,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;
}

View File

@@ -26,6 +26,7 @@
#include <QObject>
#include <QString>
#include "wheelcircumference.h"
#include "devices/bike.h"
#ifdef Q_OS_IOS
@@ -73,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,

View File

@@ -32,7 +32,7 @@ wahookickrsnapbike::wahookickrsnapbike(bool noWriteResistance, bool noHeartServi
connect(refresh, &QTimer::timeout, this, &wahookickrsnapbike::update);
QSettings settings;
refresh->start(settings.value(QZSettings::poll_device_time, QZSettings::default_poll_device_time).toInt());
GearTable g;
wheelCircumference::GearTable g;
g.printTable();
}
@@ -197,7 +197,7 @@ void wahookickrsnapbike::update() {
}
QThread::msleep(700);
QByteArray d = setWheelCircumference(gearsToWheelDiameter(gears()));
QByteArray d = setWheelCircumference(wheelCircumference::gearsToWheelDiameter(gears()));
uint8_t e[20];
setGears(settings.value(QZSettings::gears_current_value, QZSettings::default_gears_current_value).toDouble());
memcpy(e, d.constData(), d.length());
@@ -271,7 +271,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, false);
@@ -295,17 +295,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());
}
@@ -872,7 +861,7 @@ bool wahookickrsnapbike::inclinationAvailableByHardware() {
}
double wahookickrsnapbike::maxGears() {
GearTable g;
wheelCircumference::GearTable g;
return g.maxGears;
}

View File

@@ -26,6 +26,7 @@
#include <QObject>
#include <QString>
#include "wheelcircumference.h"
#include "devices/bike.h"
#include "virtualdevices/virtualbike.h"
@@ -113,87 +114,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<GearInfo> gears;
};
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}

View File

@@ -0,0 +1,38 @@
//
// 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 {
var simulation = BLEReceiver_Zwift_SimulationParam()
simulation.inclineX100 = Int32(inclination * 100.0)
var command = BLEReceiver_Zwift_HubCommand()
command.simulation = simulation
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
var command = BLEReceiver_Zwift_HubCommand()
command.physical = physical
let data = try command.serializedData()
var fullData = Data([0x04])
fullData.append(data)
return fullData
}
}

View File

@@ -1,6 +1,8 @@
#ifndef LOCKSCREEN_H
#define LOCKSCREEN_H
#include <QByteArray>
class lockscreen {
public:
void setTimerDisabled();
@@ -78,6 +80,10 @@ class lockscreen {
float zwift_api_getlatitude();
float zwift_api_getlongitude();
// 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);
static const char* get_action_profile();

View File

@@ -341,4 +341,32 @@ float lockscreen::zwift_api_getlatitude() {
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() << "error zwift_hub_inclinationCommand: " << error;
return QByteArray();
} else {
const char* bytes = static_cast<const char*>([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<const char*>([command bytes]);
NSUInteger length = [command length];
return QByteArray(bytes, length);
}
}
#endif

View File

@@ -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 rrr = QAndroidJniObject::callStaticObjectMethod(
"org/cagnulen/qdomyoszwift/ZwiftHub",
"inclinationCommand",
"(D)[B",
8.0);
if(!rrr.isValid()) {
qDebug() << "inclinationCommand returned invalid value";
}
jbyteArray array = rrr.object<jbyteArray>();
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 rr = QAndroidJniObject::callStaticObjectMethod(
"org/cagnulen/qdomyoszwift/ZwiftHub",
"setGearCommand",
"(I)[B",
32608);
if (!rr.isValid()) {
qDebug() << "setGearCommand returned invalid value";
return;
}
jbyteArray array = rr.object<jbyteArray>();
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)

View File

@@ -312,6 +312,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 \
@@ -772,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 \

120
src/wheelcircumference.h Normal file
View File

@@ -0,0 +1,120 @@
#ifndef WHEELCIRCUMFERENCE_H
#define WHEELCIRCUMFERENCE_H
#include <QtCore/qbytearray.h>
#ifndef Q_OS_ANDROID
#include <QtCore/qcoreapplication.h>
#else
#include <QtGui/qguiapplication.h>
#endif
#include <QtCore/qlist.h>
#include <QtCore/qmutex.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimer.h>
#include <QDateTime>
#include <QObject>
#include <QString>
#include <QSettings>
#include <QDebug>
#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<GearInfo> gears;
};
};
#endif // WHEELCIRCUMFERENCE_H