mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 23:41:50 +01:00
Compare commits
42 Commits
Mobi-Rower
...
build-939
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5cfb1c936 | ||
|
|
f737e1be83 | ||
|
|
eafe75e65f | ||
|
|
7c52e9263f | ||
|
|
9bb016f4b9 | ||
|
|
797ae5f102 | ||
|
|
190011a365 | ||
|
|
159f96dd8d | ||
|
|
839213e917 | ||
|
|
e349ca9bad | ||
|
|
9ca3fca817 | ||
|
|
095d8bc1a3 | ||
|
|
07606facba | ||
|
|
bd0e7033c2 | ||
|
|
0337c08252 | ||
|
|
8b60b56dc2 | ||
|
|
f3d8b3964c | ||
|
|
06253998ee | ||
|
|
dbb9ba3510 | ||
|
|
ad802f7d2d | ||
|
|
9cc02dede9 | ||
|
|
82a6afe6b6 | ||
|
|
a7b8e63235 | ||
|
|
d7c3a84adb | ||
|
|
64b9ec9d72 | ||
|
|
379cf8d7de | ||
|
|
2f2989f90d | ||
|
|
6fac9770be | ||
|
|
8d07d7c3f7 | ||
|
|
f85f743499 | ||
|
|
74c37f5624 | ||
|
|
527396eafc | ||
|
|
0f149448b3 | ||
|
|
ed1599ca8e | ||
|
|
89808ae65b | ||
|
|
2da194f073 | ||
|
|
d712621b7b | ||
|
|
1c06260036 | ||
|
|
2d1364497e | ||
|
|
cc7757bfcd | ||
|
|
49c7a96c81 | ||
|
|
05d598ffcf |
@@ -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;
|
||||
|
||||
74
src/android/src/ZwiftHub.java
Normal file
74
src/android/src/ZwiftHub.java
Normal 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;
|
||||
}
|
||||
}
|
||||
162
src/android/src/main/proto/zwift_hub.proto
Normal file
162
src/android/src/main/proto/zwift_hub.proto
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
1838
src/devices/zwifthubbike/Zwift hub.pb.swift
Normal file
1838
src/devices/zwifthubbike/Zwift hub.pb.swift
Normal file
File diff suppressed because it is too large
Load Diff
165
src/devices/zwifthubbike/Zwift hub.proto
Normal file
165
src/devices/zwifthubbike/Zwift hub.proto
Normal 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;
|
||||
}
|
||||
38
src/devices/zwifthubbike/zwifthubbike.swift
Normal file
38
src/devices/zwifthubbike/zwifthubbike.swift
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
44
src/main.cpp
44
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 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)
|
||||
|
||||
@@ -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
120
src/wheelcircumference.h
Normal 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
|
||||
Reference in New Issue
Block a user