Compare commits

...

52 Commits

Author SHA1 Message Date
Roberto Viola
63c45c1689 Update gradle-wrapper.properties 2023-11-30 10:35:14 +01:00
Roberto Viola
91e1623689 Update build.gradle 2023-11-30 10:03:39 +01:00
Roberto Viola
278c5d58a2 trying java version 2023-11-30 09:37:55 +01:00
Roberto Viola
8de55c85af Update build.gradle 2023-11-30 09:13:14 +01:00
Roberto Viola
bc19418432 Update build.gradle 2023-11-30 08:50:33 +01:00
Roberto Viola
e9f711ae49 Update build.gradle 2023-11-30 08:15:22 +01:00
Roberto Viola
560df01ca1 Update build.gradle 2023-11-30 07:46:37 +01:00
Roberto Viola
a53e7a6e44 Kingsmith G1 Walking Pad #1833 2023-11-28 14:35:08 +01:00
Roberto Viola
5eefb07e9e XTERRA TRX3500 no control from QZ App (Issue #1835) 2023-11-28 13:53:32 +01:00
Roberto Viola
55b0689d1d XTERRA TRX3500 no control from QZ App (Issue #1835) 2023-11-28 13:28:26 +01:00
Roberto Viola
beb0a8a80c adding elite fan file to iOS project 2023-11-28 11:31:27 +01:00
Roberto Viola
65f57d111a Elite Qubo Digital Smart B+ #1834 2023-11-28 09:05:41 +01:00
Roberto Viola
7de08d223f Kingsmith G1 Walking Pad #1833 2023-11-28 08:59:03 +01:00
Roberto Viola
2b69f2dcd0 Debug Log- Lifespan X41 elliptical cross trainer #1804 2023-11-27 11:57:58 +01:00
Roberto Viola
27da18fed9 Elite Aria #1803 2023-11-27 11:54:40 +01:00
Roberto Viola
22fd82cdff Debug Log- Lifespan X41 elliptical cross trainer #1804 2023-11-27 11:49:55 +01:00
Roberto Viola
d9202218dd Debug Log- Lifespan X41 elliptical cross trainer #1804 2023-11-27 11:25:58 +01:00
Roberto Viola
baf9e24640 Update ypooelliptical.cpp 2023-11-27 11:11:50 +01:00
Roberto Viola
9c0ab9b9f6 Rogue echo bike V3.0 #1828 2023-11-27 09:29:49 +01:00
Roberto Viola
49e0506815 Update label to include NordicTrack info #1817 2023-11-26 11:46:06 +01:00
Roberto Viola
80c528f370 Update technogymmyruntreadmillrfcomm.cpp 2023-11-24 09:14:17 +01:00
Roberto Viola
54fa926596 Debug Log- Lifespan X41 elliptical cross trainer (Issue #1804) 2023-11-24 08:33:13 +01:00
Roberto Viola
9f80a7b679 Android OpenSSL in CI (#1809)
* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* qthttpserver added
2023-11-23 16:08:05 +01:00
Roberto Viola
7639d19f5e Elite Aria #1803 2023-11-23 09:15:01 +01:00
Roberto Viola
9870626695 Elite Aria #1803 2023-11-23 08:50:22 +01:00
Roberto Viola
5be30f8f6c Elite Aria #1803 2023-11-23 08:38:30 +01:00
Roberto Viola
751f9c4f84 Elite Aria #1803 2023-11-22 15:07:25 +01:00
Roberto Viola
dd61b8e2ca Elite Aria #1803 2023-11-22 14:26:12 +01:00
Roberto Viola
7dfc994888 Elite Aria #1803 2023-11-22 14:18:05 +01:00
Roberto Viola
c662b84bc2 Debug Log- Lifespan X41 elliptical cross trainer (Issue #1804) 2023-11-22 13:39:00 +01:00
Roberto Viola
7b0ec35303 Elite Aria #1803 2023-11-22 11:15:41 +01:00
Roberto Viola
5d235eaab7 Elite Aria #1803 2023-11-22 10:45:33 +01:00
Roberto Viola
4e55e2b085 Elite Aria #1803 2023-11-22 09:27:13 +01:00
Roberto Viola
e221e4bf7e Update README.md 2023-11-22 08:26:20 +01:00
Roberto Viola
67f0be7c06 Update README.md 2023-11-22 08:25:10 +01:00
Roberto Viola
415025e43d Impossible to connect to treadmill BH S7Ti #1800 2023-11-21 14:07:32 +01:00
Roberto Viola
ba1854659a Impossible to connect to treadmill BH S7Ti #1800 2023-11-21 13:47:36 +01:00
Roberto Viola
f622a3e3f7 Life-Span X-41 Elliptical Cross trainer #1799 2023-11-20 12:05:09 +01:00
Roberto Viola
aad888cdab Adding fit hi way 2023-11-19 17:37:41 +01:00
Roberto Viola
b3d45319ae Adding FIT HI WAY 2023-11-19 17:36:10 +01:00
Roberto Viola
0ecff51b14 Android 14 QZ on Documents Folder (#1794)
* Update homeform.cpp

* implemented in all the 4 places where the user can open files

* Update homeform.h

* Update homeform.cpp

* profiles correctly copied

* change path only for android 14 or greater

* Update homeform.cpp
2023-11-17 22:39:07 +01:00
Roberto Viola
face819d09 Update trxappgateusbtreadmill.cpp 2023-11-17 18:45:29 +01:00
Roberto Viola
f8b9c65dab Focus Fitness Senator 54 iplus #1790 2023-11-17 17:36:16 +01:00
Roberto Viola
0b0081a506 version 2.16.24 2023-11-17 15:21:04 +01:00
Roberto Viola
94c63f5b77 Focus Fitness Senator 54 iplus #1790 2023-11-17 15:18:50 +01:00
Roberto Viola
87725cdd1d Focus Fitness Senator 54 iplus #1790 2023-11-17 13:54:37 +01:00
Roberto Viola
0ddeb747bf Focus Fitness Senator 54 iplus #1790 2023-11-17 13:05:05 +01:00
Roberto Viola
63a7476d14 Focus Fitness Senator 54 iplus #1790 2023-11-17 11:33:37 +01:00
Roberto Viola
28701625ac copying old elements on android from the private folder 2023-11-16 22:44:13 +01:00
Roberto Viola
e0bc9071c5 Update nordictrackifitadbtreadmill.cpp 2023-11-16 14:11:24 +01:00
Roberto Viola
5bd16f8200 No connection to x22i (Issue #65)
https://github.com/cagnulein/QZCompanionNordictrackTreadmill/issues/65#issuecomment-1813990744
2023-11-16 14:01:10 +01:00
Roberto Viola
d2b844ffb9 technogym myrun recommends trying full discovery 2023-11-14 21:09:54 +01:00
34 changed files with 1079 additions and 137 deletions

View File

@@ -528,37 +528,12 @@ jobs:
sudo apt-get install -y xvfb
Xvfb -ac ${{ env.DISPLAY }} -screen 0 1280x780x24 &
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Checkout submodule repo
- name: Checkout repository
uses: actions/checkout@v2
with:
repository: bluetiger9/SmtpClient-for-Qt
path: "src/smtpclient/"
ref: 3fa4a0fe5797070339422cf18b5e9ed8dcb91f9c
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: cagnulein/qmdnsengine
path: "src/qmdnsengine/"
ref: "zwift"
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: google/googletest
path: "tst/googletest/"
ref: "release-1.12.1"
- uses: actions/checkout@v2
- name: Checkout qHttpServer
uses: actions/checkout@v2
with:
repository: qt-labs/qthttpserver
path: "src/qthttpserver"
# This token is provided by Actions, you do not need to create your own token
token: ${{ secrets.GITHUB_TOKEN }}
submodules: recursive # or 'true' if you want to check out only immediate submodules
- name: Install packages required to run QZ inside workflow
run: sudo apt update -y && sudo apt-get install -y qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 libqt5networkauth5-dev libqt5websockets5* libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev
@@ -605,10 +580,10 @@ jobs:
cache-key-prefix: 'install-qt-action-android'
- name: Install Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '11'
java-version: '17'
- name: patching qt for bluetooth
run: cp qt-patches/android/5.15.0/jar/*.* ${{ github.workspace }}/output/android/Qt/5.15.0/android/jar/
@@ -646,6 +621,8 @@ jobs:
ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK
rm -rf /usr/local/lib/android/sdk/ndk/25.1.8937393
qmake -spec android-clang 'ANDROID_ABIS=armeabi-v7a arm64-v8a x86 x86_64' 'ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk/21.4.7075529' && make -j4 && make INSTALL_ROOT=${{ github.workspace }}/output/android/ install
sed -i '1s|{|{\n "android-extra-libs": "${{ github.workspace }}/android_openssl/no-asm/latest/arm/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm64/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm64/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86_64/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86_64/libssl_1_1.so",|' src/android-qdomyos-zwift-deployment-settings.json
cat src/android-qdomyos-zwift-deployment-settings.json
- name: Build APK (not usable for production due to unpatched QT library)
run: cd src; androiddeployqt --input android-qdomyos-zwift-deployment-settings.json --output ${{ github.workspace }}/output/android/ --android-platform android-31 --gradle --aab

3
.gitmodules vendored
View File

@@ -13,3 +13,6 @@
path = tst/googletest
url = https://github.com/google/googletest.git
branch = tags/release-1.12.1
[submodule "src/qthttpserver"]
path = src/qthttpserver
url = https://github.com/qt-labs/qthttpserver

106
README.md
View File

@@ -7,31 +7,95 @@ Zwift bridge for Treadmills and Bike!
[<img src="docs/img/app_store.png">](https://apps.apple.com/app/id1543684531?fbclid=IwAR10H6y3mEgwkTlGJON3e8voYOh2wt3kLFOpFzoIXaYZ_N0y0pDvKxHMUaM)
<a href="https://www.buymeacoffee.com/cagnulein" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
![UI](docs/img/treadmill-bridge-schema.png)
[![Video](https://img.youtube.com/vi/GgG3dMhmo2Y/0.jpg)](https://www.youtube.com/watch?v=GgG3dMhmo2Y)
![UI](docs/img/ui.png)
![UI](docs/img/realtime-chart.png)
UI on Linux
![UI](docs/img/ui-mac.png)
UI on MacOS
<table>
<tr>
<td>
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot1.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
</td>
<td>
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot2.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
</td>
<td>
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot3.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
</td>
<td>
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot4.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
</td>
<td>
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot5.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
</td>
</tr>
</table>
### Features
1. Domyos compatible
2. Toorx TRX Route Key compatible
3. Echelon Connect Sport compatible
4. Zwift compatible
5. Create, load and save train programs
6. Measure distance, elevation gain and watts
7. Gpx import (with difficulty slider)
8. Realtime Charts
# UI Features
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|:---|:---:|:---:|:---:|:---:|---:|
|Tiles Customization|X|X|X|X|Order and visibility of each tile|
|Profiles|X|X|X|X|Different user or different fitness device profiles|
|UI Zoom Customization|X|X|X|X||
# Peloton Features
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|:---|:---:|:---:|:---:|:---:|---:|
|Bike metrics on the peloton app|X||X|||
|Power zone with auto resistance|X|||||
|Peloton real-time resistance conversion|X||X||with the possibility to customize it|
|Peloton real-time auto-resistance|X||X||with the possibility to customize it|
|Peloton auto speed and auto inclination||X|X||with the possibility to customize it|
# Heart Rate Features
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|:---|:---:|:---:|:---:|:---:|---:|
|Heart Rate support|X|X|X|X|Apple Watch, ANT+ devices and Bluetooth devices|
|Heart Rate Zones Customizations|X|X|X|X||
|Ability to calculate Wattage from HR and Cadence|X||||for the bikes that doesn't have a power sensor|
# 3rd Apps Compatibility
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|:---|:---:|:---:|:---:|:---:|---:|
|Zwift Compatibility|X|X|X|X||
|Zwift Auto resistance|X||X|||
|Zwift Auto inclination and speed||X|X||https://www.youtube.com/watch?v=KTQ2n7yeDbo|
|Wahoo RGT Compatibility|X|X|X|X||
|VzFit Compatibility|X|X|X|X||
|Rouvy Compatibility|X|X|X|X||
|IFIT app Compatibility|X|||||
|Echelon app Compatibility|X|||||
|Wahoo Dircon Compatibility|X|X|X|X|in order to send data to Zwift or RGT with Wifi only!|
|One device only support for Zwift and Wahoo RGT|X|X|X|X|using Wahoo Dircon https://www.youtube.com/watch?v=gYYUXNWFAok|
# Training Program
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|:---|:---:|:---:|:---:|:---:|---:|
|Builtin video support (Kinomap like)|X|X|X|X|Files could be local or on the cloud!|
|GPX auto following|X|X|X|X||
|2D/3D maps for GPX|X|X|X|X||
|ZWO (Zwift workout file) compatibility|X|X|X|X||
|XML Workout file compatibility|X|X|X|X||
|Auto follow workout based on your heart rate|X|X|X|X||
|Random workout|X|X|X|X||
# Statistics
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|:---|:---:|:---:|:---:|:---:|---:|
|E-Mail report|X|X|X|X|at the end of the workout|
|Strava integration|X|X|X|X|press stop at the end of the workout to auto upload it|
# Misc
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|:---|:---:|:---:|:---:|:---:|---:|
|Resistance shifting with bluetooth remote|X||X|||
|TTS support|X|X|X|X||
![First Success](docs/img/first_success.jpg)
### Installation

View File

@@ -261,6 +261,9 @@
874D272029AFA11F0007C079 /* apexbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 874D271E29AFA11F0007C079 /* apexbike.cpp */; };
874D272229AFA13B0007C079 /* moc_apexbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 874D272129AFA13B0007C079 /* moc_apexbike.cpp */; };
8752B4CD27F43D9200E2EC6C /* qz.storekit in Copy Bundle Resources */ = {isa = PBXBuildFile; fileRef = 8752B4CC27F43D9200E2EC6C /* qz.storekit */; };
8752C0E32B15D84100C3D1A5 /* moc_eliteariafan.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8752C0E22B15D84100C3D1A5 /* moc_eliteariafan.cpp */; };
8752C0E82B15D85600C3D1A5 /* eliteariafan.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8752C0E42B15D85600C3D1A5 /* eliteariafan.cpp */; };
8752C0E92B15D85600C3D1A5 /* ios_eliteariafan.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8752C0E62B15D85600C3D1A5 /* ios_eliteariafan.mm */; };
87540FAD2848FD70005E0D44 /* libqtexttospeech_speechios.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 87540FAC2848FD70005E0D44 /* libqtexttospeech_speechios.a */; };
8754D24C27F786F0003D7054 /* virtualrower.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8754D24B27F786F0003D7054 /* virtualrower.swift */; };
87586A4125B8340E00A243C4 /* proformbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87586A4025B8340E00A243C4 /* proformbike.cpp */; };
@@ -1018,6 +1021,11 @@
874D271F29AFA11F0007C079 /* apexbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = apexbike.h; path = ../src/apexbike.h; sourceTree = "<group>"; };
874D272129AFA13B0007C079 /* moc_apexbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_apexbike.cpp; sourceTree = "<group>"; };
8752B4CC27F43D9200E2EC6C /* qz.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = qz.storekit; sourceTree = "<group>"; };
8752C0E22B15D84100C3D1A5 /* moc_eliteariafan.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_eliteariafan.cpp; sourceTree = "<group>"; };
8752C0E42B15D85600C3D1A5 /* eliteariafan.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = eliteariafan.cpp; path = ../src/eliteariafan.cpp; sourceTree = "<group>"; };
8752C0E52B15D85600C3D1A5 /* eliteariafan.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = eliteariafan.h; path = ../src/eliteariafan.h; sourceTree = "<group>"; };
8752C0E62B15D85600C3D1A5 /* ios_eliteariafan.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_eliteariafan.mm; path = ../src/ios/ios_eliteariafan.mm; sourceTree = "<group>"; };
8752C0E72B15D85600C3D1A5 /* ios_eliteariafan.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ios_eliteariafan.h; path = ../src/ios/ios_eliteariafan.h; sourceTree = "<group>"; };
87540FAC2848FD70005E0D44 /* libqtexttospeech_speechios.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtexttospeech_speechios.a; path = ../../Qt/5.15.2/ios/plugins/texttospeech/libqtexttospeech_speechios.a; sourceTree = "<group>"; };
8754D24B27F786F0003D7054 /* virtualrower.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = virtualrower.swift; path = ../src/ios/virtualrower.swift; sourceTree = "<group>"; };
87586A3F25B8340D00A243C4 /* proformbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformbike.h; path = ../src/proformbike.h; sourceTree = "<group>"; };
@@ -1895,6 +1903,11 @@
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
isa = PBXGroup;
children = (
8752C0E42B15D85600C3D1A5 /* eliteariafan.cpp */,
8752C0E52B15D85600C3D1A5 /* eliteariafan.h */,
8752C0E72B15D85600C3D1A5 /* ios_eliteariafan.h */,
8752C0E62B15D85600C3D1A5 /* ios_eliteariafan.mm */,
8752C0E22B15D84100C3D1A5 /* moc_eliteariafan.cpp */,
8745B2772AFCB52800991A39 /* AdbClient.h */,
87A0D7502A3A4517005147F2 /* fakerower.cpp */,
87A0D7512A3A4517005147F2 /* fakerower.h */,
@@ -2995,6 +3008,8 @@
87DF68BD25E2675100FCDA46 /* moc_eslinkertreadmill.cpp in Compile Sources */,
87646C2027B5064600F82131 /* bhfitnesselliptical.cpp in Compile Sources */,
8718CBAD263063CE004BF4EE /* moc_templateinfosender.cpp in Compile Sources */,
8752C0E32B15D84100C3D1A5 /* moc_eliteariafan.cpp in Compile Sources */,
8752C0E92B15D85600C3D1A5 /* ios_eliteariafan.mm in Compile Sources */,
87C5F0D326285E7E0067A1B5 /* moc_mimecontentformatter.cpp in Compile Sources */,
8718CBAB263063CE004BF4EE /* moc_templateinfosenderbuilder.cpp in Compile Sources */,
C6B3CD471768392E18F85819 /* fit_accumulated_field.cpp in Compile Sources */,
@@ -3002,6 +3017,7 @@
2A61806454201575EDB3F94F /* fit_buffer_encode.cpp in Compile Sources */,
87F02E4229178545000DB52C /* moc_octaneelliptical.cpp in Compile Sources */,
87E2F85D291ED308002BDC65 /* lifefitnesstreadmill.cpp in Compile Sources */,
8752C0E82B15D85600C3D1A5 /* eliteariafan.cpp in Compile Sources */,
87917A7328E768D200F8D9AC /* Browser.swift in Compile Sources */,
873CD20B27EF8D8A000131BC /* inapptransaction.cpp in Compile Sources */,
873824EF27E647A9004F1B46 /* query.cpp in Compile Sources */,
@@ -3676,7 +3692,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 661;
CURRENT_PROJECT_VERSION = 668;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
HEADER_SEARCH_PATHS = (
@@ -3845,7 +3861,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 661;
CURRENT_PROJECT_VERSION = 668;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4050,7 +4066,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 661;
CURRENT_PROJECT_VERSION = 668;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4146,7 +4162,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 661;
CURRENT_PROJECT_VERSION = 668;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -4238,7 +4254,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 661;
CURRENT_PROJECT_VERSION = 668;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -4352,7 +4368,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 661;
CURRENT_PROJECT_VERSION = 668;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;

View File

@@ -1,5 +1,5 @@
<?xml version="1.0"?>
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.16.23" android:versionCode="657" android:installLocation="auto">
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.16.24" android:versionCode="658" 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 -->

View File

@@ -5,7 +5,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.0'
classpath 'com.android.tools.build:gradle:7.0.0'
}
}
@@ -83,7 +83,7 @@ android {
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
}
lintOptions {
abortOnError false

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -426,6 +426,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
QString ftms_bike = settings.value(QZSettings::ftms_bike, QZSettings::default_ftms_bike).toString();
QString ftms_treadmill = settings.value(QZSettings::ftms_treadmill, QZSettings::default_ftms_treadmill).toString();
bool saris_trainer = settings.value(QZSettings::saris_trainer, QZSettings::default_saris_trainer).toBool();
bool iconsole_elliptical = settings.value(QZSettings::iconsole_elliptical, QZSettings::default_iconsole_elliptical).toBool();
if (!heartRateBeltFound) {
@@ -824,7 +825,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(domyosElliptical);
} else if (b.name().toUpper().startsWith(QStringLiteral("YPOO-U3-")) && !ypooElliptical && filter) {
} else if ((b.name().toUpper().startsWith(QStringLiteral("YPOO-U3-")) ||
(b.name().startsWith(QStringLiteral("FS-")) && iconsole_elliptical)) && !ypooElliptical && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
ypooElliptical =
@@ -1004,7 +1006,9 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
b.name().toUpper().startsWith(QStringLiteral("KS-HDSC-X21C")) ||
b.name().toUpper().startsWith(QStringLiteral("KS-HDSY-X21C")) ||
b.name().toUpper().startsWith(QStringLiteral("KS-NACH-X21C")) ||
b.name().toUpper().startsWith(QStringLiteral("KS-NGCH-X21C"))) &&
b.name().toUpper().startsWith(QStringLiteral("KS-NGCH-X21C")) ||
// KingSmith Walking Pad G1
b.name().toUpper().startsWith(QStringLiteral("KS-NGCH-G1C"))) &&
!kingsmithR2Treadmill && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -1322,6 +1326,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith("FLXCY-")) || // Pro FlexBike
(b.name().toUpper().startsWith("QB-WC01")) || // Nexgim QB-C01 smart bike
(b.name().toUpper().startsWith("XBR55")) || // Sprint XBR555
(b.name().toUpper().startsWith("ECHO_BIKE_")) || // Rogue echo bike V3.0
(b.name().toUpper().startsWith("EW-JS-")) || // EW-JS-4990
(b.name().toUpper().startsWith("DT-") && b.name().length() >= 14) || // SOLE SB700
(b.name().toUpper().startsWith("URSB") && b.name().length() == 7) || // URSB005
@@ -1377,6 +1382,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
horizonGr7Bike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(horizonGr7Bike);
} else if ((b.name().toUpper().startsWith(QStringLiteral("STAGES ")) ||
(b.name().toUpper().startsWith(QStringLiteral("QD")) && b.name().length() == 2) ||
(b.name().toUpper().startsWith(QStringLiteral("ASSIOMA")) &&
powerSensorName.startsWith(QStringLiteral("Disabled")))) &&
!stagesBike && !ftmsBike && filter) {
@@ -1783,7 +1789,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
// SLOT(inclinationChanged(double)));
mcfBike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(mcfBike);
} else if ((b.name().startsWith(QStringLiteral("TRX ROUTE KEY"))) && !toorx && filter) {
} else if ((b.name().startsWith(QStringLiteral("TRX ROUTE KEY")) ||
b.name().toUpper().startsWith(QStringLiteral("BH-TR-"))) && !toorx && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
toorx = new toorxtreadmill();
@@ -1868,6 +1875,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
this->signalBluetoothDeviceConnected(trxappgateusb);
} else if ((b.name().toUpper().startsWith(QStringLiteral("TUN ")) ||
b.name().toUpper().startsWith(QStringLiteral("FITHIWAY")) ||
b.name().toUpper().startsWith(QStringLiteral("FIT HI WAY")) ||
((b.name().startsWith(QStringLiteral("TOORX")) ||
b.name().toUpper().startsWith(QStringLiteral("I-CONSOIE+")) ||
b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")) ||
@@ -1991,13 +1999,13 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
// connect(fitPlusBike, SIGNAL(debug(QString)), this, SLOT(debug(QString)));
fitPlusBike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(fitPlusBike);
} else if (((b.name().startsWith(QStringLiteral("FS-")) && !snode_bike && !fitplus_bike && !ftmsBike) ||
} else if (((b.name().startsWith(QStringLiteral("FS-")) && !snode_bike && !fitplus_bike && !ftmsBike && !iconsole_elliptical) ||
b.name().toUpper().startsWith(QStringLiteral("NOBLEPRO CONNECT")) || // FTMS
(b.name().startsWith(QStringLiteral("SW")) && b.name().length() == 14 &&
!b.name().contains('(') && !b.name().contains(')')) ||
(b.name().toUpper().startsWith(QStringLiteral("WINFITA"))) || // also FTMS
(b.name().startsWith(QStringLiteral("BF70")))) &&
!fitshowTreadmill && filter) {
!fitshowTreadmill && !iconsole_elliptical && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
fitshowTreadmill = new fitshowtreadmill(this->pollDeviceTime, noConsole, noHeartService);
@@ -2207,6 +2215,16 @@ void bluetooth::connectedAndDiscovered() {
f->deviceDiscovered(b);
wahookickrHeadWind.append(f);
break;
} else if (((b.name().toUpper().startsWith("ARIA")) && b.name().length() == 4) && !fitmetria_fanfit_isconnected(b.name())) {
eliteariafan *f = new eliteariafan(this->device());
connect(f, &eliteariafan::debug, this, &bluetooth::debug);
connect(this->device(), SIGNAL(fanSpeedChanged(uint8_t)), f, SLOT(fanSpeedRequest(uint8_t)));
f->deviceDiscovered(b);
eliteAriaFan.append(f);
break;
}
}
}
@@ -2879,6 +2897,14 @@ void bluetooth::restart() {
}
wahookickrHeadWind.clear();
}
if (eliteAriaFan.length()) {
foreach (eliteariafan *f, eliteAriaFan) {
delete f;
f = nullptr;
}
eliteAriaFan.clear();
}
if (cadenceSensor) {
// heartRateBelt->disconnectBluetooth(); // to test
@@ -3207,6 +3233,10 @@ bool bluetooth::fitmetria_fanfit_isconnected(QString name) {
if (!name.compare(f->bluetoothDevice.name()))
return true;
}
foreach (eliteariafan *f, eliteAriaFan) {
if (!name.compare(f->bluetoothDevice.name()))
return true;
}
return false;
}

View File

@@ -41,6 +41,7 @@
#include "echelonconnectsport.h"
#include "echelonrower.h"
#include "eliteariafan.h"
#include "eliterizer.h"
#include "elitesterzosmart.h"
#include "eslinkertreadmill.h"
@@ -250,6 +251,7 @@ class bluetooth : public QObject, public SignalHandler {
faketreadmill *fakeTreadmill = nullptr;
QList<fitmetria_fanfit *> fitmetriaFanfit;
QList<wahookickrheadwind *> wahookickrHeadWind;
QList<eliteariafan *> eliteAriaFan;
QString filterDevice = QLatin1String("");
bool testResistance = false;

334
src/eliteariafan.cpp Normal file
View File

@@ -0,0 +1,334 @@
#include "eliteariafan.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QEventLoop>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <QThread>
using namespace std::chrono_literals;
#ifdef Q_OS_IOS
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
#endif
// this module on iOS is completely handled from the ObjectiveC module in order to test if it's more stable than the Qt Bluetooth Implementation (crash midride)
eliteariafan::eliteariafan(bluetoothdevice *parentDevice) {
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif
this->parentDevice = parentDevice;
#ifndef Q_OS_IOS
refresh = new QTimer(this);
connect(refresh, &QTimer::timeout, this, &eliteariafan::update);
refresh->start(1000ms);
#endif
}
void eliteariafan::update() {
if (initRequest) {
initRequest = false;
uint8_t init1[] = {0x02, 0x00, 0x00, 0x3d, 0x00};
writeCharacteristic(gattWrite1Service, &gattWrite1Characteristic, init1, sizeof(init1), "init", false, true);
uint8_t init2[] = {0x05, 0x00};
writeCharacteristic(gattWrite1Service, &gattWrite2Characteristic, init2, sizeof(init2), "init", false, true);
initDone = true;
}
}
void eliteariafan::serviceDiscovered(const QBluetoothUuid &gatt) {
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString());
}
void eliteariafan::disconnectBluetooth() {
qDebug() << QStringLiteral("eliteariafan::disconnect") << m_control;
if (m_control) {
m_control->disconnectFromDevice();
}
}
void eliteariafan::characteristicChanged(const QLowEnergyCharacteristic &characteristic,
const QByteArray &newValue) {
Q_UNUSED(characteristic);
emit packetReceived();
qDebug() << QStringLiteral(" << ") << newValue.toHex(' ');
}
void eliteariafan::fanSpeedRequest(uint8_t speed) {
QSettings settings;
if (speed > 100)
speed = 100;
double max = settings.value(QZSettings::fitmetria_fanfit_max, QZSettings::default_fitmetria_fanfit_max).toDouble();
double min = settings.value(QZSettings::fitmetria_fanfit_min, QZSettings::default_fitmetria_fanfit_min).toDouble();
uint16_t speed8 = (uint8_t)((double)speed * (max - min) / 100.0 + min);
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
iOS_EliteAriaFan->eliteAriaFan_fanSpeedRequest(speed8);
#endif
#else
uint8_t init10[] = {0x03, 0x01, 0x0e};
init10[2] = speed8;
writeCharacteristic(gattWrite1Service, &gattWrite2Characteristic, init10, sizeof(init10),
"forcing fan" + QString::number(speed));
#endif
}
void eliteariafan::writeCharacteristic(QLowEnergyService *service, QLowEnergyCharacteristic *writeChar,
uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
bool wait_for_response) {
QEventLoop loop;
QTimer timeout;
if (service == nullptr || writeChar->isValid() == false) {
qDebug() << QStringLiteral(
"eliteariafan trying to change the fan speed before the connection is estabilished");
return;
}
// if there are some crash here, maybe it's better to use 2 separate event for the characteristicChanged.
// one for the resistance changed event (spontaneous), and one for the other ones.
if (wait_for_response) {
connect(service, &QLowEnergyService::characteristicChanged, &loop, &QEventLoop::quit);
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
} else {
connect(service, &QLowEnergyService::characteristicWritten, &loop, &QEventLoop::quit);
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
}
if (service->state() != QLowEnergyService::ServiceState::ServiceDiscovered ||
m_control->state() == QLowEnergyController::UnconnectedState) {
qDebug() << QStringLiteral("writeCharacteristic error because the connection is closed");
return;
}
if (!writeChar->isValid()) {
qDebug() << QStringLiteral("gattWriteCharacteristic is invalid");
return;
}
if (writeBuffer) {
delete writeBuffer;
}
writeBuffer = new QByteArray((const char *)data, data_len);
service->writeCharacteristic(*writeChar, *writeBuffer, QLowEnergyService::WriteWithoutResponse);
if (!disable_log) {
qDebug() << QStringLiteral(" >> ") + writeBuffer->toHex(' ') + QStringLiteral(" // ") + info;
}
loop.exec();
}
void eliteariafan::stateChanged(QLowEnergyService::ServiceState state) {
QBluetoothUuid _gattWriteCharacteristicId1(QStringLiteral("347b0012-7635-408b-8918-8ff3949ce592")); // handle 0x1d
QBluetoothUuid _gattWriteCharacteristicId2(QStringLiteral("347b0040-7635-408b-8918-8ff3949ce592")); // handle 0x27
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
for (QLowEnergyService *s : qAsConst(gattCommunicationChannelService)) {
qDebug() << QStringLiteral("stateChanged") << s->serviceUuid() << s->state();
if (s->state() != QLowEnergyService::ServiceDiscovered && s->state() != QLowEnergyService::InvalidService) {
qDebug() << QStringLiteral("not all services discovered");
return;
}
}
if (state != QLowEnergyService::ServiceState::ServiceDiscovered) {
qDebug() << QStringLiteral("ignoring this state");
return;
}
qDebug() << QStringLiteral("all services discovered!");
for (QLowEnergyService *s : qAsConst(gattCommunicationChannelService)) {
if (s->state() == QLowEnergyService::ServiceDiscovered) {
// establish hook into notifications
connect(s, &QLowEnergyService::characteristicChanged, this, &eliteariafan::characteristicChanged);
connect(s, &QLowEnergyService::characteristicWritten, this, &eliteariafan::characteristicWritten);
connect(
s, static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
this, &eliteariafan::errorService);
connect(s, &QLowEnergyService::descriptorWritten, this, &eliteariafan::descriptorWritten);
qDebug() << s->serviceUuid() << QStringLiteral("connected!");
auto characteristics_list = s->characteristics();
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
qDebug() << QStringLiteral("char uuid") << c.uuid() << QStringLiteral("handle") << c.handle();
auto descriptors_list = c.descriptors();
for (const QLowEnergyDescriptor &d : qAsConst(descriptors_list)) {
qDebug() << QStringLiteral("descriptor uuid") << d.uuid() << QStringLiteral("handle") << d.handle();
}
if ((c.properties() & QLowEnergyCharacteristic::Notify) == QLowEnergyCharacteristic::Notify) {
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
if (c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).isValid()) {
s->writeDescriptor(c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
} else {
qDebug() << QStringLiteral("ClientCharacteristicConfiguration") << c.uuid()
<< c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).uuid()
<< c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).handle()
<< QStringLiteral(" is not valid");
}
qDebug() << s->serviceUuid() << c.uuid() << QStringLiteral("notification subscribed!");
} else if ((c.properties() & QLowEnergyCharacteristic::Indicate) ==
QLowEnergyCharacteristic::Indicate) {
QByteArray descriptor;
descriptor.append((char)0x02);
descriptor.append((char)0x00);
if (c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).isValid()) {
s->writeDescriptor(c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
} else {
qDebug() << QStringLiteral("ClientCharacteristicConfiguration") << c.uuid()
<< c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).uuid()
<< c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).handle()
<< QStringLiteral(" is not valid");
}
qDebug() << s->serviceUuid() << c.uuid() << QStringLiteral("indication subscribed!");
} else if ((c.properties() & QLowEnergyCharacteristic::Read) == QLowEnergyCharacteristic::Read) {
// s->readCharacteristic(c);
// qDebug() << s->serviceUuid() << c.uuid() << "reading!";
}
if (c.uuid() == _gattWriteCharacteristicId1) {
qDebug() << QStringLiteral("_gattWriteCharacteristicId1 found");
gattWrite1Characteristic = c;
gattWrite1Service = s;
} else if (c.uuid() == _gattWriteCharacteristicId2) {
qDebug() << QStringLiteral("_gattWriteCharacteristicId2 found");
gattWrite2Characteristic = c;
}
}
}
}
}
void eliteariafan::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
emit debug(QStringLiteral("descriptorWritten ") + descriptor.name() + " " + newValue.toHex(' '));
initRequest = true;
}
void eliteariafan::characteristicWritten(const QLowEnergyCharacteristic &characteristic,
const QByteArray &newValue) {
Q_UNUSED(characteristic);
emit debug(QStringLiteral("characteristicWritten ") + newValue.toHex(' '));
}
void eliteariafan::serviceScanDone(void) {
emit debug(QStringLiteral("serviceScanDone"));
initRequest = false;
auto services_list = m_control->services();
for (const QBluetoothUuid &s : qAsConst(services_list)) {
gattCommunicationChannelService.append(m_control->createServiceObject(s));
if (gattCommunicationChannelService.constLast()) {
connect(gattCommunicationChannelService.constLast(), &QLowEnergyService::stateChanged, this,
&eliteariafan::stateChanged);
gattCommunicationChannelService.constLast()->discoverDetails();
} else {
m_control->disconnectFromDevice();
}
}
}
void eliteariafan::errorService(QLowEnergyService::ServiceError err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
emit debug(QStringLiteral("eliteariafan::errorService") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString());
}
void eliteariafan::error(QLowEnergyController::Error err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
emit debug(QStringLiteral("eliteariafan::error") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString());
}
void eliteariafan::deviceDiscovered(const QBluetoothDeviceInfo &device) {
QSettings settings;
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')');
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
iOS_EliteAriaFan = new lockscreen();
iOS_EliteAriaFan->eliteAriaFan();
return;
#endif
#endif
{
bluetoothDevice = device;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &eliteariafan::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &eliteariafan::serviceScanDone);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, &eliteariafan::error);
connect(m_control, &QLowEnergyController::stateChanged, this, &eliteariafan::controllerStateChanged);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, [this](QLowEnergyController::Error error) {
Q_UNUSED(error);
Q_UNUSED(this);
emit debug(QStringLiteral("Cannot connect to remote device."));
emit disconnected();
});
connect(m_control, &QLowEnergyController::connected, this, [this]() {
Q_UNUSED(this);
emit debug(QStringLiteral("Controller connected. Search services..."));
m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
Q_UNUSED(this);
emit debug(QStringLiteral("LowEnergy controller disconnected"));
emit disconnected();
});
// Connect
m_control->connectToDevice();
return;
}
}
bool eliteariafan::connected() {
#ifdef Q_OS_IOS
return true;
#endif
if (!m_control) {
return false;
}
return m_control->state() == QLowEnergyController::DiscoveredState;
}
void eliteariafan::controllerStateChanged(QLowEnergyController::ControllerState state) {
#ifdef Q_OS_IOS
return;
#endif
qDebug() << QStringLiteral("controllerStateChanged") << state;
if (state == QLowEnergyController::UnconnectedState && m_control) {
qDebug() << QStringLiteral("trying to connect back again...");
initRequest = false;
initDone = false;
m_control->connectToDevice();
}
}

