mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 23:41:50 +01:00
Compare commits
52 Commits
ios_floati
...
fixing-and
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63c45c1689 | ||
|
|
91e1623689 | ||
|
|
278c5d58a2 | ||
|
|
8de55c85af | ||
|
|
bc19418432 | ||
|
|
e9f711ae49 | ||
|
|
560df01ca1 | ||
|
|
a53e7a6e44 | ||
|
|
5eefb07e9e | ||
|
|
55b0689d1d | ||
|
|
beb0a8a80c | ||
|
|
65f57d111a | ||
|
|
7de08d223f | ||
|
|
2b69f2dcd0 | ||
|
|
27da18fed9 | ||
|
|
22fd82cdff | ||
|
|
d9202218dd | ||
|
|
baf9e24640 | ||
|
|
9c0ab9b9f6 | ||
|
|
49e0506815 | ||
|
|
80c528f370 | ||
|
|
54fa926596 | ||
|
|
9f80a7b679 | ||
|
|
7639d19f5e | ||
|
|
9870626695 | ||
|
|
5be30f8f6c | ||
|
|
751f9c4f84 | ||
|
|
dd61b8e2ca | ||
|
|
7dfc994888 | ||
|
|
c662b84bc2 | ||
|
|
7b0ec35303 | ||
|
|
5d235eaab7 | ||
|
|
4e55e2b085 | ||
|
|
e221e4bf7e | ||
|
|
67f0be7c06 | ||
|
|
415025e43d | ||
|
|
ba1854659a | ||
|
|
f622a3e3f7 | ||
|
|
aad888cdab | ||
|
|
b3d45319ae | ||
|
|
0ecff51b14 | ||
|
|
face819d09 | ||
|
|
f8b9c65dab | ||
|
|
0b0081a506 | ||
|
|
94c63f5b77 | ||
|
|
87725cdd1d | ||
|
|
0ddeb747bf | ||
|
|
63a7476d14 | ||
|
|
28701625ac | ||
|
|
e0bc9071c5 | ||
|
|
5bd16f8200 | ||
|
|
d2b844ffb9 |
39
.github/workflows/main.yml
vendored
39
.github/workflows/main.yml
vendored
@@ -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
3
.gitmodules
vendored
@@ -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
106
README.md
@@ -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>
|
||||
|
||||

|
||||
|
||||
[](https://www.youtube.com/watch?v=GgG3dMhmo2Y)
|
||||
|
||||

|
||||

|
||||
|
||||
UI on Linux
|
||||
|
||||

|
||||
|
||||
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||
|
||||
|
||||

|
||||
|
||||
### Installation
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
334
src/eliteariafan.cpp
Normal 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
90
src/eliteariafan.h
Normal 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
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
20
src/ios/ios_eliteariafan.h
Normal file
20
src/ios/ios_eliteariafan.h
Normal 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
|
||||
91
src/ios/ios_eliteariafan.mm
Normal file
91
src/ios/ios_eliteariafan.mm
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
1
src/qthttpserver
Submodule
Submodule src/qthttpserver added at 983e93c3b1
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
111
src/settings.qml
111
src/settings.qml
@@ -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!"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user