Compare commits
33 Commits
chart
...
chartEndWo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a732edbd5 | ||
|
|
1c20a2c77d | ||
|
|
a6b7f4ff94 | ||
|
|
a42d218eda | ||
|
|
48d985cf67 | ||
|
|
43bb830c23 | ||
|
|
82888440cf | ||
|
|
f45de06bcf | ||
|
|
6f081cc6b4 | ||
|
|
11c137e0e3 | ||
|
|
dea69cc74a | ||
|
|
e7eb0822e7 | ||
|
|
1943a08632 | ||
|
|
167dc93a55 | ||
|
|
9159af36f7 | ||
|
|
2763ce6e8a | ||
|
|
f4c6dfaeb6 | ||
|
|
bda4f5cf6b | ||
|
|
5b33d479e0 | ||
|
|
907d494803 | ||
|
|
1c4a3e6185 | ||
|
|
b11a0cca1c | ||
|
|
dde586ecdd | ||
|
|
390cf9bfef | ||
|
|
758349b80f | ||
|
|
896c641851 | ||
|
|
5cb44c17e8 | ||
|
|
2c124f4365 | ||
|
|
6d4c030754 | ||
|
|
0fad920c39 | ||
|
|
6bd2327165 | ||
|
|
497865f2e6 | ||
|
|
b279344dbc |
3
.gitignore
vendored
@@ -14,3 +14,6 @@ src/ui_charts.h
|
||||
src/ui_mainwindow.h
|
||||
|
||||
src/debug-*
|
||||
|
||||
*.swo
|
||||
*.swp
|
||||
|
||||
32
README.md
@@ -39,35 +39,9 @@ Read the [installation procedure](docs/10_Installation.md)
|
||||
|
||||
### Tested on
|
||||
|
||||
- Raspberry PI 0W and Domyos Intense Run
|
||||
You can run the app on [Macintosh or Linux devices](docs/10_Installation.md). IOS and Android are also supported.
|
||||
|
||||
- MacBook Air 2011 and Domyos Intense Run
|
||||
|
||||
- Raspberry 3b+ and Domyos T900C
|
||||
|
||||
- Raspberry 3b+ and Toorx TRX Route Key
|
||||
|
||||
- Android Pixel 2 and Echelon Connect Sport
|
||||
|
||||
- Raspberry Pi 0W and SportsTech ESX500
|
||||
|
||||
### Your machine is not compatible?
|
||||
|
||||
Open an issue and follow these steps!
|
||||
|
||||
1. first of all you need an android device (phone or tablet)
|
||||
2. you need to become developer on your phone https://wccftech.com/how-to/how-to-enable-developer-options-on-android-10-tutorial/
|
||||
3. Go to Settings
|
||||
4. Go into developer options
|
||||
5. Enable the option Enable Bluetooth HCI snoop log
|
||||
6. restart your phone
|
||||
7. open your machine app and play with it collecting inclination and speed
|
||||
8. Disable the option Enable Bluetooth HCI snoop log
|
||||
9. in Developer Options: Bug report->Full report
|
||||
10. wait a random amount of time (10-20 seconds)
|
||||
11. A notification will appear at the top of the device. Click on it, share, email it to yourself
|
||||
12. You'll get a zip file with the entire report. In the FS/Data/Log/bt directory of the zipfile is the file you want.
|
||||
13. attach the log file in a new issue with a short description of the steps you did in the app when you used it
|
||||
QDomyos-Zwift works on every [FTMS-compatible application](docs/20_supported_devices_and_applications.md), and virtually any [bluetooth enabled device](docs/20_supported_devices_and_applications.md).
|
||||
|
||||
### No gui version
|
||||
|
||||
@@ -81,6 +55,8 @@ https://github.com/ProH4Ck/treadmill-bridge
|
||||
|
||||
https://www.livestrong.com/article/422012-what-is-10-degrees-in-incline-on-a-treadmill/
|
||||
|
||||
Icons used in this documentation comes from [flaticon.com](https://www.flaticon.com)
|
||||
|
||||
### Blog
|
||||
|
||||
https://robertoviola.cloud
|
||||
|
||||
@@ -149,6 +149,9 @@
|
||||
876EE3CDDF69DA139329ADD8 /* qquicklayoutsplugin in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 307B2F8E9A717B514EA118E0 /* qquicklayoutsplugin */; };
|
||||
8772A0E625E43ADB0080718C /* trxappgateusbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772A0E525E43ADA0080718C /* trxappgateusbbike.cpp */; };
|
||||
8772A0E825E43AE70080718C /* moc_trxappgateusbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772A0E725E43AE70080718C /* moc_trxappgateusbbike.cpp */; };
|
||||
8781907E2615089D0085E656 /* peloton.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8781907C2615089D0085E656 /* peloton.cpp */; };
|
||||
87819080261508B10085E656 /* moc_peloton.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8781907F261508B10085E656 /* moc_peloton.cpp */; };
|
||||
8781908526150C8E0085E656 /* libqtlabsplatformplugin.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 8781908126150B490085E656 /* libqtlabsplatformplugin.a */; };
|
||||
8783153B25E8D81E0007817C /* moc_sportstechbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8783153A25E8D81E0007817C /* moc_sportstechbike.cpp */; };
|
||||
8783153C25E8DAFD0007817C /* sportstechbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3EBBA25D2CFED0040EB4C /* sportstechbike.cpp */; };
|
||||
878A331A25AB4FF800BD13E1 /* yesoulbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878A331725AB4FF800BD13E1 /* yesoulbike.cpp */; };
|
||||
@@ -556,6 +559,11 @@
|
||||
8772A0E425E43AD90080718C /* trxappgateusbbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = trxappgateusbbike.h; path = ../src/trxappgateusbbike.h; sourceTree = "<group>"; };
|
||||
8772A0E525E43ADA0080718C /* trxappgateusbbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = trxappgateusbbike.cpp; path = ../src/trxappgateusbbike.cpp; sourceTree = "<group>"; };
|
||||
8772A0E725E43AE70080718C /* moc_trxappgateusbbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_trxappgateusbbike.cpp; sourceTree = "<group>"; };
|
||||
8781907C2615089D0085E656 /* peloton.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = peloton.cpp; path = ../src/peloton.cpp; sourceTree = "<group>"; };
|
||||
8781907D2615089D0085E656 /* peloton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = peloton.h; path = ../src/peloton.h; sourceTree = "<group>"; };
|
||||
8781907F261508B10085E656 /* moc_peloton.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_peloton.cpp; sourceTree = "<group>"; };
|
||||
8781908126150B490085E656 /* libqtlabsplatformplugin.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtlabsplatformplugin.a; path = ../../Qt/5.15.2/ios/qml/Qt/labs/platform/libqtlabsplatformplugin.a; sourceTree = "<group>"; };
|
||||
8781908326150C5B0085E656 /* libqtlabsplatformplugin_debug.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtlabsplatformplugin_debug.a; path = ../../Qt/5.15.2/ios/qml/Qt/labs/platform/libqtlabsplatformplugin_debug.a; sourceTree = "<group>"; };
|
||||
878225C234983ACB863D2D29 /* fit_nmea_sentence_mesg.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = fit_nmea_sentence_mesg.hpp; path = "/Users/cagnulein/qdomyos-zwift/src/fit-sdk/fit_nmea_sentence_mesg.hpp"; sourceTree = "<absolute>"; };
|
||||
8783153A25E8D81E0007817C /* moc_sportstechbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_sportstechbike.cpp; sourceTree = "<group>"; };
|
||||
87842E7E25AF88FB00321E69 /* secret.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = secret.h; path = ../src/secret.h; sourceTree = "<group>"; };
|
||||
@@ -816,6 +824,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8781908526150C8E0085E656 /* libqtlabsplatformplugin.a in Link Binary With Libraries */,
|
||||
38B66B7A90741F136A02EDEB /* qios in Link Binary With Libraries */,
|
||||
B460F624007324313696BE86 /* QuartzCore.framework in Link Binary With Libraries */,
|
||||
4EB68D0C8182BCE33529C421 /* AudioToolbox.framework in Link Binary With Libraries */,
|
||||
@@ -928,6 +937,7 @@
|
||||
25B08E2869634E9BCBA333A2 /* Generated Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8781907F261508B10085E656 /* moc_peloton.cpp */,
|
||||
87D269A225F535300076AA48 /* moc_m3ibike.cpp */,
|
||||
87D269A125F535300076AA48 /* moc_skandikawiribike.cpp */,
|
||||
87B617EF25F260140094A1CB /* moc_fitshowtreadmill.cpp */,
|
||||
@@ -975,6 +985,8 @@
|
||||
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8781907C2615089D0085E656 /* peloton.cpp */,
|
||||
8781907D2615089D0085E656 /* peloton.h */,
|
||||
8762D5112601F89500F6F049 /* scanrecordresult.cpp */,
|
||||
8762D5122601F89500F6F049 /* scanrecordresult.h */,
|
||||
8762D50E2601F7EA00F6F049 /* M3iIOS-Interface.h */,
|
||||
@@ -1145,6 +1157,8 @@
|
||||
AF39DD055C3EF8226FBE929D /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8781908326150C5B0085E656 /* libqtlabsplatformplugin_debug.a */,
|
||||
8781908126150B490085E656 /* libqtlabsplatformplugin.a */,
|
||||
2D4A13931169E5681CE465F0 /* Qt5NetworkAuth */,
|
||||
BC3A8C3E433A8FA00BB15F07 /* qios */,
|
||||
7EC00404ACD5AB0E97726B0E /* QuartzCore.framework */,
|
||||
@@ -1749,6 +1763,7 @@
|
||||
DD2E0091F3318F053D2995AA /* fit_mesg_broadcaster.cpp in Compile Sources */,
|
||||
FE77C778768741F1A161682E /* fit_mesg_definition.cpp in Compile Sources */,
|
||||
87DF68B825E2673B00FCDA46 /* eslinkertreadmill.cpp in Compile Sources */,
|
||||
8781907E2615089D0085E656 /* peloton.cpp in Compile Sources */,
|
||||
2B800DC34C91D8B080DEFBE8 /* fit_mesg_with_event_broadcaster.cpp in Compile Sources */,
|
||||
6DC5D7C695B8763F9E2E029F /* fit_profile.cpp in Compile Sources */,
|
||||
23191C28CB29474279752FD3 /* fit_protocol_validator.cpp in Compile Sources */,
|
||||
@@ -1767,6 +1782,7 @@
|
||||
47E45EE0BB22C1E4332F1D1D /* trxappgateusbtreadmill.cpp in Compile Sources */,
|
||||
6943DA124B60175E1F9EBD1B /* virtualbike.cpp in Compile Sources */,
|
||||
0317752B0C295CAB82D37E45 /* virtualtreadmill.cpp in Compile Sources */,
|
||||
87819080261508B10085E656 /* moc_peloton.cpp in Compile Sources */,
|
||||
7EC1321DD83EAAFAA2B7109C /* domyosbike.cpp in Compile Sources */,
|
||||
614192CB787D12C3E98ADE55 /* lockscreen.mm in Compile Sources */,
|
||||
0F974CB18B3E792B42270F19 /* FitDecode.mm in Compile Sources */,
|
||||
@@ -2164,7 +2180,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2.5.11;
|
||||
CURRENT_PROJECT_VERSION = 2.5.16;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
@@ -2187,6 +2203,37 @@
|
||||
"../../Qt/5.15.2/ios/mkspecs/macx-ios-clang",
|
||||
../../Qt/5.15.2/ios/include/QtNetworkAuth,
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/platforms,
|
||||
/System/Library/Frameworks/,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/lib,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/imageformats,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/position,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/lib,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/qmltooling,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/bearer,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtGraphicalEffects/private,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQml/Models.2,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQml/WorkerScript.2,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Dialogs,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/Qt/labs/folderlistmodel,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/Qt/labs/platform,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/Qt/labs/settings,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Dialogs/Private,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Controls,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/PrivateWidgets,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQml,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Layouts,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Window.2,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick.2,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Templates.2,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Controls.2,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Controls.2/Material,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Controls.2/Fusion,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Controls.2/Universal,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtGraphicalEffects,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Controls.2/Imagine,
|
||||
);
|
||||
MARKETING_VERSION = 2.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cagnulein.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = qdomyoszwift;
|
||||
@@ -2216,7 +2263,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2.5.11;
|
||||
CURRENT_PROJECT_VERSION = 2.5.16;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
@@ -2241,6 +2288,37 @@
|
||||
"../../Qt/5.15.2/ios/mkspecs/macx-ios-clang",
|
||||
../../Qt/5.15.2/ios/include/QtNetworkAuth,
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/platforms,
|
||||
/System/Library/Frameworks/,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/lib,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/imageformats,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/position,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/lib,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/qmltooling,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/bearer,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtGraphicalEffects/private,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQml/Models.2,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQml/WorkerScript.2,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Dialogs,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/Qt/labs/folderlistmodel,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/Qt/labs/platform,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/Qt/labs/settings,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Dialogs/Private,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Controls,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/PrivateWidgets,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQml,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Layouts,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Window.2,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick.2,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Templates.2,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Controls.2,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Controls.2/Material,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Controls.2/Fusion,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Controls.2/Universal,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtGraphicalEffects,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/qml/QtQuick/Controls.2/Imagine,
|
||||
);
|
||||
MARKETING_VERSION = 2.5;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cagnulein.${PRODUCT_NAME:rfc1034identifier}";
|
||||
@@ -2304,7 +2382,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2.5.11;
|
||||
CURRENT_PROJECT_VERSION = 2.5.16;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
@@ -2394,7 +2472,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2.5.11;
|
||||
CURRENT_PROJECT_VERSION = 2.5.16;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
@@ -2478,7 +2556,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2.5.11;
|
||||
CURRENT_PROJECT_VERSION = 2.5.16;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -2565,7 +2643,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2.5.11;
|
||||
CURRENT_PROJECT_VERSION = 2.5.16;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
# Installation
|
||||
|
||||
QDomyos-Zwift can be installed from source on MacOs, Linux, Android and IOS.
|
||||
|
||||
Once you've installed QDomyos-Zwift, you can access the [operation guide](30_usage.md) for more information.
|
||||
|
||||
## On a Linux System (from source)
|
||||
|
||||
```buildoutcfg
|
||||
$ sudo apt update && sudo apt upgrade # this is very important on raspberry pi: you need the bluetooth firmware updated!
|
||||
$ sudo apt install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-default libqt5networkauth5-dev
|
||||
$ git clone https://github.com/cagnulein/qdomyos-zwift.git
|
||||
$ cd qdomyos-zwift
|
||||
$ cd src
|
||||
$ qmake
|
||||
$ make -j4
|
||||
$ sudo ./qdomyos-zwift
|
||||
```
|
||||
|
||||
|
||||
## MacOs installation
|
||||
|
||||
You will need to (at a minimum) to install the xcode Command Line Tools (CLI) thanks to @richardwait
|
||||
@@ -97,6 +103,7 @@ This operation takes a moment to complete.
|
||||
`sudo apt install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-default libqt5networkauth5-dev`
|
||||
|
||||
`git clone https://github.com/cagnulein/qdomyos-zwift.git`
|
||||
`cd qdomyos-zwift`
|
||||
`cd src`
|
||||
`qmake`
|
||||
`make`
|
||||
|
||||
63
docs/20_supported_devices_and_applications.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Supported applications
|
||||
|
||||
QDomyos-Zwift should support every application able to read a [FTMS](specs/FTMS_v1.0.pdf) bluetooth signal.
|
||||
|
||||
This list is not exhaustive. Please report any application known to be working with QDomyos-Zwift !
|
||||
|
||||
|Application|Sport|Platform|Speed|RPM|Power|HRM |Resistance Control|
|
||||
|-----------|-----|--------|------------|---|-----|-----|----------------|
|
||||
|[Zwift](21_applications_detail.md#zwift)| |  | Yes|Yes|Yes| Yes, no FTMS support | Yes |
|
||||
|[BKool](21_applications_detail.md#bkool) ||  |Yes|Yes|Yes|Yes| Yes |
|
||||
|[Fulgaz](21_applications_detail.md#fulgaz)||  |Yes|Yes|Yes|Yes, no FTMS support (see note)|Yes (see note) |
|
||||
|
||||
# Supported devices
|
||||
This list is not exhaustive.
|
||||
Try the qdomyos app with your fitness appliance and report how it is going.
|
||||
If it's not working, you can [ask for your device to be supported](#ask-for-device-support)
|
||||
|
||||
## Supported bikes
|
||||
|
||||
|Manufacturer|Model|Speed|RPM|Power|HRM|Resistence Control|
|
||||
|------------|-----|------------|---|-----|---|------------------|
|
||||
|[Echelon](22_devices_detail.md#echelon)|Connect Sport|Yes|Yes|Yes|Yes|N/A|
|
||||
|[Sportstech](22_devices_detail.md#sportstech)|ESX500|Yes|Yes|Yes|Yes|Yes|
|
||||
|
||||
## Supported treadmills
|
||||
|
||||
|Manufacturer|Model|Speed|HRM|Inclinaison Control| Speed control|
|
||||
|------------|-----|------------|---|-------------------|--------------|
|
||||
|Domyos|Intense Run|Yes|Yes|Yes|Yes|
|
||||
|Domyos|T900c|Yes|Yes|Yes|Yes|
|
||||
|Toorx|TRX Route Key|Yes|Yes|Yes|Yes|
|
||||
|
||||
|
||||
# Ask for device support
|
||||
|
||||
You can ask for supporting a device by opening an issue and following these steps.
|
||||
We need to "spy" the bluetooth activity from your fitness device and it's application, in order to guess how they communicate.
|
||||
|
||||
An android device is required for this operation.
|
||||
|
||||
## Android device
|
||||
|
||||
1. first of all you need an android device (phone or tablet)
|
||||
2. you need to become developer on your phone https://wccftech.com/how-to/how-to-enable-developer-options-on-android-10-tutorial/
|
||||
3. Go to Settings
|
||||
4. Go into developer options
|
||||
5. Enable the option Enable Bluetooth HCI snoop log
|
||||
6. restart your phone
|
||||
7. open your machine app and play with it collecting inclination and speed
|
||||
8. Disable the option Enable Bluetooth HCI snoop log
|
||||
9. in Developer Options: Bug report->Full report
|
||||
10. wait a random amount of time (10-20 seconds)
|
||||
11. A notification will appear at the top of the device. Click on it, share, email it to yourself
|
||||
12. You'll get a zip file with the entire report. In the FS/Data/Log/bt directory of the zipfile is the file you want.
|
||||
13. attach the log file in a new issue with a short description of the steps you did in the app when you used it
|
||||
|
||||
## Android Device (oppo based OS : oppo, ColorOS, RealMe, ...)
|
||||
1. Dial *#800# on the phone app. A special menu should appear.
|
||||
2. Go to Bluetooth and press "Start capture" (green button)
|
||||
3. open your machine app and play with it collecting inclination and speed
|
||||
4. Go back to the special menu by dialing *#800# on the phone app. Stop the bluetooth capture.
|
||||
5. A new debug directory is stored onto `/oppo_log/` with a time stamp.
|
||||
6. Fetch the CFA file stored in the `btsnoop_hci` folder.
|
||||
30
docs/21_applications_detail.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Applications supported
|
||||
|
||||
This list is not exhaustive !
|
||||
Open a discussion to tell us if a missing application is supported.
|
||||
|
||||
## BKool
|
||||
Everything is working out of the box (read FTMS data).
|
||||
|
||||
|
||||
## Fulgaz
|
||||
|
||||
### HRM Support
|
||||
The application do not read the FTMS value. It is required to start the application with `-heart-service` or `bike_heartrate_service: true` in settings.
|
||||
The HRM captor will not be shown by default, you need to go in the `Application Settings > Advanced > Disable Bluetooth Filter`.
|
||||
|
||||
### Resistance management
|
||||
Fulgaz is known to be very severe by default in resistance adjustment. It is advised to adjust [application settings](https://tempocyclist.com/2020/04/29/fulgaz-resistance-too-hard/).
|
||||
The resistance is automatically adjusted with the slope. However, you can override it using
|
||||
|
||||
### Additional notes
|
||||
You can have ae true 4k video stream while you ride ("extreme quality" setting) however it requires about 10 gb per hour.
|
||||
|
||||
|
||||
## Zwift
|
||||
### HRM support
|
||||
The application do not read the FTMS value. It is required to start the application with `-heart-service` or `bike_heartrate_service: true` in settings.
|
||||
|
||||
### Resistance management
|
||||
You can adjust resistence using arrows [up and down](img/21_zwift-resistance-buttons.jpg) rom the riding screen.
|
||||
|
||||
13
docs/22_devices_detail.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Devices detail
|
||||
|
||||
## Echelon
|
||||
|
||||
|
||||
## Sportstech
|
||||
|
||||
### ESX 500
|
||||
#### HRM
|
||||
The [cardio belt provided](https://www.sports-tech.uk.com/chest-strap-sportstech-uncoded) with the bike is also supported, the bike reads the value and forwards it in its bluetooth signal.
|
||||
The cardio captors are reported to not be accurate.
|
||||
#### Resistance
|
||||
The resistance is adjusted after a few seconds delay (time for the engine to adapt magnetic resistance).
|
||||
52
docs/30_usage.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# QDomyos-Zwift operation guide
|
||||
|
||||
|
||||
# Usage
|
||||
|
||||
The QDomyos-Zwift can be started in two modes : QML or NativeQT.
|
||||
The main difference is the configuration management : you can change settings within the application with QML, where you need to specify settings at startup in NativeQT.
|
||||
|
||||
**Note:** Android and IOS are always running in QML mode.
|
||||
|
||||
On MacOS and Linux, you start QDomyos-Zwift in NativeQT mode (where settings are defined in commandline switches).
|
||||
|
||||
You can start the application in QML mode with the command-line switch -qml.
|
||||
|
||||
## Configuration in QML mode
|
||||
Please refer to this article for more information under [QML Operations](https://robertoviola.cloud/qdomyos-zwift-guide/) with several useful information.
|
||||
|
||||
|
||||
## Configuration in NativeQT mode
|
||||
|
||||
This is the list of settings available in the application. These settings needs to be appended to the binary command line.
|
||||
*Example :* `sudo ./qdomyos-zwift -no-gui` for disabling any graphical interface.
|
||||
|
||||
| **Option** | **Type** | **Default** | **Function** |
|
||||
|:------------------------|:---------|:------------|:-----------------------------------------------------------------------------|
|
||||
| -no-gui | Boolean | False | Disable GUI |
|
||||
| -qml | Boolean | False | Enables the QML interface |
|
||||
| -miles | Boolean | False | Swithes to Imperial Units System |
|
||||
| -no-console | Boolean | False | Not in use |
|
||||
| -test-resistance | Boolean | False | |
|
||||
| -no-log | Boolean | False | Disable Logging |
|
||||
| -no-write-resistance | Boolean | False | Disable resistance instructions from QZ to your fitness equipment |
|
||||
| -no-heart-service | Boolean | False | Do not simulate external HR monitor, use only FTMS |
|
||||
| -heart-service | Boolean | True | Simulate HR service (required for applications not reading FTMS) |
|
||||
| -only-virtualbike | Boolean | False | |
|
||||
| -only-virtualtreadmill | Boolean | False | |
|
||||
| -no-reconnection | Boolean | False | QZ will not try to reconnect your fitness equipement if enabled |
|
||||
| -bluetooth-relaxed | Boolean | False | In case of deconnections from QZ to your fitness equipement |
|
||||
| -bike-cadence-sensor | Boolean | False | |
|
||||
| -bike-power-sensor | Boolean | False | |
|
||||
| -battery-service | Boolean | False | |
|
||||
| -service-changed | Boolean | False | |
|
||||
| -bike-wheel-revs | Boolean | False | |
|
||||
| -run-cadence-sensor | Boolean | False | |
|
||||
| -train | String | | Force training program |
|
||||
| -name | String | | Force bluetooth device name (if QZ struggles finding your fitness equipment) |
|
||||
| -poll-device-time | Int | 200 (ms) | Frequency to refresh informations from QZ to Fitness equipment |
|
||||
| -bike-resistance-gain | Int | | Adjust resistance from the fitness application |
|
||||
| -bike-resistance-offset | Int | | Set another resistance point than default |
|
||||
|
||||
|
||||
|
||||
BIN
docs/img/20_android.png
Normal file
|
After Width: | Height: | Size: 491 B |
BIN
docs/img/20_apple.png
Normal file
|
After Width: | Height: | Size: 427 B |
BIN
docs/img/20_bike.png
Normal file
|
After Width: | Height: | Size: 349 B |
BIN
docs/img/20_macintosh.png
Normal file
|
After Width: | Height: | Size: 374 B |
BIN
docs/img/20_pc.png
Normal file
|
After Width: | Height: | Size: 380 B |
BIN
docs/img/20_treadmill.png
Normal file
|
After Width: | Height: | Size: 377 B |
BIN
docs/img/20_windows.png
Normal file
|
After Width: | Height: | Size: 369 B |
BIN
docs/img/21_zwift-resistance-buttons.jpg
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
qt-patches/ios/5.15.2/binary/libQt5Bluetooth.a
Normal file
28
qt-patches/ios/5.15.2/binary/libQt5Bluetooth.la
Normal file
@@ -0,0 +1,28 @@
|
||||
# libQt5Bluetooth.la - a libtool library file
|
||||
# Generated by qmake/libtool (3.1) (Qt 5.15.2)
|
||||
# The name that we can dlopen(3).
|
||||
dlname=''
|
||||
|
||||
# Names of this library.
|
||||
library_names=' '
|
||||
|
||||
# The name of the static archive.
|
||||
old_library='libQt5Bluetooth.a'
|
||||
|
||||
# Libraries that this one depends upon.
|
||||
dependency_libs='-L=/usr/local/Qt-5.15.2/lib -lQt5Core -framework MobileCoreServices -lm -framework UIKit -framework CoreFoundation -framework Foundation -lz -lqtpcre2'
|
||||
|
||||
# Version information for libQt5Bluetooth.la
|
||||
current=65
|
||||
age=0
|
||||
revision=2
|
||||
|
||||
# Is this an already installed library.
|
||||
installed=yes
|
||||
|
||||
# Files to dlopen/dlpreopen.
|
||||
dlopen=''
|
||||
dlpreopen=''
|
||||
|
||||
# Directory that this library needs to be installed in:
|
||||
libdir='=/usr/local/Qt-5.15.2/lib'
|
||||
7
qt-patches/ios/5.15.2/binary/libQt5Bluetooth.prl
Normal file
@@ -0,0 +1,7 @@
|
||||
QMAKE_PRL_BUILD_DIR = /Users/cagnulein/Downloads/qt-everywhere-src-5.15.2/qtconnectivity/src/bluetooth
|
||||
QMAKE_PRO_INPUT = bluetooth.pro
|
||||
QMAKE_PRL_TARGET = libQt5Bluetooth.a
|
||||
QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl bitcode reduce_exports shallow_bundle no_qt_rpath app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style cross_compile static ios uikit mac darwin unix posix gcc clang llvm cross_compile compile_examples largefile neon precompile_header prefix_build force_independent force_bootstrap builtin_testdata utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib release ReleaseBuild Release build_pass device iphoneos simulator iphonesimulator sse sse2 generated_privates relative_qt_rpath app_extension_api_only target_qt c++11 strict_c++ c++14 c++1z c99 c11 hide_symbols qt_install_headers need_fwd_pri qt_install_module debug_and_release build_all create_cmake absolute_library_soname compiler_supports_fpmath create_pc create_libtool release ReleaseBuild Release build_pass have_target staticlib exclusive_builds objective_c c++11 no_autoqmake thread moc resources gc_binaries
|
||||
QMAKE_PRL_VERSION = 5.15.2
|
||||
QMAKE_PRL_LIBS = $$[QT_INSTALL_LIBS]/libQt5Core.a -framework MobileCoreServices -lm -framework UIKit -framework CoreFoundation -framework Foundation -lz $$[QT_INSTALL_LIBS]/libqtpcre2.a -framework Foundation -framework CoreBluetooth
|
||||
QMAKE_PRL_LIBS_FOR_CMAKE = $$[QT_INSTALL_LIBS]/libQt5Core.a;-framework;MobileCoreServices;-lm;-framework;UIKit;-framework;CoreFoundation;-framework;Foundation;-lz;$$[QT_INSTALL_LIBS]/libqtpcre2.a;-framework;Foundation;-framework;CoreBluetooth;;
|
||||
BIN
qt-patches/ios/5.15.2/binary/libQt5Bluetooth_debug.a
Normal file
28
qt-patches/ios/5.15.2/binary/libQt5Bluetooth_debug.la
Normal file
@@ -0,0 +1,28 @@
|
||||
# libQt5Bluetooth_debug.la - a libtool library file
|
||||
# Generated by qmake/libtool (3.1) (Qt 5.15.2)
|
||||
# The name that we can dlopen(3).
|
||||
dlname=''
|
||||
|
||||
# Names of this library.
|
||||
library_names=' '
|
||||
|
||||
# The name of the static archive.
|
||||
old_library='libQt5Bluetooth_debug.a'
|
||||
|
||||
# Libraries that this one depends upon.
|
||||
dependency_libs='-L=/usr/local/Qt-5.15.2/lib -lQt5Core_debug -framework MobileCoreServices -lm -framework UIKit -framework CoreFoundation -framework Foundation -lz -lqtpcre2_debug'
|
||||
|
||||
# Version information for libQt5Bluetooth_debug.la
|
||||
current=65
|
||||
age=0
|
||||
revision=2
|
||||
|
||||
# Is this an already installed library.
|
||||
installed=yes
|
||||
|
||||
# Files to dlopen/dlpreopen.
|
||||
dlopen=''
|
||||
dlpreopen=''
|
||||
|
||||
# Directory that this library needs to be installed in:
|
||||
libdir='=/usr/local/Qt-5.15.2/lib'
|
||||
7
qt-patches/ios/5.15.2/binary/libQt5Bluetooth_debug.prl
Normal file
@@ -0,0 +1,7 @@
|
||||
QMAKE_PRL_BUILD_DIR = /Users/cagnulein/Downloads/qt-everywhere-src-5.15.2/qtconnectivity/src/bluetooth
|
||||
QMAKE_PRO_INPUT = bluetooth.pro
|
||||
QMAKE_PRL_TARGET = libQt5Bluetooth_debug.a
|
||||
QMAKE_PRL_CONFIG = lex yacc debug depend_includepath testcase_targets import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on link_prl bitcode reduce_exports shallow_bundle no_qt_rpath app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style cross_compile debug static ios uikit mac darwin unix posix gcc clang llvm cross_compile compile_examples largefile neon precompile_header prefix_build force_independent force_bootstrap builtin_testdata utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib debug DebugBuild Debug build_pass device iphoneos simulator iphonesimulator sse sse2 generated_privates relative_qt_rpath app_extension_api_only target_qt c++11 strict_c++ c++14 c++1z c99 c11 hide_symbols qt_install_headers need_fwd_pri qt_install_module debug_and_release build_all create_cmake absolute_library_soname compiler_supports_fpmath create_libtool debug DebugBuild Debug build_pass have_target staticlib no_plist exclusive_builds debug_info objective_c c++11 no_autoqmake thread moc resources gc_binaries
|
||||
QMAKE_PRL_VERSION = 5.15.2
|
||||
QMAKE_PRL_LIBS = $$[QT_INSTALL_LIBS]/libQt5Core_debug.a -framework MobileCoreServices -lm -framework UIKit -framework CoreFoundation -framework Foundation -lz $$[QT_INSTALL_LIBS]/libqtpcre2_debug.a -framework Foundation -framework CoreBluetooth
|
||||
QMAKE_PRL_LIBS_FOR_CMAKE = $$[QT_INSTALL_LIBS]/libQt5Core_debug.a;-framework;MobileCoreServices;-lm;-framework;UIKit;-framework;CoreFoundation;-framework;Foundation;-lz;$$[QT_INSTALL_LIBS]/libqtpcre2_debug.a;-framework;Foundation;-framework;CoreBluetooth;;
|
||||
@@ -50,6 +50,8 @@
|
||||
#include <vector>
|
||||
#include <limits>
|
||||
|
||||
quint8 QZ_EnableDiscoveryCharsAndDescripttors = false;
|
||||
|
||||
Q_DECLARE_METATYPE(QLowEnergyHandle)
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
@@ -555,15 +557,23 @@ QT_USE_NAMESPACE
|
||||
|
||||
if (!service.characteristics || !service.characteristics.count)
|
||||
return [self serviceDetailsDiscoveryFinished:service];
|
||||
/* rviola
|
||||
NSArray *const cs = service.characteristics;
|
||||
for (CBCharacteristic *c in cs) {
|
||||
if (c.properties & CBCharacteristicPropertyRead) {
|
||||
[self watchAfter:c timeout:OperationTimeout::characteristicRead];
|
||||
return [peripheral readValueForCharacteristic:c];
|
||||
|
||||
//rviola
|
||||
if(QZ_EnableDiscoveryCharsAndDescripttors == true)
|
||||
{
|
||||
qCWarning(QT_BT_OSX) << "QZ_EnableDiscoveryCharsAndDescripttors turned on";
|
||||
NSArray *const cs = service.characteristics;
|
||||
for (CBCharacteristic *c in cs) {
|
||||
if (c.properties & CBCharacteristicPropertyRead) {
|
||||
[self watchAfter:c timeout:OperationTimeout::characteristicRead];
|
||||
return [peripheral readValueForCharacteristic:c];
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
else
|
||||
{
|
||||
qCWarning(QT_BT_OSX) << "QZ_EnableDiscoveryCharsAndDescripttors turned off";
|
||||
}
|
||||
// No readable properties? Discover descriptors then:
|
||||
[self discoverDescriptors:service];
|
||||
}
|
||||
@@ -602,18 +612,26 @@ QT_USE_NAMESPACE
|
||||
Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
|
||||
|
||||
QT_BT_MAC_AUTORELEASEPOOL;
|
||||
/* rviola
|
||||
NSArray *const cs = service.characteristics;
|
||||
// We can never be here if we have no characteristics.
|
||||
Q_ASSERT_X(cs && cs.count, Q_FUNC_INFO, "invalid service");
|
||||
for (CBCharacteristic *c in cs) {
|
||||
if (c.descriptors && c.descriptors.count) {
|
||||
CBDescriptor *desc = [c.descriptors objectAtIndex:0];
|
||||
[self watchAfter:desc timeout:OperationTimeout::descriptorRead];
|
||||
return [peripheral readValueForDescriptor:desc];
|
||||
//rviola
|
||||
if(QZ_EnableDiscoveryCharsAndDescripttors == true)
|
||||
{
|
||||
qCWarning(QT_BT_OSX) << "QZ_EnableDiscoveryCharsAndDescripttors turned on";
|
||||
NSArray *const cs = service.characteristics;
|
||||
// We can never be here if we have no characteristics.
|
||||
Q_ASSERT_X(cs && cs.count, Q_FUNC_INFO, "invalid service");
|
||||
for (CBCharacteristic *c in cs) {
|
||||
if (c.descriptors && c.descriptors.count) {
|
||||
CBDescriptor *desc = [c.descriptors objectAtIndex:0];
|
||||
[self watchAfter:desc timeout:OperationTimeout::descriptorRead];
|
||||
return [peripheral readValueForDescriptor:desc];
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
else
|
||||
{
|
||||
qCWarning(QT_BT_OSX) << "QZ_EnableDiscoveryCharsAndDescripttors turned off";
|
||||
}
|
||||
|
||||
// No descriptors to read, done.
|
||||
[self serviceDetailsDiscoveryFinished:service];
|
||||
}
|
||||
|
||||
64
src/AccordionCheckElement.qml
Normal file
@@ -0,0 +1,64 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 2.15
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
ColumnLayout {
|
||||
property Settings settings: null
|
||||
id: rootElement
|
||||
property bool invert: false
|
||||
property bool linkedBoolSettingDefault: false
|
||||
property string linkedBoolSetting: "example_setting"
|
||||
property bool isOpen: invert ? !settings[linkedBoolSetting]:settings[linkedBoolSetting]
|
||||
property string title: ""
|
||||
default property alias accordionContent: contentPlaceholder.data
|
||||
spacing: 0
|
||||
|
||||
Layout.fillWidth: true;
|
||||
|
||||
/*RowLayout {
|
||||
id: accordionHeader
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true;
|
||||
|
||||
Rectangle{
|
||||
id:indicatRect
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
width: 8; height: 8
|
||||
radius: 8
|
||||
color: "white"
|
||||
}
|
||||
|
||||
Text {
|
||||
id: accordionText
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
color: "#FFFFFF"
|
||||
text: rootElement.title
|
||||
}
|
||||
|
||||
}*/
|
||||
SwitchDelegate {
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
bottomPadding: 0
|
||||
topPadding: 0
|
||||
rightPadding: 0
|
||||
leftPadding: 0
|
||||
clip: false
|
||||
id: indicatCbx
|
||||
text: rootElement.title
|
||||
checked: rootElement.isOpen
|
||||
onClicked: {
|
||||
rootElement.isOpen = checked
|
||||
settings[rootElement.linkedBoolSetting] = rootElement.invert? !checked:checked
|
||||
}
|
||||
}
|
||||
|
||||
// This will get filled with the content
|
||||
ColumnLayout {
|
||||
id: contentPlaceholder
|
||||
visible: rootElement.isOpen
|
||||
Layout.fillWidth: true;
|
||||
}
|
||||
}
|
||||
68
src/AccordionElement.qml
Normal file
@@ -0,0 +1,68 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
ColumnLayout {
|
||||
id: rootElement
|
||||
property bool isOpen: false
|
||||
property string title: ""
|
||||
property alias color: accordionHeader.color
|
||||
property alias textColor: accordionText.color
|
||||
property alias textFont: accordionText.font.family
|
||||
property alias textFontSize: accordionText.font.pixelSize
|
||||
property alias indicatRectColor: indicatRect.color
|
||||
default property alias accordionContent: contentPlaceholder.data
|
||||
spacing: 0
|
||||
|
||||
Layout.fillWidth: true;
|
||||
|
||||
Rectangle {
|
||||
id: accordionHeader
|
||||
color: "red"
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true;
|
||||
height: 48
|
||||
|
||||
Rectangle{
|
||||
id:indicatRect
|
||||
x: 16; y: 20
|
||||
width: 8; height: 8
|
||||
radius: 8
|
||||
color: "white"
|
||||
}
|
||||
|
||||
Text {
|
||||
id: accordionText
|
||||
x:34;y:13
|
||||
color: "#FFFFFF"
|
||||
text: rootElement.title
|
||||
}
|
||||
Image {
|
||||
y:13
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 20
|
||||
width: 30; height: 30
|
||||
id: indicatImg
|
||||
source: "qrc:/icons/arrow-collapse-vertical.png"
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
rootElement.isOpen = !rootElement.isOpen
|
||||
if(rootElement.isOpen)
|
||||
{
|
||||
indicatImg.source = "qrc:/icons/arrow-expand-vertical.png"
|
||||
}else{
|
||||
indicatImg.source = "qrc:/icons/arrow-collapse-vertical.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This will get filled with the content
|
||||
ColumnLayout {
|
||||
id: contentPlaceholder
|
||||
visible: rootElement.isOpen
|
||||
Layout.fillWidth: true;
|
||||
}
|
||||
}
|
||||
22
src/ChartsEndWorkout.qml
Normal file
@@ -0,0 +1,22 @@
|
||||
import QtQuick 2.4
|
||||
|
||||
ChartsEndWorkoutForm {
|
||||
Component.onCompleted: {
|
||||
//console.log("ChartsEndWorkoutForm completed " + rootItem.workout_sample_points)
|
||||
for(var i=0;i<rootItem.workout_sample_points;i+=10)
|
||||
{
|
||||
//console.log("ChartsEndWorkoutForm completed " + i + " " + rootItem.workout_watt_points[i])
|
||||
powerSeries.append(i, rootItem.workout_watt_points[i]);
|
||||
heartSeries.append(i, rootItem.workout_heart_points[i]);
|
||||
cadenceSeries.append(i, rootItem.workout_cadence_points[i]);
|
||||
resistanceSeries.append(i, rootItem.workout_resistance_points[i]);
|
||||
pelotonResistanceSeries.append(i, rootItem.workout_peloton_resistance_points[i]);
|
||||
}
|
||||
rootItem.update_chart_power(powerChart);
|
||||
//rootItem.update_axes(valueAxisX, valueAxisY);
|
||||
rootItem.update_chart_heart(heartChart);
|
||||
//rootItem.update_axes(valueAxisXHR, valueAxisYHR);
|
||||
//rootItem.update_chart(cadenceChart);
|
||||
//rootItem.update_axes(valueAxisXCadence, valueAxisYCadence);
|
||||
}
|
||||
}
|
||||
274
src/ChartsEndWorkoutForm.ui.qml
Normal file
@@ -0,0 +1,274 @@
|
||||
import QtQuick 2.4
|
||||
import QtCharts 2.2
|
||||
import Qt.labs.settings 1.0
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
property alias powerSeries: powerSeries
|
||||
property alias powerChart: powerChart
|
||||
property alias heartSeries: heartSeries
|
||||
property alias heartChart: heartChart
|
||||
property alias cadenceSeries: cadenceSeries
|
||||
property alias resistanceSeries: resistanceSeries
|
||||
property alias pelotonResistanceSeries: pelotonResistanceSeries
|
||||
property alias cadenceChart: cadenceChart
|
||||
|
||||
Settings {
|
||||
id: settings
|
||||
property real ftp: 200.0
|
||||
}
|
||||
|
||||
Row {
|
||||
id: row
|
||||
anchors.fill: parent
|
||||
|
||||
Text {
|
||||
id: date
|
||||
text: rootItem.workoutStartDate
|
||||
font.pixelSize: 16
|
||||
color: "white"
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.top: date.bottom
|
||||
id: title
|
||||
text: rootItem.workoutName
|
||||
font.pixelSize: 24
|
||||
color: "yellow"
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
/*
|
||||
ChartView {
|
||||
id: caloriesChart
|
||||
title: "KCal"
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
legend.visible: false
|
||||
antialiasing: true
|
||||
width: 64
|
||||
height: 64
|
||||
margins.bottom: 1
|
||||
margins.top: 1
|
||||
margins.left: 1
|
||||
margins.right: 1
|
||||
|
||||
Text {
|
||||
anchors.topMargin: 10
|
||||
anchors.fill: caloriesChart
|
||||
text: rootItem.Pcalories
|
||||
font.pixelSize: 16
|
||||
color: "black"
|
||||
z: 1
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
anchors.horizontalCenter: cadenceChart.horizontalCenter
|
||||
}
|
||||
|
||||
PieSeries {
|
||||
size: 1.0
|
||||
holeSize: 0.7
|
||||
PieSlice { value: 1.0; color: "red" }
|
||||
}
|
||||
}
|
||||
|
||||
ChartView {
|
||||
id: outputChart
|
||||
title: "KJoul"
|
||||
anchors.left: caloriesChart.right
|
||||
anchors.top: parent.top
|
||||
legend.visible: false
|
||||
antialiasing: true
|
||||
width: 64
|
||||
height: 64
|
||||
margins.bottom: 1
|
||||
margins.top: 1
|
||||
margins.left: 1
|
||||
margins.right: 1
|
||||
|
||||
Text {
|
||||
anchors.topMargin: 10
|
||||
anchors.fill: outputChart
|
||||
text: rootItem.Pkjoules
|
||||
font.pixelSize: 16
|
||||
color: "black"
|
||||
z: 1
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
anchors.horizontalCenter: cadenceChart.horizontalCenter
|
||||
}
|
||||
|
||||
PieSeries {
|
||||
size: 1.0
|
||||
holeSize: 0.7
|
||||
PieSlice { value: 1.0; color: "yellow" }
|
||||
}
|
||||
}*/
|
||||
|
||||
ScrollView {
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: title.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
contentHeight: powerChart.height+heartChart.height+cadenceChart.height
|
||||
|
||||
ChartView {
|
||||
id: powerChart
|
||||
antialiasing: true
|
||||
legend.visible: false
|
||||
height: 400
|
||||
width: parent.width
|
||||
title: "Power"
|
||||
titleFont.pixelSize: 20
|
||||
|
||||
ValueAxis {
|
||||
id: valueAxisX
|
||||
min: 0
|
||||
max: rootItem.workout_sample_points
|
||||
//tickCount: 60
|
||||
labelFormat: "%.0f"
|
||||
//labelsVisible: false
|
||||
gridVisible: false
|
||||
//lineVisible: false
|
||||
}
|
||||
|
||||
ValueAxis {
|
||||
id: valueAxisY
|
||||
min: 0
|
||||
max: rootItem.wattMaxChart
|
||||
//tickCount: 60
|
||||
tickCount: 6
|
||||
labelFormat: "%.0f"
|
||||
//labelsVisible: false
|
||||
//gridVisible: false
|
||||
//lineVisible: false
|
||||
}
|
||||
|
||||
LineSeries {
|
||||
//name: "Power"
|
||||
id: powerSeries
|
||||
visible: true
|
||||
axisX: valueAxisX
|
||||
axisY: valueAxisY
|
||||
color: "black"
|
||||
width: 3
|
||||
}
|
||||
}
|
||||
|
||||
ChartView {
|
||||
id: heartChart
|
||||
height: 400
|
||||
width: parent.width
|
||||
antialiasing: true
|
||||
legend.visible: false
|
||||
anchors.top: powerChart.bottom
|
||||
title: "Heart Rate"
|
||||
titleFont.pixelSize: 20
|
||||
|
||||
ValueAxis {
|
||||
id: valueAxisXHR
|
||||
min: 0
|
||||
max: rootItem.workout_sample_points
|
||||
//tickCount: 60
|
||||
labelFormat: "%.0f"
|
||||
//labelsVisible: false
|
||||
gridVisible: false
|
||||
//lineVisible: false
|
||||
}
|
||||
|
||||
ValueAxis {
|
||||
id: valueAxisYHR
|
||||
min: 0
|
||||
max: 220
|
||||
tickCount: 5
|
||||
labelFormat: "%.0f"
|
||||
//labelsVisible: false
|
||||
//gridVisible: false
|
||||
//lineVisible: false
|
||||
}
|
||||
|
||||
LineSeries {
|
||||
//name: "Power"
|
||||
id: heartSeries
|
||||
visible: true
|
||||
axisX: valueAxisXHR
|
||||
axisY: valueAxisYHR
|
||||
color: "black"
|
||||
width: 3
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ChartView {
|
||||
id: cadenceChart
|
||||
height: 400
|
||||
width: parent.width
|
||||
antialiasing: true
|
||||
legend.visible: true
|
||||
anchors.top: heartChart.bottom
|
||||
title: "Cadence"
|
||||
titleFont.pixelSize: 20
|
||||
|
||||
ValueAxis {
|
||||
id: valueAxisXCadence
|
||||
min: 0
|
||||
max: rootItem.workout_sample_points
|
||||
//tickCount: 60
|
||||
labelFormat: "%.0f"
|
||||
//labelsVisible: false
|
||||
gridVisible: false
|
||||
//lineVisible: false
|
||||
}
|
||||
|
||||
ValueAxis {
|
||||
id: valueAxisYCadence
|
||||
min: 0
|
||||
max: 200
|
||||
//tickCount: 60
|
||||
labelFormat: "%.0f"
|
||||
//labelsVisible: false
|
||||
gridVisible: false
|
||||
//lineVisible: false
|
||||
}
|
||||
|
||||
LineSeries {
|
||||
name: "Cadence"
|
||||
id: cadenceSeries
|
||||
visible: true
|
||||
axisX: valueAxisXCadence
|
||||
axisY: valueAxisYCadence
|
||||
color: "black"
|
||||
width: 3
|
||||
}
|
||||
|
||||
LineSeries {
|
||||
name: "Resistance"
|
||||
id: resistanceSeries
|
||||
visible: true
|
||||
axisX: valueAxisXCadence
|
||||
axisY: valueAxisYCadence
|
||||
color: "blue"
|
||||
width: 3
|
||||
}
|
||||
|
||||
LineSeries {
|
||||
name: "Peloton Resistance"
|
||||
id: pelotonResistanceSeries
|
||||
visible: true
|
||||
axisX: valueAxisXCadence
|
||||
axisY: valueAxisYCadence
|
||||
color: "red"
|
||||
width: 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/Home.qml
@@ -4,19 +4,30 @@ import QtQuick.Controls.Material 2.12
|
||||
import QtGraphicalEffects 1.12
|
||||
import QtQuick.Window 2.12
|
||||
import Qt.labs.settings 1.0
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
HomeForm{
|
||||
objectName: "home"
|
||||
signal start_clicked;
|
||||
signal stop_clicked;
|
||||
signal lap_clicked;
|
||||
signal peloton_start_workout;
|
||||
signal plus_clicked(string name)
|
||||
signal minus_clicked(string name)
|
||||
|
||||
Settings {
|
||||
id: settings
|
||||
property real ui_zoom: 100.0
|
||||
property bool chart_footer: true
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: messagePelotonAskStart
|
||||
text: "Peloton Workout in progress"
|
||||
informativeText: "Do you want to follow the resistance?"
|
||||
buttons: (MessageDialog.Yes | MessageDialog.No)
|
||||
onYesClicked: {rootItem.pelotonAskStart = false; peloton_start_workout();}
|
||||
onNoClicked: rootItem.pelotonAskStart = false;
|
||||
visible: rootItem.pelotonAskStart
|
||||
}
|
||||
|
||||
Popup {
|
||||
@@ -54,56 +65,17 @@ HomeForm{
|
||||
onTriggered: popupLap.close();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: refreshChartTimer
|
||||
interval: 500
|
||||
running: true
|
||||
repeat: true
|
||||
property int timeline: 0
|
||||
onTriggered: {
|
||||
if(settings.chart_footer) chartView.height = 130 * settings.ui_zoom / 100; else chartView.height = 0;
|
||||
if(rootItem.labelHelp || rootItem.wattMax === 0 || !settings.chart_footer) return;
|
||||
series1.upperSeries.append(timeline, rootItem.wattZ1);
|
||||
series2.upperSeries.append(timeline, rootItem.wattZ2);
|
||||
series3.upperSeries.append(timeline, rootItem.wattZ3);
|
||||
series4.upperSeries.append(timeline, rootItem.wattZ4);
|
||||
series5.upperSeries.append(timeline, rootItem.wattZ5);
|
||||
series6.upperSeries.append(timeline, rootItem.wattZ6);
|
||||
series7.upperSeries.append(timeline, rootItem.wattZ7);
|
||||
series1.axisX.min = (timeline - 60 > 0) ? timeline - 60 : 0;
|
||||
series2.axisX.min = (timeline - 60 > 0) ? timeline - 60 : 0;
|
||||
series3.axisX.min = (timeline - 60 > 0) ? timeline - 60 : 0;
|
||||
series4.axisX.min = (timeline - 60 > 0) ? timeline - 60 : 0;
|
||||
series5.axisX.min = (timeline - 60 > 0) ? timeline - 60 : 0;
|
||||
series6.axisX.min = (timeline - 60 > 0) ? timeline - 60 : 0;
|
||||
series7.axisX.min = (timeline - 60 > 0) ? timeline - 60 : 0;
|
||||
series1.axisX.max = (timeline - 60 > 0) ? timeline : 60;
|
||||
series2.axisX.max = (timeline - 60 > 0) ? timeline : 60;
|
||||
series3.axisX.max = (timeline - 60 > 0) ? timeline : 60;
|
||||
series4.axisX.max = (timeline - 60 > 0) ? timeline : 60;
|
||||
series5.axisX.max = (timeline - 60 > 0) ? timeline : 60;
|
||||
series6.axisX.max = (timeline - 60 > 0) ? timeline : 60;
|
||||
series7.axisX.max = (timeline - 60 > 0) ? timeline : 60;
|
||||
/*series1.append(1, 5);
|
||||
series1.append(2, 50);
|
||||
series1.append(3, 500);*/
|
||||
//rootItem.dataSource.update(chartView.series(0));
|
||||
//console.log("refreshChartTimer" + timeline);
|
||||
timeline++;
|
||||
}
|
||||
}
|
||||
|
||||
start.onClicked: { start_clicked(); }
|
||||
stop.onClicked: { stop_clicked(); }
|
||||
stop.onClicked: { stop_clicked(); stackView.push("ChartsEndWorkout.qml") }
|
||||
lap.onClicked: { lap_clicked(); popupLap.open(); popupLapAutoClose.running = true; }
|
||||
|
||||
Component.onCompleted: { console.log("completed"); }
|
||||
|
||||
GridView {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.fill: parent
|
||||
cellWidth: 175 * settings.ui_zoom / 100
|
||||
cellHeight: 130 * settings.ui_zoom / 100
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
model: appModel
|
||||
leftMargin: { (parent.width % cellWidth) / 2 }
|
||||
@@ -155,7 +127,7 @@ HomeForm{
|
||||
id: myIcon
|
||||
x: 5
|
||||
anchors {
|
||||
bottom: id1.bottom
|
||||
bottom: id1.bottom
|
||||
}
|
||||
width: 48 * settings.ui_zoom / 100
|
||||
height: 48 * settings.ui_zoom / 100
|
||||
@@ -194,7 +166,7 @@ HomeForm{
|
||||
top: myIcon.top
|
||||
}
|
||||
font.bold: true
|
||||
font.pointSize: labelFontSize
|
||||
font.pointSize: labelFontSize
|
||||
color: "white"
|
||||
text: name
|
||||
anchors.left: parent.left
|
||||
|
||||
@@ -2,7 +2,6 @@ import QtQuick 2.12
|
||||
import QtQuick.Controls 2.5
|
||||
import QtQuick.Controls.Material 2.12
|
||||
import QtGraphicalEffects 1.12
|
||||
import QtCharts 2.15
|
||||
|
||||
Page {
|
||||
|
||||
@@ -14,15 +13,6 @@ Page {
|
||||
property alias lap: lap
|
||||
property alias row: row
|
||||
|
||||
property alias chartView: chartView
|
||||
property alias series1: series1
|
||||
property alias series2: series2
|
||||
property alias series3: series3
|
||||
property alias series4: series4
|
||||
property alias series5: series5
|
||||
property alias series6: series6
|
||||
property alias series7: series7
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: rootItem.topBarHeight
|
||||
@@ -171,112 +161,6 @@ Page {
|
||||
visible: rootItem.labelHelp
|
||||
}
|
||||
}
|
||||
|
||||
footer:
|
||||
ChartView {
|
||||
id: chartView
|
||||
backgroundColor: Material.backgroundColor
|
||||
margins.bottom: 0
|
||||
margins.left: 0
|
||||
margins.right: 0
|
||||
margins.top: 0
|
||||
plotAreaColor: Material.backgroundColor
|
||||
//title: "Spline"
|
||||
//anchors.top: gridView.bottom
|
||||
antialiasing: true
|
||||
legend.visible: false
|
||||
width: stackView.width
|
||||
//Label.visible: false
|
||||
//visible: settings.chart_footer
|
||||
|
||||
ValueAxis {
|
||||
id: valueAxisX
|
||||
min: 0
|
||||
max: 60
|
||||
tickCount: 60
|
||||
labelFormat: "%.0f"
|
||||
labelsVisible: false
|
||||
gridVisible: false
|
||||
lineVisible: false
|
||||
}
|
||||
|
||||
ValueAxis {
|
||||
id: valueAxisY
|
||||
min: 0
|
||||
max: rootItem.wattMax
|
||||
//tickCount: 60
|
||||
labelFormat: "%.0f"
|
||||
labelsVisible: false
|
||||
gridVisible: false
|
||||
lineVisible: false
|
||||
titleVisible: false
|
||||
}
|
||||
|
||||
AreaSeries {
|
||||
id: series1
|
||||
useOpenGL: true
|
||||
axisX: valueAxisX
|
||||
axisY: valueAxisY
|
||||
name: "Z1"
|
||||
color: "white"
|
||||
borderColor: color
|
||||
upperSeries: LineSeries {}
|
||||
}
|
||||
AreaSeries {
|
||||
id: series2
|
||||
useOpenGL: true
|
||||
axisX: valueAxisX
|
||||
axisY: valueAxisY
|
||||
color: "limegreen"
|
||||
borderColor: color
|
||||
upperSeries: LineSeries {}
|
||||
}
|
||||
AreaSeries {
|
||||
id: series3
|
||||
useOpenGL: true
|
||||
axisX: valueAxisX
|
||||
axisY: valueAxisY
|
||||
color: "gold"
|
||||
borderColor: color
|
||||
upperSeries: LineSeries {}
|
||||
}
|
||||
AreaSeries {
|
||||
id: series4
|
||||
useOpenGL: true
|
||||
axisX: valueAxisX
|
||||
axisY: valueAxisY
|
||||
color: "orange"
|
||||
borderColor: color
|
||||
upperSeries: LineSeries {}
|
||||
}
|
||||
AreaSeries {
|
||||
id: series5
|
||||
useOpenGL: true
|
||||
axisX: valueAxisX
|
||||
axisY: valueAxisY
|
||||
color: "darkorange"
|
||||
borderColor: color
|
||||
upperSeries: LineSeries {}
|
||||
}
|
||||
AreaSeries {
|
||||
id: series6
|
||||
useOpenGL: true
|
||||
axisX: valueAxisX
|
||||
axisY: valueAxisY
|
||||
color: "orangered"
|
||||
borderColor: color
|
||||
upperSeries: LineSeries {}
|
||||
}
|
||||
AreaSeries {
|
||||
id: series7
|
||||
useOpenGL: true
|
||||
axisX: valueAxisX
|
||||
axisY: valueAxisY
|
||||
color: "red"
|
||||
borderColor: color
|
||||
upperSeries: LineSeries {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*##^##
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="2.5.10" android:versionCode="111" android:installLocation="auto">
|
||||
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="2.5.16" android:versionCode="113" 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 -->
|
||||
|
||||
@@ -16,6 +16,7 @@ uint8_t bike::fanSpeed() { return FanSpeed; }
|
||||
bool bike::connected() { return false; }
|
||||
uint16_t bike::watts() { return 0; }
|
||||
metric bike::pelotonResistance() { return m_pelotonResistance; }
|
||||
int bike::pelotonToBikeResistance(int pelotonResistance) {return pelotonResistance;}
|
||||
|
||||
bluetoothdevice::BLUETOOTH_TYPE bike::deviceType() { return bluetoothdevice::BIKE; }
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ public:
|
||||
virtual uint16_t lastCrankEventTime();
|
||||
virtual bool connected();
|
||||
virtual uint16_t watts();
|
||||
virtual int pelotonToBikeResistance(int pelotonResistance);
|
||||
bluetoothdevice::BLUETOOTH_TYPE deviceType();
|
||||
metric pelotonResistance();
|
||||
void clearStats();
|
||||
|
||||
@@ -149,13 +149,11 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
heartRateBeltFound = heartRateBeltAvaiable();
|
||||
}
|
||||
|
||||
if(!device.name().length()) return;
|
||||
|
||||
bool found = false;
|
||||
QMutableListIterator<QBluetoothDeviceInfo> i(devices);
|
||||
while (i.hasNext()) {
|
||||
QBluetoothDeviceInfo b = i.next();
|
||||
if(SAME_BLUETOOTH_DEVICE(b,device))
|
||||
if(SAME_BLUETOOTH_DEVICE(b,device) && b.name().length())
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
@@ -166,6 +164,9 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
|
||||
emit deviceFound(device.name());
|
||||
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')' + " " + device.majorDeviceClass() + ":" + device.minorDeviceClass());
|
||||
#if defined(Q_OS_DARWIN) || defined(Q_OS_IOS)
|
||||
qDebug() << device.deviceUuid();
|
||||
#endif
|
||||
|
||||
if(onlyDiscover) return;
|
||||
|
||||
@@ -244,7 +245,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
if(!discoveryAgent->isActive())
|
||||
emit searchingStop();
|
||||
}
|
||||
else if((b.name().toUpper().startsWith("HORIZON") || b.name().toUpper().startsWith("F80")) && !horizonTreadmill && filter)
|
||||
else if((b.name().toUpper().startsWith("HORIZON") || b.name().toUpper().startsWith("F80") || b.name().toUpper().startsWith("ESANGLINKER")) && !horizonTreadmill && filter)
|
||||
{
|
||||
discoveryAgent->stop();
|
||||
horizonTreadmill = new horizontreadmill(noWriteResistance, noHeartService);
|
||||
@@ -263,7 +264,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
emit searchingStop();
|
||||
}
|
||||
else if(b.name().startsWith("ECH") && !echelonConnectSport && filter)
|
||||
{
|
||||
{
|
||||
discoveryAgent->stop();
|
||||
echelonConnectSport = new echelonconnectsport(noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
|
||||
//stateFileRead();
|
||||
@@ -471,10 +472,33 @@ void bluetooth::connectedAndDiscovered()
|
||||
|
||||
if(this->device() != nullptr)
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
QString heartRateBeltName = settings.value("heart_rate_belt_name", "Disabled").toString();
|
||||
QString b = settings.value("hrm_lastdevice_name", "").toString();
|
||||
qDebug() << "last hrm name" << b;
|
||||
if(!b.compare(heartRateBeltName))
|
||||
{
|
||||
heartRateBelt = new heartratebelt();
|
||||
//connect(heartRateBelt, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
|
||||
connect(heartRateBelt, SIGNAL(debug(QString)), this, SLOT(debug(QString)));
|
||||
connect(heartRateBelt, SIGNAL(heartRate(uint8_t)), this->device(), SLOT(heartRate(uint8_t)));
|
||||
QBluetoothDeviceInfo bt;
|
||||
bt.setDeviceUuid(QBluetoothUuid(settings.value("hrm_lastdevice_address", "").toString()));
|
||||
qDebug() << "UUID" << bt.deviceUuid();
|
||||
heartRateBelt->deviceDiscovered(bt);
|
||||
}
|
||||
#endif
|
||||
foreach(QBluetoothDeviceInfo b, devices)
|
||||
{
|
||||
if(((b.name().startsWith(heartRateBeltName))) && !heartRateBelt && !heartRateBeltName.startsWith("Disabled"))
|
||||
{
|
||||
settings.setValue("hrm_lastdevice_name", b.name());
|
||||
#ifndef Q_OS_IOS
|
||||
settings.setValue("hrm_lastdevice_address", b.address().toString());
|
||||
#else
|
||||
settings.setValue("hrm_lastdevice_address", b.deviceUuid().toString());
|
||||
#endif
|
||||
heartRateBelt = new heartratebelt();
|
||||
//connect(heartRateBelt, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
|
||||
|
||||
@@ -9,8 +9,15 @@
|
||||
#include <math.h>
|
||||
#include "ios/lockscreen.h"
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
|
||||
#endif
|
||||
|
||||
echelonconnectsport::echelonconnectsport(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain)
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
#endif
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
@@ -136,7 +143,7 @@ void echelonconnectsport::update()
|
||||
|
||||
if(requestResistance != -1)
|
||||
{
|
||||
if(requestResistance > 32) requestResistance = 32;
|
||||
if(requestResistance > max_resistance) requestResistance = max_resistance;
|
||||
else if(requestResistance <= 0) requestResistance = 1;
|
||||
|
||||
if(requestResistance != currentResistance().value())
|
||||
@@ -171,6 +178,25 @@ void echelonconnectsport::serviceDiscovered(const QBluetoothUuid &gatt)
|
||||
qDebug() << "serviceDiscovered " + gatt.toString();
|
||||
}
|
||||
|
||||
int echelonconnectsport::pelotonToBikeResistance(int pelotonResistance)
|
||||
{
|
||||
for(int i = 1; i<max_resistance-1; i++)
|
||||
{
|
||||
if(bikeResistanceToPeloton(i) <= pelotonResistance && bikeResistanceToPeloton(i+1) >= pelotonResistance)
|
||||
return i;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
double echelonconnectsport::bikeResistanceToPeloton(double resistance)
|
||||
{
|
||||
//0,0097x3 - 0,4972x2 + 10,126x - 37,08
|
||||
double p = ((pow(resistance,3) * 0.0097) - (0.4972 * pow(resistance, 2)) + (10.126 * resistance) - 37.08);
|
||||
if(p < 0)
|
||||
p = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
void echelonconnectsport::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
|
||||
{
|
||||
//qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
@@ -186,12 +212,7 @@ void echelonconnectsport::characteristicChanged(const QLowEnergyCharacteristic &
|
||||
if(newValue.length() == 5 && ((unsigned char)newValue.at(0)) == 0xf0 && ((unsigned char)newValue.at(1)) == 0xd2)
|
||||
{
|
||||
Resistance = newValue.at(3);
|
||||
|
||||
//0,0097x3 - 0,4972x2 + 10,126x - 37,08
|
||||
double p = ((pow(Resistance.value(),3) * 0.0097) - (0.4972 * pow(Resistance.value(), 2)) + (10.126 * Resistance.value()) - 37.08);
|
||||
if(p < 0)
|
||||
p = 0;
|
||||
m_pelotonResistance = p;
|
||||
m_pelotonResistance = bikeResistanceToPeloton(Resistance.value());
|
||||
|
||||
qDebug() << "Current resistance: " + QString::number(Resistance.value());
|
||||
return;
|
||||
|
||||
@@ -39,12 +39,15 @@ class echelonconnectsport : public bike
|
||||
Q_OBJECT
|
||||
public:
|
||||
echelonconnectsport(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain);
|
||||
int pelotonToBikeResistance(int pelotonResistance);
|
||||
bool connected();
|
||||
|
||||
void* VirtualBike();
|
||||
void* VirtualDevice();
|
||||
|
||||
private:
|
||||
const int max_resistance = 32;
|
||||
double bikeResistanceToPeloton(double resistance);
|
||||
double GetDistanceFromPacket(QByteArray packet);
|
||||
QTime GetElapsedFromPacket(QByteArray packet);
|
||||
void btinit();
|
||||
|
||||
@@ -127,7 +127,7 @@ void heartratebelt::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
QSettings settings;
|
||||
QString heartRateBeltName = settings.value("heart_rate_belt_name", "Disabled").toString();
|
||||
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')');
|
||||
if(device.name().startsWith(heartRateBeltName))
|
||||
//if(device.name().startsWith(heartRateBeltName))
|
||||
{
|
||||
bluetoothDevice = device;
|
||||
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
|
||||
|
||||
@@ -133,6 +133,9 @@ homeform::homeform(QQmlApplicationEngine* engine, bluetooth* bl)
|
||||
this, SLOT(refresh_bluetooth_devices_clicked()));
|
||||
QObject::connect(home, SIGNAL(lap_clicked()),
|
||||
this, SLOT(Lap()));
|
||||
QObject::connect(home, SIGNAL(peloton_start_workout()),
|
||||
this, SLOT(peloton_start_workout()));
|
||||
|
||||
|
||||
if(settings.value("top_bar_enabled", true).toBool())
|
||||
{
|
||||
@@ -146,6 +149,9 @@ homeform::homeform(QQmlApplicationEngine* engine, bluetooth* bl)
|
||||
|
||||
emit tile_orderChanged(tile_order());
|
||||
|
||||
pelotonHandler = new peloton(bl);
|
||||
connect(pelotonHandler, SIGNAL(workoutStarted(QString)), this, SLOT(pelotonWorkoutStarted(QString)));
|
||||
|
||||
//populate the UI
|
||||
#if 0
|
||||
#warning("disable me!")
|
||||
@@ -202,6 +208,34 @@ homeform::homeform(QQmlApplicationEngine* engine, bluetooth* bl)
|
||||
#endif
|
||||
}
|
||||
|
||||
void homeform::peloton_start_workout()
|
||||
{
|
||||
qDebug() << "peloton_start_workout!";
|
||||
if(pelotonHandler && pelotonHandler->trainrows.length())
|
||||
{
|
||||
if(trainProgram)
|
||||
{
|
||||
trainProgram->stop();
|
||||
delete trainProgram;
|
||||
}
|
||||
trainProgram = new trainprogram(pelotonHandler->trainrows, bluetoothManager);
|
||||
trainProgramSignals();
|
||||
trainProgram->restart();
|
||||
}
|
||||
}
|
||||
|
||||
void homeform::pelotonWorkoutStarted(QString name)
|
||||
{
|
||||
QSettings settings;
|
||||
if(!settings.value("top_bar_enabled", true).toBool()) return;
|
||||
m_info = name;
|
||||
emit infoChanged(m_info);
|
||||
|
||||
stravaPelotonActivityName = name;
|
||||
m_pelotonAskStart = true;
|
||||
emit(changePelotonAskStart(pelotonAskStart()));
|
||||
}
|
||||
|
||||
void homeform::backup()
|
||||
{
|
||||
static uint8_t index = 0;
|
||||
@@ -822,6 +856,7 @@ void homeform::update()
|
||||
double resistance = 0;
|
||||
double watts = 0;
|
||||
double pace = 0;
|
||||
double peloton_resistance = 0;
|
||||
uint8_t cadence = 0;
|
||||
|
||||
bool miles = settings.value("miles_unit", false).toBool();
|
||||
@@ -867,7 +902,8 @@ void homeform::update()
|
||||
{
|
||||
cadence = ((bike*)bluetoothManager->device())->currentCadence().value();
|
||||
resistance = ((bike*)bluetoothManager->device())->currentResistance().value();
|
||||
this->peloton_resistance->setValue(QString::number(((bike*)bluetoothManager->device())->pelotonResistance().value(), 'f', 0));
|
||||
peloton_resistance = ((bike*)bluetoothManager->device())->pelotonResistance().value();
|
||||
this->peloton_resistance->setValue(QString::number(peloton_resistance, 'f', 0));
|
||||
this->target_resistance->setValue(QString::number(((bike*)bluetoothManager->device())->lastRequestedResistance().value(), 'f', 0));
|
||||
this->resistance->setValue(QString::number(resistance, 'f', 0));
|
||||
this->cadence->setValue(QString::number(cadence));
|
||||
@@ -896,23 +932,13 @@ void homeform::update()
|
||||
QString ftpMinW = "0";
|
||||
QString ftpMaxW = "0";
|
||||
if(ftpSetting > 0)
|
||||
{
|
||||
ftpPerc = (watts / ftpSetting) * 100.0;
|
||||
m_wattZ1Max = ftpSetting * 0.54;
|
||||
m_wattZ2Max = ftpSetting * 0.75;
|
||||
m_wattZ3Max = ftpSetting * 0.90;
|
||||
m_wattZ4Max = ftpSetting * 1.05;
|
||||
m_wattZ5Max = ftpSetting * 1.20;
|
||||
m_wattZ6Max = ftpSetting * 1.50;
|
||||
}
|
||||
if(ftpPerc < 55)
|
||||
{
|
||||
ftpMinW = QString::number(0, 'f', 0);
|
||||
ftpMaxW = QString::number(ftpSetting * 0.54, 'f', 0);
|
||||
ftpZone = 1;
|
||||
ftp->setValueFontColor("white");
|
||||
m_wattZ2 = m_wattZ3 = m_wattZ4 = m_wattZ5 = m_wattZ6 = m_wattZ7 = 0;
|
||||
m_wattZ1 = watts;
|
||||
}
|
||||
else if(ftpPerc < 76)
|
||||
{
|
||||
@@ -920,8 +946,6 @@ void homeform::update()
|
||||
ftpMaxW = QString::number(ftpSetting * 0.75, 'f', 0);
|
||||
ftpZone = 2;
|
||||
ftp->setValueFontColor("limegreen");
|
||||
m_wattZ3 = m_wattZ4 = m_wattZ5 = m_wattZ6 = m_wattZ7 = 0;
|
||||
m_wattZ2 = watts;
|
||||
}
|
||||
else if(ftpPerc < 91)
|
||||
{
|
||||
@@ -929,8 +953,6 @@ void homeform::update()
|
||||
ftpMaxW = QString::number(ftpSetting * 0.90, 'f', 0);
|
||||
ftpZone = 3;
|
||||
ftp->setValueFontColor("gold");
|
||||
m_wattZ4 = m_wattZ5 = m_wattZ6 = m_wattZ7 = 0;
|
||||
m_wattZ3 = watts;
|
||||
}
|
||||
else if(ftpPerc < 106)
|
||||
{
|
||||
@@ -938,8 +960,6 @@ void homeform::update()
|
||||
ftpMaxW = QString::number(ftpSetting * 1.05, 'f', 0);
|
||||
ftpZone = 4;
|
||||
ftp->setValueFontColor("orange");
|
||||
m_wattZ5 = m_wattZ6 = m_wattZ7 = 0;
|
||||
m_wattZ4 = watts;
|
||||
}
|
||||
else if(ftpPerc < 121)
|
||||
{
|
||||
@@ -947,8 +967,6 @@ void homeform::update()
|
||||
ftpMaxW = QString::number(ftpSetting * 1.20, 'f', 0);
|
||||
ftpZone = 5;
|
||||
ftp->setValueFontColor("darkorange");
|
||||
m_wattZ6 = m_wattZ7 = 0;
|
||||
m_wattZ5 = watts;
|
||||
}
|
||||
else if(ftpPerc < 151)
|
||||
{
|
||||
@@ -956,8 +974,6 @@ void homeform::update()
|
||||
ftpMaxW = QString::number(ftpSetting * 1.50, 'f', 0);
|
||||
ftpZone = 6;
|
||||
ftp->setValueFontColor("orangered");
|
||||
m_wattZ7 = 0;
|
||||
m_wattZ6 = watts;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -965,13 +981,10 @@ void homeform::update()
|
||||
ftpMaxW = "∞";
|
||||
ftpZone = 7;
|
||||
ftp->setValueFontColor("red");
|
||||
m_wattZ7 = watts;
|
||||
}
|
||||
ftp->setValue("Z" + QString::number(ftpZone, 'f', 0));
|
||||
ftp->setSecondLine(ftpMinW + "-" + ftpMaxW + "W " + QString::number(ftpPerc, 'f', 0) + "%");
|
||||
|
||||
emit wattMaxChanged(bluetoothManager->device()->wattsMetric().max());
|
||||
|
||||
QString Z;
|
||||
double maxHeartRate = 220.0 - settings.value("age", 35).toDouble();
|
||||
if(maxHeartRate == 0) maxHeartRate = 190.0;
|
||||
@@ -1087,6 +1100,7 @@ void homeform::update()
|
||||
bluetoothManager->device()->odometer(),
|
||||
watts,
|
||||
resistance,
|
||||
peloton_resistance,
|
||||
(uint8_t)bluetoothManager->device()->currentHeart().value(),
|
||||
pace, cadence, bluetoothManager->device()->calories(),
|
||||
bluetoothManager->device()->elevationGain(),
|
||||
@@ -1101,7 +1115,7 @@ void homeform::update()
|
||||
}
|
||||
|
||||
emit changeOfdevice();
|
||||
emit changeOflap();
|
||||
emit changeOflap();
|
||||
}
|
||||
|
||||
bool homeform::getDevice()
|
||||
@@ -1372,13 +1386,20 @@ bool homeform::strava_upload_file(QByteArray &data, QString remotename)
|
||||
|
||||
// use metadata config if the user selected it
|
||||
QString activityName = " " + settings.value("strava_suffix", "#qdomyos-zwift").toString() ;
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
|
||||
if(stravaPelotonActivityName.length())
|
||||
{
|
||||
activityName = "Run" + activityName;
|
||||
activityName = stravaPelotonActivityName + activityName;
|
||||
}
|
||||
else
|
||||
{
|
||||
activityName = "Ride" + activityName;
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
|
||||
{
|
||||
activityName = "Run" + activityName;
|
||||
}
|
||||
else
|
||||
{
|
||||
activityName = "Ride" + activityName;
|
||||
}
|
||||
}
|
||||
activityNamePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain;charset=utf-8"));
|
||||
activityNamePart.setBody(activityName.toUtf8());
|
||||
|
||||
166
src/homeform.h
@@ -5,9 +5,13 @@
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QOAuth2AuthorizationCodeFlow>
|
||||
#include <QNetworkReply>
|
||||
#include <QGraphicsScene>
|
||||
#include <QChart>
|
||||
#include <QColor>
|
||||
#include "bluetooth.h"
|
||||
#include "sessionline.h"
|
||||
#include "trainprogram.h"
|
||||
#include "peloton.h"
|
||||
|
||||
class DataObject : public QObject
|
||||
{
|
||||
@@ -76,6 +80,7 @@ class homeform: public QObject
|
||||
Q_PROPERTY(bool labelHelp READ labelHelp NOTIFY changeLabelHelp)
|
||||
Q_PROPERTY( bool device READ getDevice NOTIFY changeOfdevice)
|
||||
Q_PROPERTY( bool lap READ getLap NOTIFY changeOflap)
|
||||
Q_PROPERTY( bool pelotonAskStart READ pelotonAskStart NOTIFY changePelotonAskStart WRITE setPelotonAskStart)
|
||||
Q_PROPERTY(int topBarHeight READ topBarHeight NOTIFY topBarHeightChanged)
|
||||
Q_PROPERTY(QString info READ info NOTIFY infoChanged)
|
||||
Q_PROPERTY(QString signal READ signal NOTIFY signalChanged)
|
||||
@@ -88,39 +93,108 @@ class homeform: public QObject
|
||||
Q_PROPERTY(QStringList bluetoothDevices READ bluetoothDevices NOTIFY bluetoothDevicesChanged)
|
||||
Q_PROPERTY(QStringList tile_order READ tile_order NOTIFY tile_orderChanged)
|
||||
Q_PROPERTY(bool generalPopupVisible READ generalPopupVisible NOTIFY generalPopupVisibleChanged WRITE setGeneralPopupVisible)
|
||||
Q_PROPERTY(double wattZ1 READ wattZ1)
|
||||
Q_PROPERTY(double wattZ2 READ wattZ2)
|
||||
Q_PROPERTY(double wattZ3 READ wattZ3)
|
||||
Q_PROPERTY(double wattZ4 READ wattZ4)
|
||||
Q_PROPERTY(double wattZ5 READ wattZ5)
|
||||
Q_PROPERTY(double wattZ6 READ wattZ6)
|
||||
Q_PROPERTY(double wattZ7 READ wattZ7)
|
||||
Q_PROPERTY(double wattZ1Max READ wattZ1Max)
|
||||
Q_PROPERTY(double wattZ2Max READ wattZ2Max)
|
||||
Q_PROPERTY(double wattZ3Max READ wattZ3Max)
|
||||
Q_PROPERTY(double wattZ4Max READ wattZ4Max)
|
||||
Q_PROPERTY(double wattZ5Max READ wattZ5Max)
|
||||
Q_PROPERTY(double wattZ6Max READ wattZ6Max)
|
||||
Q_PROPERTY(double wattMax READ wattMax NOTIFY wattMaxChanged)
|
||||
Q_PROPERTY(QString workoutStartDate READ workoutStartDate)
|
||||
Q_PROPERTY(QString workoutName READ workoutName)
|
||||
Q_PROPERTY(int workout_sample_points READ workout_sample_points)
|
||||
Q_PROPERTY(QList<double> workout_watt_points READ workout_watt_points)
|
||||
Q_PROPERTY(QList<double> workout_heart_points READ workout_heart_points)
|
||||
Q_PROPERTY(QList<double> workout_cadence_points READ workout_cadence_points)
|
||||
Q_PROPERTY(QList<double> workout_peloton_resistance_points READ workout_peloton_resistance_points)
|
||||
Q_PROPERTY(QList<double> workout_resistance_points READ workout_resistance_points)
|
||||
Q_PROPERTY(double wattMaxChart READ wattMaxChart)
|
||||
|
||||
public:
|
||||
Q_INVOKABLE void update_chart_power(QQuickItem *item){
|
||||
if(QGraphicsScene *scene = item->findChild<QGraphicsScene *>()){
|
||||
for(QGraphicsItem *it : scene->items()){
|
||||
if(QtCharts::QChart *chart = dynamic_cast<QtCharts::QChart *>(it)){
|
||||
// Customize chart background
|
||||
QLinearGradient backgroundGradient;
|
||||
double maxWatt = wattMaxChart();
|
||||
QSettings settings;
|
||||
double ftpSetting = settings.value("ftp", 200.0).toDouble();
|
||||
/*backgroundGradient.setStart(QPointF(0, 0));
|
||||
backgroundGradient.setFinalStop(QPointF(0, 1));
|
||||
backgroundGradient.setColorAt((maxWatt - (ftpSetting * 0.55)) / maxWatt, QColor("white"));
|
||||
backgroundGradient.setColorAt((maxWatt - (ftpSetting * 0.75)) / maxWatt, QColor("limegreen"));
|
||||
backgroundGradient.setColorAt((maxWatt - (ftpSetting * 0.90)) / maxWatt, QColor("gold"));
|
||||
backgroundGradient.setColorAt((maxWatt - (ftpSetting * 1.05)) / maxWatt, QColor("orange"));
|
||||
backgroundGradient.setColorAt((maxWatt - (ftpSetting * 1.20)) / maxWatt, QColor("darkorange"));
|
||||
backgroundGradient.setColorAt((maxWatt - (ftpSetting * 1.5)) / maxWatt, QColor("orangered"));
|
||||
backgroundGradient.setColorAt(0.0, QColor("red"));*/
|
||||
|
||||
//backgroundGradient.setCoordinateMode(QGradient::ObjectBoundingMode);
|
||||
//chart->setBackgroundBrush(backgroundGradient);
|
||||
// Customize plot area background
|
||||
QLinearGradient plotAreaGradient;
|
||||
plotAreaGradient.setStart(QPointF(0, 0));
|
||||
plotAreaGradient.setFinalStop(QPointF(0, 1));
|
||||
plotAreaGradient.setColorAt((maxWatt - (ftpSetting * 0.55)) / maxWatt, QColor("white"));
|
||||
plotAreaGradient.setColorAt((maxWatt - (ftpSetting * 0.75)) / maxWatt, QColor("limegreen"));
|
||||
plotAreaGradient.setColorAt((maxWatt - (ftpSetting * 0.90)) / maxWatt, QColor("gold"));
|
||||
plotAreaGradient.setColorAt((maxWatt - (ftpSetting * 1.05)) / maxWatt, QColor("orange"));
|
||||
plotAreaGradient.setColorAt((maxWatt - (ftpSetting * 1.20)) / maxWatt, QColor("darkorange"));
|
||||
plotAreaGradient.setColorAt((maxWatt - (ftpSetting * 1.5)) / maxWatt, QColor("orangered"));
|
||||
plotAreaGradient.setColorAt(0.0, QColor("red"));
|
||||
plotAreaGradient.setCoordinateMode(QGradient::ObjectBoundingMode);
|
||||
chart->setPlotAreaBackgroundBrush(plotAreaGradient);
|
||||
chart->setPlotAreaBackgroundVisible(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Q_INVOKABLE void update_chart_heart(QQuickItem *item){
|
||||
if(QGraphicsScene *scene = item->findChild<QGraphicsScene *>()){
|
||||
for(QGraphicsItem *it : scene->items()){
|
||||
if(QtCharts::QChart *chart = dynamic_cast<QtCharts::QChart *>(it)){
|
||||
// Customize chart background
|
||||
QLinearGradient backgroundGradient;
|
||||
QSettings settings;
|
||||
double maxHeartRate = 220.0 - settings.value("age", 35).toDouble();
|
||||
if(maxHeartRate == 0) maxHeartRate = 190.0;
|
||||
/*backgroundGradient.setStart(QPointF(0, 0));
|
||||
backgroundGradient.setFinalStop(QPointF(0, 1));
|
||||
backgroundGradient.setColorAt((220 - (maxHeartRate * settings.value("heart_rate_zone1", 70.0).toDouble() / 100)) / 220, QColor("lightsteelblue"));
|
||||
backgroundGradient.setColorAt((220 - (maxHeartRate * settings.value("heart_rate_zone2", 80.0).toDouble() / 100)) / 220, QColor("green"));
|
||||
backgroundGradient.setColorAt((220 - (maxHeartRate * settings.value("heart_rate_zone3", 90.0).toDouble() / 100)) / 220, QColor("yellow"));
|
||||
backgroundGradient.setColorAt((220 - (maxHeartRate * settings.value("heart_rate_zone4", 100.0).toDouble() / 100)) / 220, QColor("orange"));
|
||||
backgroundGradient.setColorAt(0.0, QColor("red"));*/
|
||||
|
||||
//backgroundGradient.setCoordinateMode(QGradient::ObjectBoundingMode);
|
||||
//chart->setBackgroundBrush(backgroundGradient);
|
||||
// Customize plot area background
|
||||
QLinearGradient plotAreaGradient;
|
||||
plotAreaGradient.setStart(QPointF(0, 0));
|
||||
plotAreaGradient.setFinalStop(QPointF(0, 1));
|
||||
plotAreaGradient.setColorAt((220 - (maxHeartRate * settings.value("heart_rate_zone1", 70.0).toDouble() / 100)) / 220, QColor("lightsteelblue"));
|
||||
plotAreaGradient.setColorAt((220 - (maxHeartRate * settings.value("heart_rate_zone2", 80.0).toDouble() / 100)) / 220, QColor("green"));
|
||||
plotAreaGradient.setColorAt((220 - (maxHeartRate * settings.value("heart_rate_zone3", 90.0).toDouble() / 100)) / 220, QColor("yellow"));
|
||||
plotAreaGradient.setColorAt((220 - (maxHeartRate * settings.value("heart_rate_zone4", 100.0).toDouble() / 100)) / 220, QColor("orange"));
|
||||
plotAreaGradient.setColorAt(0.0, QColor("red"));
|
||||
plotAreaGradient.setCoordinateMode(QGradient::ObjectBoundingMode);
|
||||
chart->setPlotAreaBackgroundBrush(plotAreaGradient);
|
||||
chart->setPlotAreaBackgroundVisible(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Q_INVOKABLE void update_axes(QtCharts::QAbstractAxis *axisX, QtCharts::QAbstractAxis *axisY){
|
||||
if(axisX && axisY){
|
||||
// Customize axis colors
|
||||
QPen axisPen(QRgb(0xd18952));
|
||||
axisPen.setWidth(2);
|
||||
axisX->setLinePen(axisPen);
|
||||
axisY->setLinePen(axisPen);
|
||||
// Customize grid lines and shades
|
||||
axisY->setShadesPen(Qt::NoPen);
|
||||
axisY->setShadesBrush(QBrush(QColor(0x99, 0xcc, 0xcc, 0x55)));
|
||||
}
|
||||
}
|
||||
|
||||
homeform(QQmlApplicationEngine* engine, bluetooth* bl);
|
||||
~homeform();
|
||||
int topBarHeight() {return m_topBarHeight;}
|
||||
double wattZ1() {return m_wattZ1;}
|
||||
double wattZ2() {return m_wattZ2;}
|
||||
double wattZ3() {return m_wattZ3;}
|
||||
double wattZ4() {return m_wattZ4;}
|
||||
double wattZ5() {return m_wattZ5;}
|
||||
double wattZ6() {return m_wattZ6;}
|
||||
double wattZ7() {return m_wattZ7;}
|
||||
double wattZ1Max() {return m_wattZ1Max;}
|
||||
double wattZ2Max() {return m_wattZ2Max;}
|
||||
double wattZ3Max() {return m_wattZ3Max;}
|
||||
double wattZ4Max() {return m_wattZ4Max;}
|
||||
double wattZ5Max() {return m_wattZ5Max;}
|
||||
double wattZ6Max() {return m_wattZ6Max;}
|
||||
double wattMax() {if(bluetoothManager && bluetoothManager->device()) return bluetoothManager->device()->wattsMetric().max(); else return 0;}
|
||||
QString info() {return m_info;}
|
||||
QString signal();
|
||||
QString startText();
|
||||
@@ -129,11 +203,24 @@ public:
|
||||
QString stopText();
|
||||
QString stopIcon();
|
||||
QString stopColor();
|
||||
QString workoutStartDate() {if(Session.length()) return Session.first().time.toString(); else return "";}
|
||||
QString workoutName() {if(stravaPelotonActivityName.length()) return stravaPelotonActivityName; else {if(bluetoothManager->device() && bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE) return "Ride"; else return "Run";}}
|
||||
bool pelotonAskStart() {return m_pelotonAskStart;}
|
||||
void setPelotonAskStart(bool value) {m_pelotonAskStart = value;}
|
||||
bool generalPopupVisible();
|
||||
bool labelHelp();
|
||||
QStringList bluetoothDevices();
|
||||
QStringList tile_order();
|
||||
void setGeneralPopupVisible(bool value);
|
||||
int workout_sample_points() { return Session.count();}
|
||||
|
||||
double wattMaxChart() {if(bluetoothManager && bluetoothManager->device() && bluetoothManager->device()->wattsMetric().max()) return bluetoothManager->device()->wattsMetric().max(); else { QSettings settings; return settings.value("ftp", 200.0).toDouble() * 2;} }
|
||||
|
||||
QList<double> workout_watt_points() { QList<double> l; foreach(SessionLine s, Session) {l.append(s.watt);} return l; }
|
||||
QList<double> workout_heart_points() { QList<double> l; foreach(SessionLine s, Session) {l.append(s.heart);} return l; }
|
||||
QList<double> workout_cadence_points() { QList<double> l; foreach(SessionLine s, Session) {l.append(s.cadence);} return l; }
|
||||
QList<double> workout_resistance_points() { QList<double> l; foreach(SessionLine s, Session) {l.append(s.resistance);} return l; }
|
||||
QList<double> workout_peloton_resistance_points() { QList<double> l; foreach(SessionLine s, Session) {l.append(s.peloton_resistance);} return l; }
|
||||
|
||||
private:
|
||||
QList<QObject *> dataList;
|
||||
@@ -150,25 +237,14 @@ private:
|
||||
QOAuth2AuthorizationCodeFlow* strava;
|
||||
QNetworkAccessManager* manager = 0;
|
||||
|
||||
//charts
|
||||
double m_wattZ1 = 0;
|
||||
double m_wattZ2 = 0;
|
||||
double m_wattZ3 = 0;
|
||||
double m_wattZ4 = 0;
|
||||
double m_wattZ5 = 0;
|
||||
double m_wattZ6 = 0;
|
||||
double m_wattZ7 = 0;
|
||||
double m_wattZ1Max = 0;
|
||||
double m_wattZ2Max = 0;
|
||||
double m_wattZ3Max = 0;
|
||||
double m_wattZ4Max = 0;
|
||||
double m_wattZ5Max = 0;
|
||||
double m_wattZ6Max = 0;
|
||||
|
||||
bool paused = false;
|
||||
bool stopped = false;
|
||||
bool lapTrigger = false;
|
||||
|
||||
peloton* pelotonHandler = 0;
|
||||
bool m_pelotonAskStart = false;
|
||||
QString stravaPelotonActivityName = "";
|
||||
|
||||
DataObject* speed;
|
||||
DataObject* inclination;
|
||||
DataObject* cadence;
|
||||
@@ -230,6 +306,8 @@ private slots:
|
||||
void callbackReceived(const QVariantMap &values);
|
||||
void writeFileCompleted();
|
||||
void errorOccurredUploadStrava(QNetworkReply::NetworkError code);
|
||||
void pelotonWorkoutStarted(QString name);
|
||||
void peloton_start_workout();
|
||||
|
||||
signals:
|
||||
|
||||
@@ -247,8 +325,8 @@ signals:
|
||||
void bluetoothDevicesChanged(QStringList value);
|
||||
void tile_orderChanged(QStringList value);
|
||||
void changeLabelHelp(bool value);
|
||||
void changePelotonAskStart(bool value);
|
||||
void generalPopupVisibleChanged(bool value);
|
||||
void wattMaxChanged(double value);
|
||||
};
|
||||
|
||||
#endif // HOMEFORM_H
|
||||
|
||||
BIN
src/icons/arrow-collapse-vertical.png
Normal file
|
After Width: | Height: | Size: 430 B |
BIN
src/icons/arrow-expand-vertical.png
Normal file
|
After Width: | Height: | Size: 428 B |
@@ -579,6 +579,7 @@ void m3ibike::processAdvertising(const QByteArray& data) {
|
||||
return;
|
||||
debug(" << " + data.toHex(' '));
|
||||
if (parse_data(data, &k3)) {
|
||||
QSettings settings;
|
||||
detectDisc->start(M3i_DISCONNECT_THRESHOLD);
|
||||
if (!initDone) {
|
||||
initDone = true;
|
||||
@@ -587,7 +588,6 @@ void m3ibike::processAdvertising(const QByteArray& data) {
|
||||
&& !h
|
||||
#endif
|
||||
) {
|
||||
QSettings settings;
|
||||
bool virtual_device_enabled = settings.value("virtual_device_enabled", true).toBool();
|
||||
#if defined(Q_OS_IOS) && !defined(IO_UNDER_QT)
|
||||
h = new lockscreen();
|
||||
@@ -637,8 +637,11 @@ void m3ibike::processAdvertising(const QByteArray& data) {
|
||||
Cadence = k3.rpm;
|
||||
m_watt = k3.watt;
|
||||
watts(); // to update avg and max
|
||||
Speed = k3.speed;
|
||||
KCal = k3.calorie;
|
||||
Speed = k3.speed;
|
||||
if(settings.value("m3i_bike_kcal", true).toBool())
|
||||
KCal = k3.calorie;
|
||||
else
|
||||
KCal += ((( (0.048 * ((double)watts()) + 1.19) * settings.value("weight", 75.0).toFloat() * 3.5) / 200.0 ) / (60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
|
||||
Distance = k3.distance;
|
||||
if (!not_in_pause || k3.time_orig <= 10) {
|
||||
lastTimerRestart = -1;
|
||||
@@ -657,6 +660,8 @@ void m3ibike::processAdvertising(const QByteArray& data) {
|
||||
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
|
||||
}
|
||||
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (antHeart)
|
||||
Heart = (uint8_t)KeepAwakeHelper::heart();
|
||||
@@ -678,7 +683,6 @@ void m3ibike::processAdvertising(const QByteArray& data) {
|
||||
}
|
||||
|
||||
#if defined(Q_OS_IOS) && !defined(IO_UNDER_QT)
|
||||
QSettings settings;
|
||||
bool cadence = settings.value("bike_cadence_sensor", false).toBool();
|
||||
bool ios_peloton_workaround = settings.value("ios_peloton_workaround", false).toBool();
|
||||
if (ios_peloton_workaround && cadence && h) {
|
||||
|
||||
@@ -167,6 +167,7 @@ private:
|
||||
keiser_m3i_out_t k3;
|
||||
qint64 lastTimerRestart = -1;
|
||||
int lastTimerRestartOffset = 0;
|
||||
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
|
||||
virtualbike* virtualBike = 0;
|
||||
|
||||
|
||||
@@ -152,6 +152,14 @@ ApplicationWindow {
|
||||
drawer.close()
|
||||
}
|
||||
}
|
||||
ItemDelegate {
|
||||
text: qsTr("Charts")
|
||||
width: parent.width
|
||||
onClicked: {
|
||||
stackView.push("ChartsEndWorkout.qml")
|
||||
drawer.close()
|
||||
}
|
||||
}
|
||||
ItemDelegate {
|
||||
id: gpx_open
|
||||
text: qsTr("Open GPX")
|
||||
|
||||
@@ -55,6 +55,7 @@ void MainWindow::update()
|
||||
uint8_t cadence = 0;
|
||||
double inclination = 0;
|
||||
double resistance = 0;
|
||||
double peloton_resistance = 0;
|
||||
double watts = 0;
|
||||
double pace = 0;
|
||||
|
||||
@@ -151,6 +152,7 @@ void MainWindow::update()
|
||||
bluetoothManager->device()->odometer(),
|
||||
watts,
|
||||
resistance,
|
||||
peloton_resistance,
|
||||
(uint8_t)bluetoothManager->device()->currentHeart().value(),
|
||||
pace, cadence, bluetoothManager->device()->calories(),
|
||||
bluetoothManager->device()->elevationGain(),
|
||||
|
||||
229
src/peloton.cpp
Normal file
@@ -0,0 +1,229 @@
|
||||
#include "peloton.h"
|
||||
|
||||
const bool log_request = true;
|
||||
|
||||
peloton::peloton(bluetooth* bl, QObject *parent) : QObject(parent)
|
||||
{
|
||||
QSettings settings;
|
||||
bluetoothManager = bl;
|
||||
mgr = new QNetworkAccessManager(this);
|
||||
timer = new QTimer(this);
|
||||
|
||||
if(!settings.value("peloton_username", "username").toString().compare("username"))
|
||||
{
|
||||
qDebug() << "invalid peloton credentials";
|
||||
return;
|
||||
}
|
||||
|
||||
connect(timer,SIGNAL(timeout()), this, SLOT(startEngine()));
|
||||
|
||||
startEngine();
|
||||
}
|
||||
|
||||
void peloton::startEngine()
|
||||
{
|
||||
QSettings settings;
|
||||
timer->stop();
|
||||
connect(mgr,SIGNAL(finished(QNetworkReply*)),this,SLOT(login_onfinish(QNetworkReply*)));
|
||||
QUrl url("https://api.onepeloton.com/auth/login");
|
||||
QNetworkRequest request(url);
|
||||
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, "qdomyos-zwift");
|
||||
|
||||
QJsonObject obj;
|
||||
obj["username_or_email"] = settings.value("peloton_username", "username").toString();
|
||||
obj["password"] = settings.value("peloton_password", "password").toString();
|
||||
QJsonDocument doc(obj);
|
||||
QByteArray data = doc.toJson();
|
||||
|
||||
mgr->post(request, data);
|
||||
}
|
||||
|
||||
void peloton::login_onfinish(QNetworkReply* reply)
|
||||
{
|
||||
disconnect(mgr,SIGNAL(finished(QNetworkReply*)),this,SLOT(login_onfinish(QNetworkReply*)));
|
||||
QByteArray payload = reply->readAll(); // JSON
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument document = QJsonDocument::fromJson(payload, &parseError);
|
||||
|
||||
if(log_request)
|
||||
qDebug() << "login_onfinish" << document;
|
||||
else
|
||||
qDebug() << "login_onfinish";
|
||||
|
||||
user_id = document["user_id"].toString();
|
||||
total_workout = document["user_data"]["total_workouts"].toInt();
|
||||
|
||||
getWorkoutList(1);
|
||||
}
|
||||
|
||||
void peloton::workoutlist_onfinish(QNetworkReply* reply)
|
||||
{
|
||||
disconnect(mgr,SIGNAL(finished(QNetworkReply*)),this,SLOT(workoutlist_onfinish(QNetworkReply*)));
|
||||
QByteArray payload = reply->readAll(); // JSON
|
||||
QJsonParseError parseError;
|
||||
current_workout = QJsonDocument::fromJson(payload, &parseError);
|
||||
QJsonObject json = current_workout.object();
|
||||
QJsonArray data = json["data"].toArray();
|
||||
qDebug() << "data" << data;
|
||||
QString id = data.at(0)["id"].toString();
|
||||
QString status = data.at(0)["status"].toString();
|
||||
current_workout_id = id;
|
||||
if(status.toUpper().contains("IN_PROGRESS") && !current_workout_status.contains("IN_PROGRESS"))
|
||||
{
|
||||
// starting a workout
|
||||
qDebug() << "workoutlist_onfinish IN PROGRESS!";
|
||||
|
||||
// peloton bike only
|
||||
if(bluetoothManager && bluetoothManager->device() && bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
|
||||
{
|
||||
getSummary(id);
|
||||
timer->start(60000); // timeout request
|
||||
}
|
||||
else
|
||||
{
|
||||
timer->start(10000); // check for a status changed
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//getSummary(current_workout_id); // debug
|
||||
timer->start(10000); // check for a status changed
|
||||
}
|
||||
current_workout_status = status;
|
||||
|
||||
if(log_request)
|
||||
qDebug() << "workoutlist_onfinish" << current_workout;
|
||||
qDebug() << "current workout id" << current_workout_id;
|
||||
}
|
||||
|
||||
void peloton::summary_onfinish(QNetworkReply* reply)
|
||||
{
|
||||
disconnect(mgr,SIGNAL(finished(QNetworkReply*)),this,SLOT(summary_onfinish(QNetworkReply*)));
|
||||
QByteArray payload = reply->readAll(); // JSON
|
||||
QJsonParseError parseError;
|
||||
current_workout_summary = QJsonDocument::fromJson(payload, &parseError);
|
||||
|
||||
if(log_request)
|
||||
qDebug() << "summary_onfinish" << current_workout_summary;
|
||||
else
|
||||
qDebug() << "summary_onfinish";
|
||||
|
||||
getWorkout(current_workout_id);
|
||||
}
|
||||
|
||||
void peloton::workout_onfinish(QNetworkReply* reply)
|
||||
{
|
||||
disconnect(mgr,SIGNAL(finished(QNetworkReply*)),this,SLOT(workout_onfinish(QNetworkReply*)));
|
||||
QByteArray payload = reply->readAll(); // JSON
|
||||
QJsonParseError parseError;
|
||||
workout = QJsonDocument::fromJson(payload, &parseError);
|
||||
QJsonObject ride = workout.object()["ride"].toObject();
|
||||
current_workout_name = ride["title"].toString();
|
||||
|
||||
if(log_request)
|
||||
qDebug() << "workout_onfinish" << workout;
|
||||
else
|
||||
qDebug() << "workout_onfinish";
|
||||
|
||||
getPerformance(current_workout_id);
|
||||
}
|
||||
|
||||
void peloton::performance_onfinish(QNetworkReply* reply)
|
||||
{
|
||||
disconnect(mgr,SIGNAL(finished(QNetworkReply*)),this,SLOT(performance_onfinish(QNetworkReply*)));
|
||||
QByteArray payload = reply->readAll(); // JSON
|
||||
QJsonParseError parseError;
|
||||
performance = QJsonDocument::fromJson(payload, &parseError);
|
||||
|
||||
QJsonObject json = performance.object();
|
||||
QJsonObject target_performance_metrics = json["target_performance_metrics"].toObject();
|
||||
QJsonArray target_graph_metrics = target_performance_metrics["target_graph_metrics"].toArray();
|
||||
QJsonObject resistances = target_graph_metrics[1].toObject();
|
||||
QJsonObject graph_data = resistances["graph_data"].toObject();
|
||||
QJsonArray averages = graph_data["average"].toArray();
|
||||
|
||||
trainrows.clear();
|
||||
foreach(QJsonValue a, averages)
|
||||
{
|
||||
trainrow r;
|
||||
r.duration = QTime(0,0,peloton_workout_second_resolution,0);
|
||||
r.resistance = ((bike*)bluetoothManager->device())->pelotonToBikeResistance(a.toInt());
|
||||
trainrows.append(r);
|
||||
}
|
||||
|
||||
if(log_request)
|
||||
qDebug() << "performance_onfinish" << workout;
|
||||
else
|
||||
qDebug() << "performance_onfinish" << trainrows.length();
|
||||
|
||||
if(trainrows.length())
|
||||
{
|
||||
emit workoutStarted(current_workout_name);
|
||||
}
|
||||
|
||||
timer->start(30000); // check for a status changed
|
||||
}
|
||||
|
||||
|
||||
void peloton::getPerformance(QString workout)
|
||||
{
|
||||
connect(mgr,SIGNAL(finished(QNetworkReply*)),this,SLOT(performance_onfinish(QNetworkReply*)));
|
||||
|
||||
QUrl url("https://api.onepeloton.com/api/workout/" + workout + "/performance_graph?every_n=" + QString::number(peloton_workout_second_resolution));
|
||||
QNetworkRequest request(url);
|
||||
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, "qdomyos-zwift");
|
||||
|
||||
mgr->get(request);
|
||||
}
|
||||
|
||||
void peloton::getWorkout(QString workout)
|
||||
{
|
||||
connect(mgr,SIGNAL(finished(QNetworkReply*)),this,SLOT(workout_onfinish(QNetworkReply*)));
|
||||
|
||||
QUrl url("https://api.onepeloton.com/api/workout/" + workout);
|
||||
QNetworkRequest request(url);
|
||||
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, "qdomyos-zwift");
|
||||
|
||||
mgr->get(request);
|
||||
}
|
||||
|
||||
void peloton::getSummary(QString workout)
|
||||
{
|
||||
connect(mgr,SIGNAL(finished(QNetworkReply*)),this,SLOT(summary_onfinish(QNetworkReply*)));
|
||||
|
||||
QUrl url("https://api.onepeloton.com/api/workout/" + workout + "/summary");
|
||||
QNetworkRequest request(url);
|
||||
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, "qdomyos-zwift");
|
||||
|
||||
mgr->get(request);
|
||||
}
|
||||
|
||||
void peloton::getWorkoutList(int num)
|
||||
{
|
||||
if(num == 0)
|
||||
num = this->total_workout;
|
||||
|
||||
int limit = 100;
|
||||
int pages = num / limit;
|
||||
int rem = num % limit;
|
||||
|
||||
connect(mgr,SIGNAL(finished(QNetworkReply*)),this,SLOT(workoutlist_onfinish(QNetworkReply*)));
|
||||
|
||||
int current_page = 0;
|
||||
|
||||
QUrl url("https://api.onepeloton.com/api/user/" + user_id + "/workouts?sort_by=-created&page=" + QString::number(current_page) + "&limit=" + QString::number(limit));
|
||||
QNetworkRequest request(url);
|
||||
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, "qdomyos-zwift");
|
||||
|
||||
mgr->get(request);
|
||||
}
|
||||
64
src/peloton.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#ifndef PELOTON_H
|
||||
#define PELOTON_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QAbstractOAuth2>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QOAuth2AuthorizationCodeFlow>
|
||||
#include <QOAuthHttpServerReplyHandler>
|
||||
#include <QDesktopServices>
|
||||
#include <QJsonDocument>
|
||||
#include <QUrlQuery>
|
||||
#include <QHttpMultiPart>
|
||||
#include <QSettings>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QTimer>
|
||||
#include "trainprogram.h"
|
||||
#include "bluetooth.h"
|
||||
|
||||
class peloton : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit peloton(bluetooth* bl, QObject *parent = nullptr);
|
||||
QList<trainrow> trainrows;
|
||||
|
||||
private:
|
||||
const int peloton_workout_second_resolution = 10;
|
||||
QNetworkAccessManager * mgr = 0;
|
||||
QString user_id;
|
||||
QString current_workout_id = "";
|
||||
QString current_workout_name = "";
|
||||
QString current_workout_status = "";
|
||||
|
||||
QJsonDocument current_workout;
|
||||
QJsonDocument current_workout_summary;
|
||||
QJsonDocument workout;
|
||||
QJsonDocument performance;
|
||||
|
||||
QTimer* timer;
|
||||
|
||||
bluetooth* bluetoothManager = 0;
|
||||
|
||||
int total_workout;
|
||||
void getWorkoutList(int num);
|
||||
void getSummary(QString workout);
|
||||
void getWorkout(QString workout);
|
||||
void getPerformance(QString workout);
|
||||
|
||||
private slots:
|
||||
void login_onfinish(QNetworkReply* reply);
|
||||
void workoutlist_onfinish(QNetworkReply* reply);
|
||||
void summary_onfinish(QNetworkReply* reply);
|
||||
void workout_onfinish(QNetworkReply* reply);
|
||||
void performance_onfinish(QNetworkReply* reply);
|
||||
|
||||
void startEngine();
|
||||
|
||||
signals:
|
||||
void workoutStarted(QString name);
|
||||
};
|
||||
|
||||
#endif // PELOTON_H
|
||||
@@ -1,6 +1,6 @@
|
||||
QT += bluetooth widgets xml positioning quick networkauth
|
||||
|
||||
QT+= charts
|
||||
!ios: QT+= charts
|
||||
unix:android: QT += androidextras gui-private
|
||||
|
||||
CONFIG += c++11 console debug app_bundle
|
||||
@@ -63,6 +63,7 @@ SOURCES += \
|
||||
keepawakehelper.cpp \
|
||||
main.cpp \
|
||||
metric.cpp \
|
||||
peloton.cpp \
|
||||
proformbike.cpp \
|
||||
proformtreadmill.cpp \
|
||||
qfit.cpp \
|
||||
@@ -334,6 +335,7 @@ HEADERS += \
|
||||
ios/M3iIOS-Interface.h \
|
||||
material.h \
|
||||
metric.h \
|
||||
peloton.h \
|
||||
proformbike.h \
|
||||
proformtreadmill.h \
|
||||
qfit.h \
|
||||
@@ -420,4 +422,4 @@ ios {
|
||||
QMAKE_TARGET_BUNDLE_PREFIX = org.cagnulein
|
||||
}
|
||||
|
||||
VERSION = 2.5.12
|
||||
VERSION = 2.5.16
|
||||
|
||||
@@ -93,7 +93,7 @@ void qfit::save(QString filename, QList<SessionLine> session, bluetoothdevice::B
|
||||
lapMesg.SetStartTime(session.first().time.toSecsSinceEpoch() - 631065600L);
|
||||
lapMesg.SetTimestamp(session.first().time.toSecsSinceEpoch() - 631065600L);
|
||||
lapMesg.SetEvent(FIT_EVENT_WORKOUT);
|
||||
lapMesg.SetEventType(FIT_EVENT_TYPE_START);
|
||||
lapMesg.SetEventType(FIT_EVENT_TYPE_STOP);
|
||||
lapMesg.SetLapTrigger(FIT_LAP_TRIGGER_TIME);
|
||||
lapMesg.SetTotalElapsedTime(0);
|
||||
lapMesg.SetTotalTimerTime(0);
|
||||
|
||||
@@ -7,5 +7,11 @@
|
||||
<file>qtquickcontrols2.conf</file>
|
||||
<file>Home.qml</file>
|
||||
<file>settings.qml</file>
|
||||
<file>AccordionElement.qml</file>
|
||||
<file>AccordionCheckElement.qml</file>
|
||||
<file>icons/arrow-collapse-vertical.png</file>
|
||||
<file>icons/arrow-expand-vertical.png</file>
|
||||
<file>ChartsEndWorkout.qml</file>
|
||||
<file>ChartsEndWorkoutForm.ui.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#include "sessionline.h"
|
||||
|
||||
SessionLine::SessionLine(double speed, int8_t inclination, double distance, uint16_t watt, int8_t resistance, uint8_t heart, double pace, uint8_t cadence, double calories, double elevationGain, uint32_t elapsed, bool lap, QDateTime time)
|
||||
SessionLine::SessionLine(double speed, int8_t inclination, double distance, uint16_t watt, int8_t resistance, int8_t peloton_resistance, uint8_t heart, double pace, uint8_t cadence, double calories, double elevationGain, uint32_t elapsed, bool lap, QDateTime time)
|
||||
{
|
||||
this->speed = speed;
|
||||
this->inclination = inclination;
|
||||
this->distance = distance;
|
||||
this->watt = watt;
|
||||
this->resistance = resistance;
|
||||
this->peloton_resistance = peloton_resistance;
|
||||
this->heart = heart;
|
||||
this->pace = pace;
|
||||
this->time = time;
|
||||
|
||||
@@ -11,6 +11,7 @@ public:
|
||||
double distance;
|
||||
uint16_t watt;
|
||||
int8_t resistance;
|
||||
int8_t peloton_resistance;
|
||||
uint8_t heart;
|
||||
double pace;
|
||||
uint8_t cadence;
|
||||
@@ -21,7 +22,7 @@ public:
|
||||
bool lapTrigger = false;
|
||||
|
||||
SessionLine();
|
||||
SessionLine(double speed, int8_t inclination, double distance, uint16_t watt, int8_t resistance, uint8_t heart, double pace, uint8_t cadence, double calories, double elevationGain, uint32_t elapsed, bool lap, QDateTime time = QDateTime::currentDateTime());
|
||||
SessionLine(double speed, int8_t inclination, double distance, uint16_t watt, int8_t resistance, int8_t peloton_resistance, uint8_t heart, double pace, uint8_t cadence, double calories, double elevationGain, uint32_t elapsed, bool lap, QDateTime time = QDateTime::currentDateTime());
|
||||
};
|
||||
|
||||
#endif // SESSIONLINE_H
|
||||
|
||||
4248
src/settings.qml
@@ -8,12 +8,12 @@
|
||||
class trainrow
|
||||
{
|
||||
public:
|
||||
QTime duration;
|
||||
double speed;
|
||||
QTime duration = QTime(0,0,0,0);
|
||||
double speed = -1;
|
||||
double fanspeed = -1;
|
||||
double inclination;
|
||||
int8_t resistance;
|
||||
bool forcespeed;
|
||||
double inclination = -1;
|
||||
int8_t resistance = -1;
|
||||
bool forcespeed = false;
|
||||
};
|
||||
|
||||
class trainprogram: public QObject
|
||||
|
||||