mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
54 Commits
Google-And
...
fixing_tra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04d37e0a68 | ||
|
|
ae280e170a | ||
|
|
d2dfb16033 | ||
|
|
64d99748c7 | ||
|
|
16d90a010b | ||
|
|
32baab9072 | ||
|
|
31dd125263 | ||
|
|
483fd87ee5 | ||
|
|
254786ea5d | ||
|
|
c06a439c0c | ||
|
|
7b76999c0d | ||
|
|
5c0190dffe | ||
|
|
aabd2824d3 | ||
|
|
2f7033cd6d | ||
|
|
756fe823f8 | ||
|
|
ed0d163944 | ||
|
|
b7ee025a6f | ||
|
|
23dca8ec93 | ||
|
|
fe05cb613f | ||
|
|
c56c6fe5e4 | ||
|
|
8f7fafa4f2 | ||
|
|
ef9ca0bfc8 | ||
|
|
34635114df | ||
|
|
3e29dd63df | ||
|
|
c575159616 | ||
|
|
ddebfc7e75 | ||
|
|
2b51e5982a | ||
|
|
00b616f4f8 | ||
|
|
38274e1056 | ||
|
|
cf1397cb81 | ||
|
|
b23c1b46ab | ||
|
|
4750ee9214 | ||
|
|
05b39acb3e | ||
|
|
26d2a59ad5 | ||
|
|
1ed382faef | ||
|
|
ebbbd4febb | ||
|
|
95014c3863 | ||
|
|
b61f5752d2 | ||
|
|
4b7533d721 | ||
|
|
6519e9ae86 | ||
|
|
cbc3b9d292 | ||
|
|
1dde627a4c | ||
|
|
06727f23e4 | ||
|
|
1c8279d2fc | ||
|
|
aa0193b41e | ||
|
|
fcf6a8b586 | ||
|
|
574c51bcec | ||
|
|
01fa8602a0 | ||
|
|
b5890ea818 | ||
|
|
83627f5397 | ||
|
|
2b8af5d777 | ||
|
|
3f6b284468 | ||
|
|
1be9e2620d | ||
|
|
805981df4d |
8
.github/workflows/main.yml
vendored
8
.github/workflows/main.yml
vendored
@@ -128,6 +128,7 @@ jobs:
|
||||
run: |
|
||||
cd src
|
||||
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
|
||||
echo "#define PELOTON_SECRET_KEY ${{ secrets.peloton_secret_key }}" > secret.h
|
||||
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
|
||||
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
|
||||
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
|
||||
@@ -304,6 +305,7 @@ jobs:
|
||||
# qmake
|
||||
# cd src
|
||||
# echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
|
||||
# echo "#define PELOTON_SECRET_KEY ${{ secrets.peloton_secret_key }}" > secret.h
|
||||
# echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
|
||||
# echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
|
||||
# echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
|
||||
@@ -606,6 +608,7 @@ jobs:
|
||||
export ANDROID_NDK_ROOT="${ANDROID_NDK}"
|
||||
cd src
|
||||
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
|
||||
echo "#define PELOTON_SECRET_KEY ${{ secrets.peloton_secret_key }}" > secret.h
|
||||
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
|
||||
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
|
||||
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
|
||||
@@ -709,6 +712,7 @@ jobs:
|
||||
run: |
|
||||
cd src
|
||||
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
|
||||
echo "#define PELOTON_SECRET_KEY ${{ secrets.peloton_secret_key }}" > secret.h
|
||||
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
|
||||
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
|
||||
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
|
||||
@@ -819,6 +823,7 @@ jobs:
|
||||
run: |
|
||||
cd src
|
||||
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
|
||||
echo "#define PELOTON_SECRET_KEY ${{ secrets.peloton_secret_key }}" > secret.h
|
||||
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
|
||||
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
|
||||
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
|
||||
@@ -1006,6 +1011,7 @@ jobs:
|
||||
run: |
|
||||
cd src
|
||||
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
|
||||
echo "#define PELOTON_SECRET_KEY ${{ secrets.peloton_secret_key }}" > secret.h
|
||||
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
|
||||
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
|
||||
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
|
||||
@@ -1100,6 +1106,7 @@ jobs:
|
||||
run: |
|
||||
cd src
|
||||
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
|
||||
echo "#define PELOTON_SECRET_KEY ${{ secrets.peloton_secret_key }}" > secret.h
|
||||
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
|
||||
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
|
||||
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
|
||||
@@ -1158,6 +1165,7 @@ jobs:
|
||||
run: |
|
||||
cd src
|
||||
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
|
||||
echo "#define PELOTON_SECRET_KEY ${{ secrets.peloton_secret_key }}" > secret.h
|
||||
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
|
||||
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
|
||||
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
|
||||
|
||||
@@ -413,6 +413,7 @@
|
||||
87958F1B27628D5400124B24 /* moc_elitesterzosmart.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87958F1A27628D5400124B24 /* moc_elitesterzosmart.cpp */; };
|
||||
8798C8872733E103003148B3 /* strydrunpowersensor.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8798C8862733E103003148B3 /* strydrunpowersensor.cpp */; };
|
||||
8798C8892733E10E003148B3 /* moc_strydrunpowersensor.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8798C8882733E10E003148B3 /* moc_strydrunpowersensor.cpp */; };
|
||||
8798FDC52D66075B00CF8EE8 /* OSXBtManagerInternal.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8798FDC32D66075B00CF8EE8 /* OSXBtManagerInternal.mm */; };
|
||||
879A38C8281BD83300F78B2A /* characteristicnotifier2ad9.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 879A38C7281BD83300F78B2A /* characteristicnotifier2ad9.cpp */; };
|
||||
879E5AA8289C057E00FEA38A /* proformwifitreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 879E5AA6289C057E00FEA38A /* proformwifitreadmill.cpp */; };
|
||||
879E5AAA289C05A500FEA38A /* moc_proformwifitreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 879E5AA9289C05A500FEA38A /* moc_proformwifitreadmill.cpp */; };
|
||||
@@ -435,6 +436,8 @@
|
||||
87A18F072660D5C1002D7C96 /* ftmsrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A18F052660D5C0002D7C96 /* ftmsrower.cpp */; };
|
||||
87A18F092660D5D9002D7C96 /* moc_ftmsrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A18F082660D5D9002D7C96 /* moc_ftmsrower.cpp */; };
|
||||
87A2E0222B2B053E00E6168F /* swiftDebug.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A2E0212B2B053E00E6168F /* swiftDebug.mm */; };
|
||||
87A33F1A2D611D8400BFFF29 /* moc_logwriter.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A33F192D611D8400BFFF29 /* moc_logwriter.cpp */; };
|
||||
87A33F1D2D611D9500BFFF29 /* logwriter.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A33F1C2D611D9500BFFF29 /* logwriter.cpp */; };
|
||||
87A3BC222656429600D302E3 /* rower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC1F2656429400D302E3 /* rower.cpp */; };
|
||||
87A3BC232656429600D302E3 /* echelonrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC202656429400D302E3 /* echelonrower.cpp */; };
|
||||
87A3BC26265642A300D302E3 /* moc_rower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC24265642A200D302E3 /* moc_rower.cpp */; };
|
||||
@@ -1375,6 +1378,11 @@
|
||||
8798C8852733E103003148B3 /* strydrunpowersensor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = strydrunpowersensor.h; path = ../src/devices/strydrunpowersensor/strydrunpowersensor.h; sourceTree = "<group>"; };
|
||||
8798C8862733E103003148B3 /* strydrunpowersensor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = strydrunpowersensor.cpp; path = ../src/devices/strydrunpowersensor/strydrunpowersensor.cpp; sourceTree = "<group>"; };
|
||||
8798C8882733E10E003148B3 /* moc_strydrunpowersensor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_strydrunpowersensor.cpp; sourceTree = "<group>"; };
|
||||
8798FDC02D66075B00CF8EE8 /* osxbluetooth_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = osxbluetooth_p.h; path = ../src/ios/BluetoothPatch/osxbluetooth_p.h; sourceTree = SOURCE_ROOT; };
|
||||
8798FDC12D66075B00CF8EE8 /* osxbtcentralmanager_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = osxbtcentralmanager_p.h; path = ../src/ios/BluetoothPatch/osxbtcentralmanager_p.h; sourceTree = SOURCE_ROOT; };
|
||||
8798FDC22D66075B00CF8EE8 /* osxbtgcdtimer_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = osxbtgcdtimer_p.h; path = ../src/ios/BluetoothPatch/osxbtgcdtimer_p.h; sourceTree = SOURCE_ROOT; };
|
||||
8798FDC32D66075B00CF8EE8 /* OSXBtManagerInternal.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = OSXBtManagerInternal.mm; path = ../src/ios/BluetoothPatch/OSXBtManagerInternal.mm; sourceTree = SOURCE_ROOT; };
|
||||
8798FDC42D66075B00CF8EE8 /* osxbtutility_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = osxbtutility_p.h; path = ../src/ios/BluetoothPatch/osxbtutility_p.h; sourceTree = SOURCE_ROOT; };
|
||||
879A38C7281BD83300F78B2A /* characteristicnotifier2ad9.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = characteristicnotifier2ad9.cpp; path = ../src/characteristics/characteristicnotifier2ad9.cpp; sourceTree = "<group>"; };
|
||||
879E5AA6289C057E00FEA38A /* proformwifitreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = proformwifitreadmill.cpp; path = ../src/devices/proformwifitreadmill/proformwifitreadmill.cpp; sourceTree = "<group>"; };
|
||||
879E5AA7289C057E00FEA38A /* proformwifitreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformwifitreadmill.h; path = ../src/devices/proformwifitreadmill/proformwifitreadmill.h; sourceTree = "<group>"; };
|
||||
@@ -1407,6 +1415,9 @@
|
||||
87A18F082660D5D9002D7C96 /* moc_ftmsrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_ftmsrower.cpp; sourceTree = "<group>"; };
|
||||
87A2E0202B2B024200E6168F /* swiftDebug.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = swiftDebug.h; path = ../src/ios/swiftDebug.h; sourceTree = "<group>"; };
|
||||
87A2E0212B2B053E00E6168F /* swiftDebug.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = swiftDebug.mm; path = ../src/ios/swiftDebug.mm; sourceTree = "<group>"; };
|
||||
87A33F192D611D8400BFFF29 /* moc_logwriter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_logwriter.cpp; sourceTree = "<group>"; };
|
||||
87A33F1B2D611D9500BFFF29 /* logwriter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = logwriter.h; path = ../src/logwriter.h; sourceTree = SOURCE_ROOT; };
|
||||
87A33F1C2D611D9500BFFF29 /* logwriter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = logwriter.cpp; path = ../src/logwriter.cpp; sourceTree = SOURCE_ROOT; };
|
||||
87A3BC1E2656429300D302E3 /* echelonrower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = echelonrower.h; path = ../src/devices/echelonrower/echelonrower.h; sourceTree = "<group>"; };
|
||||
87A3BC1F2656429400D302E3 /* rower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = rower.cpp; path = ../src/devices/rower.cpp; sourceTree = "<group>"; };
|
||||
87A3BC202656429400D302E3 /* echelonrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = echelonrower.cpp; path = ../src/devices/echelonrower/echelonrower.cpp; sourceTree = "<group>"; };
|
||||
@@ -2168,6 +2179,14 @@
|
||||
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8798FDC02D66075B00CF8EE8 /* osxbluetooth_p.h */,
|
||||
8798FDC12D66075B00CF8EE8 /* osxbtcentralmanager_p.h */,
|
||||
8798FDC22D66075B00CF8EE8 /* osxbtgcdtimer_p.h */,
|
||||
8798FDC32D66075B00CF8EE8 /* OSXBtManagerInternal.mm */,
|
||||
8798FDC42D66075B00CF8EE8 /* osxbtutility_p.h */,
|
||||
87A33F1B2D611D9500BFFF29 /* logwriter.h */,
|
||||
87A33F1C2D611D9500BFFF29 /* logwriter.cpp */,
|
||||
87A33F192D611D8400BFFF29 /* moc_logwriter.cpp */,
|
||||
87A3DD982D3413790060BAEB /* lifespantreadmill.h */,
|
||||
87A3DD992D3413790060BAEB /* lifespantreadmill.cpp */,
|
||||
87A3DD9A2D3413790060BAEB /* moc_lifespantreadmill.cpp */,
|
||||
@@ -3626,6 +3645,7 @@
|
||||
87C7074327E4CF5900E79C46 /* keepbike.cpp in Compile Sources */,
|
||||
87B871932CE1E94D009B06CA /* zwifthubbike.swift in Compile Sources */,
|
||||
878C9E6928B77E7C00669129 /* nordictrackifitadbbike.cpp in Compile Sources */,
|
||||
87A33F1D2D611D9500BFFF29 /* logwriter.cpp in Compile Sources */,
|
||||
8798C8892733E10E003148B3 /* moc_strydrunpowersensor.cpp in Compile Sources */,
|
||||
8781907E2615089D0085E656 /* peloton.cpp in Compile Sources */,
|
||||
2B800DC34C91D8B080DEFBE8 /* fit_mesg_with_event_broadcaster.cpp in Compile Sources */,
|
||||
@@ -3749,6 +3769,7 @@
|
||||
872261F0289EA887006A6F75 /* moc_nordictrackelliptical.cpp in Compile Sources */,
|
||||
873824E327E647A8004F1B46 /* bitmap.cpp in Compile Sources */,
|
||||
87FE5BB12692F31E0056EFC8 /* moc_tacxneo2.cpp in Compile Sources */,
|
||||
8798FDC52D66075B00CF8EE8 /* OSXBtManagerInternal.mm in Compile Sources */,
|
||||
873824E827E647A8004F1B46 /* provider.cpp in Compile Sources */,
|
||||
8791A8AA25C8603F003B50B2 /* moc_inspirebike.cpp in Compile Sources */,
|
||||
03F49BBCF19B73B18385B13D /* FitMesgDefinition.mm in Compile Sources */,
|
||||
@@ -3800,6 +3821,7 @@
|
||||
E62DA5FF2436135448C94671 /* moc_toorxtreadmill.cpp in Compile Sources */,
|
||||
87586A4325B8341B00A243C4 /* moc_proformbike.cpp in Compile Sources */,
|
||||
87CC3BA325A0885F001EC5A8 /* domyoselliptical.cpp in Compile Sources */,
|
||||
87A33F1A2D611D8400BFFF29 /* moc_logwriter.cpp in Compile Sources */,
|
||||
87D105542909971100B3935B /* moc_mepanelbike.cpp in Compile Sources */,
|
||||
87B617F325F260150094A1CB /* moc_snodebike.cpp in Compile Sources */,
|
||||
87DA8467284933DE00B550E9 /* moc_fakeelliptical.cpp in Compile Sources */,
|
||||
@@ -4233,7 +4255,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1023;
|
||||
CURRENT_PROJECT_VERSION = 1035;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1";
|
||||
@@ -4427,7 +4449,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1023;
|
||||
CURRENT_PROJECT_VERSION = 1035;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -4657,7 +4679,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1023;
|
||||
CURRENT_PROJECT_VERSION = 1035;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -4753,7 +4775,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1023;
|
||||
CURRENT_PROJECT_VERSION = 1035;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
@@ -4845,7 +4867,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1023;
|
||||
CURRENT_PROJECT_VERSION = 1035;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -4961,7 +4983,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1023;
|
||||
CURRENT_PROJECT_VERSION = 1035;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
|
||||
26
src/OAuth2.h
Normal file
26
src/OAuth2.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef OAUTH2_H
|
||||
#define OAUTH2_H
|
||||
|
||||
#include <QString>
|
||||
#include <QTextStream>
|
||||
|
||||
struct OAuth2Parameter {
|
||||
QString responseType = QStringLiteral("code");
|
||||
QString approval_prompt = QStringLiteral("force");
|
||||
|
||||
inline bool isEmpty() const { return responseType.isEmpty() && approval_prompt.isEmpty(); }
|
||||
|
||||
QString toString() const {
|
||||
QString msg;
|
||||
QTextStream out(&msg);
|
||||
out << QStringLiteral("OAuth2Parameter{\n") << QStringLiteral("responseType: ") << this->responseType
|
||||
<< QStringLiteral("\n") << QStringLiteral("approval_prompt: ") << this->approval_prompt
|
||||
<< QStringLiteral("\n");
|
||||
return msg;
|
||||
}
|
||||
};
|
||||
|
||||
#define _STR(x) #x
|
||||
#define STRINGIFY(x) _STR(x)
|
||||
|
||||
#endif // OAUTH2_H
|
||||
58
src/WebPelotonAuth.qml
Normal file
58
src/WebPelotonAuth.qml
Normal file
@@ -0,0 +1,58 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.5
|
||||
import QtQuick.Controls.Material 2.12
|
||||
import QtQuick.Dialogs 1.0
|
||||
import QtGraphicalEffects 1.12
|
||||
import Qt.labs.settings 1.0
|
||||
import QtMultimedia 5.15
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtWebView 1.1
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
visible: true
|
||||
|
||||
WebView {
|
||||
anchors.fill: parent
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
visible: !rootItem.pelotonPopupVisible
|
||||
url: rootItem.getPelotonAuthUrl
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: popupPelotonConnectedWeb
|
||||
parent: Overlay.overlay
|
||||
enabled: rootItem.pelotonPopupVisible
|
||||
onEnabledChanged: { if(rootItem.pelotonPopupVisible) popupPelotonConnectedWeb.open() }
|
||||
onClosed: { rootItem.pelotonPopupVisible = false; }
|
||||
|
||||
x: Math.round((parent.width - width) / 2)
|
||||
y: Math.round((parent.height - height) / 2)
|
||||
width: 380
|
||||
height: 120
|
||||
modal: true
|
||||
focus: true
|
||||
palette.text: "white"
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
enter: Transition
|
||||
{
|
||||
NumberAnimation { property: "opacity"; from: 0.0; to: 1.0 }
|
||||
}
|
||||
exit: Transition
|
||||
{
|
||||
NumberAnimation { property: "opacity"; from: 1.0; to: 0.0 }
|
||||
}
|
||||
Column {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
Label {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: 370
|
||||
height: 120
|
||||
text: qsTr("Your Peloton account is now connected!<br><br>Restart the app to apply this!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.18.18" android:versionCode="1014" android:installLocation="auto">
|
||||
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.18.20" android:versionCode="1020" android:installLocation="auto">
|
||||
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
|
||||
Remove the comment if you do not require these default permissions. -->
|
||||
<!-- %%INSERT_PERMISSIONS -->
|
||||
|
||||
@@ -972,7 +972,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit searchingStop();
|
||||
}
|
||||
this->signalBluetoothDeviceConnected(trxappgateusbRower);
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("FAL-SPORTS")) ||
|
||||
} else if (((b.name().toUpper().startsWith(QStringLiteral("FAL-SPORTS")) && !toorx_bike) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")) && iconsole_elliptical)) &&
|
||||
!trxappgateusbElliptical && ftms_bike.contains(QZSettings::default_ftms_bike) && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
@@ -990,7 +990,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit searchingStop();
|
||||
}
|
||||
this->signalBluetoothDeviceConnected(trxappgateusbElliptical);
|
||||
} else if (b.name().startsWith(QStringLiteral("Domyos-EL")) &&
|
||||
} else if (b.name().startsWith(QStringLiteral("Domyos-EL")) && !settings.value(QZSettings::domyos_elliptical_fmts, QZSettings::default_domyos_elliptical_fmts).toBool() &&
|
||||
!b.name().startsWith(QStringLiteral("DomyosBridge")) && !domyosElliptical && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
@@ -1010,6 +1010,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("YPOO-U3-")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("SCH_590E")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("KETTLER ")) ||
|
||||
(b.name().startsWith(QStringLiteral("Domyos-EL")) && settings.value(QZSettings::domyos_elliptical_fmts, QZSettings::default_domyos_elliptical_fmts).toBool()) ||
|
||||
(b.name().toUpper().startsWith("SF-") && b.name().midRef(3).toInt() > 0) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("MYELLIPTICAL ")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("CARDIOPOWER EEGO")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("E35")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
|
||||
@@ -1396,13 +1398,12 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.name().toUpper().startsWith(QStringLiteral("BFX_T9_")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("AB300S-")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("TF04-")) || // Sport Synology Z5 Treadmill #2415
|
||||
b.name().toUpper().startsWith(QStringLiteral("FIT-")) || // FIT-1596
|
||||
(b.name().toUpper().startsWith(QStringLiteral("FIT-")) && !b.name().toUpper().startsWith(QStringLiteral("FIT-BK-"))) || // FIT-1596 and sports tech f37s treadmill #2412
|
||||
b.name().toUpper().startsWith(QStringLiteral("LJJ-")) || // LJJ-02351A
|
||||
b.name().toUpper().startsWith(QStringLiteral("WLT-EP-")) || // Flow elliptical
|
||||
(b.name().toUpper().startsWith("SCHWINN 810")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("KS-MC")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("KS-HD-Z1D"))) || // Kingsmith WalkingPad Z1
|
||||
(b.name().toUpper().startsWith(QStringLiteral("FIT-")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // sports tech f37s treadmill #2412
|
||||
(b.name().toUpper().startsWith(QStringLiteral("NOBLEPRO CONNECT")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // FTMS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("TT8")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("ST90")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
|
||||
@@ -1519,6 +1520,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.address() == QBluetoothAddress("C1:14:D9:9C:FB:01") || // specific TACX NEO 2 #1707
|
||||
(b.name().toUpper().startsWith("TACX SMART BIKE"))) &&
|
||||
!b.name().toUpper().startsWith("TACX SATORI") &&
|
||||
ftms_bike.contains(QZSettings::default_ftms_bike) &&
|
||||
!tacxneo2Bike && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
@@ -1641,6 +1643,12 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
(b.name().toUpper().startsWith("AVANTI")) ||
|
||||
(b.name().toUpper().startsWith("T300P_")) ||
|
||||
(b.name().toUpper().startsWith("T200_")) ||
|
||||
(b.name().toUpper().startsWith("BZ9110 ")) ||
|
||||
(b.name().toUpper().startsWith("TITAN 7000")) ||
|
||||
(b.name().toUpper().startsWith("LYDSTO")) ||
|
||||
(b.name().toUpper().startsWith("CYCLO_")) ||
|
||||
(b.name().toUpper().startsWith("LCR")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("FIT-BK-"))) ||
|
||||
(b.name().toUpper().startsWith("VFSPINBIKE")) ||
|
||||
(b.name().toUpper().startsWith("GLT") && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
|
||||
(b.name().toUpper().startsWith("SPORT01-") && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // Labgrey Magnetic Exercise Bike https://www.amazon.co.uk/dp/B0CXMF1NPY?_encoding=UTF8&psc=1&ref=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&ref_=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&social_share=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&skipTwisterOG=1
|
||||
@@ -1724,6 +1732,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
(b.name().toUpper().startsWith("RACER S")) ||
|
||||
((b.name().toUpper().startsWith("KU")) && b.name().length() == 2) ||
|
||||
(b.name().toUpper().startsWith("ELITETRAINER")) ||
|
||||
(b.name().toUpper().startsWith("TOUR 600")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("QD")) && b.name().length() == 2) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("DFC")) && b.name().length() == 3) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("ASSIOMA")) &&
|
||||
@@ -2076,7 +2085,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
// connect(proformBike, SIGNAL(inclinationChanged(double)), this, SLOT(inclinationChanged(double)));
|
||||
proformBike->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(proformBike);
|
||||
} else if ((b.name().startsWith(QStringLiteral("I_TL")) || b.name().startsWith(QStringLiteral("I_IT"))) &&
|
||||
} else if ((b.name().startsWith(QStringLiteral("I_TL")) || b.name().startsWith(QStringLiteral("I_IT")) ||
|
||||
b.name().toUpper().contains(QStringLiteral("_IFIT TREADMILL"))) &&
|
||||
!proformTreadmill && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
@@ -2220,6 +2230,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if ((b.name().startsWith(QStringLiteral("TRX ROUTE KEY")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("MASTERT40-")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("BH DUALKIT TREAD")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("TF-T")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("BH-TR-"))) && !toorx && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
@@ -2318,6 +2329,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.name().toUpper().startsWith(QStringLiteral("VIFHTR2.1")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("REEBOK"))) ||
|
||||
b.name().toUpper().contains(QStringLiteral("CR011R")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("FAL-SPORTS")) && toorx_bike) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("DKN MOTION"))) &&
|
||||
(toorx_bike))) &&
|
||||
!trxappgateusb && !toorx_ftms && !toorx_ftms_treadmill && !trxappgateusbBike && filter && !iconsole_elliptical && !iconsole_rower) {
|
||||
@@ -2407,7 +2419,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
pafersBike->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(pafersBike);
|
||||
} else if (((b.name().startsWith(QStringLiteral("FS-")) && snode_bike) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("TF-")) &&
|
||||
(b.name().toUpper().startsWith(QStringLiteral("TF-")) && !b.name().toUpper().startsWith(QStringLiteral("TF-T")) &&
|
||||
!horizon_treadmill_force_ftms)) && // TF-769DF2
|
||||
!snodeBike &&
|
||||
!ftmsBike && !fitPlusBike && filter) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "keepawakehelper.h"
|
||||
#endif
|
||||
#include "homeform.h"
|
||||
#include "virtualdevices/virtualbike.h"
|
||||
#include "virtualdevices/virtualtreadmill.h"
|
||||
#include <QBluetoothLocalDevice>
|
||||
@@ -513,6 +514,14 @@ void domyoselliptical::serviceScanDone(void) {
|
||||
|
||||
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &domyoselliptical::stateChanged);
|
||||
if(!gattCommunicationChannelService) {
|
||||
QSettings settings;
|
||||
settings.setValue(QZSettings::domyos_elliptical_fmts, true);
|
||||
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested("Domyos Elliptial it's a FTMS. Restart QZ to apply the fix, thanks.");
|
||||
return;
|
||||
}
|
||||
gattCommunicationChannelService->discoverDetails();
|
||||
}
|
||||
|
||||
|
||||
@@ -131,8 +131,12 @@ void ftmsbike::init() {
|
||||
|
||||
uint8_t write[] = {FTMS_REQUEST_CONTROL};
|
||||
bool ret = writeCharacteristic(write, sizeof(write), "requestControl", false, true);
|
||||
write[0] = {FTMS_START_RESUME};
|
||||
ret = writeCharacteristic(write, sizeof(write), "start simulation", false, true);
|
||||
if(resistance_lvl_mode && DIRETO_XR) {
|
||||
setWheelDiameter(2070.0);
|
||||
} else {
|
||||
write[0] = {FTMS_START_RESUME};
|
||||
ret = writeCharacteristic(write, sizeof(write), "start simulation", false, true);
|
||||
}
|
||||
|
||||
if(ret) {
|
||||
initDone = true;
|
||||
@@ -140,6 +144,13 @@ void ftmsbike::init() {
|
||||
}
|
||||
}
|
||||
|
||||
ftmsbike::~ftmsbike() {
|
||||
// Set wheel circumference back to 2070 when object is destroyed
|
||||
if (DIRETO_XR) {
|
||||
setWheelDiameter(2070.0);
|
||||
}
|
||||
}
|
||||
|
||||
void ftmsbike::zwiftPlayInit() {
|
||||
QSettings settings;
|
||||
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
|
||||
@@ -240,7 +251,7 @@ void ftmsbike::forceResistance(resistance_t requestResistance) {
|
||||
writeCharacteristic(write, sizeof(write),
|
||||
QStringLiteral("forceResistance ") + QString::number(requestResistance));
|
||||
} else {
|
||||
if(JFBK5_0) {
|
||||
if(JFBK5_0 || DIRETO_XR) {
|
||||
uint8_t write[] = {FTMS_SET_TARGET_RESISTANCE_LEVEL, 0x00, 0x00};
|
||||
write[1] = ((uint16_t)requestResistance * 10) & 0xFF;
|
||||
write[2] = ((uint16_t)requestResistance * 10) >> 8;
|
||||
@@ -310,8 +321,11 @@ void ftmsbike::update() {
|
||||
|
||||
if(DIRETO_XR && gears_zwift_ratio)
|
||||
setWheelDiameter(wheelCircumference::gearsToWheelDiameter(gears()));
|
||||
else
|
||||
else {
|
||||
forceResistance(requestResistance + (gears() * 5));
|
||||
if(DIRETO_XR)
|
||||
Resistance = requestResistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
requestResistance = -1;
|
||||
@@ -420,6 +434,8 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
bool disable_hr_frommachinery =
|
||||
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
|
||||
bool heart = false;
|
||||
bool watt_ignore_builtin =
|
||||
settings.value(QZSettings::watt_ignore_builtin, QZSettings::default_watt_ignore_builtin).toBool();
|
||||
|
||||
qDebug() << characteristic.uuid() << newValue.length() << QStringLiteral(" << ") << newValue.toHex(' ');
|
||||
|
||||
@@ -602,6 +618,10 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
// power table from an user
|
||||
if(DU30_bike) {
|
||||
m_watt = wattsFromResistance(Resistance.value());
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
|
||||
} else if (LYDSTO && watt_ignore_builtin) {
|
||||
m_watt = wattFromHR(true);
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
|
||||
} else if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
|
||||
.toString()
|
||||
.startsWith(QStringLiteral("Disabled")))
|
||||
@@ -1275,6 +1295,12 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if ((bluetoothDevice.name().toUpper().startsWith("SMB1"))) {
|
||||
qDebug() << QStringLiteral("SMB1 found");
|
||||
SMB1 = true;
|
||||
} else if ((bluetoothDevice.name().toUpper().startsWith("SPAX-BK-"))) {
|
||||
qDebug() << QStringLiteral("SPAX-BK found");
|
||||
resistance_lvl_mode = true;
|
||||
} else if ((bluetoothDevice.name().toUpper().startsWith("LYDSTO"))) {
|
||||
qDebug() << QStringLiteral("LYDSTO found");
|
||||
LYDSTO = true;
|
||||
}
|
||||
|
||||
if(settings.value(QZSettings::force_resistance_instead_inclination, QZSettings::default_force_resistance_instead_inclination).toBool()) {
|
||||
|
||||
@@ -70,6 +70,7 @@ class ftmsbike : public bike {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ftmsbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset, double bikeResistanceGain);
|
||||
~ftmsbike();
|
||||
bool connected() override;
|
||||
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
|
||||
resistance_t maxResistance() override { return max_resistance; }
|
||||
@@ -135,6 +136,7 @@ class ftmsbike : public bike {
|
||||
bool JFBK5_0 = false;
|
||||
bool BIKE_ = false;
|
||||
bool SMB1 = false;
|
||||
bool LYDSTO = false;
|
||||
|
||||
uint8_t battery_level = 0;
|
||||
|
||||
|
||||
@@ -396,6 +396,7 @@ void ftmsrower::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
}
|
||||
}
|
||||
|
||||
notificationSubscribed = 0;
|
||||
qDebug() << QStringLiteral("all services discovered!");
|
||||
|
||||
for (QLowEnergyService *s : qAsConst(gattCommunicationChannelService)) {
|
||||
@@ -436,6 +437,7 @@ void ftmsrower::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
descriptor.append((char)0x01);
|
||||
descriptor.append((char)0x00);
|
||||
if (c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).isValid()) {
|
||||
notificationSubscribed++;
|
||||
s->writeDescriptor(c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
|
||||
} else {
|
||||
qDebug() << QStringLiteral("ClientCharacteristicConfiguration") << c.uuid()
|
||||
@@ -451,6 +453,7 @@ void ftmsrower::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
descriptor.append((char)0x02);
|
||||
descriptor.append((char)0x00);
|
||||
if (c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).isValid()) {
|
||||
notificationSubscribed++;
|
||||
s->writeDescriptor(c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
|
||||
} else {
|
||||
qDebug() << QStringLiteral("ClientCharacteristicConfiguration") << c.uuid()
|
||||
@@ -529,8 +532,15 @@ void ftmsrower::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
void ftmsrower::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
|
||||
emit debug(QStringLiteral("descriptorWritten ") + descriptor.name() + " " + newValue.toHex(' '));
|
||||
|
||||
initRequest = true;
|
||||
emit connectedAndDiscovered();
|
||||
if (notificationSubscribed)
|
||||
notificationSubscribed--;
|
||||
|
||||
qDebug() << "notificationSubscribed=" << notificationSubscribed;
|
||||
|
||||
if (!notificationSubscribed) {
|
||||
initRequest = true;
|
||||
emit connectedAndDiscovered();
|
||||
}
|
||||
}
|
||||
|
||||
void ftmsrower::descriptorRead(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
|
||||
|
||||
@@ -57,6 +57,7 @@ class ftmsrower : public rower {
|
||||
QByteArray lastPacket;
|
||||
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
uint8_t firstStateChanged = 0;
|
||||
volatile int notificationSubscribed = 0;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
@@ -182,6 +182,10 @@ void horizontreadmill::btinit() {
|
||||
QStringLiteral("init"), false, true);
|
||||
waitForAPacket();
|
||||
|
||||
if(homeform::singleton()) {
|
||||
homeform::singleton()->setToastRequested("Treadmill initialization in progress...0%");
|
||||
}
|
||||
|
||||
init1:
|
||||
initPacketRecv = false;
|
||||
|
||||
@@ -271,6 +275,10 @@ void horizontreadmill::btinit() {
|
||||
goto init1;
|
||||
}
|
||||
|
||||
if(homeform::singleton()) {
|
||||
homeform::singleton()->setToastRequested("Treadmill initialization in progress...15%");
|
||||
}
|
||||
|
||||
init2:
|
||||
initPacketRecv = false;
|
||||
|
||||
@@ -357,6 +365,10 @@ void horizontreadmill::btinit() {
|
||||
goto init2;
|
||||
}
|
||||
|
||||
if(homeform::singleton()) {
|
||||
homeform::singleton()->setToastRequested("Treadmill initialization in progress...25%");
|
||||
}
|
||||
|
||||
init3:
|
||||
initPacketRecv = false;
|
||||
|
||||
@@ -443,6 +455,10 @@ void horizontreadmill::btinit() {
|
||||
goto init3;
|
||||
}
|
||||
|
||||
if(homeform::singleton()) {
|
||||
homeform::singleton()->setToastRequested("Treadmill initialization in progress...35%");
|
||||
}
|
||||
|
||||
init4:
|
||||
initPacketRecv = false;
|
||||
|
||||
@@ -529,6 +545,10 @@ void horizontreadmill::btinit() {
|
||||
goto init4;
|
||||
}
|
||||
|
||||
if(homeform::singleton()) {
|
||||
homeform::singleton()->setToastRequested("Treadmill initialization in progress...50%");
|
||||
}
|
||||
|
||||
init5:
|
||||
initPacketRecv = false;
|
||||
|
||||
@@ -615,6 +635,10 @@ void horizontreadmill::btinit() {
|
||||
goto init5;
|
||||
}
|
||||
|
||||
if(homeform::singleton()) {
|
||||
homeform::singleton()->setToastRequested("Treadmill initialization in progress...65%");
|
||||
}
|
||||
|
||||
init6:
|
||||
initPacketRecv = false;
|
||||
|
||||
@@ -701,6 +725,10 @@ void horizontreadmill::btinit() {
|
||||
goto init6;
|
||||
}
|
||||
|
||||
if(homeform::singleton()) {
|
||||
homeform::singleton()->setToastRequested("Treadmill initialization in progress...80%");
|
||||
}
|
||||
|
||||
init7:
|
||||
initPacketRecv = false;
|
||||
|
||||
@@ -787,6 +815,10 @@ void horizontreadmill::btinit() {
|
||||
goto init7;
|
||||
}
|
||||
|
||||
if(homeform::singleton()) {
|
||||
homeform::singleton()->setToastRequested("Treadmill initialization in progress...90%");
|
||||
}
|
||||
|
||||
init8:
|
||||
initPacketRecv = false;
|
||||
|
||||
@@ -812,6 +844,10 @@ void horizontreadmill::btinit() {
|
||||
waitForAPacket();
|
||||
goto init8;
|
||||
}
|
||||
|
||||
if(homeform::singleton()) {
|
||||
homeform::singleton()->setToastRequested("Treadmill initialization completed!");
|
||||
}
|
||||
}
|
||||
messageID = 0x10;
|
||||
}
|
||||
@@ -2450,6 +2486,9 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("MX-TM "))) {
|
||||
qDebug() << QStringLiteral("MX-TM found");
|
||||
MX_TM = true;
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("FIT-"))) {
|
||||
qDebug() << QStringLiteral("FIT- found");
|
||||
FIT = true;
|
||||
}
|
||||
|
||||
if (device.name().toUpper().startsWith(QStringLiteral("TRX3500"))) {
|
||||
@@ -3174,7 +3213,7 @@ void horizontreadmill::testProfileCRC() {
|
||||
double horizontreadmill::minStepInclination() {
|
||||
QSettings settings;
|
||||
bool toorx_ftms_treadmill = settings.value(QZSettings::toorx_ftms_treadmill, QZSettings::default_toorx_ftms_treadmill).toBool();
|
||||
if (kettler_treadmill || trx3500_treadmill || toorx_ftms_treadmill || sole_tt8_treadmill || ICONCEPT_FTMS_treadmill || SW_TREADMILL || sole_s77_treadmill)
|
||||
if (kettler_treadmill || trx3500_treadmill || toorx_ftms_treadmill || sole_tt8_treadmill || ICONCEPT_FTMS_treadmill || SW_TREADMILL || sole_s77_treadmill || FIT)
|
||||
return 1.0;
|
||||
else
|
||||
return 0.5;
|
||||
|
||||
@@ -107,6 +107,7 @@ class horizontreadmill : public treadmill {
|
||||
bool BOWFLEX_T9 = false;
|
||||
bool YPOO_MINI_PRO = false;
|
||||
bool MX_TM = false;
|
||||
bool FIT = false;
|
||||
|
||||
void testProfileCRC();
|
||||
void updateProfileCRC();
|
||||
|
||||
@@ -268,7 +268,7 @@ void nordictrackifitadbtreadmill::processPendingDatagrams() {
|
||||
requestInclination = -100;
|
||||
}
|
||||
|
||||
int currentRequestInclination = requestInclination;
|
||||
double currentRequestInclination = requestInclination;
|
||||
|
||||
// since the motor of the treadmill is slow, let's filter the inclination changes to more than 1 second
|
||||
if (requestInclination != -100 && lastInclinationChanged.secsTo(QDateTime::currentDateTime()) > 1) {
|
||||
|
||||
@@ -188,6 +188,89 @@ void proformbike::forceResistance(resistance_t requestResistance) {
|
||||
writeCharacteristic((uint8_t *)res1, sizeof(res1), QStringLiteral("resistance1"), false, false);
|
||||
writeCharacteristic((uint8_t *)res2, sizeof(res2), QStringLiteral("resistance2"), false, false);
|
||||
writeCharacteristic((uint8_t *)res3, sizeof(res3), QStringLiteral("resistance3"), false, true);
|
||||
} else if (proform_xbike) {
|
||||
const uint8_t res1[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x6c, 0x07, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt994
|
||||
const uint8_t res2[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x54, 0x0b, 0x00, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt1325
|
||||
const uint8_t res3[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x3c, 0x0f, 0x00, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt1636
|
||||
const uint8_t res4[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x24, 0x13, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt1786
|
||||
const uint8_t res5[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x0c, 0x17, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt1905
|
||||
const uint8_t res6[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0xf4, 0x1a, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt2015
|
||||
const uint8_t res7[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0xdc, 0x1e, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt2134
|
||||
const uint8_t res8[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0xc4, 0x22, 0x00, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt2209
|
||||
const uint8_t res9[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0xac, 0x26, 0x00, 0xe9, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt2293
|
||||
const uint8_t res10[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0xdc, 0x1e, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt3007
|
||||
const uint8_t res11[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x0c, 0x17, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt3502
|
||||
const uint8_t res12[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x24, 0x13, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt3578
|
||||
const uint8_t res13[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x3c, 0x0f, 0x00, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt3666
|
||||
const uint8_t res14[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x54, 0x0b, 0x00, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt3740
|
||||
const uint8_t res15[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x6c, 0x07, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt3824
|
||||
const uint8_t res16[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x84, 0x03, 0x00, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt3920
|
||||
const uint8_t res17[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x02, 0x00, 0x10, 0x04, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt4208
|
||||
const uint8_t res18[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x02, 0x00, 0x10, 0x01, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt4227
|
||||
const uint8_t res19[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x02, 0x00, 0x10, 0x03, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt4235
|
||||
|
||||
uint8_t noOpData7[] = {0xfe, 0x02, 0x0d, 0x02};
|
||||
writeCharacteristic((uint8_t *)noOpData7, sizeof(noOpData7), QStringLiteral("resrequest"), false, false);
|
||||
|
||||
switch (requestResistance) {
|
||||
case 1:
|
||||
writeCharacteristic((uint8_t *)res1, sizeof(res1), QStringLiteral("resistance1"), false, true);
|
||||
break;
|
||||
case 2:
|
||||
writeCharacteristic((uint8_t *)res2, sizeof(res2), QStringLiteral("resistance2"), false, true);
|
||||
break;
|
||||
case 3:
|
||||
writeCharacteristic((uint8_t *)res3, sizeof(res3), QStringLiteral("resistance3"), false, true);
|
||||
break;
|
||||
case 4:
|
||||
writeCharacteristic((uint8_t *)res4, sizeof(res4), QStringLiteral("resistance4"), false, true);
|
||||
break;
|
||||
case 5:
|
||||
writeCharacteristic((uint8_t *)res5, sizeof(res5), QStringLiteral("resistance5"), false, true);
|
||||
break;
|
||||
case 6:
|
||||
writeCharacteristic((uint8_t *)res6, sizeof(res6), QStringLiteral("resistance6"), false, true);
|
||||
break;
|
||||
case 7:
|
||||
writeCharacteristic((uint8_t *)res7, sizeof(res7), QStringLiteral("resistance7"), false, true);
|
||||
break;
|
||||
case 8:
|
||||
writeCharacteristic((uint8_t *)res8, sizeof(res8), QStringLiteral("resistance8"), false, true);
|
||||
break;
|
||||
case 9:
|
||||
writeCharacteristic((uint8_t *)res9, sizeof(res9), QStringLiteral("resistance9"), false, true);
|
||||
break;
|
||||
case 10:
|
||||
writeCharacteristic((uint8_t *)res10, sizeof(res10), QStringLiteral("resistance10"), false, true);
|
||||
break;
|
||||
case 11:
|
||||
writeCharacteristic((uint8_t *)res11, sizeof(res11), QStringLiteral("resistance11"), false, true);
|
||||
break;
|
||||
case 12:
|
||||
writeCharacteristic((uint8_t *)res12, sizeof(res12), QStringLiteral("resistance12"), false, true);
|
||||
break;
|
||||
case 13:
|
||||
writeCharacteristic((uint8_t *)res13, sizeof(res13), QStringLiteral("resistance13"), false, true);
|
||||
break;
|
||||
case 14:
|
||||
writeCharacteristic((uint8_t *)res14, sizeof(res14), QStringLiteral("resistance14"), false, true);
|
||||
break;
|
||||
case 15:
|
||||
writeCharacteristic((uint8_t *)res15, sizeof(res15), QStringLiteral("resistance15"), false, true);
|
||||
break;
|
||||
case 16:
|
||||
writeCharacteristic((uint8_t *)res16, sizeof(res16), QStringLiteral("resistance16"), false, true);
|
||||
break;
|
||||
case 17:
|
||||
writeCharacteristic((uint8_t *)res17, sizeof(res17), QStringLiteral("resistance17"), false, true);
|
||||
break;
|
||||
case 18:
|
||||
writeCharacteristic((uint8_t *)res18, sizeof(res18), QStringLiteral("resistance18"), false, true);
|
||||
break;
|
||||
case 19:
|
||||
writeCharacteristic((uint8_t *)res19, sizeof(res19), QStringLiteral("resistance19"), false, true);
|
||||
break;
|
||||
}
|
||||
} else if (proform_hybrid_trainer_PFEL03815 || proform_bike_sb) {
|
||||
const uint8_t res1[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01,
|
||||
0x04, 0x32, 0x02, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
@@ -820,9 +903,18 @@ void proformbike::update() {
|
||||
uint8_t noOpData3_proform_bike_325_csx[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x85, 0xb9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t noOpData5_proform_bike_325_csx[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x00, 0x03, 0x80, 0x00, 0x40, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
// proform_xbike
|
||||
uint8_t noOpData2_proform_xbike[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x00,
|
||||
0x0d, 0x3c, 0x9c, 0x31, 0x00, 0x00, 0x40, 0x40, 0x00, 0x80};
|
||||
uint8_t noOpData3_proform_xbike[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x81, 0xb3, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t noOpData5_proform_xbike[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x00,
|
||||
0x03, 0x80, 0x00, 0x40, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
|
||||
switch (counterPoll) {
|
||||
case 0:
|
||||
if (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci || proform_hybrid_trainer_PFEL03815 || proform_bike_sb || proform_bike_225_csx || proform_bike_325_csx) {
|
||||
if (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci || proform_hybrid_trainer_PFEL03815 || proform_bike_sb || proform_bike_225_csx || proform_bike_325_csx || proform_xbike) {
|
||||
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"));
|
||||
} else if(proform_bike_PFEVEX71316_0) {
|
||||
writeCharacteristic(noOpData1_proform_bike_PFEVEX71316_0, sizeof(noOpData1_proform_bike_PFEVEX71316_0), QStringLiteral("noOp"));
|
||||
@@ -831,7 +923,9 @@ void proformbike::update() {
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (proform_studio || proform_tdf_10)
|
||||
if (proform_xbike) {
|
||||
writeCharacteristic(noOpData2_proform_xbike, sizeof(noOpData2_proform_xbike), QStringLiteral("noOp"));
|
||||
} else if (proform_studio || proform_tdf_10)
|
||||
writeCharacteristic(noOpData2_proform_studio, sizeof(noOpData2_proform_studio), QStringLiteral("noOp"));
|
||||
else if (proform_bike_325_csx) {
|
||||
writeCharacteristic(noOpData2_proform_bike_325_csx, sizeof(noOpData2_proform_bike_325_csx),
|
||||
@@ -863,7 +957,9 @@ void proformbike::update() {
|
||||
writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("noOp"));
|
||||
break;
|
||||
case 2:
|
||||
if (proform_studio || proform_tdf_10)
|
||||
if (proform_xbike) {
|
||||
writeCharacteristic(noOpData3_proform_xbike, sizeof(noOpData3_proform_xbike), QStringLiteral("noOp"));
|
||||
} else if (proform_studio || proform_tdf_10)
|
||||
writeCharacteristic(noOpData3_proform_studio, sizeof(noOpData3_proform_studio), QStringLiteral("noOp"));
|
||||
else if (proform_bike_325_csx) {
|
||||
writeCharacteristic(noOpData3_proform_bike_325_csx, sizeof(noOpData3_proform_bike_325_csx),
|
||||
@@ -895,7 +991,10 @@ void proformbike::update() {
|
||||
writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("noOp"));
|
||||
break;
|
||||
case 3:
|
||||
if (proform_studio || proform_tdf_10)
|
||||
if (proform_xbike) {
|
||||
innerWriteResistance();
|
||||
writeCharacteristic(noOpData7, sizeof(noOpData7), QStringLiteral("noOp"));
|
||||
} else if (proform_studio || proform_tdf_10)
|
||||
writeCharacteristic(noOpData4_proform_studio, sizeof(noOpData4_proform_studio), QStringLiteral("noOp"));
|
||||
else if (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci) {
|
||||
innerWriteResistance();
|
||||
@@ -915,7 +1014,9 @@ void proformbike::update() {
|
||||
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"));
|
||||
break;
|
||||
case 4:
|
||||
if (proform_studio || proform_tdf_10)
|
||||
if (proform_xbike) {
|
||||
writeCharacteristic(noOpData5_proform_xbike, sizeof(noOpData5_proform_xbike), QStringLiteral("noOp"));
|
||||
} else if (proform_studio || proform_tdf_10)
|
||||
writeCharacteristic(noOpData5_proform_studio, sizeof(noOpData5_proform_studio), QStringLiteral("noOp"));
|
||||
else if (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci) {
|
||||
writeCharacteristic(noOpData5_nordictrack_gx_2_7, sizeof(noOpData5_nordictrack_gx_2_7),
|
||||
@@ -948,7 +1049,19 @@ void proformbike::update() {
|
||||
QStringLiteral("noOp"));
|
||||
} else if(proform_bike_PFEVEX71316_0) {
|
||||
writeCharacteristic(noOpData6_proform_bike_PFEVEX71316_0, sizeof(noOpData6_proform_bike_PFEVEX71316_0), QStringLiteral("noOp"), false, true);
|
||||
innerWriteResistance();
|
||||
if (requestResistance != -1)
|
||||
innerWriteResistance();
|
||||
else if (requestInclination != -100) {
|
||||
writeCharacteristic(noOpData7, sizeof(noOpData7), QStringLiteral("noOp"));
|
||||
// only 0.5 steps ara available
|
||||
double inc = qRound(requestInclination * 2.0) / 2.0;
|
||||
if (inc != currentInclination().value()) {
|
||||
emit debug(QStringLiteral("writing inclination ") + QString::number(requestInclination) +
|
||||
" rounded " + QString::number(inc));
|
||||
forceIncline(inc);
|
||||
}
|
||||
requestInclination = -100;
|
||||
}
|
||||
} else if (proform_bike_225_csx) {
|
||||
writeCharacteristic(noOpData6_proform_bike_225_csx, sizeof(noOpData6_proform_bike_225_csx),
|
||||
QStringLiteral("noOp"));
|
||||
@@ -971,7 +1084,7 @@ void proformbike::update() {
|
||||
if (!proform_studio && !proform_tdf_10) {
|
||||
innerWriteResistance();
|
||||
}
|
||||
if (requestInclination != -100 && (proform_studio || proform_tdf_10 || proform_bike_PFEVEX71316_0)) {
|
||||
if (requestInclination != -100 && (proform_studio || proform_tdf_10)) {
|
||||
// only 0.5 steps ara available
|
||||
double inc = qRound(requestInclination * 2.0) / 2.0;
|
||||
if (inc != currentInclination().value()) {
|
||||
@@ -987,14 +1100,14 @@ void proformbike::update() {
|
||||
counterPoll++;
|
||||
if (counterPoll > 6) {
|
||||
counterPoll = 0;
|
||||
} else if(counterPoll == 6 && proform_bike_225_csx) {
|
||||
} else if(counterPoll == 6 && (proform_bike_225_csx || proform_bike_PFEVEX71316_0)) {
|
||||
counterPoll = 0;
|
||||
} else if (counterPoll == 6 &&
|
||||
(proform_tour_de_france_clc || proform_cycle_trainer_400 || proform_bike_PFEVEX71316_1 || proform_bike_PFEVEX71316_0) &&
|
||||
(proform_tour_de_france_clc || proform_cycle_trainer_400 || proform_bike_PFEVEX71316_1) &&
|
||||
requestResistance == -1) {
|
||||
// this bike sends the frame noOpData7 only when it needs to change the resistance
|
||||
counterPoll = 0;
|
||||
} else if (counterPoll == 5 && (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci || proform_hybrid_trainer_PFEL03815 || proform_bike_sb || proform_bike_325_csx)) {
|
||||
} else if (counterPoll == 5 && (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci || proform_hybrid_trainer_PFEL03815 || proform_bike_sb || proform_bike_325_csx || proform_xbike)) {
|
||||
counterPoll = 0;
|
||||
}
|
||||
|
||||
@@ -1470,6 +1583,55 @@ void proformbike::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
m_pelotonResistance = 100;
|
||||
break;
|
||||
}
|
||||
} else if (proform_xbike) {
|
||||
switch ((uint8_t)newValue.at(11)) {
|
||||
case 0x07:
|
||||
Resistance = 2;
|
||||
m_pelotonResistance = 25;
|
||||
break;
|
||||
case 0x0b:
|
||||
case 0x0c:
|
||||
Resistance = 3;
|
||||
m_pelotonResistance = 35;
|
||||
break;
|
||||
case 0x0f:
|
||||
Resistance = 4;
|
||||
m_pelotonResistance = 40;
|
||||
break;
|
||||
case 0x13:
|
||||
Resistance = 5;
|
||||
m_pelotonResistance = 50;
|
||||
break;
|
||||
case 0x17:
|
||||
Resistance = 6;
|
||||
m_pelotonResistance = 60;
|
||||
break;
|
||||
case 0x1a:
|
||||
case 0x1b:
|
||||
Resistance = 7;
|
||||
m_pelotonResistance = 65;
|
||||
break;
|
||||
case 0x1f:
|
||||
Resistance = 8;
|
||||
m_pelotonResistance = 75;
|
||||
break;
|
||||
case 0x23:
|
||||
case 0x24:
|
||||
Resistance = 9;
|
||||
m_pelotonResistance = 85;
|
||||
break;
|
||||
case 0x26:
|
||||
case 0x27:
|
||||
Resistance = 10;
|
||||
m_pelotonResistance = 100;
|
||||
break;
|
||||
/* when the proform bike is changing the resistance, it sends some strange values, so i'm keeping
|
||||
the last good one default: Resistance = 0; m_pelotonResistance = 0; break;
|
||||
*/
|
||||
default:
|
||||
Resistance = 1;
|
||||
m_pelotonResistance = 10;
|
||||
}
|
||||
} else if (!nordictrack_gx_2_7) {
|
||||
switch ((uint8_t)newValue.at(11)) {
|
||||
case 0x00:
|
||||
@@ -1730,6 +1892,7 @@ void proformbike::btinit() {
|
||||
proform_bike_PFEVEX71316_1 = settings.value(QZSettings::proform_bike_PFEVEX71316_1, QZSettings::default_proform_bike_PFEVEX71316_1).toBool();
|
||||
nordictrack_gx_44_pro = settings.value(QZSettings::nordictrack_gx_44_pro, QZSettings::default_nordictrack_gx_44_pro).toBool();
|
||||
proform_bike_PFEVEX71316_0 = settings.value(QZSettings::proform_bike_PFEVEX71316_0, QZSettings::default_proform_bike_PFEVEX71316_0).toBool();
|
||||
proform_xbike = settings.value(QZSettings::proform_xbike, QZSettings::default_proform_xbike).toBool();
|
||||
|
||||
if(nordictrack_GX4_5_bike)
|
||||
max_resistance = 25;
|
||||
@@ -1794,6 +1957,128 @@ void proformbike::btinit() {
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
} else if (settings.value(QZSettings::proform_xbike, QZSettings::default_proform_xbike).toBool()) {
|
||||
max_resistance = 10;
|
||||
|
||||
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
|
||||
uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData3[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x07, 0x04, 0x80, 0x8b,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x07, 0x04, 0x88, 0x93,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData5[] = {0xfe, 0x02, 0x0a, 0x02};
|
||||
uint8_t initData6[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x82, 0x00,
|
||||
0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData7[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00,
|
||||
0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData8[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData9[] = {0xfe, 0x02, 0x2c, 0x04};
|
||||
uint8_t initData10[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x07, 0x28, 0x90, 0x07, 0x01, 0xdb, 0x20, 0x77, 0xc4, 0x13, 0x60, 0xb7, 0x1c, 0x7b};
|
||||
uint8_t initData11[] = {0x01, 0x12, 0xc0, 0x27, 0x84, 0xe3, 0x40, 0xa7, 0x0c, 0x9b, 0xe0, 0x77, 0xc4, 0x53, 0xa0, 0x37, 0xbc, 0x3b, 0x80, 0x07};
|
||||
uint8_t initData12[] = {0xFF, 0x08, 0x84, 0x03, 0x80, 0x88, 0x02, 0x00, 0x00, 0xEE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData13[] = {0xfe, 0x02, 0x19, 0x03};
|
||||
uint8_t initData14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x07, 0x15, 0x02, 0x0e,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData15[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3d, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData16[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
uint8_t initData17[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x0c,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData18[] = {0xff, 0x05, 0x00, 0x80, 0x01, 0x00, 0xa9, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
uint8_t xbike_pkt582[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
uint8_t xbike_pkt585[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x00, 0x0d, 0x00,
|
||||
0x10, 0x00, 0xc0, 0x1c, 0x4c, 0x00, 0x00, 0xe0};
|
||||
uint8_t xbike_pkt588[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x10, 0x51, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t xbike_pkt594[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
uint8_t xbike_pkt599[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x0c, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t xbike_pkt602[] = {0xff, 0x05, 0x00, 0x80, 0x01, 0x00, 0xa9, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t xbike_pkt607[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
uint8_t xbike_pkt610[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x0c, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t xbike_pkt613[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t xbike_pkt618[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
uint8_t xbike_pkt621[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x0c, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t xbike_pkt624[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
// The initialization sequence requires specific timing between messages
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData13, sizeof(initData13), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData14, sizeof(initData14), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData15, sizeof(initData15), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData16, sizeof(initData16), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData18, sizeof(initData18), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(xbike_pkt582, sizeof(xbike_pkt582), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(xbike_pkt585, sizeof(xbike_pkt585), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(xbike_pkt588, sizeof(xbike_pkt588), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(xbike_pkt594, sizeof(xbike_pkt594), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(xbike_pkt599, sizeof(xbike_pkt599), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(xbike_pkt602, sizeof(xbike_pkt602), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(xbike_pkt607, sizeof(xbike_pkt607), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(xbike_pkt610, sizeof(xbike_pkt610), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(xbike_pkt613, sizeof(xbike_pkt613), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(xbike_pkt618, sizeof(xbike_pkt618), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(xbike_pkt621, sizeof(xbike_pkt621), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(xbike_pkt624, sizeof(xbike_pkt624), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
} else if (settings.value(QZSettings::nordictrack_gx_44_pro, QZSettings::default_nordictrack_gx_44_pro).toBool()) {
|
||||
max_resistance = 25; // Most NordicTrack bikes use resistance range 1-25
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ class proformbike : public bike {
|
||||
bool proform_bike_PFEVEX71316_1 = false;
|
||||
bool nordictrack_gx_44_pro = false;
|
||||
bool proform_bike_PFEVEX71316_0 = false;
|
||||
bool proform_xbike = false;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
lockscreen *h = 0;
|
||||
|
||||
@@ -241,8 +241,12 @@ void strydrunpowersensor::characteristicChanged(const QLowEnergyCharacteristic &
|
||||
((double)(((int16_t)((int8_t)newValue.at(index + 1)) << 8) | (int16_t)((uint8_t)newValue.at(index)))) /
|
||||
10.0;
|
||||
// steps of 0.5 only to send to the Inclination override function
|
||||
inc = qRound(inc * 2.0) / 2.0;
|
||||
Inclination = treadmillInclinationOverride(inc);
|
||||
if(!areInclinationSettingsDefault()) {
|
||||
inc = qRound(inc * 2.0) / 2.0;
|
||||
Inclination = treadmillInclinationOverride(inc);
|
||||
} else {
|
||||
Inclination = inc;
|
||||
}
|
||||
index += 4; // the ramo value is useless
|
||||
emit debug(QStringLiteral("Current Inclination: ") + QString::number(Inclination.value()));
|
||||
bool stryd_inclination_instead_treadmill = settings.value(QZSettings::stryd_inclination_instead_treadmill, QZSettings::default_stryd_inclination_instead_treadmill).toBool();
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "keepawakehelper.h"
|
||||
#include <QLowEnergyConnectionParameters>
|
||||
#endif
|
||||
#include "homeform.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
@@ -28,6 +29,17 @@ tacxneo2::tacxneo2(bool noWriteResistance, bool noHeartService) {
|
||||
|
||||
void tacxneo2::writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
|
||||
bool wait_for_response) {
|
||||
|
||||
if(!gattCustomService) {
|
||||
qDebug() << "gattCustomService is null!";
|
||||
QSettings settings;
|
||||
settings.setValue(QZSettings::ftms_bike, bluetoothDevice.name());
|
||||
qDebug() << "forcing FTMS bike since it has FTMS";
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested("FTMS bike found, restart the app to apply the change!");
|
||||
return;
|
||||
}
|
||||
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
if (wait_for_response) {
|
||||
|
||||
@@ -293,6 +293,69 @@ double treadmill::treadmillInclinationOverrideReverse(double Inclination) {
|
||||
return treadmillInclinationOverride(15);
|
||||
}
|
||||
|
||||
bool treadmill::areInclinationSettingsDefault() {
|
||||
QSettings settings;
|
||||
|
||||
// Check gain and offset settings first
|
||||
if (settings.value(QZSettings::treadmill_inclination_ovveride_gain).toDouble() !=
|
||||
QZSettings::default_treadmill_inclination_ovveride_gain) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (settings.value(QZSettings::treadmill_inclination_ovveride_offset).toDouble() !=
|
||||
QZSettings::default_treadmill_inclination_ovveride_offset) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Array of settings to check - pairs of setting key and its default value
|
||||
const struct {
|
||||
QString setting;
|
||||
double defaultValue;
|
||||
} checkPairs[] = {
|
||||
{QZSettings::treadmill_inclination_override_0, QZSettings::default_treadmill_inclination_override_0},
|
||||
{QZSettings::treadmill_inclination_override_05, QZSettings::default_treadmill_inclination_override_05},
|
||||
{QZSettings::treadmill_inclination_override_10, QZSettings::default_treadmill_inclination_override_10},
|
||||
{QZSettings::treadmill_inclination_override_15, QZSettings::default_treadmill_inclination_override_15},
|
||||
{QZSettings::treadmill_inclination_override_20, QZSettings::default_treadmill_inclination_override_20},
|
||||
{QZSettings::treadmill_inclination_override_25, QZSettings::default_treadmill_inclination_override_25},
|
||||
{QZSettings::treadmill_inclination_override_30, QZSettings::default_treadmill_inclination_override_30},
|
||||
{QZSettings::treadmill_inclination_override_35, QZSettings::default_treadmill_inclination_override_35},
|
||||
{QZSettings::treadmill_inclination_override_40, QZSettings::default_treadmill_inclination_override_40},
|
||||
{QZSettings::treadmill_inclination_override_45, QZSettings::default_treadmill_inclination_override_45},
|
||||
{QZSettings::treadmill_inclination_override_50, QZSettings::default_treadmill_inclination_override_50},
|
||||
{QZSettings::treadmill_inclination_override_55, QZSettings::default_treadmill_inclination_override_55},
|
||||
{QZSettings::treadmill_inclination_override_60, QZSettings::default_treadmill_inclination_override_60},
|
||||
{QZSettings::treadmill_inclination_override_65, QZSettings::default_treadmill_inclination_override_65},
|
||||
{QZSettings::treadmill_inclination_override_70, QZSettings::default_treadmill_inclination_override_70},
|
||||
{QZSettings::treadmill_inclination_override_75, QZSettings::default_treadmill_inclination_override_75},
|
||||
{QZSettings::treadmill_inclination_override_80, QZSettings::default_treadmill_inclination_override_80},
|
||||
{QZSettings::treadmill_inclination_override_85, QZSettings::default_treadmill_inclination_override_85},
|
||||
{QZSettings::treadmill_inclination_override_90, QZSettings::default_treadmill_inclination_override_90},
|
||||
{QZSettings::treadmill_inclination_override_95, QZSettings::default_treadmill_inclination_override_95},
|
||||
{QZSettings::treadmill_inclination_override_100, QZSettings::default_treadmill_inclination_override_100},
|
||||
{QZSettings::treadmill_inclination_override_105, QZSettings::default_treadmill_inclination_override_105},
|
||||
{QZSettings::treadmill_inclination_override_110, QZSettings::default_treadmill_inclination_override_110},
|
||||
{QZSettings::treadmill_inclination_override_115, QZSettings::default_treadmill_inclination_override_115},
|
||||
{QZSettings::treadmill_inclination_override_120, QZSettings::default_treadmill_inclination_override_120},
|
||||
{QZSettings::treadmill_inclination_override_125, QZSettings::default_treadmill_inclination_override_125},
|
||||
{QZSettings::treadmill_inclination_override_130, QZSettings::default_treadmill_inclination_override_130},
|
||||
{QZSettings::treadmill_inclination_override_135, QZSettings::default_treadmill_inclination_override_135},
|
||||
{QZSettings::treadmill_inclination_override_140, QZSettings::default_treadmill_inclination_override_140},
|
||||
{QZSettings::treadmill_inclination_override_145, QZSettings::default_treadmill_inclination_override_145},
|
||||
{QZSettings::treadmill_inclination_override_150, QZSettings::default_treadmill_inclination_override_150}
|
||||
};
|
||||
|
||||
// Check each setting against its default value
|
||||
for (const auto& pair : checkPairs) {
|
||||
if (settings.value(pair.setting).toDouble() != pair.defaultValue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If we got here, all settings match their defaults
|
||||
return true;
|
||||
}
|
||||
|
||||
double treadmill::treadmillInclinationOverride(double Inclination) {
|
||||
QSettings settings;
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ class treadmill : public bluetoothdevice {
|
||||
|
||||
void parseSpeed(double speed);
|
||||
void parseInclination(double speed);
|
||||
bool areInclinationSettingsDefault();
|
||||
|
||||
private:
|
||||
bool simulateInclinationWithSpeed();
|
||||
|
||||
@@ -81,6 +81,8 @@ void trxappgateusbbike::forceResistance(resistance_t requestResistance) {
|
||||
resistance[2] = 0x3f;
|
||||
} else if (bike_type == TOORX_SRX_500) {
|
||||
resistance[2] = 0x23;
|
||||
} else if (bike_type == FAL_SPORTS) {
|
||||
resistance[2] = 0x1b;
|
||||
}
|
||||
|
||||
resistance[4] = requestResistance + 1;
|
||||
@@ -138,6 +140,10 @@ void trxappgateusbbike::update() {
|
||||
|
||||
const uint8_t noOpData[] = {0xf0, 0xa2, 0x3b, 0x01, 0xce};
|
||||
writeCharacteristic((uint8_t *)noOpData, sizeof(noOpData), QStringLiteral("noOp"), false, true);
|
||||
} else if (bike_type == TYPE::FAL_SPORTS) {
|
||||
|
||||
const uint8_t noOpData[] = {0xf0, 0xa2, 0x1b, 0x01, 0xae};
|
||||
writeCharacteristic((uint8_t *)noOpData, sizeof(noOpData), QStringLiteral("noOp"), false, true);
|
||||
} else if (bike_type == TYPE::TUNTURI || bike_type == TYPE::TUNTURI_2) {
|
||||
|
||||
const uint8_t noOpData[] = {0xf0, 0xa2, 0x03, 0x01, 0x96};
|
||||
@@ -219,7 +225,7 @@ void trxappgateusbbike::characteristicChanged(const QLowEnergyCharacteristic &ch
|
||||
|
||||
lastPacket = newValue;
|
||||
if ((newValue.length() != 21 && (bike_type != JLL_IC400 && bike_type != ASVIVA && bike_type != FYTTER_RI08 &&
|
||||
bike_type != TUNTURI && bike_type != TUNTURI_2 && bike_type != TOORX_SRX_500)) ||
|
||||
bike_type != TUNTURI && bike_type != TUNTURI_2 && bike_type != TOORX_SRX_500 && bike_type != FAL_SPORTS)) ||
|
||||
(newValue.length() != 19 && (bike_type == JLL_IC400 || bike_type == ASVIVA || bike_type == FYTTER_RI08 || bike_type == PASYOU)) ||
|
||||
(newValue.length() != 20 && newValue.length() != 21 &&
|
||||
(bike_type == TUNTURI || bike_type == TYPE::TUNTURI_2))) {
|
||||
@@ -241,7 +247,7 @@ void trxappgateusbbike::characteristicChanged(const QLowEnergyCharacteristic &ch
|
||||
(60000.0 / ((double)lastTimeCharChanged.msecsTo(
|
||||
now)))); //(( (0.048* Output in watts +1.19) *
|
||||
// body weight in kg * 3.5) / 200 ) / 60
|
||||
} else if (bike_type == TUNTURI || bike_type == TUNTURI_2) {
|
||||
} else if (bike_type == TUNTURI || bike_type == TUNTURI_2 || bike_type == FAL_SPORTS) {
|
||||
speed = cadence * 0.37407407407407407407407407407407;
|
||||
resistance = GetResistanceFromPacket(newValue);
|
||||
watt = GetWattFromPacket(newValue);
|
||||
@@ -746,6 +752,22 @@ void trxappgateusbbike::btinit(bool startTape) {
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic((uint8_t *)initData4, sizeof(initData4), QStringLiteral("init"), false, true);
|
||||
QThread::msleep(400);
|
||||
} else if (bike_type == TYPE::FAL_SPORTS) {
|
||||
const uint8_t initData1[] = {0xf0, 0xa0, 0x01, 0x01, 0x92};
|
||||
const uint8_t initData2[] = {0xf0, 0xa0, 0x1b, 0x01, 0xac};
|
||||
const uint8_t initData3[] = {0xf0, 0xa1, 0x1b, 0x01, 0xad};
|
||||
const uint8_t initData4[] = {0xf0, 0xa3, 0x1b, 0x01, 0x01, 0xb0};
|
||||
const uint8_t initData5[] = {0xf0, 0xa5, 0x1b, 0x01, 0x02, 0xb3};
|
||||
|
||||
writeCharacteristic((uint8_t *)initData1, sizeof(initData1), QStringLiteral("init"), false, true);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic((uint8_t *)initData2, sizeof(initData2), QStringLiteral("init"), false, true);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic((uint8_t *)initData3, sizeof(initData3), QStringLiteral("init"), false, true);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic((uint8_t *)initData4, sizeof(initData4), QStringLiteral("init"), false, true);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic((uint8_t *)initData5, sizeof(initData5), QStringLiteral("init"), false, true);
|
||||
} else if (bike_type == TYPE::FYTTER_RI08) {
|
||||
const uint8_t initData1[] = {0xf0, 0xa0, 0x00, 0x00, 0x90};
|
||||
const uint8_t initData2[] = {0xf0, 0xa0, 0x00, 0xc8, 0x58};
|
||||
@@ -829,7 +851,8 @@ void trxappgateusbbike::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
if (bike_type == TYPE::IRUNNING || bike_type == TYPE::CHANGYOW || bike_type == TYPE::ICONSOLE ||
|
||||
bike_type == TYPE::JLL_IC400 || bike_type == TYPE::DKN_MOTION_2 || bike_type == TYPE::FYTTER_RI08 ||
|
||||
bike_type == TYPE::HERTZ_XR_770_2 || bike_type == TYPE::VIRTUFIT_2 || bike_type == TYPE::TUNTURI ||
|
||||
bike_type == TYPE::FITHIWAY || bike_type == TYPE::ENERFIT_SPX_9500_2 || bike_type == TYPE::REEBOK_2) {
|
||||
bike_type == TYPE::FITHIWAY || bike_type == TYPE::ENERFIT_SPX_9500_2 || bike_type == TYPE::REEBOK_2 ||
|
||||
bike_type == TYPE::FAL_SPORTS) {
|
||||
uuidWrite = QStringLiteral("49535343-8841-43f4-a8d4-ecbe34729bb3");
|
||||
uuidNotify1 = QStringLiteral("49535343-1E4D-4BD9-BA61-23C647249616");
|
||||
uuidNotify2 = QStringLiteral("49535343-4c8a-39b3-2f49-511cff073b7e");
|
||||
@@ -920,7 +943,7 @@ void trxappgateusbbike::serviceScanDone(void) {
|
||||
QString uuid3 = QStringLiteral("0000fff0-0000-1000-8000-00805f9b34fb");
|
||||
if (bike_type == TYPE::IRUNNING || bike_type == TYPE::CHANGYOW || bike_type == TYPE::ICONSOLE ||
|
||||
bike_type == TYPE::JLL_IC400 || bike_type == TYPE::FYTTER_RI08 || bike_type == TYPE::TUNTURI ||
|
||||
bike_type == TYPE::FITHIWAY) {
|
||||
bike_type == TYPE::FITHIWAY || bike_type == TYPE::FAL_SPORTS) {
|
||||
uuid = uuid2;
|
||||
}
|
||||
|
||||
@@ -1162,6 +1185,10 @@ void trxappgateusbbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
|
||||
bike_type = TYPE::PASYOU;
|
||||
qDebug() << QStringLiteral("PASYOU bike found");
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("FAL-SPORTS"))) {
|
||||
|
||||
bike_type = TYPE::FAL_SPORTS;
|
||||
qDebug() << QStringLiteral("FAL-SPORTS bike found");
|
||||
}
|
||||
|
||||
bluetoothDevice = device;
|
||||
|
||||
@@ -114,6 +114,7 @@ class trxappgateusbbike : public bike {
|
||||
TOORX_SRX_500 = 25,
|
||||
IRUNNING_2 = 26,
|
||||
PASYOU = 27,
|
||||
FAL_SPORTS = 28,
|
||||
} TYPE;
|
||||
TYPE bike_type = TRXAPPGATE;
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ void ypooelliptical::forceInclination(double inclination) {
|
||||
|
||||
void ypooelliptical::forceResistance(resistance_t requestResistance) {
|
||||
|
||||
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO || MYELLIPTICAL) {
|
||||
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO || MYELLIPTICAL || SKANDIKA || DOMYOS) {
|
||||
uint8_t write[] = {FTMS_SET_TARGET_RESISTANCE_LEVEL, 0x00};
|
||||
write[1] = ((uint16_t)requestResistance * 10) & 0xFF;
|
||||
writeCharacteristic(&gattFTMSWriteCharControlPointId, gattFTMSService, write, sizeof(write),
|
||||
@@ -108,7 +108,7 @@ void ypooelliptical::update() {
|
||||
|
||||
if (initRequest) {
|
||||
initRequest = false;
|
||||
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO || MYELLIPTICAL) {
|
||||
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO || MYELLIPTICAL || SKANDIKA || DOMYOS) {
|
||||
uint8_t write[] = {FTMS_REQUEST_CONTROL};
|
||||
writeCharacteristic(&gattFTMSWriteCharControlPointId, gattFTMSService, write, sizeof(write), "requestControl", false, true);
|
||||
} else {
|
||||
@@ -250,7 +250,7 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
|
||||
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2ACE) && !iconsole_elliptical) {
|
||||
|
||||
if(E35 == false && SCH_590E == false && KETTLER == false && CARDIOPOWER_EEGO == false && MYELLIPTICAL == false) {
|
||||
if(E35 == false && SCH_590E == false && KETTLER == false && CARDIOPOWER_EEGO == false && MYELLIPTICAL == false && SKANDIKA == false && DOMYOS == false) {
|
||||
if (newvalue.length() == 18) {
|
||||
qDebug() << QStringLiteral("let's wait for the next piece of frame");
|
||||
lastPacket = newvalue;
|
||||
@@ -270,7 +270,7 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
index += 3;
|
||||
|
||||
if (!Flags.moreData) {
|
||||
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO || MYELLIPTICAL) {
|
||||
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO || MYELLIPTICAL || SKANDIKA || DOMYOS) {
|
||||
Speed = ((double)(((uint16_t)((uint8_t)lastPacket.at(index + 1)) << 8) |
|
||||
(uint16_t)((uint8_t)lastPacket.at(index)))) /
|
||||
100.0;
|
||||
@@ -282,7 +282,7 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
// this particular device, seems to send the actual speed here
|
||||
if (Flags.avgSpeed) {
|
||||
// double avgSpeed;
|
||||
if(!E35 && !SCH_590E && !KETTLER && !CARDIOPOWER_EEGO && !MYELLIPTICAL) {
|
||||
if(!E35 && !SCH_590E && !KETTLER && !CARDIOPOWER_EEGO && !MYELLIPTICAL && !SKANDIKA && !DOMYOS) {
|
||||
Speed = ((double)(((uint16_t)((uint8_t)lastPacket.at(index + 1)) << 8) |
|
||||
(uint16_t)((uint8_t)lastPacket.at(index)))) /
|
||||
100.0;
|
||||
@@ -292,7 +292,7 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
}
|
||||
|
||||
if (Flags.totDistance) {
|
||||
if(!E35 && !SCH_590E && !KETTLER && !CARDIOPOWER_EEGO && !MYELLIPTICAL) {
|
||||
if(!E35 && !SCH_590E && !KETTLER && !CARDIOPOWER_EEGO && !MYELLIPTICAL && !SKANDIKA && !DOMYOS) {
|
||||
Distance = ((double)((((uint32_t)((uint8_t)lastPacket.at(index + 2)) << 16) |
|
||||
(uint32_t)((uint8_t)lastPacket.at(index + 1)) << 8) |
|
||||
(uint32_t)((uint8_t)lastPacket.at(index)))) /
|
||||
@@ -314,7 +314,7 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
.toString()
|
||||
.startsWith(QStringLiteral("Disabled"))) {
|
||||
double divisor = 1.0;
|
||||
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO || MYELLIPTICAL)
|
||||
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO || MYELLIPTICAL || SKANDIKA || DOMYOS)
|
||||
divisor = 2.0;
|
||||
Cadence = (((double)(((uint16_t)((uint8_t)lastPacket.at(index + 1)) << 8) |
|
||||
(uint16_t)((uint8_t)lastPacket.at(index))))) / divisor;
|
||||
@@ -382,7 +382,7 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
.startsWith(QStringLiteral("Disabled"))) {
|
||||
double divisor = 100.0; // i added this because this device seems to send it multiplied by 100
|
||||
|
||||
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO || MYELLIPTICAL)
|
||||
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO || MYELLIPTICAL || SKANDIKA || DOMYOS)
|
||||
divisor = 1.0;
|
||||
|
||||
m_watt = ((double)(((uint16_t)((uint8_t)lastPacket.at(index + 1)) << 8) |
|
||||
@@ -391,9 +391,12 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
}
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
|
||||
index += 2;
|
||||
} else if(DOMYOS) {
|
||||
m_watt = elliptical::watts();
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
|
||||
}
|
||||
|
||||
if (Flags.avgPower && lastPacket.length() > index + 1 && !E35 && !SCH_590E && !KETTLER && !CARDIOPOWER_EEGO && !MYELLIPTICAL) { // E35 has a bug about this
|
||||
if (Flags.avgPower && lastPacket.length() > index + 1 && !E35 && !SCH_590E && !KETTLER && !CARDIOPOWER_EEGO && !MYELLIPTICAL && !SKANDIKA && !DOMYOS) { // E35 has a bug about this
|
||||
double avgPower;
|
||||
avgPower = ((double)(((uint16_t)((uint8_t)lastPacket.at(index + 1)) << 8) |
|
||||
(uint16_t)((uint8_t)lastPacket.at(index))));
|
||||
@@ -402,8 +405,8 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
}
|
||||
|
||||
if (Flags.expEnergy && lastPacket.length() > index + 1) {
|
||||
KCal = ((double)(((uint16_t)((uint8_t)lastPacket.at(index + 1)) << 8) |
|
||||
(uint16_t)((uint8_t)lastPacket.at(index))));
|
||||
/*KCal = ((double)(((uint16_t)((uint8_t)lastPacket.at(index + 1)) << 8) |
|
||||
(uint16_t)((uint8_t)lastPacket.at(index))));*/
|
||||
index += 2;
|
||||
|
||||
// energy per hour
|
||||
@@ -411,16 +414,16 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
|
||||
// energy per minute
|
||||
index += 1;
|
||||
} else {
|
||||
if (watts())
|
||||
KCal += ((((0.048 * ((double)watts()) + 1.19) *
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 /
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
// kg * 3.5) / 200 ) / 60
|
||||
}
|
||||
|
||||
if (watts())
|
||||
KCal += ((((0.048 * ((double)watts()) + 1.19) *
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 /
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
// kg * 3.5) / 200 ) / 60
|
||||
|
||||
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
|
||||
|
||||
@@ -483,15 +486,15 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
|
||||
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
|
||||
}
|
||||
|
||||
if (heartRateBeltName.startsWith(QStringLiteral("Disabled")) &&
|
||||
(!Flags.heartRate || Heart.value() == 0 || disable_hr_frommachinery)) {
|
||||
update_hr_from_external();
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (heartRateBeltName.startsWith(QStringLiteral("Disabled")) &&
|
||||
(!Flags.heartRate || Heart.value() == 0 || disable_hr_frommachinery)) {
|
||||
update_hr_from_external();
|
||||
}
|
||||
|
||||
lastRefreshCharacteristicChanged = now;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
@@ -538,7 +541,7 @@ void ypooelliptical::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
qDebug() << "skipping service" << s->serviceUuid();
|
||||
continue;
|
||||
}
|
||||
else if(s->serviceUuid() != _gattFTMSService && (SCH_590E || MYELLIPTICAL)) {
|
||||
else if(s->serviceUuid() != _gattFTMSService && (SCH_590E || MYELLIPTICAL || SKANDIKA || DOMYOS)) {
|
||||
qDebug() << "skipping service" << s->serviceUuid();
|
||||
continue;
|
||||
}
|
||||
@@ -759,6 +762,12 @@ void ypooelliptical::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if(device.name().toUpper().startsWith(QStringLiteral("MYELLIPTICAL "))) {
|
||||
MYELLIPTICAL = true;
|
||||
qDebug() << "MYELLIPTICAL workaround ON!";
|
||||
} else if(device.name().toUpper().startsWith(QStringLiteral("SF-")) && device.name().midRef(3).toInt() > 0) {
|
||||
SKANDIKA = true;
|
||||
qDebug() << "SKANDIKA workaround ON!";
|
||||
} else if(device.name().toUpper().startsWith(QStringLiteral("DOMYOS-EL"))) {
|
||||
DOMYOS = true;
|
||||
qDebug() << "DOMYOS workaround ON!";
|
||||
}
|
||||
|
||||
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
|
||||
|
||||
@@ -81,6 +81,8 @@ class ypooelliptical : public elliptical {
|
||||
bool KETTLER = false;
|
||||
bool CARDIOPOWER_EEGO = false;
|
||||
bool MYELLIPTICAL = false;
|
||||
bool SKANDIKA = false;
|
||||
bool DOMYOS = false;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
lockscreen *h = 0;
|
||||
|
||||
@@ -51,8 +51,6 @@ using namespace std::chrono_literals;
|
||||
#pragma message "DEFINE STRAVA_CLIENT_ID!!!"
|
||||
#endif
|
||||
#endif
|
||||
#define _STR(x) #x
|
||||
#define STRINGIFY(x) _STR(x)
|
||||
#define STRAVA_CLIENT_ID_S STRINGIFY(STRAVA_CLIENT_ID)
|
||||
|
||||
DataObject::DataObject(const QString &name, const QString &icon, const QString &value, bool writable, const QString &id,
|
||||
@@ -591,6 +589,9 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) {
|
||||
connect(pelotonHandler, &peloton::workoutChanged, this, &homeform::pelotonWorkoutChanged);
|
||||
connect(pelotonHandler, &peloton::loginState, this, &homeform::pelotonLoginState);
|
||||
connect(pelotonHandler, &peloton::pzpLoginState, this, &homeform::pzpLoginState);
|
||||
connect(pelotonHandler, &peloton::pelotonAuthUrlChanged, this, &homeform::pelotonAuthUrlChanged);
|
||||
connect(pelotonHandler, &peloton::pelotonWebVisibleChanged, this, &homeform::pelotonWebVisibleChanged);
|
||||
connect(stack, SIGNAL(peloton_connect_clicked()), pelotonHandler, SLOT(peloton_connect_clicked()));
|
||||
|
||||
// copying bundles zwo files in the right path if necessary
|
||||
QDirIterator itZwo(":/zwo/");
|
||||
@@ -6363,22 +6364,6 @@ QStringList homeform::bluetoothDevices() {
|
||||
|
||||
QStringList homeform::metrics() { return bluetoothdevice::metrics(); }
|
||||
|
||||
struct OAuth2Parameter {
|
||||
QString responseType = QStringLiteral("code");
|
||||
QString approval_prompt = QStringLiteral("force");
|
||||
|
||||
inline bool isEmpty() const { return responseType.isEmpty() && approval_prompt.isEmpty(); }
|
||||
|
||||
QString toString() const {
|
||||
QString msg;
|
||||
QTextStream out(&msg);
|
||||
out << QStringLiteral("OAuth2Parameter{\n") << QStringLiteral("responseType: ") << this->responseType
|
||||
<< QStringLiteral("\n") << QStringLiteral("approval_prompt: ") << this->approval_prompt
|
||||
<< QStringLiteral("\n");
|
||||
return msg;
|
||||
}
|
||||
};
|
||||
|
||||
QAbstractOAuth::ModifyParametersFunction
|
||||
homeform::buildModifyParametersFunction(const QUrl &clientIdentifier, const QUrl &clientIdentifierSharedKey) {
|
||||
return [clientIdentifier, clientIdentifierSharedKey](QAbstractOAuth::Stage stage, QVariantMap *parameters) {
|
||||
@@ -6815,6 +6800,14 @@ void homeform::setGeneralPopupVisible(bool value) {
|
||||
emit generalPopupVisibleChanged(m_generalPopupVisible);
|
||||
}
|
||||
|
||||
bool homeform::pelotonPopupVisible() { return m_pelotonPopupVisible; }
|
||||
|
||||
void homeform::setPelotonPopupVisible(bool value) {
|
||||
|
||||
m_pelotonPopupVisible = value;
|
||||
emit pelotonPopupVisibleChanged(m_pelotonPopupVisible);
|
||||
}
|
||||
|
||||
bool homeform::licensePopupVisible() { return m_LicensePopupVisible; }
|
||||
|
||||
void homeform::setLicensePopupVisible(bool value) {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "bluetooth.h"
|
||||
#include "fit_profile.hpp"
|
||||
#include "gpx.h"
|
||||
#include "OAuth2.h"
|
||||
#include "peloton.h"
|
||||
#include "qmdnsengine/browser.h"
|
||||
#include "qmdnsengine/cache.h"
|
||||
@@ -150,6 +151,8 @@ class homeform : public QObject {
|
||||
Q_PROPERTY(QStringList tile_order READ tile_order NOTIFY tile_orderChanged)
|
||||
Q_PROPERTY(bool generalPopupVisible READ generalPopupVisible NOTIFY generalPopupVisibleChanged WRITE
|
||||
setGeneralPopupVisible)
|
||||
Q_PROPERTY(bool pelotonPopupVisible READ pelotonPopupVisible NOTIFY pelotonPopupVisibleChanged WRITE
|
||||
setPelotonPopupVisible)
|
||||
Q_PROPERTY(bool licensePopupVisible READ licensePopupVisible NOTIFY licensePopupVisibleChanged WRITE
|
||||
setLicensePopupVisible)
|
||||
Q_PROPERTY(bool mapsVisible READ mapsVisible NOTIFY mapsVisibleChanged WRITE setMapsVisible)
|
||||
@@ -193,6 +196,10 @@ class homeform : public QObject {
|
||||
Q_PROPERTY(QString getStravaAuthUrl READ getStravaAuthUrl NOTIFY stravaAuthUrlChanged)
|
||||
Q_PROPERTY(bool stravaWebVisible READ stravaWebVisible NOTIFY stravaWebVisibleChanged)
|
||||
|
||||
QString getPelotonAuthUrl() { if(!pelotonHandler) return ""; return pelotonHandler->pelotonAuthUrl; }
|
||||
bool pelotonWebVisible() { if(!pelotonHandler) return false; return pelotonHandler->pelotonAuthWebVisible; }
|
||||
Q_PROPERTY(QString getPelotonAuthUrl READ getPelotonAuthUrl NOTIFY pelotonAuthUrlChanged)
|
||||
Q_PROPERTY(bool pelotonWebVisible READ pelotonWebVisible NOTIFY pelotonWebVisibleChanged)
|
||||
|
||||
public:
|
||||
static homeform *singleton() { return m_singleton; }
|
||||
@@ -446,6 +453,7 @@ class homeform : public QObject {
|
||||
bool stravaUploadRequested() { return m_stravaUploadRequested; }
|
||||
void setPelotonProvider(const QString &value) { m_pelotonProvider = value; }
|
||||
bool generalPopupVisible();
|
||||
bool pelotonPopupVisible();
|
||||
bool licensePopupVisible();
|
||||
bool mapsVisible();
|
||||
bool videoIconVisible();
|
||||
@@ -501,6 +509,7 @@ class homeform : public QObject {
|
||||
m_stravaUploadRequested = value;
|
||||
}
|
||||
void setGeneralPopupVisible(bool value);
|
||||
void setPelotonPopupVisible(bool value);
|
||||
int workout_sample_points() { return Session.count(); }
|
||||
int preview_workout_points();
|
||||
|
||||
@@ -704,6 +713,7 @@ class homeform : public QObject {
|
||||
QString m_info = QStringLiteral("Connecting...");
|
||||
bool m_labelHelp = true;
|
||||
bool m_generalPopupVisible = false;
|
||||
bool m_pelotonPopupVisible = false;
|
||||
bool m_LicensePopupVisible = false;
|
||||
bool m_MapsVisible = false;
|
||||
bool m_VideoIconVisible = false;
|
||||
@@ -905,6 +915,7 @@ class homeform : public QObject {
|
||||
void toastRequestedChanged(QString value);
|
||||
void stravaUploadRequestedChanged(bool value);
|
||||
void generalPopupVisibleChanged(bool value);
|
||||
void pelotonPopupVisibleChanged(bool value);
|
||||
void licensePopupVisibleChanged(bool value);
|
||||
void videoIconVisibleChanged(bool value);
|
||||
void videoVisibleChanged(bool value);
|
||||
@@ -930,6 +941,8 @@ class homeform : public QObject {
|
||||
void previewWorkoutTagsChanged(QString value);
|
||||
void stravaAuthUrlChanged(QString value);
|
||||
void stravaWebVisibleChanged(bool value);
|
||||
void pelotonAuthUrlChanged(QString value);
|
||||
void pelotonWebVisibleChanged(bool value);
|
||||
|
||||
void workoutEventStateChanged(bluetoothdevice::WORKOUT_EVENT_STATE state);
|
||||
|
||||
|
||||
@@ -33,5 +33,6 @@
|
||||
<file>icons/video.png</file>
|
||||
<file>icons/mini-display.png</file>
|
||||
<file>icons/btn_strava_connectwith_orange.png</file>
|
||||
<file>icons/Button_Connect_Rect_DarkMode.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
BIN
src/icons/Button_Connect_Rect_DarkMode.png
Normal file
BIN
src/icons/Button_Connect_Rect_DarkMode.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
@@ -87,31 +87,31 @@ class Connection {
|
||||
let sender = message.slice(from: "SENDER=", to: "#")
|
||||
if sender?.contains("PHONE") ?? false && message.contains("HR=") {
|
||||
let hr : String = message.slice(from: "HR=", to: "#") ?? ""
|
||||
WatchKitConnection.currentHeartRate = (Int(hr) ?? 0)
|
||||
WatchKitConnection.currentHeartRate = (Int(hr) ?? WatchKitConnection.currentHeartRate)
|
||||
}
|
||||
if sender?.contains("PHONE") ?? false && message.contains("CAD=") {
|
||||
let cad : String = message.slice(from: "CAD=", to: "#") ?? ""
|
||||
WatchKitConnection.stepCadence = (Int(cad) ?? 0)
|
||||
WatchKitConnection.stepCadence = (Int(cad) ?? WatchKitConnection.stepCadence)
|
||||
}
|
||||
if sender?.contains("PAD") ?? false && message.contains("KCAL=") {
|
||||
let kcal : String = message.slice(from: "KCAL=", to: "#") ?? ""
|
||||
WatchKitConnection.kcal = (Double(kcal) ?? 0)
|
||||
WatchKitConnection.kcal = (Double(kcal) ?? WatchKitConnection.kcal)
|
||||
}
|
||||
if sender?.contains("PAD") ?? false && message.contains("ODO=") {
|
||||
let odo : String = message.slice(from: "ODO=", to: "#") ?? ""
|
||||
WatchKitConnection.distance = (Double(odo) ?? 0)
|
||||
WatchKitConnection.distance = (Double(odo) ?? WatchKitConnection.distance)
|
||||
}
|
||||
if sender?.contains("PAD") ?? false && message.contains("BCAD=") {
|
||||
let cad : String = message.slice(from: "BCAD=", to: "#") ?? ""
|
||||
WatchKitConnection.cadence = (Double(cad) ?? 0)
|
||||
WatchKitConnection.cadence = (Double(cad) ?? WatchKitConnection.cadence)
|
||||
}
|
||||
if sender?.contains("PAD") ?? false && message.contains("SPD=") {
|
||||
let spd : String = message.slice(from: "SPD=", to: "#") ?? ""
|
||||
WatchKitConnection.speed = (Double(spd) ?? 0)
|
||||
WatchKitConnection.speed = (Double(spd) ?? WatchKitConnection.speed)
|
||||
}
|
||||
if sender?.contains("PAD") ?? false && message.contains("PWR=") {
|
||||
let pwr : String = message.slice(from: "PWR=", to: "#") ?? ""
|
||||
WatchKitConnection.power = (Double(pwr) ?? 0)
|
||||
WatchKitConnection.power = (Double(pwr) ?? WatchKitConnection.power)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
80
src/ios/BluetoothPatch/OSXBtManagerInternal.mm
Normal file
80
src/ios/BluetoothPatch/OSXBtManagerInternal.mm
Normal file
@@ -0,0 +1,80 @@
|
||||
#define QT_IOS_BLUETOOTH
|
||||
#import "osxbtcentralmanager_p.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
QT_USE_NAMESPACE
|
||||
|
||||
// Definizione della struttura dei membri privati
|
||||
@interface QT_MANGLE_NAMESPACE(OSXBTCentralManager) () {
|
||||
@package // Rende i membri accessibili alla categoria
|
||||
OSXBluetooth::CharHash charMap;
|
||||
OSXBluetooth::DescHash descMap;
|
||||
OSXBluetooth::ValueHash valuesToWrite;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation QT_MANGLE_NAMESPACE(OSXBTCentralManager) (SafeCache)
|
||||
|
||||
- (bool)cacheWriteValue:(const QByteArray &)value for:(NSObject *)obj
|
||||
{
|
||||
@try {
|
||||
// Accesso alle variabili private tramite self
|
||||
OSXBluetooth::CharHash &localCharMap = ((QT_MANGLE_NAMESPACE(OSXBTCentralManager) *)self)->charMap;
|
||||
OSXBluetooth::DescHash &localDescMap = ((QT_MANGLE_NAMESPACE(OSXBTCentralManager) *)self)->descMap;
|
||||
OSXBluetooth::ValueHash &localValuesToWrite = ((QT_MANGLE_NAMESPACE(OSXBTCentralManager) *)self)->valuesToWrite;
|
||||
|
||||
// Verifica validità oggetto
|
||||
if (!obj) {
|
||||
qDebug() << "Error: Invalid object (nil)";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verifica tipo oggetto
|
||||
if ([obj isKindOfClass:[CBCharacteristic class]]) {
|
||||
@try {
|
||||
CBCharacteristic *const ch = (CBCharacteristic *)obj;
|
||||
if (!localCharMap.key(ch)) {
|
||||
qDebug() << "Error: Unexpected characteristic, no handle found";
|
||||
return false;
|
||||
}
|
||||
} @catch (NSException *e) {
|
||||
qDebug() << "Exception handling characteristic:" << e.reason;
|
||||
return false;
|
||||
}
|
||||
} else if ([obj isKindOfClass:[CBDescriptor class]]) {
|
||||
@try {
|
||||
CBDescriptor *const d = (CBDescriptor *)obj;
|
||||
if (!localDescMap.key(d)) {
|
||||
qDebug() << "Error: Unexpected descriptor, no handle found";
|
||||
return false;
|
||||
}
|
||||
} @catch (NSException *e) {
|
||||
qDebug() << "Exception handling descriptor:" << e.reason;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Error: Invalid object type, characteristic or descriptor required";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Gestione cache esistente
|
||||
@try {
|
||||
if (localValuesToWrite.contains(obj)) {
|
||||
qDebug() << "Warning: Already has a cached value for this object, the value will be replaced";
|
||||
}
|
||||
|
||||
localValuesToWrite[obj] = value;
|
||||
} @catch (NSException *e) {
|
||||
qDebug() << "Exception during cache operation:" << e.reason;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} @catch (NSException *e) {
|
||||
qDebug() << "Unexpected exception in cacheWriteValue:" << e.reason;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
63
src/ios/BluetoothPatch/osxbluetooth_p.h
Normal file
63
src/ios/BluetoothPatch/osxbluetooth_p.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtBluetooth module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef OSXBLUETOOTH_P_H
|
||||
#define OSXBLUETOOTH_P_H
|
||||
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
|
||||
#include <CoreBluetooth/CoreBluetooth.h>
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
#include <IOBluetooth/IOBluetooth.h>
|
||||
#endif
|
||||
|
||||
#endif // OSXBLUETOOTH_P_H
|
||||
176
src/ios/BluetoothPatch/osxbtcentralmanager_p.h
Normal file
176
src/ios/BluetoothPatch/osxbtcentralmanager_p.h
Normal file
@@ -0,0 +1,176 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtBluetooth module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef OSXBTCENTRALMANAGER_P_H
|
||||
#define OSXBTCENTRALMANAGER_P_H
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#include "qlowenergycontroller.h"
|
||||
#include "qlowenergyservice.h"
|
||||
#include "osxbtgcdtimer_p.h"
|
||||
#include "qbluetoothuuid.h"
|
||||
#include "osxbtutility_p.h"
|
||||
#include "osxbluetooth_p.h"
|
||||
|
||||
#include <QtCore/qbytearray.h>
|
||||
#include <QtCore/qglobal.h>
|
||||
#include <QtCore/qqueue.h>
|
||||
#include <QtCore/qhash.h>
|
||||
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
@class QT_MANGLE_NAMESPACE(OSXBTCentralManager);
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QLowEnergyServicePrivate;
|
||||
|
||||
namespace OSXBluetooth {
|
||||
|
||||
class LECBManagerNotifier;
|
||||
|
||||
enum CentralManagerState
|
||||
{
|
||||
// QLowEnergyController already has some of these states,
|
||||
// but it's not enough and we need more special states here.
|
||||
CentralManagerIdle,
|
||||
// Required by CBCentralManager to avoid problems with dangled pointers.
|
||||
CentralManagerUpdating,
|
||||
CentralManagerConnecting,
|
||||
CentralManagerDiscovering,
|
||||
CentralManagerDisconnecting
|
||||
};
|
||||
|
||||
// In Qt we work with handles and UUIDs. Core Bluetooth
|
||||
// has NSArrays (and nested NSArrays inside servces/characteristics).
|
||||
// To simplify a navigation, I need a simple way to map from a handle
|
||||
// to a Core Bluetooth object. These are weak pointers,
|
||||
// will probably require '__weak' with ARC.
|
||||
typedef QHash<QLowEnergyHandle, CBService *> ServiceHash;
|
||||
typedef QHash<QLowEnergyHandle, CBCharacteristic *> CharHash;
|
||||
typedef QHash<QLowEnergyHandle, CBDescriptor *> DescHash;
|
||||
|
||||
// Descriptor/charactesirstic read/write requests
|
||||
// - we have to serialize 'concurrent' requests.
|
||||
struct LERequest {
|
||||
enum RequestType {
|
||||
CharRead,
|
||||
CharWrite,
|
||||
DescRead,
|
||||
DescWrite,
|
||||
ClientConfiguration,
|
||||
Invalid
|
||||
};
|
||||
|
||||
LERequest() : type(Invalid),
|
||||
withResponse(false),
|
||||
handle(0)
|
||||
{}
|
||||
|
||||
RequestType type;
|
||||
bool withResponse;
|
||||
QLowEnergyHandle handle;
|
||||
QByteArray value;
|
||||
};
|
||||
|
||||
typedef QQueue<LERequest> RequestQueue;
|
||||
|
||||
// Core Bluetooth's write confirmation does not provide
|
||||
// the updated value (characteristic or descriptor).
|
||||
// But the Qt's Bluetooth API ('write with response')
|
||||
// expects this updated value as a response, so we have
|
||||
// to cache this write value and report it back.
|
||||
// 'NSObject *' will require '__weak' with ARC.
|
||||
typedef QHash<NSObject *, QByteArray> ValueHash;
|
||||
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
@interface QT_MANGLE_NAMESPACE(OSXBTCentralManager) : NSObject<CBCentralManagerDelegate,
|
||||
CBPeripheralDelegate,
|
||||
QT_MANGLE_NAMESPACE(GCDTimerDelegate)>
|
||||
- (id)initWith:(QT_PREPEND_NAMESPACE(OSXBluetooth)::LECBManagerNotifier *)notifier;
|
||||
- (void)dealloc;
|
||||
|
||||
- (CBPeripheral *)peripheral;
|
||||
|
||||
// IMPORTANT: _all_ these methods are to be executed on qt_LE_queue,
|
||||
// when passing parameters - C++ objects _must_ be copied (see the controller's code).
|
||||
- (void)connectToDevice:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)aDeviceUuid;
|
||||
|
||||
- (void)disconnectFromDevice;
|
||||
|
||||
- (void)discoverServices;
|
||||
- (void)discoverServiceDetails:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid;
|
||||
|
||||
- (void)setNotifyValue:(const QT_PREPEND_NAMESPACE(QByteArray) &)value
|
||||
forCharacteristic:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))charHandle
|
||||
onService:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid;
|
||||
|
||||
- (void)readCharacteristic:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))charHandle
|
||||
onService:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid;
|
||||
|
||||
- (void)write:(const QT_PREPEND_NAMESPACE(QByteArray) &)value
|
||||
charHandle:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))charHandle
|
||||
onService:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid
|
||||
withResponse:(bool)writeWithResponse;
|
||||
|
||||
- (void)readDescriptor:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))descHandle
|
||||
onService:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid;
|
||||
|
||||
- (void)write:(const QT_PREPEND_NAMESPACE(QByteArray) &)value
|
||||
descHandle:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))descHandle
|
||||
onService:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid;
|
||||
|
||||
- (void)detach;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
108
src/ios/BluetoothPatch/osxbtgcdtimer_p.h
Normal file
108
src/ios/BluetoothPatch/osxbtgcdtimer_p.h
Normal file
@@ -0,0 +1,108 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2018 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtBluetooth module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef OSXBTGCDTIMER_P_H
|
||||
#define OSXBTGCDTIMER_P_H
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#include "osxbtutility_p.h"
|
||||
|
||||
#include <QtCore/qelapsedtimer.h>
|
||||
#include <QtCore/qglobal.h>
|
||||
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
namespace OSXBluetooth {
|
||||
|
||||
enum class OperationTimeout
|
||||
{
|
||||
none,
|
||||
serviceDiscovery,
|
||||
includedServicesDiscovery,
|
||||
characteristicsDiscovery,
|
||||
characteristicRead,
|
||||
descriptorsDiscovery,
|
||||
descriptorRead,
|
||||
characteristicWrite
|
||||
};
|
||||
|
||||
} // namespace OSXBluetooth
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
@protocol QT_MANGLE_NAMESPACE(GCDTimerDelegate)
|
||||
@required
|
||||
- (void)timeout:(id)sender;
|
||||
@end
|
||||
|
||||
@interface QT_MANGLE_NAMESPACE(OSXBTGCDTimer) : NSObject
|
||||
- (instancetype)initWithDelegate:(id<QT_MANGLE_NAMESPACE(GCDTimerDelegate)>)delegate;
|
||||
- (void)watchAfter:(id)object withTimeoutType:(QT_PREPEND_NAMESPACE(OSXBluetooth)::OperationTimeout)type;
|
||||
- (void)startWithTimeout:(qint64)ms step:(qint64)stepMS;
|
||||
- (void)handleTimeout;
|
||||
- (void)cancelTimer;
|
||||
- (id)objectUnderWatch;
|
||||
- (QT_PREPEND_NAMESPACE(OSXBluetooth)::OperationTimeout)timeoutType;
|
||||
@end
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
namespace OSXBluetooth {
|
||||
|
||||
using GCDTimerObjC = QT_MANGLE_NAMESPACE(OSXBTGCDTimer);
|
||||
using GCDTimer = ObjCStrongReference<GCDTimerObjC>;
|
||||
|
||||
} // namespace OSXBluetooth
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // OSXBTGCDTIMER_P_H
|
||||
|
||||
336
src/ios/BluetoothPatch/osxbtutility_p.h
Normal file
336
src/ios/BluetoothPatch/osxbtutility_p.h
Normal file
@@ -0,0 +1,336 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtBluetooth module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef OSXBTUTILITY_P_H
|
||||
#define OSXBTUTILITY_P_H
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#include "osxbluetooth_p.h"
|
||||
|
||||
#include <QtCore/qloggingcategory.h>
|
||||
#include <QtCore/qscopedpointer.h>
|
||||
#include <QtCore/qglobal.h>
|
||||
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QLowEnergyCharacteristicData;
|
||||
class QBluetoothAddress;
|
||||
class QBluetoothUuid;
|
||||
|
||||
namespace OSXBluetooth {
|
||||
|
||||
struct NSObjectDeleter {
|
||||
static void cleanup(NSObject *obj)
|
||||
{
|
||||
[obj release];
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class ObjCScopedPointer : public QScopedPointer<NSObject, NSObjectDeleter>
|
||||
{
|
||||
public:
|
||||
// TODO: remove default argument, add 'retain' parameter,
|
||||
// add a default ctor??? This will make the semantics more
|
||||
// transparent + will simplify the future transition to ARC
|
||||
// (if it will ever happen).
|
||||
explicit ObjCScopedPointer(T *ptr = nullptr) : QScopedPointer(ptr){}
|
||||
operator T*() const
|
||||
{
|
||||
return data();
|
||||
}
|
||||
|
||||
T *data()const
|
||||
{
|
||||
return static_cast<T *>(QScopedPointer::data());
|
||||
}
|
||||
|
||||
T *take()
|
||||
{
|
||||
return static_cast<T *>(QScopedPointer::take());
|
||||
}
|
||||
};
|
||||
|
||||
#define QT_BT_MAC_AUTORELEASEPOOL const QMacAutoReleasePool pool;
|
||||
|
||||
template<class T>
|
||||
class ObjCStrongReference {
|
||||
public:
|
||||
ObjCStrongReference()
|
||||
: m_ptr(nil)
|
||||
{
|
||||
}
|
||||
ObjCStrongReference(T *obj, bool retain)
|
||||
{
|
||||
if (retain)
|
||||
m_ptr = [obj retain];
|
||||
else
|
||||
m_ptr = obj; // For example, created with initWithXXXX.
|
||||
}
|
||||
ObjCStrongReference(const ObjCStrongReference &rhs)
|
||||
{
|
||||
m_ptr = [rhs.m_ptr retain];
|
||||
}
|
||||
ObjCStrongReference &operator = (const ObjCStrongReference &rhs)
|
||||
{
|
||||
// "Old-style" implementation:
|
||||
if (this != &rhs && m_ptr != rhs.m_ptr) {
|
||||
[m_ptr release];
|
||||
m_ptr = [rhs.m_ptr retain];
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
#ifdef Q_COMPILER_RVALUE_REFS
|
||||
ObjCStrongReference(ObjCStrongReference &&xval)
|
||||
{
|
||||
m_ptr = xval.m_ptr;
|
||||
xval.m_ptr = nil;
|
||||
}
|
||||
|
||||
ObjCStrongReference &operator = (ObjCStrongReference &&xval)
|
||||
{
|
||||
m_ptr = xval.m_ptr;
|
||||
xval.m_ptr = nil;
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
~ObjCStrongReference()
|
||||
{
|
||||
[m_ptr release];
|
||||
}
|
||||
|
||||
void reset(T *newVal)
|
||||
{
|
||||
if (m_ptr != newVal) {
|
||||
[m_ptr release];
|
||||
m_ptr = [newVal retain];
|
||||
}
|
||||
}
|
||||
|
||||
void resetWithoutRetain(T *newVal)
|
||||
{
|
||||
if (m_ptr != newVal) {
|
||||
[m_ptr release];
|
||||
m_ptr = newVal;
|
||||
}
|
||||
}
|
||||
|
||||
operator T *() const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
T *data() const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
T *take()
|
||||
{
|
||||
T * p = m_ptr;
|
||||
m_ptr = nil;
|
||||
return p;
|
||||
}
|
||||
private:
|
||||
T *m_ptr;
|
||||
};
|
||||
|
||||
// The type 'T' is some XXXRef from CoreFoundation and co.
|
||||
// In principle, we can do a trick removing a pointer from a type
|
||||
// when template is instantiated, but it's quite a lot of ugly pp-tokens
|
||||
// like OSXBluetooth::CFStrongReference<OSXBluetooth::remove_pointer<CFUUIDRref> > strongReference;
|
||||
// so instead we use 'T' everywhere, not 'T *' as can expected
|
||||
// from a smart pointer.
|
||||
template<class T>
|
||||
class CFStrongReference {
|
||||
public:
|
||||
CFStrongReference()
|
||||
: m_ptr(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
CFStrongReference(T obj, bool retain)
|
||||
: m_ptr(obj)
|
||||
{
|
||||
if (m_ptr && retain)
|
||||
CFRetain(m_ptr);
|
||||
}
|
||||
|
||||
CFStrongReference(const CFStrongReference &rhs)
|
||||
{
|
||||
if ((m_ptr = rhs.m_ptr))
|
||||
CFRetain(m_ptr);
|
||||
}
|
||||
|
||||
CFStrongReference &operator = (const CFStrongReference &rhs)
|
||||
{
|
||||
// "Old-style" implementation:
|
||||
if (this != &rhs && m_ptr != rhs.m_ptr) {
|
||||
if (m_ptr)
|
||||
CFRelease(m_ptr);
|
||||
if ((m_ptr = rhs.m_ptr))
|
||||
CFRetain(m_ptr);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
#ifdef Q_COMPILER_RVALUE_REFS
|
||||
CFStrongReference(CFStrongReference &&xval)
|
||||
{
|
||||
m_ptr = xval.m_ptr;
|
||||
xval.m_ptr = nullptr;
|
||||
}
|
||||
|
||||
CFStrongReference &operator = (CFStrongReference &&xval)
|
||||
{
|
||||
m_ptr = xval.m_ptr;
|
||||
xval.m_ptr = nullptr;
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
~CFStrongReference()
|
||||
{
|
||||
if (m_ptr)
|
||||
CFRelease(m_ptr);
|
||||
}
|
||||
|
||||
void reset(T newVal)
|
||||
{
|
||||
if (m_ptr != newVal) {
|
||||
if (m_ptr)
|
||||
CFRelease(m_ptr);
|
||||
if ((m_ptr = newVal))
|
||||
CFRetain(m_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
operator T() const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
T data() const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
T take()
|
||||
{
|
||||
T p = m_ptr;
|
||||
m_ptr = nullptr;
|
||||
return p;
|
||||
}
|
||||
private:
|
||||
T m_ptr;
|
||||
};
|
||||
|
||||
QString qt_address(NSString *address);
|
||||
|
||||
#ifndef QT_IOS_BLUETOOTH
|
||||
|
||||
QBluetoothAddress qt_address(const BluetoothDeviceAddress *address);
|
||||
BluetoothDeviceAddress iobluetooth_address(const QBluetoothAddress &address);
|
||||
|
||||
ObjCStrongReference<IOBluetoothSDPUUID> iobluetooth_uuid(const QBluetoothUuid &uuid);
|
||||
QBluetoothUuid qt_uuid(IOBluetoothSDPUUID *uuid);
|
||||
QString qt_error_string(IOReturn errorCode);
|
||||
void qt_test_iobluetooth_runloop();
|
||||
|
||||
#endif // !QT_IOS_BLUETOOTH
|
||||
|
||||
QBluetoothUuid qt_uuid(CBUUID *uuid);
|
||||
CFStrongReference<CFUUIDRef> cf_uuid(const QBluetoothUuid &qtUuid);
|
||||
ObjCStrongReference<CBUUID> cb_uuid(const QBluetoothUuid &qtUuid);
|
||||
bool equal_uuids(const QBluetoothUuid &qtUuid, CBUUID *cbUuid);
|
||||
bool equal_uuids(CBUUID *cbUuid, const QBluetoothUuid &qtUuid);
|
||||
QByteArray qt_bytearray(NSData *data);
|
||||
QByteArray qt_bytearray(NSObject *data);
|
||||
|
||||
ObjCStrongReference<NSData> data_from_bytearray(const QByteArray &qtData);
|
||||
ObjCStrongReference<NSMutableData> mutable_data_from_bytearray(const QByteArray &qtData);
|
||||
|
||||
dispatch_queue_t qt_LE_queue();
|
||||
|
||||
extern const int defaultLEScanTimeoutMS;
|
||||
extern const int maxValueLength;
|
||||
|
||||
} // namespace OSXBluetooth
|
||||
|
||||
// Logging category for both OS X and iOS.
|
||||
Q_DECLARE_LOGGING_CATEGORY(QT_BT_OSX)
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(101300) && QT_MACOS_DEPLOYMENT_TARGET_BELOW(101300)
|
||||
|
||||
// In the macOS 10.13 SDK, the identifier property was moved from the CBPeripheral
|
||||
// and CBCentral classes to a new CBPeer base class. Because CBPeer is only available
|
||||
// on macOS 10.13 and above, the same is true for -[CBPeer identifier]. However,
|
||||
// since we know that the derived classes have always had this property,
|
||||
// we'll explicitly mark its availability here. This will not adversely affect
|
||||
// using the identifier through the CBPeer base class, which will still require macOS 10.13.
|
||||
|
||||
@interface CBPeripheral (UnguardedWorkaround)
|
||||
@property (readonly, nonatomic) NSUUID *identifier NS_AVAILABLE(10_7, 5_0);
|
||||
@end
|
||||
|
||||
@interface CBCentral (UnguardedWorkaround)
|
||||
@property (readonly, nonatomic) NSUUID *identifier NS_AVAILABLE(10_7, 5_0);
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
15
src/logwriter.cpp
Normal file
15
src/logwriter.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#include "logwriter.h"
|
||||
|
||||
LogWriter::LogWriter(QObject *parent) : QObject(parent) {
|
||||
}
|
||||
|
||||
LogWriter::~LogWriter() {
|
||||
}
|
||||
|
||||
void LogWriter::writeLog(const QString &path, const QString &txt) {
|
||||
QFile outFile(path);
|
||||
outFile.open(QIODevice::WriteOnly | QIODevice::Append);
|
||||
QTextStream ts(&outFile);
|
||||
ts << txt;
|
||||
fprintf(stderr, "%s", txt.toLocal8Bit().constData());
|
||||
}
|
||||
19
src/logwriter.h
Normal file
19
src/logwriter.h
Normal file
@@ -0,0 +1,19 @@
|
||||
// logwriter.h
|
||||
#ifndef LOGWRITER_H
|
||||
#define LOGWRITER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
|
||||
class LogWriter : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LogWriter(QObject *parent = nullptr);
|
||||
virtual ~LogWriter();
|
||||
|
||||
public slots:
|
||||
void writeLog(const QString &path, const QString &txt);
|
||||
};
|
||||
|
||||
#endif // LOGWRITER_H
|
||||
101
src/main.cpp
101
src/main.cpp
@@ -9,7 +9,7 @@
|
||||
#endif
|
||||
#endif
|
||||
#include <QQmlContext>
|
||||
|
||||
#include "logwriter.h"
|
||||
#include "bluetooth.h"
|
||||
#include "devices/domyostreadmill/domyostreadmill.h"
|
||||
#include "homeform.h"
|
||||
@@ -84,7 +84,7 @@ QString deviceName = QLatin1String("");
|
||||
uint32_t pollDeviceTime = 200;
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
QString power_sensor_name = QZSettings::default_power_sensor_name;
|
||||
QString power_sensor_name = QStringLiteral("Disabled");
|
||||
bool power_sensor_as_treadmill = false;
|
||||
QString logfilename = QStringLiteral("debug-") +
|
||||
QDateTime::currentDateTime()
|
||||
@@ -166,6 +166,70 @@ void displayHelp() {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
#ifdef Q_CC_MSVC
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
#include <rtcapi.h>
|
||||
#include <cstdio>
|
||||
|
||||
void PrintStack() {
|
||||
CONTEXT context = {};
|
||||
RtlCaptureContext(&context);
|
||||
|
||||
STACKFRAME64 stackFrame = {};
|
||||
stackFrame.AddrPC.Offset = context.Rip; // Per x64, usa Rip
|
||||
stackFrame.AddrPC.Mode = AddrModeFlat;
|
||||
stackFrame.AddrFrame.Offset = context.Rbp;
|
||||
stackFrame.AddrFrame.Mode = AddrModeFlat;
|
||||
stackFrame.AddrStack.Offset = context.Rsp;
|
||||
stackFrame.AddrStack.Mode = AddrModeFlat;
|
||||
|
||||
HANDLE process = GetCurrentProcess();
|
||||
HANDLE thread = GetCurrentThread();
|
||||
|
||||
SymInitialize(process, NULL, TRUE);
|
||||
|
||||
while (StackWalk64(
|
||||
IMAGE_FILE_MACHINE_AMD64, process, thread, &stackFrame, &context,
|
||||
NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) {
|
||||
printf("Address: 0x%llx\n", stackFrame.AddrPC.Offset);
|
||||
}
|
||||
|
||||
SymCleanup(process);
|
||||
}
|
||||
|
||||
int __cdecl CustomRTCErrorHandler(int errorType, const wchar_t* filename, int linenumber,
|
||||
const wchar_t* moduleName, const wchar_t* format, ...)
|
||||
{
|
||||
// Buffer for the formatted error message
|
||||
wchar_t errorMessage[512];
|
||||
va_list args;
|
||||
|
||||
// Format the error message using varargs
|
||||
va_start(args, format);
|
||||
vswprintf(errorMessage, sizeof(errorMessage)/sizeof(wchar_t), format, args);
|
||||
va_end(args);
|
||||
|
||||
// Print complete error information
|
||||
fwprintf(stderr, L"Runtime Error Check Failed!\n");
|
||||
fwprintf(stderr, L"Error Type: %d\n", errorType);
|
||||
fwprintf(stderr, L"File: %ls\n", filename ? filename : L"Unknown");
|
||||
fwprintf(stderr, L"Line: %d\n", linenumber);
|
||||
fwprintf(stderr, L"Module: %ls\n", moduleName ? moduleName : L"Unknown");
|
||||
fwprintf(stderr, L"Error Message: %ls\n", errorMessage);
|
||||
fwprintf(stderr, L"----------------------------------------\n");
|
||||
|
||||
#ifdef _DEBUG
|
||||
__debugbreak(); // Break into debugger in debug builds
|
||||
#endif
|
||||
|
||||
PrintStack();
|
||||
|
||||
return 1; // Return non-zero to indicate error was handled
|
||||
}
|
||||
#endif
|
||||
|
||||
QCoreApplication *createApplication(int &argc, char *argv[]) {
|
||||
|
||||
QSettings settings;
|
||||
@@ -342,6 +406,19 @@ QCoreApplication *createApplication(int &argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
// Global thread and writer instance
|
||||
static QThread *logThread = nullptr;
|
||||
static LogWriter *logWriter = nullptr;
|
||||
|
||||
void initializeLogThread() {
|
||||
if (!logThread) {
|
||||
logThread = new QThread();
|
||||
logWriter = new LogWriter();
|
||||
logWriter->moveToThread(logThread);
|
||||
logThread->start();
|
||||
}
|
||||
}
|
||||
|
||||
void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
|
||||
|
||||
QSettings settings;
|
||||
@@ -380,13 +457,15 @@ void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QS
|
||||
|
||||
QString path = homeform::getWritableAppDir();
|
||||
|
||||
// Linux log files are generated on binary location
|
||||
// Ensure thread is initialized
|
||||
initializeLogThread();
|
||||
|
||||
// Write log in the worker thread
|
||||
QMetaObject::invokeMethod(logWriter, "writeLog",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(QString, path + logfilename),
|
||||
Q_ARG(QString, txt));
|
||||
|
||||
QFile outFile(path + logfilename);
|
||||
outFile.open(QIODevice::WriteOnly | QIODevice::Append);
|
||||
QTextStream ts(&outFile);
|
||||
ts << txt;
|
||||
fprintf(stderr, "%s", txt.toLocal8Bit().constData());
|
||||
}
|
||||
(*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);
|
||||
}
|
||||
@@ -396,6 +475,10 @@ int main(int argc, char *argv[]) {
|
||||
qputenv("QT_MULTIMEDIA_PREFERRED_PLUGINS", "windowsmediafoundation");
|
||||
#endif
|
||||
|
||||
#ifdef Q_CC_MSVC
|
||||
_RTC_SetErrorFuncW(CustomRTCErrorHandler);
|
||||
#endif
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
QScopedPointer<QCoreApplication> app(createApplication(argc, argv));
|
||||
#else
|
||||
@@ -517,7 +600,7 @@ int main(int argc, char *argv[]) {
|
||||
qInstallMessageHandler(myMessageOutput);
|
||||
qDebug() << QStringLiteral("version ") << app->applicationVersion();
|
||||
foreach (QString s, settings.allKeys()) {
|
||||
if (!s.contains(QStringLiteral("password")) && !s.contains("user_email") && !s.contains("username")) {
|
||||
if (!s.contains(QStringLiteral("password")) && !s.contains("user_email") && !s.contains("username") && !s.contains("token")) {
|
||||
|
||||
qDebug() << s << settings.value(s);
|
||||
}
|
||||
|
||||
86
src/main.qml
86
src/main.qml
@@ -30,6 +30,7 @@ ApplicationWindow {
|
||||
signal fit_save_clicked()
|
||||
signal refresh_bluetooth_devices_clicked()
|
||||
signal strava_connect_clicked()
|
||||
signal peloton_connect_clicked()
|
||||
signal loadSettings(url name)
|
||||
signal saveSettings(url name)
|
||||
signal deleteSettings(url name)
|
||||
@@ -52,6 +53,8 @@ ApplicationWindow {
|
||||
property string profile_name: "default"
|
||||
property string theme_status_bar_background_color: "#800080"
|
||||
property bool volume_change_gears: false
|
||||
property string peloton_username: "username"
|
||||
property string peloton_password: "password"
|
||||
}
|
||||
|
||||
Store {
|
||||
@@ -185,6 +188,33 @@ ApplicationWindow {
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: popupPelotonAuth
|
||||
text: "Peloton Authentication Change"
|
||||
informativeText: "Peloton has moved to a new authentication system. Username and password are no longer required.\n\nWould you like to switch to the new authentication method now?"
|
||||
buttons: (MessageDialog.Yes | MessageDialog.No)
|
||||
onYesClicked: {
|
||||
settings.peloton_username = "username"
|
||||
settings.peloton_password = "password"
|
||||
stackView.push("WebPelotonAuth.qml")
|
||||
peloton_connect_clicked()
|
||||
}
|
||||
onNoClicked: this.visible = false
|
||||
visible: false
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: pelotonAuthCheck
|
||||
interval: 1000 // 1 second delay after startup
|
||||
running: true
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (settings.peloton_password !== "password") {
|
||||
popupPelotonAuth.visible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: popupClassificaHelper
|
||||
parent: Overlay.overlay
|
||||
@@ -345,6 +375,40 @@ ApplicationWindow {
|
||||
}
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: popupPelotonConnected
|
||||
parent: Overlay.overlay
|
||||
enabled: rootItem.pelotonPopupVisible
|
||||
onEnabledChanged: { if(rootItem.pelotonPopupVisible) popupPelotonConnected.open() }
|
||||
onClosed: { rootItem.pelotonPopupVisible = false; }
|
||||
|
||||
x: Math.round((parent.width - width) / 2)
|
||||
y: Math.round((parent.height - height) / 2)
|
||||
width: 380
|
||||
height: 120
|
||||
modal: true
|
||||
focus: true
|
||||
palette.text: "white"
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
enter: Transition
|
||||
{
|
||||
NumberAnimation { property: "opacity"; from: 0.0; to: 1.0 }
|
||||
}
|
||||
exit: Transition
|
||||
{
|
||||
NumberAnimation { property: "opacity"; from: 1.0; to: 0.0 }
|
||||
}
|
||||
Column {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
Label {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: 370
|
||||
height: 120
|
||||
text: qsTr("Your Peloton account is now connected!<br><br>Restart the app to apply this change!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: popupLicenseAutoClose
|
||||
interval: 120000; running: rootItem.licensePopupVisible; repeat: false
|
||||
@@ -645,6 +709,9 @@ ApplicationWindow {
|
||||
toolButtonLoadSettings.visible = true;
|
||||
toolButtonSaveSettings.visible = true;
|
||||
stackView.push("settings.qml")
|
||||
stackView.currentItem.peloton_connect_clicked.connect(function() {
|
||||
peloton_connect_clicked()
|
||||
});
|
||||
drawer.close()
|
||||
}
|
||||
}
|
||||
@@ -777,7 +844,7 @@ ApplicationWindow {
|
||||
}
|
||||
|
||||
ItemDelegate {
|
||||
text: "version 2.18.18"
|
||||
text: "version 2.18.20"
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
@@ -799,6 +866,23 @@ ApplicationWindow {
|
||||
}
|
||||
}
|
||||
|
||||
ItemDelegate {
|
||||
Image {
|
||||
anchors.left: parent.left;
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
source: "icons/icons/Button_Connect_Rect_DarkMode.png"
|
||||
fillMode: Image.PreserveAspectFit
|
||||
visible: true
|
||||
width: parent.width
|
||||
}
|
||||
width: parent.width
|
||||
onClicked: {
|
||||
stackView.push("WebPelotonAuth.qml")
|
||||
peloton_connect_clicked()
|
||||
drawer.close()
|
||||
}
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: fileDialogGPX
|
||||
title: "Please choose a file"
|
||||
|
||||
548
src/peloton.cpp
548
src/peloton.cpp
@@ -1,3 +1,13 @@
|
||||
#if __has_include("secret.h")
|
||||
#include "secret.h"
|
||||
#else
|
||||
#if defined(WIN32)
|
||||
#pragma message("PELOTON API WILL NOT WORK!!!")
|
||||
#else
|
||||
#warning "PELOTON API WILL NOT WORK!!!"
|
||||
#endif
|
||||
#endif
|
||||
#include "homeform.h"
|
||||
#include "peloton.h"
|
||||
#include <chrono>
|
||||
|
||||
@@ -5,6 +15,8 @@ using namespace std::chrono_literals;
|
||||
|
||||
const bool log_request = true;
|
||||
|
||||
#define RAWHEADER request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("qdomyos-zwift"));request.setRawHeader(QByteArray("Authorization"), QByteArray("Bearer ") + settings.value(QZSettings::peloton_accesstoken, QZSettings::default_peloton_accesstoken).toString().toUtf8());
|
||||
|
||||
peloton::peloton(bluetooth *bl, QObject *parent) : QObject(parent) {
|
||||
|
||||
QSettings settings;
|
||||
@@ -12,6 +24,9 @@ peloton::peloton(bluetooth *bl, QObject *parent) : QObject(parent) {
|
||||
mgr = new QNetworkAccessManager(this);
|
||||
timer = new QTimer(this);
|
||||
|
||||
//peloton_connect_clicked();
|
||||
peloton_refreshtoken();
|
||||
|
||||
// only for test purpose
|
||||
/*
|
||||
current_image_downloaded =
|
||||
@@ -19,9 +34,9 @@ peloton::peloton(bluetooth *bl, QObject *parent) : QObject(parent) {
|
||||
"img_1646099287_a620f71b3d6740718457b21769a7ed46.png"));
|
||||
*/
|
||||
|
||||
if (!settings.value(QZSettings::peloton_username, QZSettings::default_peloton_username)
|
||||
if (!settings.value(QZSettings::peloton_accesstoken, QZSettings::default_peloton_accesstoken)
|
||||
.toString()
|
||||
.compare(QStringLiteral("username"))) {
|
||||
.length()) {
|
||||
qDebug() << QStringLiteral("invalid peloton credentials");
|
||||
return;
|
||||
}
|
||||
@@ -627,21 +642,16 @@ void peloton::startEngine() {
|
||||
QSettings settings;
|
||||
timer->stop();
|
||||
connect(mgr, &QNetworkAccessManager::finished, this, &peloton::login_onfinish);
|
||||
QUrl url(QStringLiteral("https://api.onepeloton.com/auth/login"));
|
||||
|
||||
QUrl url(QStringLiteral("https://api-3p.onepeloton.com/api/v1/me"));
|
||||
qDebug() << "peloton::getMe" << url;
|
||||
QNetworkRequest request(url);
|
||||
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("qdomyos-zwift"));
|
||||
|
||||
QJsonObject obj;
|
||||
obj[QStringLiteral("username_or_email")] =
|
||||
settings.value(QZSettings::peloton_username, QZSettings::default_peloton_username).toString();
|
||||
obj[QStringLiteral("password")] =
|
||||
settings.value(QZSettings::peloton_password, QZSettings::default_peloton_password).toString();
|
||||
QJsonDocument doc(obj);
|
||||
QByteArray data = doc.toJson();
|
||||
|
||||
mgr->post(request, data);
|
||||
RAWHEADER
|
||||
|
||||
qDebug() << settings.value(QZSettings::peloton_accesstoken, QZSettings::default_peloton_accesstoken).toString().toLatin1() << request.rawHeader(QByteArray("authorization"));
|
||||
|
||||
mgr->get(request);
|
||||
}
|
||||
|
||||
void peloton::login_onfinish(QNetworkReply *reply) {
|
||||
@@ -663,13 +673,16 @@ void peloton::login_onfinish(QNetworkReply *reply) {
|
||||
|
||||
peloton_credentials_wrong = true;
|
||||
qDebug() << QStringLiteral("invalid peloton credentials during login ") << status;
|
||||
homeform::singleton()->setToastRequested("Peloton Auth Failed!");
|
||||
emit loginState(false);
|
||||
return;
|
||||
}
|
||||
|
||||
user_id = document[QStringLiteral("user_id")].toString();
|
||||
total_workout = document[QStringLiteral("user_data")][QStringLiteral("total_workouts")].toInt();
|
||||
user_id = document[QStringLiteral("id")].toString();
|
||||
total_workout = document[QStringLiteral("total_workouts")].toInt();
|
||||
|
||||
qDebug() << "user_id" << user_id << "total workout" << total_workout;
|
||||
|
||||
emit loginState(!user_id.isEmpty());
|
||||
|
||||
getWorkoutList(1);
|
||||
@@ -805,12 +818,12 @@ void peloton::workout_onfinish(QNetworkReply *reply) {
|
||||
current_instructor_id = ride[QStringLiteral("instructor_id")].toString();
|
||||
current_ride_id = ride[QStringLiteral("id")].toString();
|
||||
current_workout_type = ride[QStringLiteral("fitness_discipline")].toString();
|
||||
current_pedaling_duration = ride[QStringLiteral("pedaling_duration")].toInt();
|
||||
current_pedaling_duration = ride[QStringLiteral("duration")].toInt();
|
||||
current_image_url = ride[QStringLiteral("image_url")].toString();
|
||||
|
||||
qint64 time = ride[QStringLiteral("original_air_time")].toInt();
|
||||
qDebug() << QStringLiteral("original_air_time") << time;
|
||||
qDebug() << QStringLiteral("current_pedaling_duration") << current_pedaling_duration;
|
||||
qint64 time = ride[QStringLiteral("scheduled_start_time")].toInt();
|
||||
qDebug() << QStringLiteral("scheduled_start_time") << time;
|
||||
qDebug() << QStringLiteral("duration") << current_pedaling_duration;
|
||||
|
||||
current_original_air_time = QDateTime::fromSecsSinceEpoch(time, Qt::UTC);
|
||||
|
||||
@@ -1391,7 +1404,191 @@ void peloton::performance_onfinish(QNetworkReply *reply) {
|
||||
QJsonArray segment_list = json[QStringLiteral("segment_list")].toArray();
|
||||
trainrows.clear();
|
||||
|
||||
if (!target_metrics_performance_data.isEmpty() && bluetoothManager->device() &&
|
||||
if(!target_metrics_performance_data.isEmpty() && bluetoothManager->device() &&
|
||||
bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE) {
|
||||
QJsonArray targetMetrics = target_metrics_performance_data[QStringLiteral("target_metrics")].toArray();
|
||||
|
||||
if (targetMetrics.count() > 0)
|
||||
trainrows.reserve(targetMetrics.count());
|
||||
|
||||
QSettings settings;
|
||||
QString difficulty =
|
||||
settings.value(QZSettings::peloton_difficulty, QZSettings::default_peloton_difficulty).toString();
|
||||
bool powerZoneFound = false;
|
||||
|
||||
for (int i = 0; i < targetMetrics.count(); i++) {
|
||||
QJsonObject targetMetric = targetMetrics.at(i).toObject();
|
||||
QJsonObject offsets = targetMetric[QStringLiteral("offsets")].toObject();
|
||||
QJsonArray metrics = targetMetric[QStringLiteral("metrics")].toArray();
|
||||
|
||||
// Find resistance and cadence metrics
|
||||
int lowerResistance = 0, upperResistance = 0, lowerCadence = 0, upperCadence = 0;
|
||||
for (QJsonValue metricValue : metrics) {
|
||||
QJsonObject metric = metricValue.toObject();
|
||||
QString name = metric[QStringLiteral("name")].toString();
|
||||
|
||||
if (name == QStringLiteral("resistance")) {
|
||||
lowerResistance = metric[QStringLiteral("lower")].toInt();
|
||||
upperResistance = metric[QStringLiteral("upper")].toInt();
|
||||
} else if (name == QStringLiteral("cadence")) {
|
||||
lowerCadence = metric[QStringLiteral("lower")].toInt();
|
||||
upperCadence = metric[QStringLiteral("upper")].toInt();
|
||||
} else if (name == QStringLiteral("power_zone")) {
|
||||
powerZoneFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(powerZoneFound == true)
|
||||
break;
|
||||
|
||||
trainrow r;
|
||||
int duration = offsets[QStringLiteral("end")].toInt() - offsets[QStringLiteral("start")].toInt();
|
||||
if (i != 0) {
|
||||
// offsets have a 1s gap
|
||||
duration++;
|
||||
}
|
||||
|
||||
r.lower_requested_peloton_resistance = lowerResistance;
|
||||
r.upper_requested_peloton_resistance = upperResistance;
|
||||
r.lower_cadence = lowerCadence;
|
||||
r.upper_cadence = upperCadence;
|
||||
|
||||
r.average_requested_peloton_resistance =
|
||||
(r.lower_requested_peloton_resistance + r.upper_requested_peloton_resistance) / 2;
|
||||
r.average_cadence = (r.lower_cadence + r.upper_cadence) / 2;
|
||||
|
||||
if (bluetoothManager && bluetoothManager->device()) {
|
||||
if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE) {
|
||||
r.lower_resistance = ((bike *)bluetoothManager->device())
|
||||
->pelotonToBikeResistance(lowerResistance);
|
||||
r.upper_resistance = ((bike *)bluetoothManager->device())
|
||||
->pelotonToBikeResistance(upperResistance);
|
||||
r.average_resistance = ((bike *)bluetoothManager->device())
|
||||
->pelotonToBikeResistance(r.average_requested_peloton_resistance);
|
||||
} else if (bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL) {
|
||||
r.lower_resistance =
|
||||
((elliptical *)bluetoothManager->device())
|
||||
->pelotonToEllipticalResistance(lowerResistance);
|
||||
r.upper_resistance =
|
||||
((elliptical *)bluetoothManager->device())
|
||||
->pelotonToEllipticalResistance(upperResistance);
|
||||
r.average_resistance = ((elliptical *)bluetoothManager->device())
|
||||
->pelotonToEllipticalResistance(r.average_requested_peloton_resistance);
|
||||
}
|
||||
}
|
||||
|
||||
// Set for compatibility
|
||||
if (difficulty == QStringLiteral("average")) {
|
||||
r.resistance = r.average_resistance;
|
||||
r.requested_peloton_resistance = r.average_requested_peloton_resistance;
|
||||
r.cadence = r.average_cadence;
|
||||
} else if (difficulty == QStringLiteral("upper")) {
|
||||
r.resistance = r.upper_resistance;
|
||||
r.requested_peloton_resistance = r.upper_requested_peloton_resistance;
|
||||
r.cadence = r.upper_cadence;
|
||||
} else { // lower
|
||||
r.resistance = r.lower_resistance;
|
||||
r.requested_peloton_resistance = r.lower_requested_peloton_resistance;
|
||||
r.cadence = r.lower_cadence;
|
||||
}
|
||||
|
||||
// Compact rows in the training program
|
||||
if (i == 0 ||
|
||||
(r.lower_requested_peloton_resistance != trainrows.last().lower_requested_peloton_resistance ||
|
||||
r.upper_requested_peloton_resistance != trainrows.last().upper_requested_peloton_resistance ||
|
||||
r.lower_cadence != trainrows.last().lower_cadence ||
|
||||
r.upper_cadence != trainrows.last().upper_cadence)) {
|
||||
r.duration = QTime(0, 0, 0).addSecs(duration);
|
||||
trainrows.append(r);
|
||||
} else {
|
||||
trainrows.last().duration = trainrows.last().duration.addSecs(duration);
|
||||
}
|
||||
}
|
||||
|
||||
foreach(trainrow r, trainrows) {
|
||||
qDebug() << r.duration << r.average_cadence << r.average_resistance;
|
||||
}
|
||||
|
||||
QJsonArray targetMetricsList = target_metrics_performance_data[QStringLiteral("target_metrics")].toArray();
|
||||
|
||||
bool atLeastOnePower = false;
|
||||
if (trainrows.empty() && !targetMetricsList.isEmpty() &&
|
||||
bluetoothManager->device()->deviceType() != bluetoothdevice::ROWING &&
|
||||
bluetoothManager->device()->deviceType() != bluetoothdevice::TREADMILL) {
|
||||
|
||||
int lastEnd = 60;
|
||||
for (QJsonValue metric : targetMetricsList) {
|
||||
QJsonObject metricObj = metric.toObject();
|
||||
QJsonObject offsets = metricObj[QStringLiteral("offsets")].toObject();
|
||||
int start = offsets[QStringLiteral("start")].toInt();
|
||||
int end = offsets[QStringLiteral("end")].toInt();
|
||||
int len = end - start + 1;
|
||||
|
||||
// Check if there's a gap from previous segment
|
||||
if (!trainrows.isEmpty()) {
|
||||
int prevEnd = start - 1; // Expected previous end
|
||||
if (lastEnd < prevEnd) {
|
||||
// Add gap row
|
||||
trainrow gapRow;
|
||||
gapRow.duration = QTime(0, (prevEnd - lastEnd + 1) / 60, (prevEnd - lastEnd + 1) % 60, 0);
|
||||
gapRow.power = -1;
|
||||
qDebug() << "adding a gap row of " << gapRow.duration << " seconds because start was " << start << " and end " << lastEnd;
|
||||
trainrows.append(gapRow);
|
||||
}
|
||||
}
|
||||
|
||||
lastEnd = end;
|
||||
|
||||
QJsonArray metricsArray = metricObj[QStringLiteral("metrics")].toArray();
|
||||
if (!metricsArray.isEmpty()) {
|
||||
QJsonObject powerMetric = metricsArray[0].toObject();
|
||||
int zone = powerMetric[QStringLiteral("lower")].toInt();
|
||||
|
||||
trainrow r;
|
||||
r.duration = QTime(0, len / 60, len % 60, 0);
|
||||
|
||||
switch(zone) {
|
||||
case 1: // Zone 1 / Recovery
|
||||
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 0.50;
|
||||
break;
|
||||
case 2: // Zone 2
|
||||
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 0.66;
|
||||
break;
|
||||
case 3: // Zone 3
|
||||
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 0.83;
|
||||
break;
|
||||
case 4: // Zone 4 / Sweet Spot
|
||||
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 0.98;
|
||||
break;
|
||||
case 5: // Zone 5
|
||||
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 1.13;
|
||||
break;
|
||||
case 6: // Zone 6
|
||||
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 1.35;
|
||||
break;
|
||||
case 7: // Zone 7
|
||||
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 1.5;
|
||||
break;
|
||||
default:
|
||||
r.power = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (r.power != -1) {
|
||||
atLeastOnePower = true;
|
||||
}
|
||||
trainrows.append(r);
|
||||
qDebug() << r.duration << "power" << r.power << "zone" << zone;
|
||||
}
|
||||
}
|
||||
|
||||
// If this list doesn't have anything useful for this session
|
||||
if (!atLeastOnePower) {
|
||||
trainrows.clear();
|
||||
}
|
||||
}
|
||||
} else if (!target_metrics_performance_data.isEmpty() && bluetoothManager->device() &&
|
||||
bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL) {
|
||||
double miles = 1;
|
||||
bool treadmill_force_speed =
|
||||
@@ -1437,10 +1634,12 @@ void peloton::performance_onfinish(QNetworkReply *reply) {
|
||||
paceintensity_lower = oo[QStringLiteral("lower")].toInt();
|
||||
paceintensity_upper = oo[QStringLiteral("upper")].toInt();
|
||||
paceintensity_avg = ((paceintensity_upper - paceintensity_lower) / 2.0) + paceintensity_lower;
|
||||
if(paceintensity_lower < 7)
|
||||
speed_lower = treadmill_pace[paceintensity_lower].levels[peloton_treadmill_level].speed;
|
||||
speed_upper = treadmill_pace[paceintensity_upper].levels[peloton_treadmill_level].speed;
|
||||
speed_average = (((speed_upper - speed_lower) / 2.0) + speed_lower) * miles;
|
||||
if(paceintensity_lower < 7) {
|
||||
speed_lower = treadmill_pace[paceintensity_lower].levels[peloton_treadmill_level].slow_pace;
|
||||
speed_upper = treadmill_pace[paceintensity_upper].levels[peloton_treadmill_level].fast_pace;
|
||||
speed_average = (((speed_upper - speed_lower) / 2.0) + speed_lower) * miles;
|
||||
miles = 1; // the pace intensity are always in km/h
|
||||
}
|
||||
}
|
||||
}
|
||||
int offset_start = offset[QStringLiteral("start")].toInt();
|
||||
@@ -1647,74 +1846,75 @@ double peloton::rowerpaceToSpeed(double pace) {
|
||||
}
|
||||
|
||||
void peloton::getInstructor(const QString &instructor_id) {
|
||||
QSettings settings;
|
||||
connect(mgr, &QNetworkAccessManager::finished, this, &peloton::instructor_onfinish);
|
||||
|
||||
QUrl url(QStringLiteral("https://api.onepeloton.com/api/instructor/") + instructor_id);
|
||||
QUrl url(QStringLiteral("https://api-3p.onepeloton.com/api/v1/instructor/") + instructor_id);
|
||||
qDebug() << "peloton::getInstructor" << url;
|
||||
QNetworkRequest request(url);
|
||||
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("qdomyos-zwift"));
|
||||
RAWHEADER
|
||||
|
||||
mgr->get(request);
|
||||
}
|
||||
|
||||
void peloton::getRide(const QString &ride_id) {
|
||||
QSettings settings;
|
||||
connect(mgr, &QNetworkAccessManager::finished, this, &peloton::ride_onfinish);
|
||||
|
||||
QUrl url(QStringLiteral("https://api.onepeloton.com/api/ride/") + ride_id +
|
||||
QUrl url(QStringLiteral("https://api-3p.onepeloton.com/api/v1/ride/") + ride_id +
|
||||
QStringLiteral("/details?stream_source=multichannel"));
|
||||
qDebug() << "peloton::getRide" << url;
|
||||
QNetworkRequest request(url);
|
||||
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("qdomyos-zwift"));
|
||||
RAWHEADER
|
||||
|
||||
mgr->get(request);
|
||||
}
|
||||
|
||||
void peloton::getPerformance(const QString &workout) {
|
||||
QSettings settings;
|
||||
connect(mgr, &QNetworkAccessManager::finished, this, &peloton::performance_onfinish);
|
||||
|
||||
QUrl url(QStringLiteral("https://api.onepeloton.com/api/workout/") + workout +
|
||||
QUrl url(QStringLiteral("https://api-3p.onepeloton.com/api/v1/workout/") + workout +
|
||||
QStringLiteral("/performance_graph?every_n=") + QString::number(peloton_workout_second_resolution));
|
||||
qDebug() << "peloton::getPerformance" << url;
|
||||
QNetworkRequest request(url);
|
||||
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("qdomyos-zwift"));
|
||||
RAWHEADER
|
||||
|
||||
mgr->get(request);
|
||||
}
|
||||
|
||||
void peloton::getWorkout(const QString &workout) {
|
||||
QSettings settings;
|
||||
connect(mgr, &QNetworkAccessManager::finished, this, &peloton::workout_onfinish);
|
||||
|
||||
QUrl url(QStringLiteral("https://api.onepeloton.com/api/workout/") + workout);
|
||||
QUrl url(QStringLiteral("https://api-3p.onepeloton.com/api/v1/workout/") + workout);
|
||||
qDebug() << "peloton::getWorkout" << url;
|
||||
QNetworkRequest request(url);
|
||||
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("qdomyos-zwift"));
|
||||
RAWHEADER
|
||||
|
||||
mgr->get(request);
|
||||
}
|
||||
|
||||
void peloton::getSummary(const QString &workout) {
|
||||
QSettings settings;
|
||||
connect(mgr, &QNetworkAccessManager::finished, this, &peloton::summary_onfinish);
|
||||
|
||||
QUrl url(QStringLiteral("https://api.onepeloton.com/api/workout/") + workout + QStringLiteral("/summary"));
|
||||
QUrl url(QStringLiteral("https://api-3p.onepeloton.com/api/v1/workout/") + workout + QStringLiteral("/summary"));
|
||||
qDebug() << "peloton::getSummary" << url;
|
||||
QNetworkRequest request(url);
|
||||
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("qdomyos-zwift"));
|
||||
RAWHEADER
|
||||
|
||||
mgr->get(request);
|
||||
}
|
||||
|
||||
void peloton::getWorkoutList(int num) {
|
||||
Q_UNUSED(num)
|
||||
QSettings settings;
|
||||
// if (num == 0) { //NOTE: clang-analyzer-deadcode.DeadStores
|
||||
// num = this->total_workout;
|
||||
// }
|
||||
@@ -1727,16 +1927,276 @@ void peloton::getWorkoutList(int num) {
|
||||
|
||||
int current_page = 0;
|
||||
|
||||
QUrl url(QStringLiteral("https://api.onepeloton.com/api/user/") + user_id +
|
||||
QUrl url(QStringLiteral("https://api-3p.onepeloton.com/api/v1/user") +
|
||||
QStringLiteral("/workouts?sort_by=-created&page=") + QString::number(current_page) +
|
||||
QStringLiteral("&limit=") + QString::number(limit));
|
||||
qDebug() << "peloton::getWorkoutList" << url;
|
||||
QNetworkRequest request(url);
|
||||
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("qdomyos-zwift"));
|
||||
RAWHEADER
|
||||
|
||||
mgr->get(request);
|
||||
}
|
||||
|
||||
void peloton::setTestMode(bool test) { testMode = test; }
|
||||
|
||||
void peloton::onPelotonGranted() {
|
||||
|
||||
pelotonAuthWebVisible = false;
|
||||
emit pelotonWebVisibleChanged(pelotonAuthWebVisible);
|
||||
QSettings settings;
|
||||
settings.setValue(QZSettings::peloton_accesstoken, pelotonOAuth->token());
|
||||
settings.setValue(QZSettings::peloton_refreshtoken, pelotonOAuth->refreshToken());
|
||||
settings.setValue(QZSettings::peloton_lastrefresh, QDateTime::currentDateTime());
|
||||
qDebug() << QStringLiteral("peloton authenticathed") << pelotonOAuth->token() << pelotonOAuth->refreshToken();
|
||||
peloton_refreshtoken();
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setPelotonPopupVisible(true);
|
||||
}
|
||||
|
||||
void peloton::onPelotonAuthorizeWithBrowser(const QUrl &url) {
|
||||
|
||||
// ui->textBrowser->append(tr("Open with browser:") + url.toString());
|
||||
QSettings settings;
|
||||
bool strava_auth_external_webbrowser =
|
||||
settings.value(QZSettings::strava_auth_external_webbrowser, QZSettings::default_strava_auth_external_webbrowser)
|
||||
.toBool();
|
||||
#if defined(Q_OS_WIN) || (defined(Q_OS_MAC) && !defined(Q_OS_IOS))
|
||||
strava_auth_external_webbrowser = true;
|
||||
#endif
|
||||
pelotonAuthUrl = url.toString();
|
||||
emit pelotonAuthUrlChanged(pelotonAuthUrl);
|
||||
|
||||
if (strava_auth_external_webbrowser)
|
||||
QDesktopServices::openUrl(url);
|
||||
else {
|
||||
pelotonAuthWebVisible = true;
|
||||
emit pelotonWebVisibleChanged(pelotonAuthWebVisible);
|
||||
}
|
||||
}
|
||||
|
||||
void peloton::replyDataReceived(const QByteArray &v) {
|
||||
|
||||
qDebug() << v;
|
||||
|
||||
QByteArray data;
|
||||
QSettings settings;
|
||||
QString s(v);
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(s.toUtf8());
|
||||
settings.setValue(QZSettings::peloton_accesstoken, jsonResponse[QStringLiteral("access_token")]);
|
||||
settings.setValue(QZSettings::peloton_refreshtoken, jsonResponse[QStringLiteral("refresh_token")]);
|
||||
settings.setValue(QZSettings::peloton_expires, jsonResponse[QStringLiteral("expires_at")]);
|
||||
|
||||
qDebug() << jsonResponse[QStringLiteral("access_token")] << jsonResponse[QStringLiteral("refresh_token")]
|
||||
<< jsonResponse[QStringLiteral("expires_at")];
|
||||
|
||||
QString urlstr = QStringLiteral("https://www.peloton.com/oauth/token?");
|
||||
QUrlQuery params;
|
||||
params.addQueryItem(QStringLiteral("client_id"), QStringLiteral(PELOTON_CLIENT_ID_S));
|
||||
#ifdef PELOTON_SECRET_KEY
|
||||
#define _STR(x) #x
|
||||
#define STRINGIFY(x) _STR(x)
|
||||
params.addQueryItem("client_secret", STRINGIFY(PELOTON_SECRET_KEY));
|
||||
#endif
|
||||
|
||||
params.addQueryItem(QStringLiteral("code"), peloton_code);
|
||||
data.append(params.query(QUrl::FullyEncoded).toUtf8());
|
||||
|
||||
// trade-in the temporary access code retrieved by the Call-Back URL for the finale token
|
||||
QUrl url = QUrl(urlstr);
|
||||
|
||||
QNetworkRequest request = QNetworkRequest(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-www-form-urlencoded"));
|
||||
|
||||
// now get the final token - but ignore errors
|
||||
if (manager) {
|
||||
|
||||
delete manager;
|
||||
manager = 0;
|
||||
}
|
||||
manager = new QNetworkAccessManager(this);
|
||||
// connect(manager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> & )), this,
|
||||
// SLOT(onSslErrors(QNetworkReply*, const QList<QSslError> & ))); connect(manager,
|
||||
// SIGNAL(finished(QNetworkReply*)), this, SLOT(networkRequestFinished(QNetworkReply*)));
|
||||
manager->post(request, data);
|
||||
}
|
||||
|
||||
void peloton::onSslErrors(QNetworkReply *reply, const QList<QSslError> &error) {
|
||||
|
||||
reply->ignoreSslErrors();
|
||||
qDebug() << QStringLiteral("peloton::onSslErrors") << error;
|
||||
}
|
||||
|
||||
void peloton::networkRequestFinished(QNetworkReply *reply) {
|
||||
|
||||
QSettings settings;
|
||||
|
||||
// we can handle SSL handshake errors, if we got here then some kind of protocol was agreed
|
||||
if (reply->error() == QNetworkReply::NoError || reply->error() == QNetworkReply::SslHandshakeFailedError) {
|
||||
|
||||
QByteArray payload = reply->readAll(); // JSON
|
||||
QString refresh_token;
|
||||
QString access_token;
|
||||
|
||||
// parse the response and extract the tokens, pretty much the same for all services
|
||||
// although polar choose to also pass a user id, which is needed for future calls
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument document = QJsonDocument::fromJson(payload, &parseError);
|
||||
if (parseError.error == QJsonParseError::NoError) {
|
||||
refresh_token = document[QStringLiteral("refresh_token")].toString();
|
||||
access_token = document[QStringLiteral("access_token")].toString();
|
||||
}
|
||||
|
||||
settings.setValue(QZSettings::peloton_accesstoken, access_token);
|
||||
settings.setValue(QZSettings::peloton_refreshtoken, refresh_token);
|
||||
settings.setValue(QZSettings::peloton_lastrefresh, QDateTime::currentDateTime());
|
||||
|
||||
qDebug() << access_token << refresh_token;
|
||||
|
||||
} else {
|
||||
|
||||
// general error getting response
|
||||
QString error =
|
||||
QString(tr("Error retrieving access token, %1 (%2)")).arg(reply->errorString()).arg(reply->error());
|
||||
qDebug() << error << reply->url() << reply->readAll();
|
||||
}
|
||||
}
|
||||
|
||||
void peloton::callbackReceived(const QVariantMap &values) {
|
||||
qDebug() << QStringLiteral("peloton::callbackReceived") << values;
|
||||
if (!values.value(QZSettings::peloton_code).toString().isEmpty()) {
|
||||
peloton_code = values.value(QZSettings::peloton_code).toString();
|
||||
|
||||
qDebug() << peloton_code;
|
||||
}
|
||||
}
|
||||
|
||||
QOAuth2AuthorizationCodeFlow *peloton::peloton_connect() {
|
||||
if (manager) {
|
||||
|
||||
delete manager;
|
||||
manager = nullptr;
|
||||
}
|
||||
if (pelotonOAuth) {
|
||||
|
||||
delete pelotonOAuth;
|
||||
pelotonOAuth = nullptr;
|
||||
}
|
||||
if (pelotonReplyHandler) {
|
||||
|
||||
delete pelotonReplyHandler;
|
||||
pelotonReplyHandler = nullptr;
|
||||
}
|
||||
manager = new QNetworkAccessManager(this);
|
||||
OAuth2Parameter parameter;
|
||||
pelotonOAuth = new QOAuth2AuthorizationCodeFlow(manager, this);
|
||||
pelotonOAuth->setScope(QStringLiteral("openid offline_access 3p.profile:r 3p.workout:r"));
|
||||
pelotonOAuth->setClientIdentifier(QStringLiteral(PELOTON_CLIENT_ID_S));
|
||||
pelotonOAuth->setAuthorizationUrl(QUrl(QStringLiteral("https://auth.onepeloton.com/oauth/authorize")));
|
||||
pelotonOAuth->setAccessTokenUrl(QUrl(QStringLiteral("https://auth.onepeloton.com/oauth/token")));
|
||||
pelotonOAuth->setModifyParametersFunction(
|
||||
buildModifyParametersFunction(QUrl(QLatin1String("")), QUrl(QLatin1String(""))));
|
||||
pelotonReplyHandler = new QOAuthHttpServerReplyHandler(QHostAddress(QStringLiteral("127.0.0.1")), 18080, this);
|
||||
connect(pelotonReplyHandler, &QOAuthHttpServerReplyHandler::replyDataReceived, this, &peloton::replyDataReceived);
|
||||
connect(pelotonReplyHandler, &QOAuthHttpServerReplyHandler::callbackReceived, this, &peloton::callbackReceived);
|
||||
|
||||
pelotonOAuth->setReplyHandler(pelotonReplyHandler);
|
||||
|
||||
return pelotonOAuth;
|
||||
}
|
||||
|
||||
void peloton::peloton_connect_clicked() {
|
||||
QLoggingCategory::setFilterRules(QStringLiteral("qt.networkauth.*=true"));
|
||||
|
||||
peloton_connect();
|
||||
connect(pelotonOAuth, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &peloton::onPelotonAuthorizeWithBrowser);
|
||||
connect(pelotonOAuth, &QOAuth2AuthorizationCodeFlow::granted, this, &peloton::onPelotonGranted);
|
||||
|
||||
pelotonOAuth->grant();
|
||||
// qDebug() <<
|
||||
// QAbstractOAuth2::post("https://www.peloton.com/oauth/authorize?client_id=7976&scope=activity:read_all,activity:write&redirect_uri=http://127.0.0.1&response_type=code&approval_prompt=force");
|
||||
}
|
||||
|
||||
QAbstractOAuth::ModifyParametersFunction peloton::buildModifyParametersFunction(const QUrl &clientIdentifier, const QUrl &clientIdentifierSharedKey) {
|
||||
return [clientIdentifier, clientIdentifierSharedKey](QAbstractOAuth::Stage stage, QVariantMap *parameters) {
|
||||
if (stage == QAbstractOAuth::Stage::RequestingAuthorization) {
|
||||
parameters->insert(QStringLiteral("audience"), QStringLiteral("https://api-3p.onepeloton.com/"));
|
||||
parameters->insert(QStringLiteral("responseType"), QStringLiteral("code")); /* Request refresh token*/
|
||||
parameters->insert(QStringLiteral("approval_prompt"),
|
||||
QStringLiteral("force")); /* force user check scope again */
|
||||
QByteArray code = parameters->value(QStringLiteral("code")).toByteArray();
|
||||
// DON'T TOUCH THIS LINE, THANKS Roberto Viola
|
||||
(*parameters)[QStringLiteral("code")] = QUrl::fromPercentEncoding(code); // NOTE: Old code replaced by
|
||||
}
|
||||
if (stage == QAbstractOAuth::Stage::RefreshingAccessToken) {
|
||||
parameters->insert(QStringLiteral("client_id"), clientIdentifier);
|
||||
parameters->insert(QStringLiteral("client_secret"), clientIdentifierSharedKey);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void peloton::peloton_refreshtoken() {
|
||||
|
||||
QSettings settings;
|
||||
// QUrlQuery params; //NOTE: clazy-unuse-non-tirial-variable
|
||||
|
||||
if (settings.value(QZSettings::peloton_refreshtoken).toString().isEmpty()) {
|
||||
|
||||
peloton_connect();
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkRequest request(QUrl(QStringLiteral("https://auth.onepeloton.com/oauth/token?")));
|
||||
request.setRawHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
// set params
|
||||
QString data;
|
||||
data += QStringLiteral("client_id=" PELOTON_CLIENT_ID_S);
|
||||
data += QStringLiteral("&refresh_token=") + settings.value(QZSettings::peloton_refreshtoken).toString();
|
||||
data += QStringLiteral("&grant_type=refresh_token");
|
||||
|
||||
// make request
|
||||
if (manager) {
|
||||
|
||||
delete manager;
|
||||
manager = nullptr;
|
||||
}
|
||||
manager = new QNetworkAccessManager(this);
|
||||
QNetworkReply *reply = manager->post(request, data.toLatin1());
|
||||
|
||||
// blocking request
|
||||
QEventLoop loop;
|
||||
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
loop.exec();
|
||||
|
||||
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
qDebug() << QStringLiteral("HTTP response code: ") << statusCode;
|
||||
|
||||
// oops, no dice
|
||||
if (reply->error() != 0) {
|
||||
qDebug() << QStringLiteral("Got error") << reply->errorString().toStdString().c_str();
|
||||
return;
|
||||
}
|
||||
|
||||
// lets extract the access token, and possibly a new refresh token
|
||||
QByteArray r = reply->readAll();
|
||||
qDebug() << QStringLiteral("Got response:") << r.data();
|
||||
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument document = QJsonDocument::fromJson(r, &parseError);
|
||||
|
||||
// failed to parse result !?
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qDebug() << tr("JSON parser error") << parseError.errorString();
|
||||
}
|
||||
|
||||
QString access_token = document[QStringLiteral("access_token")].toString();
|
||||
QString refresh_token = document[QStringLiteral("refresh_token")].toString();
|
||||
|
||||
settings.setValue(QZSettings::peloton_accesstoken, access_token);
|
||||
settings.setValue(QZSettings::peloton_refreshtoken, refresh_token);
|
||||
settings.setValue(QZSettings::peloton_lastrefresh, QDateTime::currentDateTime());
|
||||
|
||||
homeform::singleton()->setToastRequested("Peloton Login OK!");
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define PELOTON_H
|
||||
|
||||
#include "bluetooth.h"
|
||||
#include "OAuth2.h"
|
||||
#include "powerzonepack.h"
|
||||
#include "trainprogram.h"
|
||||
#include <QAbstractOAuth2>
|
||||
@@ -24,6 +25,14 @@
|
||||
#include "filedownloader.h"
|
||||
#include "homefitnessbuddy.h"
|
||||
|
||||
#if defined(WIN32)
|
||||
#pragma message("DEFINE PELOTON_SECRET_KEY!!!")
|
||||
#else
|
||||
#warning "DEFINE PELOTON_SECRET_KEY!!!"
|
||||
#endif
|
||||
|
||||
#define PELOTON_CLIENT_ID_S STRINGIFY(PELOTON_SECRET_KEY)
|
||||
|
||||
class peloton : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
@@ -50,6 +59,10 @@ class peloton : public QObject {
|
||||
int current_pedaling_duration = 0;
|
||||
qint64 start_time = 0;
|
||||
|
||||
// OAuth
|
||||
QString pelotonAuthUrl;
|
||||
bool pelotonAuthWebVisible;
|
||||
|
||||
void setTestMode(bool test);
|
||||
|
||||
bool isWorkoutInProgress() {
|
||||
@@ -58,7 +71,7 @@ class peloton : public QObject {
|
||||
|
||||
private:
|
||||
_PELOTON_API current_api = peloton_api;
|
||||
const int peloton_workout_second_resolution = 10;
|
||||
const int peloton_workout_second_resolution = 1;
|
||||
bool peloton_credentials_wrong = false;
|
||||
QNetworkAccessManager *mgr = nullptr;
|
||||
|
||||
@@ -84,6 +97,17 @@ class peloton : public QObject {
|
||||
|
||||
bool testMode = false;
|
||||
|
||||
//OAuth
|
||||
QOAuth2AuthorizationCodeFlow *pelotonOAuth = nullptr;
|
||||
QNetworkAccessManager *manager = nullptr;
|
||||
QOAuthHttpServerReplyHandler *pelotonReplyHandler = nullptr;
|
||||
QString peloton_code;
|
||||
QOAuth2AuthorizationCodeFlow *peloton_connect();
|
||||
void peloton_refreshtoken();
|
||||
QNetworkReply *replyPeloton;
|
||||
QAbstractOAuth::ModifyParametersFunction buildModifyParametersFunction(const QUrl &clientIdentifier,
|
||||
const QUrl &clientIdentifierSharedKey);
|
||||
|
||||
// rowers
|
||||
double rowerpaceToSpeed(double pace);
|
||||
typedef struct _peloton_rower_pace_intensities_level {
|
||||
@@ -118,6 +142,9 @@ class peloton : public QObject {
|
||||
|
||||
_peloton_treadmill_pace_intensities treadmill_pace[7];
|
||||
|
||||
public slots:
|
||||
void peloton_connect_clicked();
|
||||
|
||||
private slots:
|
||||
void login_onfinish(QNetworkReply *reply);
|
||||
void workoutlist_onfinish(QNetworkReply *reply);
|
||||
@@ -130,6 +157,14 @@ class peloton : public QObject {
|
||||
void hfb_trainrows(QList<trainrow> *list);
|
||||
void pzp_loginState(bool ok);
|
||||
|
||||
// OAuth
|
||||
void onPelotonGranted();
|
||||
void onPelotonAuthorizeWithBrowser(const QUrl &url);
|
||||
void replyDataReceived(const QByteArray &v);
|
||||
void onSslErrors(QNetworkReply *reply, const QList<QSslError> &error);
|
||||
void networkRequestFinished(QNetworkReply *reply);
|
||||
void callbackReceived(const QVariantMap &values);
|
||||
|
||||
void startEngine();
|
||||
|
||||
signals:
|
||||
@@ -137,6 +172,8 @@ class peloton : public QObject {
|
||||
void pzpLoginState(bool ok);
|
||||
void workoutStarted(QString name, QString instructor);
|
||||
void workoutChanged(QString name, QString instructor);
|
||||
void pelotonAuthUrlChanged(QString value);
|
||||
void pelotonWebVisibleChanged(bool value);
|
||||
};
|
||||
|
||||
#endif // PELOTON_H
|
||||
|
||||
@@ -31,7 +31,7 @@ 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 -ldbghelp -L$$PWD
|
||||
|
||||
QML_IMPORT_NAME = org.cagnulein.qdomyoszwift
|
||||
QML_IMPORT_MAJOR_VERSION = 1
|
||||
@@ -95,6 +95,7 @@ SOURCES += \
|
||||
$$PWD/devices/technogymbike/technogymbike.cpp \
|
||||
$$PWD/devices/trxappgateusbelliptical/trxappgateusbelliptical.cpp \
|
||||
$$PWD/devices/trxappgateusbrower/trxappgateusbrower.cpp \
|
||||
$$PWD/logwriter.cpp \
|
||||
$$PWD/mqtt/qmqttauthenticationproperties.cpp \
|
||||
$$PWD/mqtt/qmqttclient.cpp \
|
||||
$$PWD/mqtt/qmqttconnection.cpp \
|
||||
@@ -334,6 +335,7 @@ INCLUDEPATH += fit-sdk/ devices/
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/EventHandler.h \
|
||||
$$PWD/OAuth2.h \
|
||||
$$PWD/devices/antbike/antbike.h \
|
||||
$$PWD/devices/crossrope/crossrope.h \
|
||||
$$PWD/devices/cycleopsphantombike/cycleopsphantombike.h \
|
||||
@@ -352,6 +354,7 @@ HEADERS += \
|
||||
$$PWD/devices/trxappgateusbelliptical/trxappgateusbelliptical.h \
|
||||
$$PWD/devices/trxappgateusbrower/trxappgateusbrower.h \
|
||||
$$PWD/ergtable.h \
|
||||
$$PWD/logwriter.h \
|
||||
$$PWD/osc.h \
|
||||
$$PWD/oscpp/client.hpp \
|
||||
$$PWD/oscpp/detail/endian.hpp \
|
||||
@@ -952,4 +955,4 @@ INCLUDEPATH += purchasing/inapp
|
||||
|
||||
WINRT_MANIFEST = AppxManifest.xml
|
||||
|
||||
VERSION = 2.18.18
|
||||
VERSION = 2.18.20
|
||||
|
||||
@@ -114,5 +114,6 @@
|
||||
<file>StaticAccordionElement.qml</file>
|
||||
<file>inner_templates/chartjs/dotreadmillchartlive.js</file>
|
||||
<file>inner_templates/chartjs/treadmillchartlive.htm</file>
|
||||
<file>WebPelotonAuth.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -764,6 +764,16 @@ const QString QZSettings::zwiftplay_swap = QStringLiteral("zwiftplay_swap");
|
||||
const QString QZSettings::gears_zwift_ratio = QStringLiteral("gears_zwift_ratio");
|
||||
const QString QZSettings::domyos_bike_500_profile_v2 = QStringLiteral("domyos_bike_500_profile_v2");
|
||||
const QString QZSettings::gears_offset = QStringLiteral("gears_offset");
|
||||
const QString QZSettings::peloton_accesstoken = QStringLiteral("peloton_accesstoken");
|
||||
const QString QZSettings::default_peloton_accesstoken = QStringLiteral("");
|
||||
const QString QZSettings::peloton_refreshtoken = QStringLiteral("peloton_refreshtoken");
|
||||
const QString QZSettings::default_peloton_refreshtoken = QStringLiteral("");
|
||||
const QString QZSettings::peloton_lastrefresh = QStringLiteral("peloton_lastrefresh");
|
||||
const QString QZSettings::default_peloton_lastrefresh = QStringLiteral("");
|
||||
const QString QZSettings::peloton_expires = QStringLiteral("peloton_expires");
|
||||
const QString QZSettings::default_peloton_expires = QStringLiteral("");
|
||||
const QString QZSettings::peloton_code = QStringLiteral("peloton_code");
|
||||
const QString QZSettings::default_peloton_code = QStringLiteral("");
|
||||
const QString QZSettings::proform_carbon_tl_PFTL59720 = QStringLiteral("proform_carbon_tl_PFTL59720");
|
||||
const QString QZSettings::proform_treadmill_sport_70 = QStringLiteral("proform_treadmill_sport_70");
|
||||
const QString QZSettings::peloton_date_format = QStringLiteral("peloton_date_format");
|
||||
@@ -875,8 +885,10 @@ const QString QZSettings::proform_bike_PFEVEX71316_0 = QStringLiteral("proform_b
|
||||
|
||||
const QString QZSettings::real_inclination_to_virtual_treamill_bridge = QStringLiteral("real_inclination_to_virtual_treamill_bridge");
|
||||
const QString QZSettings::stryd_inclination_instead_treadmill = QStringLiteral("stryd_inclination_instead_treadmill");
|
||||
const QString QZSettings::domyos_elliptical_fmts = QStringLiteral("domyos_elliptical_fmts");
|
||||
const QString QZSettings::proform_xbike = QStringLiteral("proform_xbike");
|
||||
|
||||
const uint32_t allSettingsCount = 726;
|
||||
const uint32_t allSettingsCount = 733;
|
||||
|
||||
QVariant allSettings[allSettingsCount][2] = {
|
||||
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
|
||||
@@ -1529,6 +1541,11 @@ QVariant allSettings[allSettingsCount][2] = {
|
||||
{QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio},
|
||||
{QZSettings::domyos_bike_500_profile_v2, QZSettings::default_domyos_bike_500_profile_v2},
|
||||
{QZSettings::gears_offset, QZSettings::default_gears_offset},
|
||||
{QZSettings::peloton_accesstoken, QZSettings::default_peloton_accesstoken},
|
||||
{QZSettings::peloton_refreshtoken, QZSettings::default_peloton_refreshtoken},
|
||||
{QZSettings::peloton_lastrefresh, QZSettings::default_peloton_lastrefresh},
|
||||
{QZSettings::peloton_expires, QZSettings::default_peloton_expires},
|
||||
{QZSettings::peloton_code, QZSettings::default_peloton_code},
|
||||
{QZSettings::proform_carbon_tl_PFTL59720, QZSettings::default_proform_carbon_tl_PFTL59720},
|
||||
{QZSettings::proform_treadmill_sport_70, QZSettings::default_proform_treadmill_sport_70},
|
||||
{QZSettings::peloton_date_format, QZSettings::default_peloton_date_format},
|
||||
@@ -1617,6 +1634,8 @@ QVariant allSettings[allSettingsCount][2] = {
|
||||
{QZSettings::proform_bike_PFEVEX71316_0, QZSettings::default_proform_bike_PFEVEX71316_0},
|
||||
{QZSettings::real_inclination_to_virtual_treamill_bridge, QZSettings::default_real_inclination_to_virtual_treamill_bridge},
|
||||
{QZSettings::stryd_inclination_instead_treadmill, QZSettings::default_stryd_inclination_instead_treadmill},
|
||||
{QZSettings::domyos_elliptical_fmts, QZSettings::default_domyos_elliptical_fmts},
|
||||
{QZSettings::proform_xbike, QZSettings::default_proform_xbike},
|
||||
};
|
||||
|
||||
void QZSettings::qDebugAllSettings(bool showDefaults) {
|
||||
|
||||
@@ -2138,6 +2138,21 @@ class QZSettings {
|
||||
static const QString gears_offset;
|
||||
static constexpr double default_gears_offset = 0.0;
|
||||
|
||||
static const QString peloton_accesstoken;
|
||||
static const QString default_peloton_accesstoken;
|
||||
|
||||
static const QString peloton_refreshtoken;
|
||||
static const QString default_peloton_refreshtoken;
|
||||
|
||||
static const QString peloton_lastrefresh;
|
||||
static const QString default_peloton_lastrefresh;
|
||||
|
||||
static const QString peloton_expires;
|
||||
static const QString default_peloton_expires;
|
||||
|
||||
static const QString peloton_code;
|
||||
static const QString default_peloton_code;
|
||||
|
||||
static const QString proform_carbon_tl_PFTL59720;
|
||||
static constexpr bool default_proform_carbon_tl_PFTL59720 = false;
|
||||
|
||||
@@ -2378,6 +2393,12 @@ class QZSettings {
|
||||
static const QString stryd_inclination_instead_treadmill;
|
||||
static constexpr bool default_stryd_inclination_instead_treadmill = false;
|
||||
|
||||
static const QString domyos_elliptical_fmts;
|
||||
static constexpr bool default_domyos_elliptical_fmts = false;
|
||||
|
||||
static const QString proform_xbike;
|
||||
static constexpr bool default_proform_xbike = false;
|
||||
|
||||
/**
|
||||
* @brief Write the QSettings values using the constants from this namespace.
|
||||
* @param showDefaults Optionally indicates if the default should be shown with the key.
|
||||
|
||||
@@ -4,6 +4,7 @@ import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.0
|
||||
import Qt.labs.settings 1.0
|
||||
import QtQuick.Dialogs 1.0
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
//Page {
|
||||
ScrollView {
|
||||
@@ -15,6 +16,8 @@ import QtQuick.Dialogs 1.0
|
||||
//anchors.bottomMargin: footerSettings.height + 10
|
||||
id: settingsPane
|
||||
|
||||
signal peloton_connect_clicked()
|
||||
|
||||
Settings {
|
||||
id: settings
|
||||
property real ui_zoom: 100.0
|
||||
@@ -977,6 +980,7 @@ import QtQuick.Dialogs 1.0
|
||||
property bool gears_zwift_ratio: false
|
||||
property bool domyos_bike_500_profile_v2: false
|
||||
property double gears_offset: 0.0
|
||||
|
||||
property bool proform_carbon_tl_PFTL59720: false
|
||||
|
||||
// from version 2.16.71
|
||||
@@ -1104,6 +1108,10 @@ import QtQuick.Dialogs 1.0
|
||||
property bool proform_bike_PFEVEX71316_0: false
|
||||
property bool real_inclination_to_virtual_treamill_bridge: false
|
||||
property bool stryd_inclination_instead_treadmill: false
|
||||
|
||||
// 2.18.20
|
||||
property bool domyos_elliptical_fmts: false
|
||||
property bool proform_xbike: false
|
||||
}
|
||||
|
||||
function paddingZeros(text, limit) {
|
||||
@@ -3352,6 +3360,7 @@ import QtQuick.Dialogs 1.0
|
||||
"Proform SB",
|
||||
"Nordictrack GX 4.4 Pro",
|
||||
"TDF 1.0 PFEVEX71316.0",
|
||||
"Proform XBike"
|
||||
]
|
||||
|
||||
// Initialize when the accordion content becomes visible
|
||||
@@ -3384,7 +3393,8 @@ import QtQuick.Dialogs 1.0
|
||||
settings.proform_bike_325_csx ? 13 :
|
||||
settings.proform_bike_sb ? 14 :
|
||||
settings.nordictrack_gx_44_pro ? 15 :
|
||||
settings.proform_bike_PFEVEX71316_0 ? 16 : 0;
|
||||
settings.proform_bike_PFEVEX71316_0 ? 16 :
|
||||
settings.proform_xbike ? 17 : 0;
|
||||
|
||||
console.log("bikeModelComboBox selected model: " + selectedModel);
|
||||
if (selectedModel >= 0) {
|
||||
@@ -3415,6 +3425,7 @@ import QtQuick.Dialogs 1.0
|
||||
settings.proform_bike_sb = false;
|
||||
settings.nordictrack_gx_44_pro = false;
|
||||
settings.proform_bike_PFEVEX71316_0 = false;
|
||||
settings.proform_xbike = false;
|
||||
|
||||
// Set corresponding setting for selected model
|
||||
switch (currentIndex) {
|
||||
@@ -3434,6 +3445,7 @@ import QtQuick.Dialogs 1.0
|
||||
case 14: settings.proform_bike_sb = true; break;
|
||||
case 15: settings.nordictrack_gx_44_pro = true; break;
|
||||
case 16: settings.proform_bike_PFEVEX71316_0 = true; break;
|
||||
case 17: settings.proform_xbike = true; break;
|
||||
}
|
||||
|
||||
window.settings_restart_to_apply = true;
|
||||
@@ -4252,7 +4264,7 @@ import QtQuick.Dialogs 1.0
|
||||
color: Material.backgroundColor
|
||||
accordionContent: ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
/*
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
Label {
|
||||
@@ -4329,6 +4341,23 @@ import QtQuick.Dialogs 1.0
|
||||
Layout.fillWidth: true
|
||||
color: Material.color(Material.Lime)
|
||||
}
|
||||
*/
|
||||
|
||||
ItemDelegate {
|
||||
Image {
|
||||
anchors.left: parent.left;
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
source: "icons/icons/Button_Connect_Rect_DarkMode.png"
|
||||
fillMode: Image.PreserveAspectFit
|
||||
visible: true
|
||||
width: parent.width
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
onClicked: {
|
||||
stackView.push("WebPelotonAuth.qml")
|
||||
peloton_connect_clicked()
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
@@ -5038,6 +5067,20 @@ import QtQuick.Dialogs 1.0
|
||||
color: Material.color(Material.Lime)
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: zwiftPlaySettingsDialog
|
||||
text: "Zwift Play & Click Settings"
|
||||
informativeText: "Would you like to disable Zwift Play and Zwift Click settings? Having them enabled together with 'Get gears from Zwift' may cause conflicts."
|
||||
buttons: (MessageDialog.Yes | MessageDialog.No)
|
||||
onYesClicked: {
|
||||
settings.zwift_play = false;
|
||||
settings.zwift_click = false;
|
||||
settings.zwift_play_emulator = true;
|
||||
window.settings_restart_to_apply = true;
|
||||
}
|
||||
visible: false
|
||||
}
|
||||
|
||||
IndicatorOnlySwitch {
|
||||
text: qsTr("Get Gears from Zwift")
|
||||
spacing: 0
|
||||
@@ -5049,7 +5092,16 @@ import QtQuick.Dialogs 1.0
|
||||
checked: settings.zwift_play_emulator
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
onClicked: { settings.zwift_play_emulator = checked; if(checked) { settings.watt_bike_emulator = false; } window.settings_restart_to_apply = true; }
|
||||
onClicked: {
|
||||
if (checked && !settings.zwift_play_emulator) { // Only show dialog when enabling
|
||||
if (settings.zwift_play || settings.zwift_click) {
|
||||
zwiftPlaySettingsDialog.visible = true;
|
||||
}
|
||||
settings.watt_bike_emulator = false;
|
||||
}
|
||||
window.settings_restart_to_apply = true;
|
||||
settings.zwift_play_emulator = checked;
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
|
||||
@@ -1145,6 +1145,8 @@ void TemplateInfoSenderBuilder::buildContext(bool forceReinit) {
|
||||
obj.setProperty(QStringLiteral("verticaloscillation"),
|
||||
(dep = ((treadmill *)device)->currentVerticalOscillation()).value());
|
||||
} else if (tp == bluetoothdevice::ELLIPTICAL) {
|
||||
obj.setProperty(QStringLiteral("resistance"), (dep = ((elliptical *)device)->currentResistance()).value());
|
||||
obj.setProperty(QStringLiteral("resistance_avg"), dep.average());
|
||||
obj.setProperty(QStringLiteral("cadence"), (dep = ((elliptical *)device)->currentCadence()).value());
|
||||
obj.setProperty(QStringLiteral("cadence_color"), homeform::singleton()->cadence->valueFontColor());
|
||||
obj.setProperty(QStringLiteral("cadence_avg"), dep.average());
|
||||
|
||||
@@ -586,6 +586,17 @@ void trainprogram::scheduler() {
|
||||
|
||||
QMutexLocker(&this->schedulerMutex);
|
||||
QSettings settings;
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
qint64 msecsElapsed = lastSchedulerCall.msecsTo(now);
|
||||
|
||||
// Reset jitter if it's getting too large
|
||||
if (qAbs(currentTimerJitter) > 5000) {
|
||||
currentTimerJitter = 0;
|
||||
}
|
||||
|
||||
currentTimerJitter += msecsElapsed - 1000;
|
||||
lastSchedulerCall = now;
|
||||
|
||||
// outside the if case about a valid train program because the information for the floating window url should be
|
||||
// sent anyway
|
||||
if (settings.value(QZSettings::peloton_companion_workout_ocr, QZSettings::default_companion_peloton_workout_ocr)
|
||||
@@ -811,6 +822,7 @@ void trainprogram::scheduler() {
|
||||
#endif
|
||||
}
|
||||
|
||||
currentTimerJitter = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -832,6 +844,20 @@ void trainprogram::scheduler() {
|
||||
#endif
|
||||
|
||||
ticks++;
|
||||
qDebug() << QStringLiteral("trainprogram ticks") << ticks << QStringLiteral("currentTimerJitter") << currentTimerJitter;
|
||||
|
||||
if(qAbs(currentTimerJitter) > 1000) {
|
||||
// we are late...
|
||||
if (currentTimerJitter > 1000) {
|
||||
int seconds = currentTimerJitter / 1000;
|
||||
ticks += seconds;
|
||||
currentTimerJitter -= (seconds * 1000);
|
||||
qDebug() << QStringLiteral("fixing jitter!") << seconds << ticks << currentTimerJitter;
|
||||
} else {
|
||||
// negative jitter, reset the counter without touching the ticks
|
||||
currentTimerJitter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
double odometerFromTheDevice = bluetoothManager->device()->odometer();
|
||||
|
||||
@@ -1270,6 +1296,7 @@ void trainprogram::restart() {
|
||||
ticks = 0;
|
||||
offset = 0;
|
||||
currentStep = 0;
|
||||
currentTimerJitter = 0;
|
||||
started = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -168,6 +168,9 @@ private slots:
|
||||
int lastStepTimestampChanged = 0;
|
||||
double lastCurrentStepDistance = 0.0;
|
||||
QTime lastCurrentStepTime = QTime(0, 0, 0);
|
||||
|
||||
int64_t currentTimerJitter = 0;
|
||||
QDateTime lastSchedulerCall = QDateTime::currentDateTime();
|
||||
|
||||
QUdpSocket* pelotonOCRsocket = nullptr;
|
||||
void pelotonOCRcomputeTime(QString t);
|
||||
|
||||
Reference in New Issue
Block a user