90
src/eliteariafan.h Normal file
View File

@@ -0,0 +1,90 @@
#ifndef ELITEARIAFAN_H
#define ELITEARIAFAN_H
#include <QBluetoothDeviceDiscoveryAgent>
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QtCore/qbytearray.h>
#ifndef Q_OS_ANDROID
#include <QtCore/qcoreapplication.h>
#else
#include <QtGui/qguiapplication.h>
#endif
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
#endif
#include <QtCore/qlist.h>
#include <QtCore/qmutex.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimer.h>
#include <QObject>
#include <QTime>
#include "bluetoothdevice.h"
class eliteariafan : public bluetoothdevice {
Q_OBJECT
public:
eliteariafan(bluetoothdevice *parentDevice);
bool connected() override;
private:
QList<QLowEnergyService *> gattCommunicationChannelService;
QLowEnergyCharacteristic gattNotify1Characteristic;
QLowEnergyCharacteristic gattNotify2Characteristic;
QLowEnergyCharacteristic gattWrite1Characteristic;
QLowEnergyService *gattWrite1Service;
QLowEnergyCharacteristic gattWrite2Characteristic;
void writeCharacteristic(QLowEnergyService *service, QLowEnergyCharacteristic *writeChar, uint8_t *data,
uint8_t data_len, const QString &info, bool disable_log = false,
bool wait_for_response = false);
bluetoothdevice *parentDevice = nullptr;
bool initDone = false;
bool initRequest = false;
QTimer *refresh;
#ifdef Q_OS_IOS
lockscreen* iOS_EliteAriaFan = nullptr;
#endif
signals:
void disconnected();
void debug(QString string);
void packetReceived();
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
void disconnectBluetooth();
void fanSpeedRequest(uint8_t value);
private slots:
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
void stateChanged(QLowEnergyService::ServiceState state);
void controllerStateChanged(QLowEnergyController::ControllerState state);
void serviceDiscovered(const QBluetoothUuid &gatt);
void serviceScanDone(void);
void update();
void error(QLowEnergyController::Error err);
void errorService(QLowEnergyService::ServiceError);
};
#endif // ELITEARIAFAN_H

View File

@@ -474,6 +474,7 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) {
QObject::connect(home, SIGNAL(start_clicked()), this, SLOT(Start()));
QObject::connect(home, SIGNAL(stop_clicked()), this, SLOT(Stop()));
QObject::connect(stack, SIGNAL(trainprogram_open_clicked(QUrl)), this, SLOT(trainprogram_open_clicked(QUrl)));
QObject::connect(stack, SIGNAL(profile_open_clicked(QUrl)), this, SLOT(profile_open_clicked(QUrl)));
QObject::connect(stack, SIGNAL(trainprogram_preview(QUrl)), this, SLOT(trainprogram_preview(QUrl)));
QObject::connect(stack, SIGNAL(gpxpreview_open_clicked(QUrl)), this, SLOT(gpxpreview_open_clicked(QUrl)));
QObject::connect(stack, SIGNAL(trainprogram_zwo_loaded(QString)), this, SLOT(trainprogram_zwo_loaded(QString)));
@@ -539,6 +540,21 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) {
}
}
#ifdef Q_OS_ANDROID
// Android 14 restrics access to /Android/data folder
if (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::Android, 14)) {
QDirIterator itAndroid(getAndroidDataAppDir(), QDirIterator::Subdirectories);
QDir().mkdir(getWritableAppDir());
QDir().mkdir(getProfileDir());
while (itAndroid.hasNext()) {
qDebug() << itAndroid.filePath() << itAndroid.fileName() << itAndroid.filePath().replace(itAndroid.path(), "");
if (!QFile(getWritableAppDir() + itAndroid.next().replace(itAndroid.path(), "")).exists()) {
QFile::copy(itAndroid.filePath(), getWritableAppDir() + itAndroid.filePath().replace(itAndroid.path(), ""));
}
}
}
#endif
m_speech.setLocale(QLocale::English);
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
@@ -840,8 +856,13 @@ void homeform::pelotonWorkoutChanged(const QString &name, const QString &instruc
QString homeform::getWritableAppDir() {
QString path = QLatin1String("");
#if defined(Q_OS_ANDROID)
path = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/QZ/";
QDir().mkdir(path);
// Android 14 restrics access to /Android/data folder
if (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::Android, 14)) {
path = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/QZ/";
QDir().mkdir(path);
} else {
path = getAndroidDataAppDir() + "/";
}
#elif defined(Q_OS_MACOS) || defined(Q_OS_OSX)
path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/";
#elif defined(Q_OS_IOS)
@@ -5190,10 +5211,29 @@ bool homeform::getLap() {
return true;
}
void homeform::copyAndroidContentsURI(QFile* file, QString subfolder) {
#ifdef Q_OS_ANDROID
QString filename = file->fileName();
int substr = filename.lastIndexOf("%2F");
if(substr) {
filename = filename.mid(substr + 3);
}
bool copy = file->copy(getWritableAppDir() + subfolder + "/" + filename);
qDebug() << "copy" << getWritableAppDir() + subfolder + "/" + filename << copy;
#endif
}
void homeform::profile_open_clicked(const QUrl &fileName) {
QFile file(QQmlFile::urlToLocalFileOrQrc(fileName));
copyAndroidContentsURI(&file, "profiles");
}
void homeform::trainprogram_open_clicked(const QUrl &fileName) {
qDebug() << QStringLiteral("trainprogram_open_clicked") << fileName;
QFile file(QQmlFile::urlToLocalFileOrQrc(fileName));
copyAndroidContentsURI(&file, "training");
qDebug() << file.fileName();
if (!file.fileName().isEmpty()) {
{
@@ -5329,6 +5369,8 @@ void homeform::gpx_open_clicked(const QUrl &fileName) {
qDebug() << QStringLiteral("gpx_open_clicked") << fileName;
QFile file(QQmlFile::urlToLocalFileOrQrc(fileName));
copyAndroidContentsURI(&file, "gpx");
qDebug() << file.fileName();
stravaWorkoutName = QFileInfo(file.fileName()).baseName();
if (!file.fileName().isEmpty()) {
@@ -6327,10 +6369,14 @@ void homeform::saveSettings(const QUrl &filename) {
}
void homeform::loadSettings(const QUrl &filename) {
qDebug() << "homeform::loadSettings" << filename;
QFile file(QQmlFile::urlToLocalFileOrQrc(filename));
copyAndroidContentsURI(&file, "settings");
qDebug() << "homeform::loadSettings" << file.fileName();
QSettings settings;
QSettings settings2Load(filename.toLocalFile(), QSettings::IniFormat);
QSettings settings2Load(file.fileName(), QSettings::IniFormat);
auto settings2LoadAllKeys = settings2Load.allKeys();
for (const QString &s : qAsConst(settings2LoadAllKeys)) {
if (!s.contains(QZSettings::cryptoKeySettingsProfiles)) {

View File

@@ -694,6 +694,8 @@ class homeform : public QObject {
static quint64 cryptoKeySettingsProfiles();
static void copyAndroidContentsURI(QFile* file, QString subfolder);
int16_t fanOverride = 0;
void update();
@@ -758,6 +760,7 @@ class homeform : public QObject {
void deviceConnected(QBluetoothDeviceInfo b);
void ftmsAccessoryConnected(smartspin2k *d);
void trainprogram_open_clicked(const QUrl &fileName);
void profile_open_clicked(const QUrl &fileName);
void trainprogram_preview(const QUrl &fileName);
void gpxpreview_open_clicked(const QUrl &fileName);
void trainprogram_zwo_loaded(const QString &comp);

View File

@@ -27,10 +27,6 @@ horizontreadmill::horizontreadmill(bool noWriteResistance, bool noHeartService)
testProfileCRC();
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif
m_watt.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
@@ -1960,6 +1956,7 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
// horizon treadmill and F80 treadmill, so if we want to add inclination support we have to separate the 2
// devices
// ***************************************************************************************************************
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')');
{
@@ -1973,6 +1970,14 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
qDebug() << QStringLiteral("KETTLER TREADMILL workaround ON!");
}
#ifdef Q_OS_IOS
if (device.name().toUpper().startsWith(QStringLiteral("TRX3500"))) {
QZ_EnableDiscoveryCharsAndDescripttors = false;
} else {
QZ_EnableDiscoveryCharsAndDescripttors = true;
}
#endif
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &horizontreadmill::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &horizontreadmill::serviceScanDone);

View File

@@ -0,0 +1,20 @@
#ifndef IOSELITEARIAFAN_H
#define IOSELITEARIAFAN_H
#import <Foundation/Foundation.h>
#import <CoreBluetooth/CoreBluetooth.h>
@interface ios_eliteariafan : NSObject <CBCentralManagerDelegate, CBPeripheralDelegate>
@property (strong, nonatomic) CBCentralManager *centralManager;
@property (strong, nonatomic) CBPeripheral *connectedPeripheral;
@property (strong, nonatomic) NSString *targetDeviceName;
@property (strong, nonatomic) CBCharacteristic *characteristicUUID1;
@property (strong, nonatomic) CBCharacteristic *characteristicUUID2;
- (instancetype)init;
- (void)fanSpeedRequest:(uint8_t)speed;
@end
#endif // IOSELITEARIAFAN_H

View File

@@ -0,0 +1,91 @@
#import <CoreBluetooth/CoreBluetooth.h>
#import "ios_eliteariafan.h"
@implementation ios_eliteariafan
- (instancetype)init {
self = [super init];
if (self) {
_centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
_targetDeviceName = @"ARIA"; // Nome del dispositivo Bluetooth
}
return self;
}
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
if (central.state == CBManagerStatePoweredOn) {
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
}
}
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
if ([peripheral.name isEqualToString:self.targetDeviceName]) {
self.connectedPeripheral = peripheral;
[self.centralManager stopScan];
[self.centralManager connectPeripheral:peripheral options:nil];
}
}
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
peripheral.delegate = self;
[peripheral discoverServices:nil];
}
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
NSLog(@"Peripheral disconnected: %@. Error: %@", peripheral, error);
if ([peripheral.name isEqualToString:self.targetDeviceName]) {
NSLog(@"Attempting to reconnect to %@", self.targetDeviceName);
[self.centralManager connectPeripheral:peripheral options:nil];
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
for (CBService *service in peripheral.services) {
[peripheral discoverCharacteristics:nil forService:service];
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
CBUUID *uuid1 = [CBUUID UUIDWithString:@"347b0012-7635-408b-8918-8ff3949ce592"];
CBUUID *uuid2 = [CBUUID UUIDWithString:@"347b0040-7635-408b-8918-8ff3949ce592"];
for (CBCharacteristic *characteristic in service.characteristics) {
if ([characteristic.UUID isEqual:uuid1]) {
self.characteristicUUID1 = characteristic;
} else if ([characteristic.UUID isEqual:uuid2]) {
self.characteristicUUID2 = characteristic;
}
}
// Verifica se entrambe le caratteristiche sono state trovate
if (self.characteristicUUID1 && self.characteristicUUID2) {
// Invia init1
uint8_t init1[] = {0x02, 0x00, 0x00, 0x3d, 0x00};
NSData *dataToSend1 = [NSData dataWithBytes:init1 length:sizeof(init1)];
[peripheral writeValue:dataToSend1 forCharacteristic:self.characteristicUUID1 type:CBCharacteristicWriteWithResponse];
// Invia init2
uint8_t init2[] = {0x05, 0x00};
NSData *dataToSend2 = [NSData dataWithBytes:init2 length:sizeof(init2)];
[peripheral writeValue:dataToSend2 forCharacteristic:self.characteristicUUID2 type:CBCharacteristicWriteWithResponse];
[self fanSpeedRequest:0];
}
}
- (void)fanSpeedRequest:(uint8_t)speed {
if (self.connectedPeripheral.state != CBPeripheralStateConnected) {
NSLog(@"Cannot send fan speed request: Peripheral is not connected.");
return;
}
uint8_t init10[] = {0x03, 0x01, 0x0e};
init10[2] = speed;
NSData *dataToSend = [NSData dataWithBytes:init10 length:sizeof(init10)];
if (self.characteristicUUID2) {
[self.connectedPeripheral writeValue:dataToSend forCharacteristic:self.characteristicUUID2 type:CBCharacteristicWriteWithResponse];
}
}
@end

View File

@@ -63,6 +63,10 @@ class lockscreen {
//adb
void adb_connect(const char* IP);
void adb_sendcommand(const char* command);
// Elite Aria Fan
void eliteAriaFan();
void eliteAriaFan_fanSpeedRequest(unsigned char speed);
};
#endif // LOCKSCREEN_H

View File

@@ -9,6 +9,7 @@
#include "ios/lockscreen.h"
#include <QDebug>
#include "ios/AdbClient.h"
#include "ios/ios_eliteariafan.h"
@class virtualbike_ios_swift;
@class virtualbike_zwift;
@@ -26,6 +27,8 @@ static GarminConnect* Garmin = 0;
static AdbClient *_adb = 0;
static ios_eliteariafan* ios_eliteAriaFan = nil;
void lockscreen::setTimerDisabled() {
[[UIApplication sharedApplication] setIdleTimerDisabled: YES];
}
@@ -285,4 +288,14 @@ void lockscreen::adb_sendcommand(const char* command) {
}];
}
void lockscreen::eliteAriaFan() {
ios_eliteAriaFan = [[ios_eliteariafan alloc] init];
}
void lockscreen::eliteAriaFan_fanSpeedRequest(unsigned char speed) {
if(ios_eliteAriaFan) {
[ios_eliteAriaFan fanSpeedRequest:speed];
}
}
#endif

View File

@@ -407,6 +407,9 @@ void kingsmithr2treadmill::stateChanged(QLowEnergyService::ServiceState state) {
if (KS_NACH_X21C) {
_gattWriteCharacteristicId = QBluetoothUuid(QStringLiteral("0002FED7-0000-1000-8000-00805f9b34fb"));
_gattNotifyCharacteristicId = QBluetoothUuid(QStringLiteral("0002FED8-0000-1000-8000-00805f9b34fb"));
} else if (KS_NGCH_G1C) {
_gattWriteCharacteristicId = QBluetoothUuid(QStringLiteral("0001FED7-0000-1000-8000-00805f9b34fb"));
_gattNotifyCharacteristicId = QBluetoothUuid(QStringLiteral("0001FED8-0000-1000-8000-00805f9b34fb"));
}
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
@@ -460,6 +463,8 @@ void kingsmithr2treadmill::serviceScanDone(void) {
if (KS_NACH_X21C)
_gattCommunicationChannelServiceId = QBluetoothUuid(QStringLiteral("00021234-0000-1000-8000-00805f9b34fb"));
else if(KS_NGCH_G1C)
_gattCommunicationChannelServiceId = QBluetoothUuid(QStringLiteral("00011234-0000-1000-8000-00805f9b34fb"));
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,
@@ -488,7 +493,11 @@ void kingsmithr2treadmill::deviceDiscovered(const QBluetoothDeviceInfo &device)
if (device.name().toUpper().startsWith(QStringLiteral("KS-NACH-X21C"))) {
qDebug() << "KS-NACH-X21C workaround!";
KS_NACH_X21C = true;
} else if (device.name().toUpper().startsWith(QStringLiteral("KS-NGCH-G1C"))) {
qDebug() << "KS-NGCH-G1C workaround!";
KS_NGCH_G1C = true;
}
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &kingsmithr2treadmill::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &kingsmithr2treadmill::serviceScanDone);

View File

@@ -88,6 +88,7 @@ class kingsmithr2treadmill : public treadmill {
bool initRequest = false;
bool KS_NACH_X21C = false;
bool KS_NGCH_G1C = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;

View File

@@ -20,6 +20,7 @@ ApplicationWindow {
signal gpx_open_clicked(url name)
signal gpxpreview_open_clicked(url name)
signal profile_open_clicked(url name)
signal trainprogram_open_clicked(url name)
signal trainprogram_preview(url name)
signal trainprogram_zwo_loaded(string s)
@@ -613,6 +614,7 @@ ApplicationWindow {
toolButtonLoadSettings.visible = true;
toolButtonSaveSettings.visible = true;
stackView.push("profiles.qml")
stackView.currentItem.profile_open_clicked.connect(profile_open_clicked)
drawer.close()
}
}
@@ -745,7 +747,7 @@ ApplicationWindow {
}
ItemDelegate {
text: "version 2.16.23"
text: "version 2.16.24"
width: parent.width
}

View File

@@ -254,30 +254,33 @@ void nordictrackifitadbtreadmill::processPendingDatagrams() {
#endif
requestSpeed = -1;
} else if (requestInclination != -100) {
int x1 = 75;
int y1Inclination = 807 - (int)((currentInclination().value() + 3) * 29.9);
// set speed slider to target position
int y2 = y1Inclination - (int)((requestInclination - currentInclination().value()) * 29.9);
double inc = qRound(requestInclination / 0.5) * 0.5;
if(inc != currentInclination().value()) {
requestInclination = inc;
int x1 = 75;
int y1Inclination = 807 - (int)((currentInclination().value() + 3) * 29.9);
// set speed slider to target position
int y2 = y1Inclination - (int)((requestInclination - currentInclination().value()) * 29.9);
if(nordictrack_x22i) {
x1 = 75;
y1Inclination = (int) (785 - (11.304 * (currentInclination().value() + 6)));
y2 = y1Inclination - (int)((requestInclination - currentInclination().value()) * 11.304);
}
if(nordictrack_x22i) {
x1 = 75;
y1Inclination = (int) (785 - (11.304 * (currentInclination().value() + 6)));
y2 = y1Inclination - (int)((requestInclination - currentInclination().value()) * 11.304);
}
lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Inclination) + " " +
QString::number(x1) + " " + QString::number(y2) + " 200";
qDebug() << " >> " + lastCommand;
lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Inclination) + " " +
QString::number(x1) + " " + QString::number(y2) + " 200";
qDebug() << " >> " + lastCommand;
#ifdef Q_OS_ANDROID
QAndroidJniObject command = QAndroidJniObject::fromString(lastCommand).object<jstring>();
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/QZAdbRemote", "sendCommand",
"(Ljava/lang/String;)V", command.object<jstring>());
QAndroidJniObject command = QAndroidJniObject::fromString(lastCommand).object<jstring>();
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/QZAdbRemote", "sendCommand",
"(Ljava/lang/String;)V", command.object<jstring>());
#elif defined Q_OS_IOS
#ifndef IO_UNDER_QT
h->adb_sendcommand(lastCommand.toStdString().c_str());
h->adb_sendcommand(lastCommand.toStdString().c_str());
#endif
#endif
}
requestInclination = -100;
}
}

View File

@@ -5,14 +5,35 @@ import QtQuick.Controls.Material 2.12
import Qt.labs.platform 1.1
import Qt.labs.folderlistmodel 2.15
import Qt.labs.settings 1.0
import QtQuick.Dialogs 1.0 as FileDialogClass
ColumnLayout {
anchors.top: parent.top
anchors.fill: parent
signal profile_open_clicked(url name)
Settings {
id: settings
property string profile_name: "default"
}
FileDialogClass.FileDialog {
id: fileDialogTrainProgram
title: "Please choose a file"
folder: shortcuts.home
onAccepted: {
console.log("You chose: " + fileDialogTrainProgram.fileUrl)
profile_open_clicked(fileDialogTrainProgram.fileUrl)
fileDialogTrainProgram.close()
}
onRejected: {
console.log("Canceled")
fileDialogTrainProgram.close()
}
}
MessageDialog {
id: quitDialog
title: "Profile loaded"
@@ -183,4 +204,19 @@ ColumnLayout {
}
}
}
Button {
id: searchButton
height: 50
width: parent.width
text: "Other folders"
Layout.alignment: Qt.AlignCenter | Qt.AlignVCenter
onClicked: {
console.log("folder is " + rootItem.getWritableAppDir() + 'training')
fileDialogTrainProgram.visible = true
}
anchors {
bottom: parent.bottom
}
}
}

View File

@@ -75,6 +75,7 @@ SOURCES += \
$$PWD/bkoolbike.cpp \
$$PWD/csafe.cpp \
$$PWD/csaferower.cpp \
$$PWD/eliteariafan.cpp \
$$PWD/fakerower.cpp \
$$PWD/virtualdevice.cpp \
$$PWD/androidactivityresultreceiver.cpp \
@@ -283,6 +284,7 @@ HEADERS += \
$$PWD/bkoolbike.h \
$$PWD/csafe.h \
$$PWD/csaferower.h \
$$PWD/eliteariafan.h \
$$PWD/windows_zwift_workout_paddleocr_thread.h \
$$PWD/fakerower.h \
virtualdevice.h \
@@ -785,6 +787,7 @@ ios {
ios {
OBJECTIVE_SOURCES += ios/lockscreen.mm \
ios/ios_eliteariafan.mm \
ios/ios_app_delegate.mm \
fit-sdk/FitDecode.mm \
fit-sdk/FitDeveloperField.mm \
@@ -815,4 +818,4 @@ INCLUDEPATH += purchasing/inapp
WINRT_MANIFEST = AppxManifest.xml
VERSION = 2.16.23
VERSION = 2.16.24

1
src/qthttpserver Submodule

Submodule src/qthttpserver added at 983e93c3b1

View File

@@ -680,8 +680,9 @@ const QString QZSettings::proform_pro_1000_treadmill = QStringLiteral("proform_p
const QString QZSettings::saris_trainer = QStringLiteral("saris_trainer");
const QString QZSettings::proform_studio_NTEX71021 = QStringLiteral("proform_studio_NTEX71021");
const QString QZSettings::nordictrack_x22i = QStringLiteral("nordictrack_x22i");
const QString QZSettings::iconsole_elliptical = QStringLiteral("iconsole_elliptical");
const uint32_t allSettingsCount = 570;
const uint32_t allSettingsCount = 571;
QVariant allSettings[allSettingsCount][2] = {
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
@@ -1258,6 +1259,7 @@ QVariant allSettings[allSettingsCount][2] = {
{QZSettings::saris_trainer, QZSettings::default_saris_trainer},
{QZSettings::proform_studio_NTEX71021, QZSettings::default_proform_studio_NTEX71021},
{QZSettings::nordictrack_x22i, QZSettings::default_nordictrack_x22i},
{QZSettings::iconsole_elliptical, QZSettings::default_iconsole_elliptical},
};
void QZSettings::qDebugAllSettings(bool showDefaults) {

View File

@@ -1911,6 +1911,9 @@ class QZSettings {
static const QString nordictrack_x22i;
static constexpr bool default_nordictrack_x22i = false;
static const QString iconsole_elliptical;
static constexpr bool default_iconsole_elliptical = false;
/**
* @brief Write the QSettings values using the constants from this namespace.
* @param showDefaults Optionally indicates if the default should be shown with the key.

View File

@@ -842,6 +842,9 @@ import QtQuick.Dialogs 1.0
// from version 2.16.23
property bool proform_studio_NTEX71021: false
property bool nordictrack_x22i: false
// from version 2.16.25
property bool iconsole_elliptical: false
}
function paddingZeros(text, limit) {
@@ -5484,7 +5487,7 @@ import QtQuick.Dialogs 1.0
onClicked: { settings.proform_treadmill_1800i = checked; window.settings_restart_to_apply = true; }
}
SwitchDelegate {
text: qsTr("Proform z1300i")
text: qsTr("Proform/NordicTrack z1300i")
spacing: 0
bottomPadding: 0
topPadding: 0
@@ -6625,6 +6628,19 @@ import QtQuick.Dialogs 1.0
Layout.fillWidth: true
onClicked: { settings.hertz_xr_770 = checked; window.settings_restart_to_apply = true; }
}
SwitchDelegate {
text: qsTr("iConsole Elliptical")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.iconsole_elliptical
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: { settings.iconsole_elliptical = checked; window.settings_restart_to_apply = true; }
}
}
}
@@ -8492,6 +8508,99 @@ import QtQuick.Dialogs 1.0
}
}
}
AccordionElement {
title: qsTr("Elite Aria Options")
indicatRectColor: Material.color(Material.Grey)
textColor: Material.color(Material.Yellow)
color: Material.backgroundColor
accordionContent: ColumnLayout {
spacing: 0
SwitchDelegate {
text: qsTr("Enable")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.fitmetria_fanfit_enable
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: { settings.fitmetria_fanfit_enable = checked; window.settings_restart_to_apply = true; }
}
RowLayout {
spacing: 10
Label {
text: qsTr("Mode:")
Layout.fillWidth: true
}
ComboBox {
id: eliteAriaModeTextField
model: [ "Heart", "Power", "Manual" ]
displayText: settings.fitmetria_fanfit_mode
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onActivated: {
console.log("combomodel activated" + eliteAriaModeTextField.currentIndex)
displayText = eliteAriaModeTextField.currentValue
}
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: { settings.fitmetria_fanfit_mode = eliteAriaModeTextField.displayText; toast.show("Setting saved!"); }
}
}
RowLayout {
spacing: 10
Label {
text: qsTr("Min. value (0-100):")
Layout.fillWidth: true
}
TextField {
id: eliteAriaMinTextField
text: settings.fitmetria_fanfit_min
horizontalAlignment: Text.AlignRight
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
inputMethodHints: Qt.ImhDigitsOnly
onAccepted: settings.fitmetria_fanfit_min = text
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: { settings.fitmetria_fanfit_min = eliteAriaMinTextField.text; toast.show("Setting saved!"); }
}
}
RowLayout {
spacing: 10
Label {
text: qsTr("Max value (0-100):")
Layout.fillWidth: true
}
TextField {
id: eliteAriaMaxTextField
text: settings.fitmetria_fanfit_max
horizontalAlignment: Text.AlignRight
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
inputMethodHints: Qt.ImhDigitsOnly
onAccepted: settings.fitmetria_fanfit_max = text
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: { settings.fitmetria_fanfit_max = eliteAriaMaxTextField.text; toast.show("Setting saved!"); }
}
}
}
}
}
}

View File

@@ -61,7 +61,12 @@ void technogymmyruntreadmillrfcomm::serviceFinished(void) {
#endif
emit debug(QStringLiteral("Create socket"));
socket->connectToService(serialPortService);
if(!found) {
qDebug() << QStringLiteral("technogymmyruntreadmillrfcomm::serviceFinished, no service found, trying workaround");
socket->connectToService(bluetoothDevice.address(), QBluetoothUuid(QBluetoothUuid::SerialPort));
} else {
socket->connectToService(serialPortService);
}
emit debug(QStringLiteral("ConnectToService done"));
}
}

View File

@@ -20,7 +20,8 @@ toorxtreadmill::toorxtreadmill() {
void toorxtreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')');
if (device.name().startsWith(QStringLiteral("TRX ROUTE KEY"))) {
if (device.name().startsWith(QStringLiteral("TRX ROUTE KEY")) ||
device.name().toUpper().startsWith(QStringLiteral("BH-TR-"))) {
bluetoothDevice = device;
// Create a discovery agent and connect to its signals

View File

@@ -984,7 +984,8 @@ void trxappgateusbbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
bike_type = TYPE::DKN_MOTION;
qDebug() << QStringLiteral("DKN MOTION bike found");
} else if (device.name().toUpper().startsWith(QStringLiteral("FITHIWAY"))) {
} else if (device.name().toUpper().startsWith(QStringLiteral("FITHIWAY")) ||
device.name().toUpper().startsWith(QStringLiteral("FIT HI WAY"))) {
bike_type = TYPE::FITHIWAY;
qDebug() << QStringLiteral("FITHIWAY bike found");
}

View File

@@ -223,28 +223,43 @@ void trxappgateusbtreadmill::characteristicChanged(const QLowEnergyCharacteristi
emit debug(QStringLiteral(" << ") + newValue.toHex(' '));
lastPacket = newValue;
if ((newValue.length() != 19 && treadmill_type != TYPE::DKN_2) ||
(newValue.length() != 18 && treadmill_type == TYPE::DKN_2)) {
lastPacket.append(newValue);
qDebug() << "actual lastPacket" << lastPacket.toHex(' ');
// Focus Fitness Senator 54 iplus #1790
if((newValue.length() < 18 && lastPacket.length() > 2 && (lastPacket.at(0) != 0xf0 || lastPacket.at(1) != 0xb2)) || lastPacket.length() > 19) {
if(lastPacket.length() == 3 && lastPacket.at(1) == 0xf0 && lastPacket.at(2) == 0xb2) {
lastPacket.clear();
lastPacket.append(0xf0);
lastPacket.append(0xb2);
return;
}
lastPacket.clear();
return;
}
if ((lastPacket.length() != 19 && treadmill_type != TYPE::DKN_2) ||
(lastPacket.length() != 18 && treadmill_type == TYPE::DKN_2)) {
return;
}
if (treadmill_type == TYPE::IRUNNING || treadmill_type == TYPE::IRUNNING_2) {
if (newValue.at(15) == 0x03 && newValue.at(16) == 0x02 && readyToStart == false) {
if (lastPacket.at(15) == 0x03 && lastPacket.at(16) == 0x02 && readyToStart == false) {
readyToStart = true;
requestStart = 1;
}
} else if (treadmill_type != TYPE::REEBOK && treadmill_type != TYPE::REEBOK_2 && treadmill_type != TYPE::DKN && treadmill_type != TYPE::DKN_2) {
if (newValue.at(16) == 0x04 && newValue.at(17) == 0x03 && readyToStart == false) {
if (lastPacket.at(16) == 0x04 && lastPacket.at(17) == 0x03 && readyToStart == false) {
readyToStart = true;
requestStart = 1;
}
}
double speed = GetSpeedFromPacket(newValue);
double incline = GetInclinationFromPacket(newValue);
double kcal = GetKcalFromPacket(newValue);
double distance = GetDistanceFromPacket(newValue);
double speed = GetSpeedFromPacket(lastPacket);
double incline = GetInclinationFromPacket(lastPacket);
double kcal = GetKcalFromPacket(lastPacket);
double distance = GetDistanceFromPacket(lastPacket);
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
@@ -272,7 +287,7 @@ void trxappgateusbtreadmill::characteristicChanged(const QLowEnergyCharacteristi
emit debug(QStringLiteral("Current KCal: ") + QString::number(kcal));
emit debug(QStringLiteral("Current Distance: ") + QString::number(distance));
emit debug(QStringLiteral("Current Elapsed from the treadmill (not used): ") +
QString::number(GetElapsedFromPacket(newValue)));
QString::number(GetElapsedFromPacket(lastPacket)));
emit debug(QStringLiteral("Current Distance Calculated: ") + QString::number(DistanceCalculated));
if (m_control->error() != QLowEnergyController::NoError) {
@@ -285,6 +300,8 @@ void trxappgateusbtreadmill::characteristicChanged(const QLowEnergyCharacteristi
Distance = distance;
firstCharChanged = false;
lastPacket.clear();
}
uint16_t trxappgateusbtreadmill::GetElapsedFromPacket(const QByteArray &packet) {

View File

@@ -85,30 +85,35 @@ void ypooelliptical::update() {
return;
}
QSettings settings;
bool iconsole_elliptical = settings.value(QZSettings::iconsole_elliptical, QZSettings::default_iconsole_elliptical).toBool();
if (initRequest) {
initRequest = false;
uint8_t init1[] = {0x02, 0x42, 0x42, 0x03};
uint8_t init2[] = {0x02, 0x41, 0x02, 0x43, 0x03};
uint8_t init3[] = {0x02, 0x43, 0x01, 0x42, 0x03};
uint8_t init4[] = {0x02, 0x44, 0x01, 0x45, 0x03};
uint8_t init5[] = {0x02, 0x44, 0x05, 0x01, 0x00, 0x40, 0x03};
if (!iconsole_elliptical) {
uint8_t init1[] = {0x02, 0x42, 0x42, 0x03};
uint8_t init2[] = {0x02, 0x41, 0x02, 0x43, 0x03};
uint8_t init3[] = {0x02, 0x43, 0x01, 0x42, 0x03};
uint8_t init4[] = {0x02, 0x44, 0x01, 0x45, 0x03};
uint8_t init5[] = {0x02, 0x44, 0x05, 0x01, 0x00, 0x40, 0x03};
writeCharacteristic(init1, sizeof(init1), QStringLiteral("init"), false, true);
writeCharacteristic(init2, sizeof(init2), QStringLiteral("init"), false, true);
writeCharacteristic(init3, sizeof(init3), QStringLiteral("init"), false, true);
writeCharacteristic(init1, sizeof(init1), QStringLiteral("init"), false, true);
writeCharacteristic(init4, sizeof(init4), QStringLiteral("init"), false, true);
writeCharacteristic(init3, sizeof(init3), QStringLiteral("init"), false, true);
writeCharacteristic(init5, sizeof(init5), QStringLiteral("init"), false, true);
writeCharacteristic(init1, sizeof(init1), QStringLiteral("init"), false, true);
writeCharacteristic(init5, sizeof(init5), QStringLiteral("init"), false, true);
writeCharacteristic(init1, sizeof(init1), QStringLiteral("init"), false, true);
writeCharacteristic(init2, sizeof(init2), QStringLiteral("init"), false, true);
writeCharacteristic(init3, sizeof(init3), QStringLiteral("init"), false, true);
writeCharacteristic(init1, sizeof(init1), QStringLiteral("init"), false, true);
writeCharacteristic(init4, sizeof(init4), QStringLiteral("init"), false, true);
writeCharacteristic(init3, sizeof(init3), QStringLiteral("init"), false, true);
writeCharacteristic(init5, sizeof(init5), QStringLiteral("init"), false, true);
writeCharacteristic(init1, sizeof(init1), QStringLiteral("init"), false, true);
writeCharacteristic(init5, sizeof(init5), QStringLiteral("init"), false, true);
}
} else if (bluetoothDevice.isValid() &&
m_control->state() == QLowEnergyController::DiscoveredState //&&
// gattCommunicationChannelService &&
// gattWriteCharacteristic.isValid() &&
// gattNotify1Characteristic.isValid() &&
/*initDone*/) {
update_metrics(false, watts());
update_metrics(iconsole_elliptical, watts());
// updating the treadmill console every second
if (sec1Update++ == (500 / refresh->interval())) {
@@ -170,6 +175,8 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
bool disable_hr_frommachinery =
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
bool iconsole_elliptical =
settings.value(QZSettings::iconsole_elliptical, QZSettings::default_iconsole_elliptical).toBool();
emit debug(QStringLiteral(" << ") + newvalue.toHex(' '));
@@ -205,7 +212,7 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
flags Flags;
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2ACE)) {
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2ACE) && !iconsole_elliptical) {
if (newvalue.length() == 18) {
qDebug() << QStringLiteral("let's wait for the next piece of frame");
@@ -377,6 +384,39 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
if (Flags.remainingTime) {
// todo
}
} else if (iconsole_elliptical) {
if (lastPacket.length() == 15) {
Speed = (double)((((uint8_t)lastPacket.at(10)) << 8) | ((uint8_t)lastPacket.at(9))) / 100.0;
Cadence = lastPacket.at(6);
Distance += ((Speed.value() / 3600000.0) *
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
if (watts())
KCal += ((((0.048 * ((double)watts()) + 1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
200.0) /
(60000.0 /
((double)lastRefreshCharacteristicChanged.msecsTo(
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
Heart = (uint8_t)KeepAwakeHelper::heart();
else
#endif
{
}
emit debug(QStringLiteral("Current speed: ") + QString::number(Speed.value()));
emit debug(QStringLiteral("Current cadence: ") + QString::number(Cadence.value()));
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
}
} else {
return;
}
@@ -388,18 +428,18 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
update_hr_from_external();
}
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
/*
bool cadence = settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround = settings.value(QZSettings::ios_peloton_workaround,
QZSettings::default_ios_peloton_workaround).toBool(); if (ios_peloton_workaround && cadence && h &&
firstStateChanged) { h->virtualTreadmill_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualTreadmill_setHeartRate((uint8_t)metrics_override_heartrate());
}
*/
#endif
#endif
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
/*
bool cadence = settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround = settings.value(QZSettings::ios_peloton_workaround,
QZSettings::default_ios_peloton_workaround).toBool(); if (ios_peloton_workaround && cadence && h &&
firstStateChanged) { h->virtualTreadmill_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualTreadmill_setHeartRate((uint8_t)metrics_override_heartrate());
}
*/
#endif
#endif
emit debug(QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs));
emit debug(QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime));
@@ -410,6 +450,8 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
}
void ypooelliptical::stateChanged(QLowEnergyService::ServiceState state) {
QSettings settings;
bool iconsole_elliptical = settings.value(QZSettings::iconsole_elliptical, QZSettings::default_iconsole_elliptical).toBool();
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
@@ -424,6 +466,12 @@ void ypooelliptical::stateChanged(QLowEnergyService::ServiceState state) {
qDebug() << QStringLiteral("all services discovered!");
for (QLowEnergyService *s : qAsConst(gattCommunicationChannelService)) {
QBluetoothUuid _gattCustomService((quint16)0xFFF0);
if (s->serviceUuid() != _gattCustomService && iconsole_elliptical) {
qDebug() << "skipping service" << s->serviceUuid();
continue;
}
if (s->state() == QLowEnergyService::ServiceDiscovered) {
// establish hook into notifications
connect(s, &QLowEnergyService::characteristicChanged, this, &ypooelliptical::characteristicChanged);

View File

@@ -21,6 +21,9 @@ public:
this->addDeviceName("KS-NGCH-X21C", comparison::StartsWithIgnoreCase);
this->addDeviceName("KS-NACH-X21C", comparison::StartsWithIgnoreCase);
// KingSmith Walking Pad G1
this->addDeviceName("KS-NGCH-G1C", comparison::StartsWithIgnoreCase);
}