diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 999978c8f..bea1fde85 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -70,15 +70,20 @@ jobs:
- uses: actions/setup-python@v4
with:
- python-version: 3.10.11
+ python-version: 3.7
- name: download python and paddleocr
run: |
python -VV
+ python -m pip install --upgrade pip
+ python -m pip install --upgrade setuptools
+ python -m pip install "protobuf<=3.20.2,>=3.1.0"
+ python -m pip install paddlepaddle==2.5.1
+ python -m pip install paddleocr
+ python -m pip install imutils
+ python -m pip install "Pillow<10.0.0"
python -m pip install opencv-python
+ python -m pip install numpy
python -m pip install pywin32
- python -m pip install paddlepaddle-gpu==2.4.2.post117 -f https://www.paddlepaddle.org.cn/whl/windows/mkl/avx/stable.html
- python -m pip install https://files.pythonhosted.org/packages/03/ac/13fbe0ebf110d57a89f055a292d4fe430fee3fb22c56f8c077e63e0c5a4e/paddlepaddle-2.4.2-cp310-cp310-win_amd64.whl
- python -m pip install paddleocr>=2.0.1
if: matrix.config.python
- uses: msys2/setup-msys2@v2
@@ -117,16 +122,20 @@ jobs:
make install
cd ../..
+ - name: Secrets
+ if: github.ref == 'refs/heads/main'
+ run: |
+ cd src
+ echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
+ echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
+ echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
+ echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
+ echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
+ cd ..
+
- name: Build
run: |
qmake
- cd src
- echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
- echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
- echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
- echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
- echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
- cd ..
make -j8
cd src/debug
mkdir output
@@ -144,7 +153,7 @@ jobs:
cp ../../../windows_openssl/*.* .
mkdir adb
mkdir python
- Copy-Item -Path C:\hostedtoolcache\windows\Python\3.10.11\x64 -Destination python -Recurse
+ Copy-Item -Path C:\hostedtoolcache\windows\Python\3.7.9\x64 -Destination python -Recurse
cp ../../adb/* adb/
cd ..
cd appx
@@ -154,13 +163,6 @@ jobs:
- name: Build without python
run: |
qmake
- cd src
- echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
- echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
- echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
- echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
- echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
- cd ..
make -j8
cd src/debug
mkdir output
@@ -528,37 +530,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
- 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
+ - name: Checkout repository
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
@@ -646,6 +623,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
@@ -786,15 +765,20 @@ jobs:
- uses: actions/setup-python@v4
with:
- python-version: 3.10.11
+ python-version: 3.7
- name: download python and paddleocr
run: |
python -VV
+ python -m pip install --upgrade pip
+ python -m pip install --upgrade setuptools
+ python -m pip install "protobuf<=3.20.2,>=3.1.0"
+ python -m pip install paddlepaddle==2.5.1
+ python -m pip install paddleocr
+ python -m pip install imutils
+ python -m pip install "Pillow<10.0.0"
python -m pip install opencv-python
+ python -m pip install numpy
python -m pip install pywin32
- python -m pip install paddlepaddle-gpu==2.4.2.post117 -f https://www.paddlepaddle.org.cn/whl/windows/mkl/avx/stable.html
- python -m pip install https://files.pythonhosted.org/packages/03/ac/13fbe0ebf110d57a89f055a292d4fe430fee3fb22c56f8c077e63e0c5a4e/paddlepaddle-2.4.2-cp310-cp310-win_amd64.whl
- python -m pip install paddleocr>=2.0.1
if: matrix.config.python
- name: Install Qt
@@ -829,16 +813,20 @@ jobs:
nmake install
cd ../..
+ - name: Secrets
+ if: github.ref == 'refs/heads/main'
+ run: |
+ cd src
+ echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
+ echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
+ echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
+ echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
+ echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
+ cd ..
+
- name: Build
run: |
- qmake
- cd src
- echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
- echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
- echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
- echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
- echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
- cd ..
+ qmake
nmake
cd src/debug
mkdir output
@@ -853,7 +841,7 @@ jobs:
cp ../../../windows_openssl/*.* .
mkdir adb
mkdir python
- Copy-Item -Path C:\hostedtoolcache\windows\Python\3.10.11\x64 -Destination python -Recurse
+ Copy-Item -Path C:\hostedtoolcache\windows\Python\3.7.9\x64 -Destination python -Recurse
cp ../../adb/* adb/
cd ..
cd appx
@@ -862,14 +850,7 @@ jobs:
- name: Build without python
run: |
- qmake
- cd src
- echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
- echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
- echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
- echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
- echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
- cd ..
+ qmake
nmake
cd src/debug
mkdir output
@@ -915,6 +896,121 @@ jobs:
path: windows-msvc2019-binary-no-python.zip
if: ${{ ! matrix.config.python }}
+ window-msvc2019-aiserver-build:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Checkout submodule repo
+ 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"
+
+ - name: Install Qt
+ uses: jurplel/install-qt-action@v3
+ with:
+ version: '5.15.2'
+ host: 'windows'
+ modules: 'qtnetworkauth qtcharts'
+ target: "desktop"
+ arch: win64_msvc2019_64
+ dir: "${{github.workspace}}/qt/"
+ install-deps: "true"
+ cache: 'true'
+ cache-key-prefix: 'install-qt-action-windows'
+
+ - name: Install MSVC compiler
+ uses: ilammy/msvc-dev-cmd@v1
+ with:
+ # 14.1 is for vs2017, 14.2 is vs2019, following the upstream vcpkg build from Qv2ray-deps repo
+ toolset: 14.2
+ arch: x64
+
+ - name: download 3rd party files for qthttpserver
+ run: |
+ cp qHttpServerBin/5.15.2/headers/* src/qthttpserver/src/3rdparty/http-parser/
+
+ - name: Build qthttpserver
+ run: |
+ cd src\qthttpserver
+ qmake
+ nmake
+ nmake install
+ cd ../..
+
+ - name: Secrets
+ if: github.ref == 'refs/heads/main'
+ run: |
+ cd src
+ echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
+ echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
+ echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
+ echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
+ echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
+ cd ..
+
+ - name: Build
+ run: |
+ cd src
+ echo "#define AISERVER" >> aiserver.h
+ cd ..
+ qmake
+ nmake
+ cd src/debug
+ mkdir output
+ mkdir appx
+ cp qdomyos-zwift.exe output/
+ cd output
+ windeployqt --qmldir ../../ qdomyos-zwift.exe
+ cp ../../../icons/iOS/iTunesArtwork@2x.png .
+ cp ../../AppxManifest.xml .
+ cp ../../windows/zwift-incline-ai-server.py zwift-incline.py
+ cp ../../windows/zwift-incline-climb-portal-ai-server.py zwift-incline-climb-portal.py
+ cp ../../windows/zwift-workout-ai-server.py zwift-workout.py
+ cp ../../windows/*.bat .
+ cp ../../../windows_openssl/*.* .
+ mkdir adb
+ cp ../../adb/* adb/
+ cd ..
+ cd appx
+ #../../MSIX-Toolkit/WindowsSDK/10/10.0.20348.0/x64/makeappx.exe pack /d ../output/ /p qz
+
+ - name: patching qt for bluetooth
+ run: cp qt-patches/windows/5.15.2/binary/msvc2019/*.* ${{ github.workspace }}/src/debug/output/
+
+ - name: Zip artifact for deployment
+ run: Compress-Archive src/debug/output windows-msvc2019-ai-server-binary.zip
+
+ - name: Archive windows binary
+ uses: actions/upload-artifact@v2
+ with:
+ name: windows-msvc2019-ai-server-binary
+ path: windows-msvc2019-ai-server-binary.zip
+
upload_to_release:
permissions: write-all
runs-on: ubuntu-latest
@@ -944,6 +1040,7 @@ jobs:
files: |
windows-msvc2019-binary-no-python/*
windows-msvc2019-binary/*
+ windows-msvc2019-ai-server-binary/*
windows-binary-no-python/*
windows-binary/*
fdroid-android-trial/*
diff --git a/.gitmodules b/.gitmodules
index 4020943b6..e8d32e15c 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -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
diff --git a/README.md b/README.md
index fb41e661d..96d3f2501 100644
--- a/README.md
+++ b/README.md
@@ -7,31 +7,95 @@ Zwift bridge for Treadmills and Bike!
[](https://apps.apple.com/app/id1543684531?fbclid=IwAR10H6y3mEgwkTlGJON3e8voYOh2wt3kLFOpFzoIXaYZ_N0y0pDvKxHMUaM)
-![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
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
### 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
diff --git a/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift-Bridging-Header.h b/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift-Bridging-Header.h
index 1b2cb5d6d..11eb40c0e 100644
--- a/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift-Bridging-Header.h
+++ b/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift-Bridging-Header.h
@@ -2,3 +2,4 @@
// Use this file to import your target's public headers that you would like to expose to Swift.
//
+#import "swiftDebug.h"
diff --git a/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj b/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj
index 1fd5fa58e..4a1854031 100644
--- a/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj
+++ b/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj
@@ -170,6 +170,10 @@
872261F0289EA887006A6F75 /* moc_nordictrackelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872261EF289EA887006A6F75 /* moc_nordictrackelliptical.cpp */; };
8727A47727849EA600019B5D /* paferstreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727A47627849EA600019B5D /* paferstreadmill.cpp */; };
8727A47927849EB200019B5D /* moc_paferstreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727A47827849EB200019B5D /* moc_paferstreadmill.cpp */; };
+ 8727C7D02B3BF1B8005429EB /* proformtelnetbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727C7CC2B3BF1B8005429EB /* proformtelnetbike.cpp */; };
+ 8727C7D12B3BF1B8005429EB /* QTelnet.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727C7CF2B3BF1B8005429EB /* QTelnet.cpp */; };
+ 8727C7D42B3BF1E4005429EB /* moc_QTelnet.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727C7D22B3BF1E4005429EB /* moc_QTelnet.cpp */; };
+ 8727C7D52B3BF1E4005429EB /* moc_proformtelnetbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727C7D32B3BF1E4005429EB /* moc_proformtelnetbike.cpp */; };
872A20DA28C5EC380037774D /* faketreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872A20D928C5EC380037774D /* faketreadmill.cpp */; };
872A20DC28C5F5CE0037774D /* moc_faketreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872A20DB28C5F5CE0037774D /* moc_faketreadmill.cpp */; };
872BAB4E261750EE006A59AB /* libQt5Charts.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 872BAB4D261750EE006A59AB /* libQt5Charts.a */; };
@@ -178,6 +182,11 @@
872DCC3B2A18D4C000EC9F68 /* moc_virtualdevice.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872DCC3A2A18D4C000EC9F68 /* moc_virtualdevice.cpp */; };
873063BE259DF20000DA0F44 /* heartratebelt.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 873063BC259DF20000DA0F44 /* heartratebelt.cpp */; };
873063C0259DF2C500DA0F44 /* moc_heartratebelt.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 873063BF259DF2C500DA0F44 /* moc_heartratebelt.cpp */; };
+ 8730A38B2B404081007E336D /* SwiftProtobuf in Link Binary With Libraries */ = {isa = PBXBuildFile; productRef = 8730A38A2B404081007E336D /* SwiftProtobuf */; };
+ 8730A38D2B404081007E336D /* SwiftProtobufPluginLibrary in Link Binary With Libraries */ = {isa = PBXBuildFile; productRef = 8730A38C2B404081007E336D /* SwiftProtobufPluginLibrary */; };
+ 8730A38F2B404081007E336D /* protoc-gen-swift in Link Binary With Libraries */ = {isa = PBXBuildFile; productRef = 8730A38E2B404081007E336D /* protoc-gen-swift */; };
+ 8730A3922B404159007E336D /* zwift_protobuf_layer.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8730A3912B404159007E336D /* zwift_protobuf_layer.swift */; };
+ 8730A3932B4078E6007E336D /* zwift_messages.pb.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8730A3902B4040AF007E336D /* zwift_messages.pb.swift */; };
87310B1E266FBB59008BA0D6 /* smartrowrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87310B1B266FBB54008BA0D6 /* smartrowrower.cpp */; };
87310B1F266FBB59008BA0D6 /* homefitnessbuddy.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87310B1C266FBB57008BA0D6 /* homefitnessbuddy.cpp */; };
87310B22266FBB78008BA0D6 /* moc_homefitnessbuddy.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87310B20266FBB6E008BA0D6 /* moc_homefitnessbuddy.cpp */; };
@@ -261,6 +270,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 */; };
@@ -322,6 +334,8 @@
878531652711A3E1004B153D /* activiotreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878531612711A3E1004B153D /* activiotreadmill.cpp */; };
878531682711A3EC004B153D /* moc_activiotreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878531662711A3EB004B153D /* moc_activiotreadmill.cpp */; };
878531692711A3EC004B153D /* moc_fakebike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878531672711A3EB004B153D /* moc_fakebike.cpp */; };
+ 8785D5432B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8785D5412B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp */; };
+ 8785D5442B3DD105005A2EB7 /* moc_zwift_client_auth.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8785D5422B3DD105005A2EB7 /* moc_zwift_client_auth.cpp */; };
878A331A25AB4FF800BD13E1 /* yesoulbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878A331725AB4FF800BD13E1 /* yesoulbike.cpp */; };
878A331D25AB50C300BD13E1 /* moc_yesoulbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878A331B25AB50C200BD13E1 /* moc_yesoulbike.cpp */; };
878C9E6928B77E7C00669129 /* nordictrackifitadbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878C9E6828B77E7B00669129 /* nordictrackifitadbbike.cpp */; };
@@ -364,6 +378,7 @@
87A0D7542A3A4547005147F2 /* moc_fakerower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A0D7532A3A4547005147F2 /* moc_fakerower.cpp */; };
87A18F072660D5C1002D7C96 /* ftmsrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A18F052660D5C0002D7C96 /* ftmsrower.cpp */; };
87A18F092660D5D9002D7C96 /* moc_ftmsrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A18F082660D5D9002D7C96 /* moc_ftmsrower.cpp */; };
+ 87A2E0222B2B053E00E6168F /* swiftDebug.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A2E0212B2B053E00E6168F /* swiftDebug.mm */; };
87A3BC222656429600D302E3 /* rower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC1F2656429400D302E3 /* rower.cpp */; };
87A3BC232656429600D302E3 /* echelonrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC202656429400D302E3 /* echelonrower.cpp */; };
87A3BC26265642A300D302E3 /* moc_rower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC24265642A200D302E3 /* moc_rower.cpp */; };
@@ -883,6 +898,13 @@
8727A47527849EA600019B5D /* paferstreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = paferstreadmill.h; path = ../src/paferstreadmill.h; sourceTree = ""; };
8727A47627849EA600019B5D /* paferstreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = paferstreadmill.cpp; path = ../src/paferstreadmill.cpp; sourceTree = ""; };
8727A47827849EB200019B5D /* moc_paferstreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_paferstreadmill.cpp; sourceTree = ""; };
+ 8727C7CC2B3BF1B8005429EB /* proformtelnetbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = proformtelnetbike.cpp; path = ../src/proformtelnetbike.cpp; sourceTree = ""; };
+ 8727C7CD2B3BF1B8005429EB /* proformtelnetbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformtelnetbike.h; path = ../src/proformtelnetbike.h; sourceTree = ""; };
+ 8727C7CE2B3BF1B8005429EB /* QTelnet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QTelnet.h; path = ../src/QTelnet.h; sourceTree = ""; };
+ 8727C7CF2B3BF1B8005429EB /* QTelnet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = QTelnet.cpp; path = ../src/QTelnet.cpp; sourceTree = ""; };
+ 8727C7D22B3BF1E4005429EB /* moc_QTelnet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_QTelnet.cpp; sourceTree = ""; };
+ 8727C7D32B3BF1E4005429EB /* moc_proformtelnetbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_proformtelnetbike.cpp; sourceTree = ""; };
+ 8729149E2B2B010600565E33 /* qdomyoszwift-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "qdomyoszwift-Bridging-Header.h"; sourceTree = ""; };
872A20D828C5EC380037774D /* faketreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = faketreadmill.h; path = ../src/faketreadmill.h; sourceTree = ""; };
872A20D928C5EC380037774D /* faketreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = faketreadmill.cpp; path = ../src/faketreadmill.cpp; sourceTree = ""; };
872A20DB28C5F5CE0037774D /* moc_faketreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_faketreadmill.cpp; sourceTree = ""; };
@@ -894,6 +916,8 @@
873063BC259DF20000DA0F44 /* heartratebelt.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = heartratebelt.cpp; path = ../src/heartratebelt.cpp; sourceTree = ""; };
873063BD259DF20000DA0F44 /* heartratebelt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = heartratebelt.h; path = ../src/heartratebelt.h; sourceTree = ""; };
873063BF259DF2C500DA0F44 /* moc_heartratebelt.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_heartratebelt.cpp; sourceTree = ""; };
+ 8730A3902B4040AF007E336D /* zwift_messages.pb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = zwift_messages.pb.swift; path = "../src/zwift-api/zwift_messages.pb.swift"; sourceTree = ""; };
+ 8730A3912B404159007E336D /* zwift_protobuf_layer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = zwift_protobuf_layer.swift; path = "../src/zwift-api/zwift_protobuf_layer.swift"; sourceTree = ""; };
87310B1A266FBB54008BA0D6 /* homefitnessbuddy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = homefitnessbuddy.h; path = ../src/homefitnessbuddy.h; sourceTree = ""; };
87310B1B266FBB54008BA0D6 /* smartrowrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = smartrowrower.cpp; path = ../src/smartrowrower.cpp; sourceTree = ""; };
87310B1C266FBB57008BA0D6 /* homefitnessbuddy.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = homefitnessbuddy.cpp; path = ../src/homefitnessbuddy.cpp; sourceTree = ""; };
@@ -1018,6 +1042,11 @@
874D271F29AFA11F0007C079 /* apexbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = apexbike.h; path = ../src/apexbike.h; sourceTree = ""; };
874D272129AFA13B0007C079 /* moc_apexbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_apexbike.cpp; sourceTree = ""; };
8752B4CC27F43D9200E2EC6C /* qz.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = qz.storekit; sourceTree = ""; };
+ 8752C0E22B15D84100C3D1A5 /* moc_eliteariafan.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_eliteariafan.cpp; sourceTree = ""; };
+ 8752C0E42B15D85600C3D1A5 /* eliteariafan.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = eliteariafan.cpp; path = ../src/eliteariafan.cpp; sourceTree = ""; };
+ 8752C0E52B15D85600C3D1A5 /* eliteariafan.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = eliteariafan.h; path = ../src/eliteariafan.h; sourceTree = ""; };
+ 8752C0E62B15D85600C3D1A5 /* ios_eliteariafan.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_eliteariafan.mm; path = ../src/ios/ios_eliteariafan.mm; sourceTree = ""; };
+ 8752C0E72B15D85600C3D1A5 /* ios_eliteariafan.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ios_eliteariafan.h; path = ../src/ios/ios_eliteariafan.h; sourceTree = ""; };
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 = ""; };
8754D24B27F786F0003D7054 /* virtualrower.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = virtualrower.swift; path = ../src/ios/virtualrower.swift; sourceTree = ""; };
87586A3F25B8340D00A243C4 /* proformbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformbike.h; path = ../src/proformbike.h; sourceTree = ""; };
@@ -1113,6 +1142,10 @@
878531632711A3E1004B153D /* fakebike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fakebike.h; path = ../src/fakebike.h; sourceTree = ""; };
878531662711A3EB004B153D /* moc_activiotreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_activiotreadmill.cpp; sourceTree = ""; };
878531672711A3EB004B153D /* moc_fakebike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_fakebike.cpp; sourceTree = ""; };
+ 8785D53F2B3DD0EC005A2EB7 /* zwift_client_auth.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zwift_client_auth.h; path = "../src/zwift-api/zwift_client_auth.h"; sourceTree = ""; };
+ 8785D5402B3DD0EC005A2EB7 /* PlayerStateWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PlayerStateWrapper.h; path = "../src/zwift-api/PlayerStateWrapper.h"; sourceTree = ""; };
+ 8785D5412B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_PlayerStateWrapper.cpp; sourceTree = ""; };
+ 8785D5422B3DD105005A2EB7 /* moc_zwift_client_auth.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_zwift_client_auth.cpp; sourceTree = ""; };
8789DCDB6A4F681A76DF3F92 /* Qt5Widgets */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = Qt5Widgets; path = "/Users/cagnulein/Qt/5.15.2/ios/lib/libQt5Widgets$(QT_LIBRARY_SUFFIX).a"; sourceTree = ""; };
878A331725AB4FF800BD13E1 /* yesoulbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = yesoulbike.cpp; path = ../src/yesoulbike.cpp; sourceTree = ""; };
878A331825AB4FF800BD13E1 /* yesoulbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = yesoulbike.h; path = ../src/yesoulbike.h; sourceTree = ""; };
@@ -1173,6 +1206,8 @@
87A18F052660D5C0002D7C96 /* ftmsrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ftmsrower.cpp; path = ../src/ftmsrower.cpp; sourceTree = ""; };
87A18F062660D5C1002D7C96 /* ftmsrower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ftmsrower.h; path = ../src/ftmsrower.h; sourceTree = ""; };
87A18F082660D5D9002D7C96 /* moc_ftmsrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_ftmsrower.cpp; sourceTree = ""; };
+ 87A2E0202B2B024200E6168F /* swiftDebug.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = swiftDebug.h; path = ../src/ios/swiftDebug.h; sourceTree = ""; };
+ 87A2E0212B2B053E00E6168F /* swiftDebug.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = swiftDebug.mm; path = ../src/ios/swiftDebug.mm; sourceTree = ""; };
87A3BC1E2656429300D302E3 /* echelonrower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = echelonrower.h; path = ../src/echelonrower.h; sourceTree = ""; };
87A3BC1F2656429400D302E3 /* rower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = rower.cpp; path = ../src/rower.cpp; sourceTree = ""; };
87A3BC202656429400D302E3 /* echelonrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = echelonrower.cpp; path = ../src/echelonrower.cpp; sourceTree = ""; };
@@ -1650,6 +1685,7 @@
4FA524144B680D741D33EACB /* qtgraphicaleffectsprivate in Link Binary With Libraries */,
879F74152893D732009A64C8 /* CoreMedia.framework in Link Binary With Libraries */,
1C823E40F377B93A664EAC1B /* modelsplugin in Link Binary With Libraries */,
+ 8730A38D2B404081007E336D /* SwiftProtobufPluginLibrary in Link Binary With Libraries */,
B9DED9CC16B0F3339F363FBF /* workerscriptplugin in Link Binary With Libraries */,
023642106C14651D2E1F4D5D /* dialogplugin in Link Binary With Libraries */,
133CA0345CD2BFB03079A655 /* qmlfolderlistmodelplugin in Link Binary With Libraries */,
@@ -1658,6 +1694,7 @@
3BD5A5F95DF5239184791B58 /* dialogsprivateplugin in Link Binary With Libraries */,
EF98F8C34BE322582E9B73D7 /* qtquickcontrolsplugin in Link Binary With Libraries */,
7C8D236C48F2964061C3457C /* widgetsplugin in Link Binary With Libraries */,
+ 8730A38B2B404081007E336D /* SwiftProtobuf in Link Binary With Libraries */,
401B341C04019FFA2146E79D /* Qt5Widgets in Link Binary With Libraries */,
61EC5BE7EEC8D905C63FF628 /* qmlplugin in Link Binary With Libraries */,
877A080D2893DC4300C0F0AB /* CoreVideo.framework in Link Binary With Libraries */,
@@ -1671,6 +1708,7 @@
5A4A6C1B12D4D769431E876E /* qtquickcontrols2materialstyleplugin in Link Binary With Libraries */,
A044AC393BA2327284BB63B4 /* qtquickcontrols2fusionstyleplugin in Link Binary With Libraries */,
B413AFE7A08F2D63D57F683E /* qtquickcontrols2universalstyleplugin in Link Binary With Libraries */,
+ 8730A38F2B404081007E336D /* protoc-gen-swift in Link Binary With Libraries */,
7BA3E396471B90F086588B5C /* qtgraphicaleffectsplugin in Link Binary With Libraries */,
F020E5470020A5BF3EB828A3 /* qtquickcontrols2imaginestyleplugin in Link Binary With Libraries */,
964DFEF4056724121ED9A98D /* Qt5QuickControls2 in Link Binary With Libraries */,
@@ -1895,6 +1933,24 @@
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
isa = PBXGroup;
children = (
+ 8730A3902B4040AF007E336D /* zwift_messages.pb.swift */,
+ 8785D5412B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp */,
+ 8785D5422B3DD105005A2EB7 /* moc_zwift_client_auth.cpp */,
+ 8785D5402B3DD0EC005A2EB7 /* PlayerStateWrapper.h */,
+ 8785D53F2B3DD0EC005A2EB7 /* zwift_client_auth.h */,
+ 8727C7D32B3BF1E4005429EB /* moc_proformtelnetbike.cpp */,
+ 8727C7D22B3BF1E4005429EB /* moc_QTelnet.cpp */,
+ 8727C7CC2B3BF1B8005429EB /* proformtelnetbike.cpp */,
+ 8727C7CD2B3BF1B8005429EB /* proformtelnetbike.h */,
+ 8727C7CF2B3BF1B8005429EB /* QTelnet.cpp */,
+ 8727C7CE2B3BF1B8005429EB /* QTelnet.h */,
+ 87A2E0212B2B053E00E6168F /* swiftDebug.mm */,
+ 8729149E2B2B010600565E33 /* qdomyoszwift-Bridging-Header.h */,
+ 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 */,
@@ -2253,6 +2309,8 @@
35E903698E72424585D33829 /* virtualtreadmill.h */,
C8CE72E7B224D8B886614E3F /* domyosbike.h */,
8710707229C4A5E70094D0F3 /* GarminConnect.swift */,
+ 87A2E0202B2B024200E6168F /* swiftDebug.h */,
+ 8730A3912B404159007E336D /* zwift_protobuf_layer.swift */,
);
name = Sources;
sourceTree = "";
@@ -2780,6 +2838,11 @@
876E4E312594748100BD5714 /* PBXTargetDependency */,
);
name = qdomyoszwift;
+ packageProductDependencies = (
+ 8730A38A2B404081007E336D /* SwiftProtobuf */,
+ 8730A38C2B404081007E336D /* SwiftProtobufPluginLibrary */,
+ 8730A38E2B404081007E336D /* protoc-gen-swift */,
+ );
productName = qdomyoszwift;
productReference = 040B10E2EF2CEF79F2205FE2 /* qdomyoszwift.app */;
productType = "com.apple.product-type.application";
@@ -2855,6 +2918,9 @@
Base,
);
mainGroup = E8C543AB96796ECAA2E65C57 /* qdomyoszwift */;
+ packageReferences = (
+ 8730A3892B404081007E336D /* XCRemoteSwiftPackageReference "swift-protobuf" */,
+ );
productRefGroup = FE0A091FDBFB3E9C31B7A1BD /* Products */;
projectDirPath = "";
projectRoot = "";
@@ -2995,15 +3061,20 @@
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 */,
+ 8730A3932B4078E6007E336D /* zwift_messages.pb.swift in Compile Sources */,
3D7395B0A17915A06361C7F3 /* fit_accumulator.cpp in Compile Sources */,
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 */,
+ 8727C7D52B3BF1E4005429EB /* moc_proformtelnetbike.cpp in Compile Sources */,
873824EF27E647A9004F1B46 /* query.cpp in Compile Sources */,
876F45FF279350D9003CDA5A /* moc_concept2skierg.cpp in Compile Sources */,
BE93C6EF2C2A6BFEEC9EA565 /* fit_buffered_mesg_broadcaster.cpp in Compile Sources */,
@@ -3031,6 +3102,7 @@
87CC3B9D25A08812001EC5A8 /* moc_domyoselliptical.cpp in Compile Sources */,
87900DC6268B672E000CB351 /* renphobike.cpp in Compile Sources */,
879F16462847E55C00CE4945 /* proformellipticaltrainer.cpp in Compile Sources */,
+ 8730A3922B404159007E336D /* zwift_protobuf_layer.swift in Compile Sources */,
87917A7728E768D200F8D9AC /* Client.swift in Compile Sources */,
873824B927E64707004F1B46 /* moc_provider.cpp in Compile Sources */,
8727A47727849EA600019B5D /* paferstreadmill.cpp in Compile Sources */,
@@ -3096,6 +3168,7 @@
87097D2F275EA9A30020EE6F /* sportsplusbike.cpp in Compile Sources */,
333C629F93DB3941862924F7 /* fit_field_base.cpp in Compile Sources */,
87473A9827ECAA0500C203F5 /* moc_proformrower.cpp in Compile Sources */,
+ 8785D5442B3DD105005A2EB7 /* moc_zwift_client_auth.cpp in Compile Sources */,
EEC10275DF646075E08DDC9B /* fit_field_definition.cpp in Compile Sources */,
87062645259480AB00D06586 /* LocalNotificationHelper.swift in Compile Sources */,
8718CBA4263063BD004BF4EE /* templateinfosenderbuilder.cpp in Compile Sources */,
@@ -3137,6 +3210,7 @@
87EB918727EE5FE7002535E1 /* moc_nautilusbike.cpp in Compile Sources */,
873824B827E64707004F1B46 /* moc_cache.cpp in Compile Sources */,
873824E427E647A8004F1B46 /* cache.cpp in Compile Sources */,
+ 87A2E0222B2B053E00E6168F /* swiftDebug.mm in Compile Sources */,
87310B1E266FBB59008BA0D6 /* smartrowrower.cpp in Compile Sources */,
87A3BC222656429600D302E3 /* rower.cpp in Compile Sources */,
C719682D8D421AF6B2DAAEA9 /* main.cpp in Compile Sources */,
@@ -3238,9 +3312,11 @@
873824E727E647A8004F1B46 /* record.cpp in Compile Sources */,
B38F3288D4AE4025465C1953 /* moc_bike.cpp in Compile Sources */,
87EFB57025BD704A0039DD5A /* moc_proformtreadmill.cpp in Compile Sources */,
+ 8727C7D42B3BF1E4005429EB /* moc_QTelnet.cpp in Compile Sources */,
C3D1FD2587BF6F15B58BA675 /* moc_bluetooth.cpp in Compile Sources */,
87062648259480B700D06586 /* WorkoutTracking.swift in Compile Sources */,
8C3422A825EF7ECD78951307 /* moc_bluetoothdevice.cpp in Compile Sources */,
+ 8785D5432B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp in Compile Sources */,
1FBBC7C86C436CAAAFD37E56 /* moc_domyostreadmill.cpp in Compile Sources */,
876BFCA027BE35D8001D7645 /* moc_proformelliptical.cpp in Compile Sources */,
873824BD27E64707004F1B46 /* moc_resolver_p.cpp in Compile Sources */,
@@ -3286,6 +3362,7 @@
87D269A025F535200076AA48 /* skandikawiribike.cpp in Compile Sources */,
8738249427E646E3004F1B46 /* characteristicnotifier2a5b.cpp in Compile Sources */,
8768D1FB285081FE00F58E3A /* nordictrackifitadbtreadmill.cpp in Compile Sources */,
+ 8727C7D12B3BF1B8005429EB /* QTelnet.cpp in Compile Sources */,
8775008329E876F8008E48B7 /* iconceptelliptical.cpp in Compile Sources */,
87B187BD29B8C577007EEF9D /* moc_ziprotreadmill.cpp in Compile Sources */,
877FBA2B276E684E00F6C0C9 /* moc_bowflextreadmill.cpp in Compile Sources */,
@@ -3317,6 +3394,7 @@
8703BAED273C67B60058E206 /* moc_pafersbike.cpp in Compile Sources */,
873824E627E647A8004F1B46 /* hostname.cpp in Compile Sources */,
74C43649C9C4E2E5F9378019 /* moc_domyosbike.cpp in Compile Sources */,
+ 8727C7D02B3BF1B8005429EB /* proformtelnetbike.cpp in Compile Sources */,
87E0761D277A081A00FDA0F9 /* technogymmyruntreadmillrfcomm.cpp in Compile Sources */,
873824B327E64707004F1B46 /* moc_dirconprocessor.cpp in Compile Sources */,
87A0771229B6420200A368BF /* moc_wahookickrheadwind.cpp in Compile Sources */,
@@ -3676,7 +3754,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 = 705;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
HEADER_SEARCH_PATHS = (
@@ -3829,6 +3907,7 @@
QT_LIBRARY_SUFFIX = "";
SDKROOT = iphoneos;
SWIFT_INSTALL_OBJC_HEADER = YES;
+ SWIFT_OBJC_BRIDGING_HEADER = "qdomyoszwift-Bridging-Header.h";
SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(SWIFT_MODULE_NAME)-Swift2.h";
SWIFT_PRECOMPILE_BRIDGING_HEADER = YES;
SWIFT_VERSION = 5.0;
@@ -3845,7 +3924,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 = 705;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4001,6 +4080,7 @@
QT_LIBRARY_SUFFIX = _debug;
SDKROOT = iphoneos;
SWIFT_INSTALL_OBJC_HEADER = YES;
+ SWIFT_OBJC_BRIDGING_HEADER = "qdomyoszwift-Bridging-Header.h";
SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(SWIFT_MODULE_NAME)-Swift2.h";
SWIFT_PRECOMPILE_BRIDGING_HEADER = YES;
SWIFT_VERSION = 5.0;
@@ -4050,7 +4130,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 = 705;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4146,7 +4226,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 = 705;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -4238,7 +4318,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 = 705;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -4352,7 +4432,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 = 705;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
@@ -4462,6 +4542,35 @@
defaultConfigurationName = Debug;
};
/* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+ 8730A3892B404081007E336D /* XCRemoteSwiftPackageReference "swift-protobuf" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/apple/swift-protobuf.git";
+ requirement = {
+ kind = exactVersion;
+ version = 1.25.2;
+ };
+ };
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 8730A38A2B404081007E336D /* SwiftProtobuf */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 8730A3892B404081007E336D /* XCRemoteSwiftPackageReference "swift-protobuf" */;
+ productName = SwiftProtobuf;
+ };
+ 8730A38C2B404081007E336D /* SwiftProtobufPluginLibrary */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 8730A3892B404081007E336D /* XCRemoteSwiftPackageReference "swift-protobuf" */;
+ productName = SwiftProtobufPluginLibrary;
+ };
+ 8730A38E2B404081007E336D /* protoc-gen-swift */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 8730A3892B404081007E336D /* XCRemoteSwiftPackageReference "swift-protobuf" */;
+ productName = "protoc-gen-swift";
+ };
+/* End XCSwiftPackageProductDependency section */
};
rootObject = 6DB9C3763D02B1415CD9D565 /* Project object */;
}
diff --git a/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/watchkit Extension/WatchWorkoutTracking.swift b/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/watchkit Extension/WatchWorkoutTracking.swift
index 053886591..627835218 100755
--- a/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/watchkit Extension/WatchWorkoutTracking.swift
+++ b/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/watchkit Extension/WatchWorkoutTracking.swift
@@ -162,6 +162,12 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
HKSampleType.quantityType(forIdentifier: .cyclingPower)!,
HKSampleType.quantityType(forIdentifier: .cyclingSpeed)!,
HKSampleType.quantityType(forIdentifier: .cyclingCadence)!,
+ HKSampleType.quantityType(forIdentifier: .runningPower)!,
+ HKSampleType.quantityType(forIdentifier: .runningSpeed)!,
+ HKSampleType.quantityType(forIdentifier: .runningStrideLength)!,
+ HKSampleType.quantityType(forIdentifier: .runningVerticalOscillation)!,
+ HKSampleType.quantityType(forIdentifier: .walkingSpeed)!,
+ HKSampleType.quantityType(forIdentifier: .walkingStepLength)!,
HKSampleType.workoutType()
])
} else {
@@ -335,64 +341,131 @@ extension WorkoutTracking: HKLiveWorkoutBuilderDelegate {
}
}
- if #available(watchOSApplicationExtension 10.0, *) {
- let wattPerInterval = HKQuantity(unit: HKUnit.watt(),
- doubleValue: WorkoutTracking.power)
-
- if(WorkoutTracking.lastDateMetric.distance(to: Date()) < 1) {
+ if(sport == 0) {
+ if #available(watchOSApplicationExtension 10.0, *) {
+ let wattPerInterval = HKQuantity(unit: HKUnit.watt(),
+ doubleValue: WorkoutTracking.power)
+
+ if(WorkoutTracking.lastDateMetric.distance(to: Date()) < 1) {
+ return
+ }
+
+ guard let powerType = HKQuantityType.quantityType(
+ forIdentifier: .cyclingPower) else {
return
- }
-
- guard let powerType = HKQuantityType.quantityType(
- forIdentifier: .cyclingPower) else {
- return
- }
- let wattPerIntervalSample = HKQuantitySample(type: powerType,
- quantity: wattPerInterval,
- start: WorkoutTracking.lastDateMetric,
- end: Date())
- workoutBuilder.add([wattPerIntervalSample]) {(success, error) in
- if let error = error {
- print(error)
}
- }
+ let wattPerIntervalSample = HKQuantitySample(type: powerType,
+ quantity: wattPerInterval,
+ start: WorkoutTracking.lastDateMetric,
+ end: Date())
+ workoutBuilder.add([wattPerIntervalSample]) {(success, error) in
+ if let error = error {
+ print(error)
+ }
+ }
- let cadencePerInterval = HKQuantity(unit: HKUnit.count().unitDivided(by: HKUnit.second()),
- doubleValue: WorkoutTracking.cadence / 60.0)
-
- guard let cadenceType = HKQuantityType.quantityType(
- forIdentifier: .cyclingCadence) else {
- return
- }
- let cadencePerIntervalSample = HKQuantitySample(type: cadenceType,
- quantity: cadencePerInterval,
- start: WorkoutTracking.lastDateMetric,
- end: Date())
- workoutBuilder.add([cadencePerIntervalSample]) {(success, error) in
- if let error = error {
- print(error)
+ let cadencePerInterval = HKQuantity(unit: HKUnit.count().unitDivided(by: HKUnit.second()),
+ doubleValue: WorkoutTracking.cadence / 60.0)
+
+ guard let cadenceType = HKQuantityType.quantityType(
+ forIdentifier: .cyclingCadence) else {
+ return
+ }
+ let cadencePerIntervalSample = HKQuantitySample(type: cadenceType,
+ quantity: cadencePerInterval,
+ start: WorkoutTracking.lastDateMetric,
+ end: Date())
+ workoutBuilder.add([cadencePerIntervalSample]) {(success, error) in
+ if let error = error {
+ print(error)
+ }
}
+
+ let speedPerInterval = HKQuantity(unit: HKUnit.meter().unitDivided(by: HKUnit.second()),
+ doubleValue: WorkoutTracking.speed * 0.277778)
+
+ guard let speedType = HKQuantityType.quantityType(
+ forIdentifier: .cyclingSpeed) else {
+ return
+ }
+ let speedPerIntervalSample = HKQuantitySample(type: speedType,
+ quantity: speedPerInterval,
+ start: WorkoutTracking.lastDateMetric,
+ end: Date())
+ workoutBuilder.add([speedPerIntervalSample]) {(success, error) in
+ if let error = error {
+ print(error)
+ }
+ }
+
+ } else {
+ // Fallback on earlier versions
}
+ } else if(sport == 1) {
+ if #available(watchOSApplicationExtension 10.0, *) {
+ let wattPerInterval = HKQuantity(unit: HKUnit.watt(),
+ doubleValue: WorkoutTracking.power)
+
+ if(WorkoutTracking.lastDateMetric.distance(to: Date()) < 1) {
+ return
+ }
+
+ guard let powerType = HKQuantityType.quantityType(
+ forIdentifier: .runningPower) else {
+ return
+ }
+ let wattPerIntervalSample = HKQuantitySample(type: powerType,
+ quantity: wattPerInterval,
+ start: WorkoutTracking.lastDateMetric,
+ end: Date())
+ workoutBuilder.add([wattPerIntervalSample]) {(success, error) in
+ if let error = error {
+ print(error)
+ }
+ }
- let speedPerInterval = HKQuantity(unit: HKUnit.meter().unitDivided(by: HKUnit.second()),
- doubleValue: WorkoutTracking.speed * 0.277778)
-
- guard let speedType = HKQuantityType.quantityType(
- forIdentifier: .cyclingSpeed) else {
- return
- }
- let speedPerIntervalSample = HKQuantitySample(type: speedType,
- quantity: speedPerInterval,
- start: WorkoutTracking.lastDateMetric,
- end: Date())
- workoutBuilder.add([speedPerIntervalSample]) {(success, error) in
- if let error = error {
- print(error)
+ let speedPerInterval = HKQuantity(unit: HKUnit.meter().unitDivided(by: HKUnit.second()),
+ doubleValue: WorkoutTracking.speed * 0.277778)
+
+ guard let speedType = HKQuantityType.quantityType(
+ forIdentifier: .runningSpeed) else {
+ return
}
+ let speedPerIntervalSample = HKQuantitySample(type: speedType,
+ quantity: speedPerInterval,
+ start: WorkoutTracking.lastDateMetric,
+ end: Date())
+ workoutBuilder.add([speedPerIntervalSample]) {(success, error) in
+ if let error = error {
+ print(error)
+ }
+ }
+
+ } else {
+ // Fallback on earlier versions
}
+ } else if(sport == 2) {
+ if #available(watchOSApplicationExtension 10.0, *) {
+ let speedPerInterval = HKQuantity(unit: HKUnit.meter().unitDivided(by: HKUnit.second()),
+ doubleValue: WorkoutTracking.speed * 0.277778)
+
+ guard let speedType = HKQuantityType.quantityType(
+ forIdentifier: .walkingSpeed) else {
+ return
+ }
+ let speedPerIntervalSample = HKQuantitySample(type: speedType,
+ quantity: speedPerInterval,
+ start: WorkoutTracking.lastDateMetric,
+ end: Date())
+ workoutBuilder.add([speedPerIntervalSample]) {(success, error) in
+ if let error = error {
+ print(error)
+ }
+ }
- } else {
- // Fallback on earlier versions
+ } else {
+ // Fallback on earlier versions
+ }
}
WorkoutTracking.lastDateMetric = Date()
diff --git a/src/GPXList.qml b/src/GPXList.qml
index 9f0e62ff7..c3fe5eeb9 100644
--- a/src/GPXList.qml
+++ b/src/GPXList.qml
@@ -11,6 +11,7 @@ import QtLocation 5.6
ColumnLayout {
signal trainprogram_open_clicked(url name)
+ signal trainprogram_open_other_folder(url name)
signal trainprogram_preview(url name)
FileDialog {
id: fileDialogTrainProgram
@@ -18,7 +19,11 @@ ColumnLayout {
folder: shortcuts.home
onAccepted: {
console.log("You chose: " + fileDialogTrainProgram.fileUrl)
- trainprogram_open_clicked(fileDialogTrainProgram.fileUrl)
+ if(OS_VERSION === "Android") {
+ trainprogram_open_other_folder(fileDialogTrainProgram.fileUrl)
+ } else {
+ trainprogram_open_clicked(fileDialogTrainProgram.fileUrl)
+ }
fileDialogTrainProgram.close()
}
onRejected: {
diff --git a/src/QTelnet.cpp b/src/QTelnet.cpp
new file mode 100644
index 000000000..e148d2733
--- /dev/null
+++ b/src/QTelnet.cpp
@@ -0,0 +1,522 @@
+#include "QTelnet.h"
+
+#include "QTelnet.h"
+#include
+
+const char QTelnet::IACWILL[2] = { IAC, WILL };
+const char QTelnet::IACWONT[2] = { IAC, WONT };
+const char QTelnet::IACDO[2] = { IAC, DO };
+const char QTelnet::IACDONT[2] = { IAC, DONT };
+const char QTelnet::IACSB[2] = { IAC, SB };
+const char QTelnet::IACSE[2] = { IAC, SE };
+
+char QTelnet::_sendCodeArray[2] = { IAC, 0 };
+char QTelnet::_arrCRLF[2] = { 13, 10 };
+char QTelnet::_arrCR[2] = { 13, 0 };
+
+QTelnet::QTelnet(QObject *parent) :
+ QTcpSocket(parent), m_actualSB(0)
+{
+ connect( this, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)) );
+ connect( this, SIGNAL(readyRead()), this, SLOT(onReadyRead()) );
+}
+
+QString QTelnet::peerInfo() const
+{
+ return QString("%1 (%2):%3").arg(peerName()).arg(peerAddress().toString()).arg(peerPort());
+}
+
+bool QTelnet::isConnected() const
+{
+ return state() == QAbstractSocket::ConnectedState;
+}
+
+bool QTelnet::testBinaryMode() const
+{
+ return m_receivedDX[(unsigned char)TELOPT_BINARY] == DO;
+}
+
+void QTelnet::connectToHost(const QString &host, quint16 port)
+{
+ if( !isConnected() )
+ {
+ resetProtocol();
+ abort();
+ QTcpSocket::connectToHost(host, port);
+ }
+}
+
+void QTelnet::sendData(const QByteArray &ba)
+{
+ if( isConnected() )
+ transpose( ba.constData(), ba.count() );
+}
+
+void QTelnet::socketError(QAbstractSocket::SocketError err)
+{
+ Q_UNUSED(err);
+ disconnectFromHost();
+}
+
+void QTelnet::write(const char c)
+{
+ QTcpSocket::write( (char*)&c, 1 );
+}
+
+void QTelnet::setCustomCR(char cr, char cr2)
+{
+ _arrCR[0] = cr;
+ _arrCR[1] = cr2;
+}
+
+void QTelnet::setCustomCRLF(char lf, char cr)
+{
+ _arrCR[0] = lf;
+ _arrCR[1] = cr;
+}
+
+// Envia el codigo de control al servidor.
+void QTelnet::sendTelnetControl(char codigo)
+{
+ _sendCodeArray[1] = codigo;
+ QTcpSocket::write(_sendCodeArray, 2);
+}
+
+void QTelnet::writeCustomCRLF()
+{
+ QTcpSocket::write(_arrCRLF, 2);
+}
+
+void QTelnet::writeCustomCR()
+{
+ QTcpSocket::write(_arrCR, 2);
+}
+
+/// Resetea los datos del protocolo. Debe llamarse cada vez que se inicia una conexión nueva.
+void QTelnet::resetProtocol()
+{
+ for( int i = 0; i < 256; i++ )
+ {
+ m_receivedDX[i] =
+ m_receivedWX[i] =
+ m_sentDX[i] =
+ m_sentWX[i] = 0;
+ m_negotiationState = STATE_DATA;
+ m_buffSB.clear();
+ m_actualSB = 0;
+ }
+ m_oldWinSize.setHeight(-1);
+ m_oldWinSize.setWidth(-1);
+}
+
+void QTelnet::sendSB(char code, char *arr, int iLen)
+{
+ write(IAC);
+ write(SB);
+ write(code);
+
+ QTcpSocket::write(arr, iLen);
+
+ write(IAC);
+ write(SE);
+}
+void QTelnet::sendWindowSize()
+{
+ if( isConnected() && (m_receivedDX[TELOPT_NAWS] == DO) && (m_oldWinSize != m_winSize) )
+ {
+ char size[4];
+
+ m_oldWinSize = m_winSize;
+ size[0] = (m_winSize.width()>>8) & 0xFF;
+ size[1] = m_winSize.width() & 0xFF;
+ size[2] = (m_winSize.height()>>8) & 0xFF;
+ size[3] = m_winSize.height() & 0xFF;
+ sendSB(TELOPT_NAWS, size, 4);
+ }
+}
+
+// Handle an incoming IAC SB type chars IAC SE
+void QTelnet::handleSB()
+{
+ switch( m_actualSB )
+ {
+ case TELOPT_TTYPE:
+ if( (m_buffSB.count() > 0) && ((unsigned char)m_buffSB[0] == (unsigned char)TELQUAL_SEND) )
+ {
+ QTcpSocket::write(IACSB, 2);
+ write(TELOPT_TTYPE);
+ write(TELQUAL_IS);
+ /* FIXME: need more logic here if we use
+ * more than one terminal type
+ */
+ QTcpSocket::write("SiraggaTerminal", 15);
+ QTcpSocket::write(IACSE, 2);
+ }
+ break;
+ }
+}
+
+// Analiza el texto saliente para que cumpla las normas del protocolo.
+// Además ya lo escribe en el socket.
+void QTelnet::transpose(const char *buf, int iLen)
+{
+ for( int i = 0; i < iLen; i++ )
+ {
+ switch( buf[i] )
+ {
+ case IAC:
+ // Escape IAC twice in stream ... to be telnet protocol compliant
+ // this is there in binary and non-binary mode.
+ write(IAC);
+ write(IAC);
+ break;
+ case 10: // \n
+ // We need to heed RFC 854. LF (\n) is 10, CR (\r) is 13
+ // we assume that the Terminal sends \n for lf+cr and \r for just cr
+ // linefeed+carriage return is CR LF
+
+ // En modo binario no se traduce nada.
+ if( testBinaryMode() )
+ write(buf[i]);
+ else
+ writeCustomCRLF();
+ break;
+ case 13: // \r
+ // carriage return is CR NUL */
+
+ // En modo binario no se traduce nada.
+ if( testBinaryMode() )
+ write(buf[i]);
+ else
+ writeCustomCR();
+ break;
+ default:
+ // all other characters are just copied
+ write(buf[i]);
+ break;
+ }
+ }
+}
+
+void QTelnet::willsReply(char action, char reply)
+{
+ if( (reply != m_sentDX[(unsigned char)action]) || (WILL != m_receivedWX[(unsigned char)action]) )
+ {
+ write(IAC);
+ write(reply);
+ write(action);
+
+ m_sentDX[(unsigned char)action] = reply;
+ m_receivedWX[(unsigned char)action] = WILL;
+ }
+}
+
+void QTelnet::wontsReply(char action, char reply)
+{
+ if( (reply != m_sentDX[(unsigned char)action]) || (WONT != m_receivedWX[(unsigned char)action]) )
+ {
+ write(IAC);
+ write(reply);
+ write(action);
+
+ m_sentDX[(unsigned char)action] = reply;
+ m_receivedWX[(unsigned char)action] = WONT;
+ }
+}
+
+void QTelnet::doesReply(char action, char reply)
+{
+ if( (reply != m_sentWX[(unsigned char)action]) || (DO != m_receivedDX[(unsigned char)action]) )
+ {
+ write(IAC);
+ write(reply);
+ write(action);
+
+ m_sentWX[(unsigned char)action] = reply;
+ m_receivedDX[(unsigned char)action] = DO;
+ }
+}
+
+void QTelnet::dontsReply(char action, char reply)
+{
+ if( (reply != m_sentWX[(unsigned char)action]) || (DONT != m_receivedDX[(unsigned char)action]) )
+ {
+ write(IAC);
+ write(reply);
+ write(action);
+
+ m_sentWX[(unsigned char)action] = reply;
+ m_receivedDX[(unsigned char)action] = DONT;
+ }
+}
+
+// Analiza el buffer de entrada colocá ndolo en el buffer de procesado usando el protocolo telnet.
+qint64 QTelnet::doTelnetInProtocol(qint64 buffSize)
+{
+ qint64 iIn, iOut;
+ char b;
+
+ for( iIn = 0, iOut = 0; iIn < buffSize; iIn++ )
+ {
+ b = m_buffIncoming[iIn];
+
+ switch( m_negotiationState )
+ {
+ case STATE_DATA:
+ switch( b )
+ {
+ case IAC:
+ m_negotiationState = STATE_IAC;
+ break;
+ case '\r':
+ m_negotiationState = STATE_DATAR;
+ break;
+ case '\n':
+ m_negotiationState = STATE_DATAN;
+ break;
+ default:
+ m_buffProcessed[iOut++] = b;
+ break;
+ }
+ break;
+ case STATE_DATAN:
+ case STATE_DATAR:
+ switch( b )
+ {
+ case IAC:
+ m_negotiationState = STATE_IAC;
+ break;
+ case '\r':
+ case '\n':
+ m_buffProcessed[iOut++] = '\n';
+ m_negotiationState = STATE_DATA;
+ break;
+ default:
+ m_buffProcessed[iOut++] = b;
+ m_negotiationState = STATE_DATA;
+ break;
+ }
+ break;
+ case STATE_IAC:
+ switch( b )
+ {
+ case IAC: // Dos IAC seguidos, se intenta enviar un caracter con el valor IAC.
+ m_negotiationState = STATE_DATA;
+ m_buffProcessed[iOut++] = IAC;
+ break;
+ case WILL:
+ m_negotiationState = STATE_IACWILL;
+ break;
+ case WONT:
+ m_negotiationState = STATE_IACWONT;
+ break;
+ case DONT:
+ m_negotiationState = STATE_IACDONT;
+ break;
+ case DO:
+ m_negotiationState = STATE_IACDO;
+ break;
+ case EOR:
+ emitEndOfRecord();
+ m_negotiationState = STATE_DATA;
+ break;
+ case SB:
+ m_negotiationState = STATE_IACSB;
+ m_buffSB.clear();
+ break;
+ default:
+ m_negotiationState = STATE_DATA;
+ break;
+ }
+ break;
+ case STATE_IACWILL:
+ switch( b )
+ {
+ case TELOPT_ECHO:
+ emitEchoLocal(false);
+ willsReply(b, DO);
+ break;
+ case TELOPT_SGA:
+ willsReply(b, DO);
+ break;
+ case TELOPT_EOR:
+ willsReply(b, DO);
+ break;
+ case TELOPT_BINARY:
+ willsReply(b, DO);
+ break;
+ default:
+ willsReply(b, DONT);
+ break;
+ }
+ m_negotiationState = STATE_DATA;
+ break;
+ case STATE_IACWONT:
+ switch(b)
+ {
+ case TELOPT_ECHO:
+ emitEchoLocal(true);
+ wontsReply(b, DONT);
+ break;
+ case TELOPT_SGA:
+ wontsReply(b, DONT);
+ break;
+ case TELOPT_EOR:
+ wontsReply(b, DONT);
+ break;
+ case TELOPT_BINARY:
+ wontsReply(b, DONT);
+ break;
+ default:
+ wontsReply(b, DONT);
+ break;
+ }
+ m_negotiationState = STATE_DATA;
+ break;
+ case STATE_IACDO:
+ switch( b )
+ {
+ case TELOPT_ECHO:
+ doesReply(b, WILL);
+ emitEchoLocal(true);
+ break;
+ case TELOPT_SGA:
+ doesReply(b, WILL);
+ break;
+ case TELOPT_TTYPE:
+ doesReply(b, WILL);
+ break;
+ case TELOPT_BINARY:
+ doesReply(b, WILL);
+ break;
+ case TELOPT_NAWS:
+ m_receivedDX[(unsigned char)b] = (unsigned char)DO;
+ m_sentWX[(unsigned char)b] = (unsigned char)WILL;
+ write(IAC);
+ write(WILL);
+ write(b);
+
+ // Enviamos el tamaño de la pantalla.
+ sendWindowSize();
+ break;
+ default:
+ doesReply(b, WONT);
+ break;
+ }
+ m_negotiationState = STATE_DATA;
+ break;
+ case STATE_IACDONT:
+ switch (b)
+ {
+ case TELOPT_ECHO:
+ dontsReply(b, WONT);
+ emitEchoLocal(false);
+ break;
+ case TELOPT_SGA:
+ dontsReply(b, WONT);
+ break;
+ case TELOPT_NAWS:
+ dontsReply(b, WONT);
+ break;
+ case TELOPT_BINARY:
+ dontsReply(b, WONT);
+ break;
+ default:
+ dontsReply(b, WONT);
+ break;
+ }
+ m_negotiationState = STATE_DATA;
+ break;
+ case STATE_IACSB:
+ switch( b )
+ {
+ case IAC:
+ // Entramos en estado IAC en la sub-negociación.
+ m_negotiationState = STATE_IACSBIAC;
+ break;
+ default:
+ // Iniciamos la sub-negociación.
+ m_buffSB.clear();
+ m_actualSB = b;
+ m_negotiationState = STATE_IACSBDATA;
+ break;
+ }
+ break;
+ case STATE_IACSBDATA: // Estamos en datos de la subnegociación.
+ switch( b )
+ {
+ case IAC:
+ m_negotiationState = STATE_IACSBDATAIAC;
+ break;
+ default:
+ m_buffSB.append(b);
+ break;
+ }
+ break;
+ case STATE_IACSBIAC:
+ switch( b )
+ {
+ case IAC:
+ // Reiniciamos la sub-negociación.
+ m_buffSB.clear();
+ m_actualSB = b;
+ m_negotiationState = STATE_IACSBDATA;
+ default:
+ // Salimos de la sub-negociación.
+ m_negotiationState = STATE_DATA;
+ }
+ break;
+ case STATE_IACSBDATAIAC:
+ switch( b )
+ {
+ case IAC:
+ m_negotiationState = STATE_IACSBDATA;
+ m_buffSB.append(IAC);
+ break;
+ case SE:
+ handleSB();
+ m_actualSB = 0;
+ m_buffSB.clear();
+ m_negotiationState = STATE_DATA;
+ break;
+ case SB:
+ handleSB();
+ m_buffSB.clear();
+ m_negotiationState = STATE_IACSB;
+ break;
+ default:
+ m_buffSB.clear();
+ m_actualSB = 0;
+ m_negotiationState = STATE_DATA;
+ break;
+ }
+ break;
+ default:
+ m_negotiationState = STATE_DATA;
+ break;
+ }
+ }
+ return iOut;
+}
+
+void QTelnet::onReadyRead()
+{
+ qint64 readed;
+ qint64 processed;
+
+ while( (readed = read(m_buffIncoming, IncommingBufferSize)) != 0 )
+ {
+ switch( readed )
+ {
+ case -1:
+ disconnectFromHost();
+ break;
+ default:
+ processed = doTelnetInProtocol(readed);
+ if( processed > 0 )
+ Q_EMIT(newData(m_buffProcessed, processed));
+
+ break;
+ }
+ }
+}
diff --git a/src/QTelnet.h b/src/QTelnet.h
new file mode 100644
index 000000000..0807a8081
--- /dev/null
+++ b/src/QTelnet.h
@@ -0,0 +1,136 @@
+#ifndef QTELNET_H
+#define QTELNET_H
+
+#include
+#include
+#include
+#include
+
+#define IncommingBufferSize (1500)
+
+class QTelnet : public QTcpSocket
+{
+Q_OBJECT
+
+public:
+ enum SocketStatus
+ {
+ Disconnected,
+ Resolving, // Resolving host
+ Connecting, // Connecting to host.
+ Connected // Connected to host.
+ };
+
+protected:
+ enum TelnetStateCodes
+ {
+ STATE_DATA = (char)0,
+ STATE_IAC = (char)1,
+ STATE_IACSB = (char)2,
+ STATE_IACWILL = (char)3,
+ STATE_IACDO = (char)4,
+ STATE_IACWONT = (char)5,
+ STATE_IACDONT = (char)6,
+ STATE_IACSBIAC = (char)7,
+ STATE_IACSBDATA = (char)8,
+ STATE_IACSBDATAIAC = (char)9,
+ STATE_DATAR = (char)10,
+ STATE_DATAN = (char)11
+ };
+ enum TelnetCodes
+ {
+ // Negociación entrada/salida (cliente<->servidor)
+ IAC = (char)255, // Inicia la secuencia para la negociación telnet.
+ EOR = (char)239, // Estando en la negociación, End Of Record.
+ WILL = (char)251, // Estando en la negociación, Acepta el protocolo?
+ WONT = (char)252, // Estando en la negociación, Acepta el protocolo?
+ DO = (char)253, // Estando en la negociación, Protocolo aceptado.
+ DONT = (char)254, // Estando en la negociación, Protocolo denegado.
+ SB = (char)250, // Estando en la negociación, inicia secuencia de sub-negociación.
+ SE = (char)240, // Estando en la sub-negociación, fin de sub-negociación.
+
+ // Negociación de salida (cliente->servidor)
+ TELOPT_BINARY = (char)0, // Estando en la negociación, pide modo binario.
+ TELOPT_ECHO = (char)1, // Estando en la negociación, pide echo local.
+ TELOPT_SGA = (char)2, // Estando en la negociación, pide Supress Go Ahead.
+ TELOPT_EOR = (char)25, // Estando en la negociación, informa End Of Record.
+ TELOPT_NAWS = (char)31, // Estando en la negociación, Negotiate Abaut Window Size.
+ TELOPT_TTYPE = (char)24 // Estando en la negociación, Terminal Type.
+ };
+ enum TelnetQualifiers
+ {
+ TELQUAL_IS = (char)0,
+ TELQUAL_SEND = (char)1
+ };
+
+private:
+ static const char IACWILL[2];
+ static const char IACWONT[2];
+ static const char IACDO[2];
+ static const char IACDONT[2];
+ static const char IACSB[2];
+ static const char IACSE[2];
+ static char _sendCodeArray[2];
+ static char _arrCRLF[2];
+ static char _arrCR[2];
+
+ QSize m_winSize; // Tamaño de la pantalla en caracteres.
+ QSize m_oldWinSize; // Tamaño de la pantalla que se envió por última vez al server. Para no enviar el mismo dato.
+ enum TelnetStateCodes m_negotiationState;
+ char m_receivedDX[256]; // What IAC DO(NT) request do we have received already ?
+ char m_receivedWX[256]; // What IAC WILL/WONT request do we have received already ?
+ char m_sentDX[256]; // What IAC DO/DONT request do we have sent already ?
+ char m_sentWX[256]; // What IAC WILL/WONT request do we have sent already ?
+ void resetProtocol();
+
+ char m_buffIncoming[IncommingBufferSize];
+ char m_buffProcessed[IncommingBufferSize];
+ QByteArray m_buffSB;
+ int m_actualSB;
+
+ void emitEndOfRecord() { Q_EMIT(endOfRecord()); }
+ void emitEchoLocal(bool bEcho) { Q_EMIT(echoLocal(bEcho)); }
+
+ void sendTelnetControl(char codigo);
+ void handleSB(void);
+ void transpose(const char *buf, int iLen);
+
+ void willsReply(char action, char reply);
+ void wontsReply(char action, char reply);
+ void doesReply(char action, char reply);
+ void dontsReply(char action, char reply);
+
+ void sendSB(char code, char *arr, int iLen);
+ qint64 doTelnetInProtocol(qint64 buffSize);
+
+public:
+ explicit QTelnet(QObject *parent = 0);
+
+ virtual void connectToHost(const QString &host, quint16 port);
+ void sendData(const QByteArray &ba);
+ void setCustomCRLF(char lf = 13, char cr = 10);
+ void setCustomCR(char cr = 10, char cr2 = 0);
+
+ void writeCustomCRLF();
+ void writeCustomCR();
+
+ void write(const char c);
+
+ bool isConnected() const;
+ bool testBinaryMode() const;
+ void setWindSize(QSize s) {m_winSize = s;}
+ void sendWindowSize();
+
+ QString peerInfo()const;
+
+signals:
+ void newData(const char *buff, int len);
+ void endOfRecord();
+ void echoLocal(bool echo);
+
+private slots:
+ void socketError(QAbstractSocket::SocketError err);
+ void onReadyRead();
+};
+
+#endif // QTELNET_H
diff --git a/src/TrainingProgramsList.qml b/src/TrainingProgramsList.qml
index 5e49e71d4..cb91bb053 100644
--- a/src/TrainingProgramsList.qml
+++ b/src/TrainingProgramsList.qml
@@ -9,6 +9,7 @@ import Qt.labs.settings 1.0
ColumnLayout {
signal trainprogram_open_clicked(url name)
+ signal trainprogram_open_other_folder(url name)
signal trainprogram_preview(url name)
FileDialog {
id: fileDialogTrainProgram
@@ -16,7 +17,11 @@ ColumnLayout {
folder: shortcuts.home
onAccepted: {
console.log("You chose: " + fileDialogTrainProgram.fileUrl)
- trainprogram_open_clicked(fileDialogTrainProgram.fileUrl)
+ if(OS_VERSION === "Android") {
+ trainprogram_open_other_folder(fileDialogTrainProgram.fileUrl)
+ } else {
+ trainprogram_open_clicked(fileDialogTrainProgram.fileUrl)
+ }
fileDialogTrainProgram.close()
}
onRejected: {
diff --git a/src/activiotreadmill.cpp b/src/activiotreadmill.cpp
index 7f823d760..5eb399b98 100644
--- a/src/activiotreadmill.cpp
+++ b/src/activiotreadmill.cpp
@@ -297,6 +297,7 @@ void activiotreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
Q_UNUSED(characteristic);
QByteArray value = newValue;
+ QDateTime now = QDateTime::currentDateTime();
emit debug(QStringLiteral(" << ") + QString::number(value.length()) + QStringLiteral(" ") + value.toHex(' '));
emit packetReceived();
@@ -339,12 +340,12 @@ void activiotreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
200.0) /
(60000.0 / ((double)lastTimeCharacteristicChanged.msecsTo(
- QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
Distance += ((speed / (double)3600.0) /
- ((double)1000.0 / (double)(lastTimeCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
- lastTimeCharacteristicChanged = QDateTime::currentDateTime();
+ ((double)1000.0 / (double)(lastTimeCharacteristicChanged.msecsTo(now))));
+ lastTimeCharacteristicChanged = now;
}
emit debug(QStringLiteral("Current speed: ") + QString::number(speed));
diff --git a/src/android/AndroidManifest.xml b/src/android/AndroidManifest.xml
index 0d5dbd1a5..779cdeaa6 100644
--- a/src/android/AndroidManifest.xml
+++ b/src/android/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/src/android/build.gradle b/src/android/build.gradle
index 842589948..3148f8fa1 100644
--- a/src/android/build.gradle
+++ b/src/android/build.gradle
@@ -6,9 +6,14 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:3.6.0'
+ classpath 'com.google.protobuf:protobuf-gradle-plugin:0.9.4'
}
}
+plugins {
+ id "com.google.protobuf" version "0.9.4"
+}
+
repositories {
google()
jcenter()
@@ -17,14 +22,16 @@ repositories {
}
apply plugin: 'com.android.application'
+apply plugin: 'com.google.protobuf'
def amazon = System.getenv('AMAZON')
println(amazon)
dependencies {
compile 'com.rvalerio:fgchecker:1.1.0'
- implementation "androidx.core:core-ktx:+"
+ implementation "androidx.core:core-ktx:1.12.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0"
+ implementation 'com.google.protobuf:protobuf-javalite:3.25.1'
if(amazon == "1") {
// amazon app store
@@ -49,6 +56,26 @@ dependencies {
implementation 'com.google.android.gms:play-services-wearable:+'
}
+import org.apache.tools.ant.taskdefs.condition.Os
+
+// Compatible with macOS on Apple Silicon
+def archSuffix = Os.isFamily(Os.FAMILY_MAC) ? ':osx-x86_64' : ''
+
+protobuf {
+ protoc {
+ artifact = "com.google.protobuf:protoc:3.25.1$archSuffix"
+ }
+ generateProtoTasks {
+ all().configureEach { task ->
+ task.builtins {
+ java {
+ option "lite"
+ }
+ }
+ }
+ }
+}
+
android {
/*******************************************************
* The following variables:
diff --git a/src/android/gradle.properties b/src/android/gradle.properties
index 646c51b97..094148595 100644
--- a/src/android/gradle.properties
+++ b/src/android/gradle.properties
@@ -1,2 +1,3 @@
android.useAndroidX=true
android.enableJetifier=true
+protoc_platform=osx-x86_64
diff --git a/src/android/gradlew b/src/android/gradlew
old mode 100644
new mode 100755
diff --git a/src/android/src/ContentHelper.java b/src/android/src/ContentHelper.java
new file mode 100644
index 000000000..4baa23697
--- /dev/null
+++ b/src/android/src/ContentHelper.java
@@ -0,0 +1,28 @@
+package org.cagnulen.qdomyoszwift;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.OpenableColumns;
+import android.util.Log;
+
+public class ContentHelper {
+
+ public static String getFileName(Context context, Uri uri) {
+ String result = null;
+ if (uri.getScheme().equals("content")) {
+ Log.d("ContentHelper", "content");
+ Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
+ Log.d("ContentHelper", "cursor " + cursor);
+ try {
+ if (cursor != null && cursor.moveToFirst()) {
+ result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
+ Log.d("ContentHelper", "result " + result);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/android/src/ZwiftAPI.java b/src/android/src/ZwiftAPI.java
new file mode 100644
index 000000000..0815423c6
--- /dev/null
+++ b/src/android/src/ZwiftAPI.java
@@ -0,0 +1,65 @@
+package org.cagnulen.qdomyoszwift;
+
+import android.app.ActivityManager;
+import android.app.Activity;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
+import android.os.Looper;
+import android.os.Handler;
+import android.util.Log;
+import com.garmin.android.connectiq.ConnectIQ;
+import com.garmin.android.connectiq.ConnectIQAdbStrategy;
+import com.garmin.android.connectiq.IQApp;
+import com.garmin.android.connectiq.IQDevice;
+import com.garmin.android.connectiq.exception.InvalidStateException;
+import com.garmin.android.connectiq.exception.ServiceUnavailableException;
+import android.content.BroadcastReceiver;
+import android.content.ContextWrapper;
+import android.content.IntentFilter;
+import android.widget.Toast;
+
+import org.jetbrains.annotations.Nullable;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import java.util.HashMap;
+import java.util.List;
+
+public class ZwiftAPI {
+
+ private static Context context;
+
+ private static final String TAG = "ZwiftAPI: ";
+ private static ZwiftMessages.PlayerState playerState;
+
+ public static void zwift_api_decodemessage_player(byte[] value) {
+ try {
+ playerState = ZwiftMessages.PlayerState.parseFrom(value);
+ // Ora puoi usare 'message' come un oggetto normale
+ } catch (InvalidProtocolBufferException e) {
+ // Gestisci l'eccezione se il messaggio non può essere parsato
+ Log.e(TAG, e.toString());
+ }
+ }
+
+ public static float getAltitude() {
+ Log.d(TAG, "getAltitude " + playerState.getAltitude());
+ return playerState.getAltitude();
+ }
+
+ public static float getDistance() {
+ Log.d(TAG, "getDistance " + playerState.getDistance());
+ return playerState.getDistance();
+ }
+}
diff --git a/src/android/src/main/proto/zwift_messages.proto b/src/android/src/main/proto/zwift_messages.proto
new file mode 100644
index 000000000..d7bc0d2b1
--- /dev/null
+++ b/src/android/src/main/proto/zwift_messages.proto
@@ -0,0 +1,155 @@
+syntax="proto3";
+
+package org.cagnulen.qdomyoszwift;
+
+message PlayerState {
+ int32 id = 1;
+ int64 worldTime = 2;
+ int32 distance = 3;
+ int32 roadTime = 4;
+ int32 laps = 5;
+ int32 speed = 6;
+ int32 roadPosition = 8;
+ int32 cadenceUHz = 9;
+ int32 heartrate = 11;
+ int32 power = 12;
+ int64 heading = 13;
+ int32 lean = 14;
+ int32 climbing = 15;
+ int32 time = 16;
+ int32 f19 = 19;
+ int32 f20 = 20;
+ int32 progress = 21;
+ int64 customisationId = 22;
+ int32 justWatching = 23;
+ int32 calories = 24;
+ float x = 25;
+ float altitude = 26;
+ float y = 27;
+ int32 watchingRiderId = 28;
+ int32 groupId = 29;
+ int64 sport = 31;
+}
+
+message ClientToServer {
+ int32 connected = 1;
+ int32 rider_id = 2;
+ int64 world_time = 3;
+ PlayerState state = 7;
+ int32 seqno = 4;
+ int64 tag8 = 8;
+ int64 tag9 = 9;
+ int64 last_update = 10;
+ int64 tag11 = 11;
+ int64 last_player_update = 12;
+}
+
+message SegmentResult {
+ int64 id = 1;
+ int64 rider_id = 2;
+ int64 event_subgroup_id = 6;
+ string first_name = 7;
+ string last_name = 8;
+ string finish_time_str = 10;
+ int64 elapsed_ms = 11;
+ int32 powermeter = 12;
+ int32 weight = 13;
+ int32 power = 15;
+ int32 heartrate = 19;
+}
+
+message SegmentResults {
+ int64 world_id = 1;
+ int64 segment_id = 2;
+ int64 event_subgroup_id = 3;
+ repeated SegmentResult segment_results = 4;
+}
+
+message UnknownMessage1 {
+ // string firstName=7;
+ // string lastName=8;
+ // string timestamp=17;
+}
+
+message UnknownMessage {
+ // int64 tag1=1;
+ // UnknownMessage1 tag4=4;
+}
+
+message ServerToClient {
+ int32 tag1 = 1;
+ int32 rider_id = 2;
+ int64 world_time = 3;
+ int32 seqno = 4;
+ repeated PlayerState player_states = 8;
+ repeated UnknownMessage player_updates = 9;
+ int64 tag11 = 11;
+ int64 tag17 = 17;
+ int32 num_msgs = 18;
+ int32 msgnum = 19;
+}
+
+message WorldAttributes {
+ int32 world_id = 1;
+ string name = 2;
+ int64 tag3 = 3;
+ int64 tag5 = 4;
+ int64 world_time = 6;
+ int64 clock_time = 7;
+}
+
+message WorldAttribute {
+ int64 world_time = 2;
+}
+
+message EventSubgroupProtobuf {
+ int32 id = 1;
+ string name = 2;
+ int32 rules = 8;
+ int32 route = 22;
+ int32 laps = 25;
+ int32 startLocation = 29;
+ int32 label = 30;
+ int32 paceType = 31;
+ int32 jerseyHash = 36;
+}
+
+message RiderAttributes {
+ int32 f2 = 2;
+ int32 f3 = 3;
+ message AttributeMessage {
+ int32 myId = 1;
+ int32 theirId = 2;
+ string firstName = 3;
+ string lastName = 4;
+ int32 countryCode = 5;
+ }
+ AttributeMessage attributeMessage = 4;
+ int32 theirId = 10;
+ int32 f13 = 13;
+}
+
+message Profiles {
+ repeated Profile profiles = 1;
+}
+
+message Profile {
+ int32 id = 1;
+ string firstName = 4;
+ string lastName = 5;
+ int32 male = 6;
+ int32 weight = 9;
+ int32 bodyType = 12;
+ int32 countryCode = 34;
+ int32 totalDistance = 35;
+ int32 totalDistanceClimbed = 36;
+ int32 totalTimeInMinutes = 37;
+ int32 totalWattHours = 41;
+ int32 height = 42;
+ int32 totalExperiencePoints = 46;
+ int32 achievementLevel = 49;
+ int32 powerSource = 52;
+ int32 age = 55;
+ string launchedGameClient = 108;
+ int32 currentActivityId = 109;
+}
diff --git a/src/apexbike.cpp b/src/apexbike.cpp
index 98a753a9d..b1fdbb384 100644
--- a/src/apexbike.cpp
+++ b/src/apexbike.cpp
@@ -129,6 +129,7 @@ void apexbike::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void apexbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -159,7 +160,7 @@ void apexbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
if (watts())
KCal +=
@@ -167,17 +168,17 @@ void apexbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) {
diff --git a/src/bhfitnesselliptical.cpp b/src/bhfitnesselliptical.cpp
index 69775ca87..984651bfe 100644
--- a/src/bhfitnesselliptical.cpp
+++ b/src/bhfitnesselliptical.cpp
@@ -142,6 +142,7 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
emit debug(QStringLiteral(" << ") + newValue.toHex(' '));
+ QDateTime now = QDateTime::currentDateTime();
if (characteristic.uuid() == QBluetoothUuid::HeartRate && newValue.length() > 1) {
Heart = (uint8_t)newValue[1];
@@ -191,7 +192,7 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0),
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0),
0 /* not useful for elliptical*/);
}
index += 2;
@@ -241,7 +242,7 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
index += 3;
} else {
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
}
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
@@ -288,7 +289,7 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
}
@@ -301,7 +302,7 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
#endif
{
if (Flags.heartRate && !disable_hr_frommachinery && newValue.length() > index) {
- Heart = ((double)((newValue.at(index))));
+ Heart = ((double)(((uint8_t)newValue.at(index))));
// index += 1; // NOTE: clang-analyzer-deadcode.DeadStores
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
} else {
@@ -326,7 +327,7 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
if (heartRateBeltName.startsWith(QStringLiteral("Disabled")) &&
(!Flags.heartRate || Heart.value() == 0 || disable_hr_frommachinery)) {
diff --git a/src/bike.cpp b/src/bike.cpp
index 4c7a85c13..995e84641 100644
--- a/src/bike.cpp
+++ b/src/bike.cpp
@@ -116,6 +116,7 @@ void bike::clearStats() {
Speed.clear(false);
KCal.clear(true);
Distance.clear(true);
+ Distance1s.clear(true);
Heart.clear(false);
m_jouls.clear(true);
elevationAcc = 0;
@@ -140,6 +141,7 @@ void bike::setPaused(bool p) {
Speed.setPaused(p);
KCal.setPaused(p);
Distance.setPaused(p);
+ Distance1s.setPaused(p);
Heart.setPaused(p);
m_jouls.setPaused(p);
m_watt.setPaused(p);
@@ -161,6 +163,7 @@ void bike::setLap() {
Speed.setLap(false);
KCal.setLap(true);
Distance.setLap(true);
+ Distance1s.setLap(true);
Heart.setLap(false);
m_jouls.setLap(true);
m_watt.setLap(false);
diff --git a/src/bkoolbike.cpp b/src/bkoolbike.cpp
index da6b4e3af..9e9c91017 100644
--- a/src/bkoolbike.cpp
+++ b/src/bkoolbike.cpp
@@ -192,6 +192,7 @@ void bkoolbike::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void bkoolbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -253,8 +254,8 @@ void bkoolbike::characteristicChanged(const QLowEnergyCharacteristic &characteri
if (cadence >= 0 && cadence < 255) {
Cadence = cadence;
}
- lastGoodCadence = QDateTime::currentDateTime();
- } else if (lastGoodCadence.msecsTo(QDateTime::currentDateTime()) > 2000) {
+ lastGoodCadence = now;
+ } else if (lastGoodCadence.msecsTo(now) > 2000) {
Cadence = 0;
}
@@ -266,7 +267,7 @@ void bkoolbike::characteristicChanged(const QLowEnergyCharacteristic &characteri
.toDouble();
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
// Resistance = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
// (uint16_t)((uint8_t)newValue.at(index)))); debug("Current Resistance: " +
@@ -301,9 +302,9 @@ void bkoolbike::characteristicChanged(const QLowEnergyCharacteristic &characteri
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
emit debug(QStringLiteral("Current CrankRevsRead: ") + QString::number(CrankRevsRead));
emit debug(QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime));
@@ -384,8 +385,8 @@ void bkoolbike::characteristicChanged(const QLowEnergyCharacteristic &characteri
if (cadence >= 0) {
Cadence = cadence;
}
- lastGoodCadence = QDateTime::currentDateTime();
- } else if (lastGoodCadence.msecsTo(QDateTime::currentDateTime()) > 2000) {
+ lastGoodCadence = now;
+ } else if (lastGoodCadence.msecsTo(now) > 2000) {
Cadence = 0;
}
}
@@ -403,12 +404,12 @@ void bkoolbike::characteristicChanged(const QLowEnergyCharacteristic &characteri
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
// if we change this, also change the wattsFromResistance function. We can create a standard function in
@@ -456,7 +457,7 @@ void bkoolbike::characteristicChanged(const QLowEnergyCharacteristic &characteri
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight
// in kg * 3.5) / 200 ) / 60
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
}
diff --git a/src/bluetooth.cpp b/src/bluetooth.cpp
index c7e114e44..e6f48de43 100644
--- a/src/bluetooth.cpp
+++ b/src/bluetooth.cpp
@@ -399,6 +399,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
settings.value(QZSettings::fakedevice_treadmill, QZSettings::default_fakedevice_treadmill).toBool();
bool pafers_treadmill = settings.value(QZSettings::pafers_treadmill, QZSettings::default_pafers_treadmill).toBool();
QString proformtdf4ip = settings.value(QZSettings::proformtdf4ip, QZSettings::default_proformtdf4ip).toString();
+ QString proformtdf1ip = settings.value(QZSettings::proformtdf1ip, QZSettings::default_proformtdf1ip).toString();
QString proformtreadmillip =
settings.value(QZSettings::proformtreadmillip, QZSettings::default_proformtreadmillip).toString();
QString nordictrack_2950_ip =
@@ -426,6 +427,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) {
@@ -546,6 +548,14 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
qDebug() << device.deviceUuid();
#endif
+ // not required for mobile I guess
+#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
+ if(!homeformLoaded) {
+ qDebug() << "homeform not yet loaded";
+ return;
+ }
+#endif
+
if (onlyDiscover)
return;
@@ -655,6 +665,21 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(proformWifiBike);
+ } else if (!proformtdf1ip.isEmpty() && !proformTelnetBike) {
+ this->stopDiscovery();
+ proformTelnetBike =
+ new proformtelnetbike(noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
+ emit deviceConnected(b);
+ connect(proformTelnetBike, &bluetoothdevice::connectedAndDiscovered, this,
+ &bluetooth::connectedAndDiscovered);
+ // connect(cscBike, SIGNAL(disconnected()), this, SLOT(restart()));
+ connect(proformTelnetBike, &proformtelnetbike::debug, this, &bluetooth::debug);
+ proformTelnetBike->deviceDiscovered(b);
+ // connect(this, SIGNAL(searchingStop()), cscBike, SLOT(searchingStop())); //NOTE: Commented due to #358
+ if (this->discoveryAgent && !this->discoveryAgent->isActive()) {
+ emit searchingStop();
+ }
+ this->signalBluetoothDeviceConnected(proformTelnetBike);
#ifndef Q_OS_IOS
} else if (!computrainerSerialPort.isEmpty() && !computrainerBike) {
this->stopDiscovery();
@@ -824,7 +849,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 +1030,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();
@@ -1029,6 +1057,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
b.name().toUpper().startsWith(QStringLiteral("KINGSMITH")) ||
b.name().toUpper().startsWith(QStringLiteral("DYNAMAX")) ||
b.name().toUpper().startsWith(QStringLiteral("WALKINGPAD")) ||
+ // Poland-distributed WalkingPad R2 TRR2FB
+ b.name().toUpper().startsWith(QStringLiteral("KS-SC-BLR2C")) ||
!b.name().toUpper().compare(QStringLiteral("RE")) || // just "RE"
b.name().toUpper().startsWith(QStringLiteral("KS-H")) ||
b.name().toUpper().startsWith(QStringLiteral("KS-BLC")) || // Walkingpad C2 #1672
@@ -1057,7 +1087,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
if (this->discoveryAgent && !this->discoveryAgent->isActive())
emit searchingStop();
this->signalBluetoothDeviceConnected(kingsmithR1ProTreadmill);
- } else if ((b.name().toUpper().startsWith(QStringLiteral("ZW-"))) && !shuaA5Treadmill && filter) {
+ } else if ((b.name().toUpper().startsWith(QStringLiteral("ZW-"))) && !shuaA5Treadmill && ftms_bike.contains(QZSettings::default_ftms_bike) && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
shuaA5Treadmill = new shuaa5treadmill(noWriteResistance, noHeartService);
@@ -1175,11 +1205,13 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
!toorx_ftms && toorx_ftms_treadmill) ||
!b.name().compare(ftms_treadmill, Qt::CaseInsensitive) ||
b.name().toUpper().startsWith(QStringLiteral("MOBVOI TM")) || // FTMS
+ b.name().toUpper().startsWith(QStringLiteral("TUNTURI T60-")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("KETTLER TREADMILL")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("ASSAULTRUNNER")) || // FTMS
(b.name().toUpper().startsWith(QStringLiteral("CTM")) && b.name().length() >= 15) || // FTMS
(b.name().toUpper().startsWith(QStringLiteral("F85")) && !sole_inclination) || // FMTS
(b.name().toUpper().startsWith(QStringLiteral("F80")) && !sole_inclination) || // FMTS
+ (b.name().toUpper().startsWith(QStringLiteral("ANPLUS-"))) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("ESANGLINKER"))) &&
!horizonTreadmill && filter) {
this->setLastBluetoothDevice(b);
@@ -1322,6 +1354,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
@@ -1330,10 +1363,11 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton)
.toBool()) || // ss2k on a peloton bike
(b.name().toUpper().startsWith("KICKR CORE")) ||
+ (b.name().toUpper().startsWith("DS60-")) ||
(b.name().toUpper().startsWith("ZUMO")) || (b.name().toUpper().startsWith("XS08-")) ||
(b.name().toUpper().startsWith("B94")) || (b.name().toUpper().startsWith("STAGES BIKE")) ||
(b.name().toUpper().startsWith("SUITO")) || (b.name().toUpper().startsWith("D2RIDE")) ||
- (b.name().toUpper().startsWith("DIRETO XR")) ||
+ (b.name().toUpper().startsWith("DIRETO XR")) || (b.name().toUpper().startsWith("MERACH-667-")) ||
!b.name().compare(ftms_bike, Qt::CaseInsensitive) || (b.name().toUpper().startsWith("SMB1")) ||
(b.name().toUpper().startsWith("UBIKE FTMS")) || (b.name().toUpper().startsWith("INRIDE"))) &&
!ftmsBike && !snodeBike && !fitPlusBike && !stagesBike && filter) {
@@ -1377,6 +1411,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 +1818,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 +1904,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 +2028,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-")) && !horizonTreadmill && !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 +2244,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;
}
}
}
@@ -2533,6 +2580,11 @@ void bluetooth::restart() {
delete proformWifiBike;
proformWifiBike = nullptr;
}
+ if (proformTelnetBike) {
+
+ delete proformTelnetBike;
+ proformTelnetBike = nullptr;
+ }
if (proformWifiTreadmill) {
delete proformWifiTreadmill;
@@ -2879,6 +2931,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
@@ -2938,6 +2998,8 @@ bluetoothdevice *bluetooth::device() {
return cscBike;
} else if (proformWifiBike) {
return proformWifiBike;
+ } else if (proformTelnetBike) {
+ return proformTelnetBike;
} else if (proformWifiTreadmill) {
return proformWifiTreadmill;
} else if (nordictrackifitadbTreadmill) {
@@ -3207,6 +3269,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;
}
diff --git a/src/bluetooth.h b/src/bluetooth.h
index 751d7fa49..2767f142d 100644
--- a/src/bluetooth.h
+++ b/src/bluetooth.h
@@ -41,6 +41,7 @@
#include "echelonconnectsport.h"
#include "echelonrower.h"
+#include "eliteariafan.h"
#include "eliterizer.h"
#include "elitesterzosmart.h"
#include "eslinkertreadmill.h"
@@ -85,6 +86,7 @@
#include "proformellipticaltrainer.h"
#include "proformrower.h"
#include "proformtreadmill.h"
+#include "proformtelnetbike.h"
#include "proformwifibike.h"
#include "proformwifitreadmill.h"
#include "schwinn170bike.h"
@@ -145,6 +147,7 @@ class bluetooth : public QObject, public SignalHandler {
bluetoothdevice *heartRateDevice() { return heartRateBelt; }
QList devices;
bool onlyDiscover = false;
+ volatile bool homeformLoaded = false;
private:
bool useDiscovery = false;
@@ -186,6 +189,7 @@ class bluetooth : public QObject, public SignalHandler {
pelotonbike *pelotonBike = nullptr;
proformrower *proformRower = nullptr;
proformbike *proformBike = nullptr;
+ proformtelnetbike *proformTelnetBike = nullptr;
proformwifibike *proformWifiBike = nullptr;
proformwifitreadmill *proformWifiTreadmill = nullptr;
proformelliptical *proformElliptical = nullptr;
@@ -250,6 +254,7 @@ class bluetooth : public QObject, public SignalHandler {
faketreadmill *fakeTreadmill = nullptr;
QList fitmetriaFanfit;
QList wahookickrHeadWind;
+ QList eliteAriaFan;
QString filterDevice = QLatin1String("");
bool testResistance = false;
diff --git a/src/bluetoothdevice.cpp b/src/bluetoothdevice.cpp
index 1f331654e..53fd9da37 100644
--- a/src/bluetoothdevice.cpp
+++ b/src/bluetoothdevice.cpp
@@ -267,6 +267,7 @@ void bluetoothdevice::clearStats() {
Speed.clear(false);
KCal.clear(true);
Distance.clear(true);
+ Distance1s.clear(true);
Heart.clear(false);
m_jouls.clear(true);
elevationAcc = 0;
@@ -284,6 +285,7 @@ void bluetoothdevice::setPaused(bool p) {
Speed.setPaused(p);
KCal.setPaused(p);
Distance.setPaused(p);
+ Distance1s.setPaused(p);
Heart.setPaused(p);
m_jouls.setPaused(p);
m_watt.setPaused(p);
@@ -299,6 +301,7 @@ void bluetoothdevice::setLap() {
Speed.setLap(false);
KCal.setLap(true);
Distance.setLap(true);
+ Distance1s.setLap(true);
Heart.setLap(false);
m_jouls.setLap(true);
m_watt.setLap(false);
diff --git a/src/bluetoothdevice.h b/src/bluetoothdevice.h
index b10e72dcc..3335cd262 100644
--- a/src/bluetoothdevice.h
+++ b/src/bluetoothdevice.h
@@ -100,6 +100,9 @@ class bluetoothdevice : public QObject {
* @return
*/
virtual double odometer();
+ virtual metric currentDistance() {return Distance;}
+ virtual metric currentDistance1s() {return Distance1s;}
+ void addCurrentDistance1s(double distance) { Distance1s += distance; }
/**
* @brief calories Gets a metric object to get and set the amount of energy expended.
@@ -495,6 +498,7 @@ class bluetoothdevice : public QObject {
* the length of belt traversed on a treadmill.
*/
metric Distance;
+ metric Distance1s; // used to populate the distance on the FIT file. Since Strava is using the distance to graph it, it has to have 1s trigger.
/**
* @brief FanSpeed The currently requested fan speed. Units: revolutions per second
diff --git a/src/bowflext216treadmill.cpp b/src/bowflext216treadmill.cpp
index bb48fca3d..21db4b99e 100644
--- a/src/bowflext216treadmill.cpp
+++ b/src/bowflext216treadmill.cpp
@@ -393,8 +393,14 @@ void bowflext216treadmill::serviceScanDone(void) {
QBluetoothUuid _gattCommunicationChannelServiceId(QStringLiteral("15B7BF49-1693-481E-B877-69D33CE6BAFA"));
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
if (gattCommunicationChannelService == nullptr) {
- qDebug() << "WRONG SERVICE";
- return;
+ qDebug() << "trying with the BOWFLEX BTX116 treadmill";
+ bowflex_btx116 = true;
+ QBluetoothUuid _gattCommunicationChannelServiceId(QStringLiteral("b5c78780-cad7-11e5-b9f8-0002a5d5c51b"));
+ gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
+ if (gattCommunicationChannelService == nullptr) {
+ qDebug() << "WRONG SERVICE";
+ return;
+ }
}
}
}
diff --git a/src/bowflext216treadmill.h b/src/bowflext216treadmill.h
index e407d3074..e85fe127e 100644
--- a/src/bowflext216treadmill.h
+++ b/src/bowflext216treadmill.h
@@ -74,6 +74,7 @@ class bowflext216treadmill : public treadmill {
bool initRequest = false;
bool bowflex_t6 = false;
+ bool bowflex_btx116 = false;
Q_SIGNALS:
void disconnected();
diff --git a/src/characteristicnotifier2a63.cpp b/src/characteristicnotifier2a63.cpp
index 7f7a06343..dd8e3119e 100644
--- a/src/characteristicnotifier2a63.cpp
+++ b/src/characteristicnotifier2a63.cpp
@@ -9,10 +9,57 @@ int CharacteristicNotifier2A63::notify(QByteArray &value) {
normalizeWattage = 0;
if (Bike->deviceType() == bluetoothdevice::BIKE) {
- value.append((char)0x20); // crank data present
+ /*
+ // set measurement
+ measurement[2] = power & 0xFF;
+ measurement[3] = (power >> 8) & 0xFF;
+
+ measurement[4] = wheelrev & 0xFF;
+ measurement[5] = (wheelrev >> 8) & 0xFF;
+ measurement[6] = (wheelrev >> 16) & 0xFF;
+ measurement[7] = (wheelrev >> 24) & 0xFF;
+
+ measurement[8] = lastwheel & 0xFF;
+ measurement[9] = (lastwheel >> 8) & 0xFF;
+
+ measurement[10] = crankrev & 0xFF;
+ measurement[11] = (crankrev >> 8) & 0xFF;
+
+ measurement[12] = lastcrank & 0xFF;
+ measurement[13] = (lastcrank >> 8) & 0xFF;
+
+ // speed & distance
+ // NOTE : based on Apple Watch default wheel dimension 700c x 2.5mm
+ // NOTE : 3 is theoretical crank:wheel gear ratio
+ // NOTE : 2.13 is circumference of 700c in meters
+
+ wheelCount = crankCount * 3;
+ speed = cadence * 3 * 2.13 * 60 / 1000;
+ distance = wheelCount * 2.13 / 1000;
+
+ #if defined(USEPOWER)
+ lastWheelK = lastCrankK * 2; // 1/2048 s granularity
+ #else
+ lastWheelK = lastCrankK * 1; // 1/1024 s granularity
+ #endif
+
+ */
+
+ uint32_t wheelCount = (uint32_t)Bike->currentCrankRevolutions() * 3;
+ uint16_t lastWheelK = Bike->lastCrankEventTime() * 2;
+
+ value.append((char)0x30); // crank data present and wheel for apple watch
value.append((char)0x00);
value.append((char)(((uint16_t)normalizeWattage) & 0xFF)); // watt
value.append((char)(((uint16_t)normalizeWattage) >> 8) & 0xFF); // watt
+
+ value.append((char)(((uint32_t)wheelCount) & 0xFF)); // revs count
+ value.append((char)(((uint32_t)wheelCount) >> 8) & 0xFF); // revs count
+ value.append((char)(((uint32_t)wheelCount) >> 16) & 0xFF); // revs count
+ value.append((char)(((uint32_t)wheelCount) >> 24) & 0xFF); // revs count
+ value.append((char)(lastWheelK & 0xff)); // eventtime
+ value.append((char)(lastWheelK >> 8) & 0xFF); // eventtime
+
value.append((char)(((uint16_t)Bike->currentCrankRevolutions()) & 0xFF)); // revs count
value.append((char)(((uint16_t)Bike->currentCrankRevolutions()) >> 8) & 0xFF); // revs count
value.append((char)(Bike->lastCrankEventTime() & 0xff)); // eventtime
diff --git a/src/characteristicnotifier2acc.cpp b/src/characteristicnotifier2acc.cpp
index 643362aff..e58e97ce2 100644
--- a/src/characteristicnotifier2acc.cpp
+++ b/src/characteristicnotifier2acc.cpp
@@ -10,7 +10,7 @@ int CharacteristicNotifier2ACC::notify(QByteArray &value) {
value.append((char)0x14); // heart rate and elapsed time
value.append((char)0x00);
value.append((char)0x00);
- value.append((char)0x0C); // resistance and power target supported
+ value.append((char)0x0F); // resistance, power, speed and inclination target supported
value.append((char)0xE0); // indoor simulation, wheel and spin down supported
value.append((char)0x00);
value.append((char)0x00);
diff --git a/src/characteristicnotifier2acd.cpp b/src/characteristicnotifier2acd.cpp
index f3849a78c..2a2b0ba95 100644
--- a/src/characteristicnotifier2acd.cpp
+++ b/src/characteristicnotifier2acd.cpp
@@ -8,7 +8,7 @@ CharacteristicNotifier2ACD::CharacteristicNotifier2ACD(bluetoothdevice *Bike, QO
int CharacteristicNotifier2ACD::notify(QByteArray &value) {
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL) {
- value.append(0x08); // Inclination available
+ value.append(0x0C); // Inclination available and distance for peloton
value.append((char)0x01); // heart rate available
uint16_t normalizeSpeed = (uint16_t)qRound(Bike->currentSpeed().value() * 100);
@@ -17,6 +17,17 @@ int CharacteristicNotifier2ACD::notify(QByteArray &value) {
QByteArray speedBytes;
speedBytes.append(b);
speedBytes.append(a);
+
+ uint16_t normalizeDistance = (uint16_t)qRound(Bike->odometer() * 1000);
+ a = (normalizeDistance >> 16) & 0XFF;
+ b = (normalizeDistance >> 8) & 0XFF;
+ char c = normalizeDistance & 0XFF;
+ QByteArray distanceBytes;
+ distanceBytes.append(c);
+ distanceBytes.append(b);
+ distanceBytes.append(a);
+
+
uint16_t normalizeIncline = 0;
if (dt == bluetoothdevice::TREADMILL)
normalizeIncline = (uint32_t)qRound(((treadmill *)Bike)->currentInclination().value() * 10);
@@ -36,6 +47,8 @@ int CharacteristicNotifier2ACD::notify(QByteArray &value) {
rampBytes.append(a);
value.append(speedBytes); // Actual value.
+
+ value.append(distanceBytes); // Actual value.
value.append(inclineBytes); // incline
diff --git a/src/chronobike.cpp b/src/chronobike.cpp
index c93c7016e..03f7fad15 100644
--- a/src/chronobike.cpp
+++ b/src/chronobike.cpp
@@ -126,6 +126,7 @@ void chronobike::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void chronobike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -153,7 +154,7 @@ void chronobike::characteristicChanged(const QLowEnergyCharacteristic &character
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
if (watts())
KCal +=
@@ -161,10 +162,10 @@ void chronobike::characteristicChanged(const QLowEnergyCharacteristic &character
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
double ac = 0.01243107769;
double bc = 1.145964912;
@@ -190,7 +191,7 @@ void chronobike::characteristicChanged(const QLowEnergyCharacteristic &character
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
diff --git a/src/concept2skierg.cpp b/src/concept2skierg.cpp
index 18097260c..c3665578f 100644
--- a/src/concept2skierg.cpp
+++ b/src/concept2skierg.cpp
@@ -131,6 +131,7 @@ void concept2skierg::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void concept2skierg::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
@@ -172,7 +173,7 @@ void concept2skierg::characteristicChanged(const QLowEnergyCharacteristic &chara
break;
case 0x32:
qDebug() << "32";
- if (newValue.length() >= 20) {
+ if (newValue.length() >= 19) {
// 0.001 m/s
uint16_t speed_ms = (((uint16_t)((uint16_t)newValue.at(5)) << 8) | (uint16_t)((uint8_t)newValue.at(4)));
uint8_t stroke_rate = newValue.at(6);
@@ -238,7 +239,7 @@ void concept2skierg::characteristicChanged(const QLowEnergyCharacteristic &chara
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
update_hr_from_external();
diff --git a/src/cscbike.cpp b/src/cscbike.cpp
index 55f3ed0ab..a47aa6c0f 100644
--- a/src/cscbike.cpp
+++ b/src/cscbike.cpp
@@ -138,6 +138,7 @@ void cscbike::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -211,8 +212,8 @@ void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characterist
double cadence = ((CrankRevs - oldCrankRevs) / deltaT) * 1024 * 60;
if (cadence >= 0 && cadence < 256)
Cadence = cadence;
- lastGoodCadence = QDateTime::currentDateTime();
- } else if (lastGoodCadence.msecsTo(QDateTime::currentDateTime()) > 2000) {
+ lastGoodCadence = now;
+ } else if (lastGoodCadence.msecsTo(now) > 2000) {
Cadence = 0;
}
emit cadenceChanged(Cadence.value());
@@ -228,12 +229,12 @@ void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characterist
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
double ac = 0.01243107769;
@@ -266,7 +267,7 @@ void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characterist
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
@@ -275,7 +276,7 @@ void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characterist
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
if (!noVirtualDevice) {
#ifdef Q_OS_IOS
diff --git a/src/domyosbike.cpp b/src/domyosbike.cpp
index 068f4409a..66baa946d 100644
--- a/src/domyosbike.cpp
+++ b/src/domyosbike.cpp
@@ -283,6 +283,7 @@ void domyosbike::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void domyosbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -400,7 +401,7 @@ void domyosbike::characteristicChanged(const QLowEnergyCharacteristic &character
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
@@ -433,7 +434,7 @@ void domyosbike::characteristicChanged(const QLowEnergyCharacteristic &character
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
KCal = kcal;
Distance = distance;
diff --git a/src/domyosrower.cpp b/src/domyosrower.cpp
index 8586d030d..42e74e82c 100644
--- a/src/domyosrower.cpp
+++ b/src/domyosrower.cpp
@@ -40,6 +40,11 @@ void domyosrower::writeCharacteristic(uint8_t *data, uint8_t data_len, const QSt
QEventLoop loop;
QTimer timeout;
+ if(ftmsRower) {
+ qDebug() << "ftmsRower! so don't send anything!";
+ return;
+ }
+
if (wait_for_response) {
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, &loop, &QEventLoop::quit);
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
@@ -161,7 +166,8 @@ void domyosrower::update() {
initRequest = false;
// if(bike_type == CHANG_YOW)
- btinit_changyow(false);
+ if(!ftmsRower)
+ btinit_changyow(false);
// else
// btinit_telink(false);
} else if (bluetoothDevice.isValid() && m_control->state() == QLowEnergyController::DiscoveredState &&
@@ -180,6 +186,22 @@ void domyosrower::update() {
bool virtual_device_force_bike =
settings.value(QZSettings::virtual_device_force_bike, QZSettings::default_virtual_device_force_bike)
.toBool();
+#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) {
+
+ qDebug() << "ios_peloton_workaround activated!";
+ h = new lockscreen();
+ h->virtualbike_ios();
+ } else
+
+#endif
+#endif
+ {
if (virtual_device_enabled) {
if (virtual_device_rower) {
qDebug() << QStringLiteral("creating virtual rower interface...");
@@ -202,9 +224,11 @@ void domyosrower::update() {
connect(virtualBike, &virtualbike::changeInclination, this, &domyosrower::changeInclination);
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::ALTERNATIVE);
}
- firstVirtual = 1;
+ }
}
}
+ firstVirtual = 1;
+
// ********************************************************************************************************
// updating the treadmill console every second
@@ -274,94 +298,345 @@ void domyosrower::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void domyosrower::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
- // qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
+ QDateTime now = QDateTime::currentDateTime();
+ qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
-
- emit debug(QStringLiteral(" << ") + newValue.toHex(' '));
+ bool disable_hr_frommachinery =
+ settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
lastPacket = newValue;
- if (newValue.length() != 26) {
- return;
- }
+ if(!ftmsRower) {
+ if (newValue.length() != 26) {
+ return;
+ }
- if (newValue.at(22) == 0x06) {
- emit debug(QStringLiteral("inclination up button pressed!"));
+ if (newValue.at(22) == 0x06) {
+ emit debug(QStringLiteral("inclination up button pressed!"));
- // requestStart = 1;
- } else if (newValue.at(22) == 0x07) {
- emit debug(QStringLiteral("inclination down button pressed!")); // i guess it should be the inclination down
+ // requestStart = 1;
+ } else if (newValue.at(22) == 0x07) {
+ emit debug(QStringLiteral("inclination down button pressed!")); // i guess it should be the inclination down
- // requestStop = 1;
- }
+ // requestStop = 1;
+ }
- /*if ((uint8_t)newValue.at(1) != 0xbc && newValue.at(2) != 0x04) // intense run, these are the bytes for the
- inclination and speed status return;*/
-
- double speed =
- GetSpeedFromPacket(newValue) *
- settings.value(QZSettings::domyos_elliptical_speed_ratio, QZSettings::default_domyos_elliptical_speed_ratio)
- .toDouble();
- double kcal = GetKcalFromPacket(newValue);
- double distance =
- GetDistanceFromPacket(newValue) *
- settings.value(QZSettings::domyos_elliptical_speed_ratio, QZSettings::default_domyos_elliptical_speed_ratio)
- .toDouble();
-
- if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
- .toString()
- .startsWith(QStringLiteral("Disabled"))) {
- Cadence = ((uint8_t)newValue.at(9));
- }
- Resistance = newValue.at(14);
- Inclination = newValue.at(21);
- if (Resistance.value() < 1) {
- emit debug(QStringLiteral("invalid resistance value ") + QString::number(Resistance.value()) +
- QStringLiteral(" putting to default"));
- Resistance = 1;
- }
- if (Inclination.value() < 0 || Inclination.value() > 15) {
- emit debug(QStringLiteral("invalid inclination value ") + QString::number(Inclination.value()) +
- QStringLiteral(" putting to default"));
- Inclination.setValue(0);
- }
+ /*if ((uint8_t)newValue.at(1) != 0xbc && newValue.at(2) != 0x04) // intense run, these are the bytes for the
+ inclination and speed status return;*/
+
+ double speed =
+ GetSpeedFromPacket(newValue) *
+ settings.value(QZSettings::domyos_elliptical_speed_ratio, QZSettings::default_domyos_elliptical_speed_ratio)
+ .toDouble();
+ double kcal = GetKcalFromPacket(newValue);
+ double distance =
+ GetDistanceFromPacket(newValue) *
+ settings.value(QZSettings::domyos_elliptical_speed_ratio, QZSettings::default_domyos_elliptical_speed_ratio)
+ .toDouble();
+
+ if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
+ .toString()
+ .startsWith(QStringLiteral("Disabled"))) {
+ Cadence = ((uint8_t)newValue.at(9));
+ }
+ Resistance = newValue.at(14);
+ Inclination = newValue.at(21);
+ if (Resistance.value() < 1) {
+ emit debug(QStringLiteral("invalid resistance value ") + QString::number(Resistance.value()) +
+ QStringLiteral(" putting to default"));
+ Resistance = 1;
+ }
+ if (Inclination.value() < 0 || Inclination.value() > 15) {
+ emit debug(QStringLiteral("invalid inclination value ") + QString::number(Inclination.value()) +
+ QStringLiteral(" putting to default"));
+ Inclination.setValue(0);
+ }
+
+ #ifdef Q_OS_ANDROID
+ if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
+ Heart = (uint8_t)KeepAwakeHelper::heart();
+ else
+ #endif
+ {
+ if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
+ Heart = ((uint8_t)newValue.at(18));
+ }
+ }
+
+ CrankRevs++;
+ LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
+
+ emit debug(QStringLiteral("Current speed: ") + QString::number(speed));
+ emit debug(QStringLiteral("Current cadence: ") + QString::number(Cadence.value()));
+ emit debug(QStringLiteral("Current resistance: ") + QString::number(Resistance.value()));
+ emit debug(QStringLiteral("Current inclination: ") + QString::number(Inclination.value()));
+ emit debug(QStringLiteral("Current heart: ") + QString::number(Heart.value()));
+ emit debug(QStringLiteral("Current KCal: ") + QString::number(kcal));
+ emit debug(QStringLiteral("Current Distance: ") + QString::number(distance));
+ emit debug(QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs));
+ emit debug(QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime));
+ emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
+
+ if (m_control->error() != QLowEnergyController::NoError) {
+ qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString();
+ }
+
+ Speed = speed;
+ KCal = kcal;
+ Distance += ((Speed.value() / 3600000.0) *
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
+ lastRefreshCharacteristicChanged = now;
+ } else {
+ union flags {
+ struct {
+
+ uint16_t moreData : 1;
+ uint16_t avgStroke : 1;
+ uint16_t totDistance : 1;
+ uint16_t instantPace : 1;
+ uint16_t avgPace : 1;
+ uint16_t instantPower : 1;
+ uint16_t avgPower : 1;
+ uint16_t resistanceLvl : 1;
+ uint16_t expEnergy : 1;
+ uint16_t heartRate : 1;
+ uint16_t metabolic : 1;
+ uint16_t elapsedTime : 1;
+ uint16_t remainingTime : 1;
+ uint16_t spare : 3;
+ };
+
+ uint16_t word_flags;
+ };
+
+ flags Flags;
+ int index = 0;
+ double cadence_divider = 2.0;
+ if(newValue.length() < 2) {
+ qDebug() << "index out of range" << 0;
+ return;
+ }
+ Flags.word_flags = (newValue.at(1) << 8) | newValue.at(0);
+ index += 2;
+
+ if (!Flags.moreData) {
+
+ if(index >= newValue.length()) {
+ qDebug() << "index out of range" << index;
+ return;
+ }
+ Cadence = ((uint8_t)newValue.at(index)) / cadence_divider;
+
+ if(index + 2 >= newValue.length()) {
+ qDebug() << "index out of range" << index;
+ return;
+ }
+ StrokesCount =
+ (((uint16_t)((uint8_t)newValue.at(index + 2)) << 8) | (uint16_t)((uint8_t)newValue.at(index + 1)));
+
+ if (lastStrokesCount != StrokesCount.value()) {
+ lastStroke = now;
+ }
+ lastStrokesCount = StrokesCount.value();
+
+ index += 3;
+
+ /*
+ * the concept 2 sends the pace in 2 frames, so this condition will create a bogus speed
+ if (!Flags.instantPace) {
+ // eredited by echelon rower, probably we need to change this
+ Speed = (0.37497622 * ((double)Cadence.value())) / 2.0;
+ emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
+ }*/
+ emit debug(QStringLiteral("Strokes Count: ") + QString::number(StrokesCount.value()));
+ }
+
+ if (Flags.avgStroke) {
+
+ double avgStroke;
+ if(index >= newValue.length()) {
+ qDebug() << "index out of range" << index;
+ return;
+ }
+ avgStroke = ((double)(uint16_t)((uint8_t)newValue.at(index))) / cadence_divider;
+ index += 1;
+ emit debug(QStringLiteral("Current Average Stroke: ") + QString::number(avgStroke));
+ }
+
+ if (Flags.totDistance) {
+ if(index + 2 >= newValue.length()) {
+ qDebug() << "index out of range" << index;
+ return;
+ }
+ Distance = ((double)((((uint32_t)((uint8_t)newValue.at(index + 2)) << 16) |
+ (uint32_t)((uint8_t)newValue.at(index + 1)) << 8) |
+ (uint32_t)((uint8_t)newValue.at(index)))) /
+ 1000.0;
+ index += 3;
+ } else {
+ Distance += ((Speed.value() / 3600000.0) *
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
+ }
+
+ emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
+
+ if (Flags.instantPace) {
+
+ if(index + 1 >= newValue.length()) {
+ qDebug() << "index out of range" << index;
+ return;
+ }
+
+ double instantPace;
+ instantPace =
+ ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index))));
+ index += 2;
+ emit debug(QStringLiteral("Current Pace: ") + QString::number(instantPace));
+
+ Speed = (60.0 / instantPace) *
+ 30.0; // translating pace (min/500m) to km/h in order to match the pace function in the rower.cpp
+ emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
+ }
+
+ if (Flags.avgPace) {
+
+ if(index + 1>= newValue.length()) {
+ qDebug() << "index out of range" << index;
+ return;
+ }
+
+ double avgPace;
+ avgPace =
+ ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index))));
+ index += 2;
+ emit debug(QStringLiteral("Current Average Pace: ") + QString::number(avgPace));
+ }
+
+ if (Flags.instantPower) {
+ if(index + 1 >= newValue.length()) {
+ qDebug() << "index out of range" << index;
+ return;
+ }
+
+ double watt =
+ ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index))));
+ index += 2;
+ m_watt = watt;
+ emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
+ }
+
+ if (Flags.avgPower) {
+ if(index + 1 >= newValue.length()) {
+ qDebug() << "index out of range" << index;
+ return;
+ }
+
+ double avgPower;
+ avgPower =
+ ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index))));
+ index += 2;
+ emit debug(QStringLiteral("Current Average Watt: ") + QString::number(avgPower));
+ }
+
+ if (Flags.resistanceLvl) {
+ if(index + 1 >= newValue.length()) {
+ qDebug() << "index out of range" << index;
+ return;
+ }
+ Resistance =
+ ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index))));
+ emit resistanceRead(Resistance.value());
+ index += 2;
+ emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
+ }
+
+ if (Flags.expEnergy && index + 1 < newValue.length()) {
+ KCal = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index))));
+ index += 2;
+
+ // energy per hour
+ index += 2;
+
+ // energy per minute
+ index += 1;
+ } else {
+ if (watts())
+ KCal +=
+ ((((0.048 * ((double)watts()) + 1.19) *
+ settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
+ 200.0) /
+ (60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
+ // kg * 3.5) / 200 ) / 60
+ }
+
+ emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
+
+ #ifdef Q_OS_ANDROID
+ if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
+ Heart = (uint8_t)KeepAwakeHelper::heart();
+ else
+ #endif
+ {
+ if (Flags.heartRate && !disable_hr_frommachinery) {
+ if (index < newValue.length()) {
+ Heart = ((double)(((uint8_t)newValue.at(index))));
+ // index += 1; //NOTE: clang-analyzer-deadcode.DeadStores
+ emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
+ } else
+ emit debug(QStringLiteral("Error on parsing heart"));
+ }
+ }
+
+ if (Flags.metabolic) {
+
+ // todo
+ }
+
+ if (Flags.elapsedTime) {
+
+ // todo
+ }
+
+ if (Flags.remainingTime) {
+
+ // todo
+ }
+
+ if (Cadence.value() > 0) {
+
+ CrankRevs++;
+ LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
+ }
+
+ lastRefreshCharacteristicChanged = now;
-#ifdef Q_OS_ANDROID
- if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
- Heart = (uint8_t)KeepAwakeHelper::heart();
- else
-#endif
- {
if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
- Heart = ((uint8_t)newValue.at(18));
+ update_hr_from_external();
}
- }
- CrankRevs++;
- LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
-
- emit debug(QStringLiteral("Current speed: ") + QString::number(speed));
- emit debug(QStringLiteral("Current cadence: ") + QString::number(Cadence.value()));
- emit debug(QStringLiteral("Current resistance: ") + QString::number(Resistance.value()));
- emit debug(QStringLiteral("Current inclination: ") + QString::number(Inclination.value()));
- emit debug(QStringLiteral("Current heart: ") + QString::number(Heart.value()));
- emit debug(QStringLiteral("Current KCal: ") + QString::number(kcal));
- emit debug(QStringLiteral("Current Distance: ") + QString::number(distance));
- emit debug(QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs));
- emit debug(QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime));
- emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
-
- if (m_control->error() != QLowEnergyController::NoError) {
- qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString();
- }
+ #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 && firstVirtual) {
+
+ h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
+ h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
+ }
+ #endif
+ #endif
+
+ emit debug(QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs));
+ emit debug(QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime));
- Speed = speed;
- KCal = kcal;
- Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ if (m_control->error() != QLowEnergyController::NoError) {
+ qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString();
+ }
+ }
}
double domyosrower::GetSpeedFromPacket(const QByteArray &packet) {
@@ -457,40 +732,133 @@ void domyosrower::btinit_telink(bool startTape) {
}
void domyosrower::stateChanged(QLowEnergyService::ServiceState state) {
- QBluetoothUuid _gattWriteCharacteristicId(QStringLiteral("49535343-8841-43f4-a8d4-ecbe34729bb3"));
- QBluetoothUuid _gattNotifyCharacteristicId(QStringLiteral("49535343-1e4d-4bd9-ba61-23c647249616"));
-
- QMetaEnum metaEnum = QMetaEnum::fromType();
- emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
-
- if (state == QLowEnergyService::ServiceDiscovered) {
-
- // qDebug() << gattCommunicationChannelService->characteristics();
-
- gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
- gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(_gattNotifyCharacteristicId);
- Q_ASSERT(gattWriteCharacteristic.isValid());
- Q_ASSERT(gattNotifyCharacteristic.isValid());
-
- // establish hook into notifications
- connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this,
- &domyosrower::characteristicChanged);
- connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this,
- &domyosrower::characteristicWritten);
- connect(gattCommunicationChannelService,
- static_cast(&QLowEnergyService::error),
- this, &domyosrower::errorService);
- connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this,
- &domyosrower::descriptorWritten);
-
- QByteArray descriptor;
- descriptor.append((char)0x01);
- descriptor.append((char)0x00);
- gattCommunicationChannelService->writeDescriptor(
- gattNotifyCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
+ if(!ftmsRower) {
+ QBluetoothUuid _gattWriteCharacteristicId(QStringLiteral("49535343-8841-43f4-a8d4-ecbe34729bb3"));
+ QBluetoothUuid _gattNotifyCharacteristicId(QStringLiteral("49535343-1e4d-4bd9-ba61-23c647249616"));
+
+ QMetaEnum metaEnum = QMetaEnum::fromType();
+ emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
+
+ if (state == QLowEnergyService::ServiceDiscovered) {
+
+ // qDebug() << gattCommunicationChannelService->characteristics();
+
+ gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
+ gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(_gattNotifyCharacteristicId);
+ Q_ASSERT(gattWriteCharacteristic.isValid());
+ Q_ASSERT(gattNotifyCharacteristic.isValid());
+
+ // establish hook into notifications
+ connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this,
+ &domyosrower::characteristicChanged);
+ connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this,
+ &domyosrower::characteristicWritten);
+ connect(gattCommunicationChannelService,
+ static_cast(&QLowEnergyService::error),
+ this, &domyosrower::errorService);
+ connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this,
+ &domyosrower::descriptorWritten);
+
+ QByteArray descriptor;
+ descriptor.append((char)0x01);
+ descriptor.append((char)0x00);
+ gattCommunicationChannelService->writeDescriptor(
+ gattNotifyCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
+ }
+ } else {
+ QMetaEnum metaEnum = QMetaEnum::fromType();
+ emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
+
+ for (QLowEnergyService *s : qAsConst(gattCommunicationChannelServiceArray)) {
+ qDebug() << QStringLiteral("stateChanged") << s->serviceUuid() << s->state();
+ if (s->state() != QLowEnergyService::ServiceDiscovered && s->state() != QLowEnergyService::InvalidService) {
+ qDebug() << QStringLiteral("not all services discovered");
+
+ return;
+ }
+ }
+
+ qDebug() << QStringLiteral("all services discovered!");
+
+ for (QLowEnergyService *s : qAsConst(gattCommunicationChannelServiceArray)) {
+ if (s->state() == QLowEnergyService::ServiceDiscovered) {
+
+ // establish hook into notifications
+ connect(s, &QLowEnergyService::characteristicChanged, this, &domyosrower::characteristicChanged);
+ connect(s, &QLowEnergyService::characteristicWritten, this, &domyosrower::characteristicWritten);
+ connect(s, &QLowEnergyService::characteristicRead, this, &domyosrower::characteristicRead);
+ connect(
+ s, static_cast(&QLowEnergyService::error),
+ this, &domyosrower::errorService);
+ connect(s, &QLowEnergyService::descriptorWritten, this, &domyosrower::descriptorWritten);
+ connect(s, &QLowEnergyService::descriptorRead, this, &domyosrower::descriptorRead);
+
+ qDebug() << s->serviceUuid() << QStringLiteral("connected!");
+
+ auto characteristics_list = s->characteristics();
+ for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
+ qDebug() << "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!";
+ }
+
+ QBluetoothUuid _gattWriteCharControlPointId((quint16)0x2AD9);
+ if (c.properties() & QLowEnergyCharacteristic::Write && c.uuid() == _gattWriteCharControlPointId) {
+ qDebug() << QStringLiteral("FTMS service and Control Point found");
+
+ gattWriteCharControlPointId = c;
+ gattFTMSService = s;
+ }
+ }
+ }
+ }
}
}
+void domyosrower::descriptorRead(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
+ qDebug() << QStringLiteral("descriptorRead ") << descriptor.name() << descriptor.uuid() << newValue.toHex(' ');
+}
+
+void domyosrower::characteristicRead(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ qDebug() << QStringLiteral("characteristicRead ") << characteristic.uuid() << newValue.toHex(' ');
+}
+
void domyosrower::searchingStop() { searchStopped = true; }
void domyosrower::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
@@ -511,8 +879,19 @@ void domyosrower::serviceScanDone(void) {
QBluetoothUuid _gattCommunicationChannelServiceId(QStringLiteral("49535343-fe7d-4ae5-8fa9-9fafd205e455"));
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
- connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &domyosrower::stateChanged);
- gattCommunicationChannelService->discoverDetails();
+ if(gattCommunicationChannelService) {
+ connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &domyosrower::stateChanged);
+ gattCommunicationChannelService->discoverDetails();
+ } else {
+ ftmsRower = true;
+ auto services_list = m_control->services();
+ for (const QBluetoothUuid &s : qAsConst(services_list)) {
+ gattCommunicationChannelServiceArray.append(m_control->createServiceObject(s));
+ connect(gattCommunicationChannelServiceArray.constLast(), &QLowEnergyService::stateChanged, this,
+ &domyosrower::stateChanged);
+ gattCommunicationChannelServiceArray.constLast()->discoverDetails();
+ }
+ }
}
void domyosrower::errorService(QLowEnergyService::ServiceError err) {
diff --git a/src/domyosrower.h b/src/domyosrower.h
index 2d44d6608..e8f0e8619 100644
--- a/src/domyosrower.h
+++ b/src/domyosrower.h
@@ -28,6 +28,10 @@
#include "rower.h"
+#ifdef Q_OS_IOS
+#include "ios/lockscreen.h"
+#endif
+
class domyosrower : public rower {
Q_OBJECT
public:
@@ -58,11 +62,16 @@ class domyosrower : public rower {
QLowEnergyCharacteristic gattWriteCharacteristic;
QLowEnergyCharacteristic gattNotifyCharacteristic;
+ QList gattCommunicationChannelServiceArray;
+ QLowEnergyCharacteristic gattWriteCharControlPointId;
+ QLowEnergyService *gattFTMSService = nullptr;
+
bool initDone = false;
bool initRequest = false;
bool noWriteResistance = false;
bool noHeartService = false;
bool testResistance = false;
+ bool ftmsRower = false;
uint8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
bool searchStopped = false;
@@ -70,6 +79,13 @@ class domyosrower : public rower {
QByteArray lastPacket;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ QDateTime lastStroke = QDateTime::currentDateTime();
+ double lastStrokesCount = 0;
+
+#ifdef Q_OS_IOS
+ lockscreen *h = 0;
+#endif
+
enum _BIKE_TYPE {
CHANG_YOW,
TELINK,
@@ -86,6 +102,8 @@ class domyosrower : public rower {
private slots:
+ void characteristicRead(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
+ void descriptorRead(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
diff --git a/src/echelonrower.cpp b/src/echelonrower.cpp
index a1659f4da..088c4d966 100644
--- a/src/echelonrower.cpp
+++ b/src/echelonrower.cpp
@@ -25,6 +25,7 @@ echelonrower::echelonrower(bool noWriteResistance, bool noHeartService, uint8_t
#endif
m_watt.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
+ speedRaw.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
this->noHeartService = noHeartService;
@@ -236,10 +237,13 @@ void echelonrower::characteristicChanged(const QLowEnergyCharacteristic &charact
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())) / 60000;
}
// instant pace to km/h
- if (((uint8_t)lastPacket.at(14)) > 0 && Cadence.value() > 0)
- Speed = (60.0 / (double)((uint8_t)lastPacket.at(14))) * 30.0;
- else
+ if ((((uint8_t)lastPacket.at(14)) > 0 || ((uint8_t)lastPacket.at(13)) > 0) && Cadence.value() > 0) {
+ speedRaw = (60.0 / (double)(((uint16_t)lastPacket.at(13) << 8) | ((uint16_t)lastPacket.at(14)))) * 30.0;
+ Speed = speedRaw.average5s();
+ } else {
Speed = 0;
+ speedRaw = 0;
+ }
StrokesLength =
((Speed.value() / 60.0) * 1000.0) /
diff --git a/src/echelonrower.h b/src/echelonrower.h
index 96a5a0477..502bd3ca0 100644
--- a/src/echelonrower.h
+++ b/src/echelonrower.h
@@ -76,6 +76,7 @@ class echelonrower : public rower {
bool noWriteResistance = false;
bool noHeartService = false;
+ metric speedRaw;
#ifdef Q_OS_IOS
lockscreen *h = 0;
diff --git a/src/echelonstride.cpp b/src/echelonstride.cpp
index 1ec34dee5..868cacc52 100644
--- a/src/echelonstride.cpp
+++ b/src/echelonstride.cpp
@@ -237,6 +237,7 @@ void echelonstride::serviceDiscovered(const QBluetoothUuid &gatt) {
double echelonstride::minStepInclination() { return 1.0; }
void echelonstride::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
QSettings settings;
QString heartRateBeltName =
@@ -300,10 +301,10 @@ void echelonstride::characteristicChanged(const QLowEnergyCharacteristic &charac
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
200.0) /
(60000.0 / ((double)lastTimeCharacteristicChanged.msecsTo(
- QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
Distance += ((Speed.value() / 3600.0) /
- (1000.0 / (lastTimeCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
+ (1000.0 / (lastTimeCharacteristicChanged.msecsTo(now))));
}
if ((uint8_t)newValue.at(1) == 0xD1 && newValue.length() > 11)
@@ -334,7 +335,7 @@ void echelonstride::characteristicChanged(const QLowEnergyCharacteristic &charac
if (m_control->error() != QLowEnergyController::NoError)
qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString();
- lastTimeCharacteristicChanged = QDateTime::currentDateTime();
+ lastTimeCharacteristicChanged = now;
firstCharacteristicChanged = false;
}
diff --git a/src/eliteariafan.cpp b/src/eliteariafan.cpp
new file mode 100644
index 000000000..759b6181b
--- /dev/null
+++ b/src/eliteariafan.cpp
@@ -0,0 +1,334 @@
+#include "eliteariafan.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+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();
+ 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(&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();
+ emit debug(QStringLiteral("eliteariafan::errorService") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
+ m_control->errorString());
+}
+
+void eliteariafan::error(QLowEnergyController::Error err) {
+ QMetaEnum metaEnum = QMetaEnum::fromType();
+ 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(&QLowEnergyController::error),
+ this, &eliteariafan::error);
+ connect(m_control, &QLowEnergyController::stateChanged, this, &eliteariafan::controllerStateChanged);
+
+ connect(m_control,
+ static_cast(&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();
+ }
+}
diff --git a/src/eliteariafan.h b/src/eliteariafan.h
new file mode 100644
index 000000000..8bb29f735
--- /dev/null
+++ b/src/eliteariafan.h
@@ -0,0 +1,90 @@
+#ifndef ELITEARIAFAN_H
+#define ELITEARIAFAN_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifndef Q_OS_ANDROID
+#include
+#else
+#include
+#endif
+
+#ifdef Q_OS_IOS
+#include "ios/lockscreen.h"
+#endif
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "bluetoothdevice.h"
+
+class eliteariafan : public bluetoothdevice {
+ Q_OBJECT
+ public:
+ eliteariafan(bluetoothdevice *parentDevice);
+ bool connected() override;
+
+ private:
+ QList 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
diff --git a/src/elliptical.cpp b/src/elliptical.cpp
index 03b3a026b..3127c9d40 100644
--- a/src/elliptical.cpp
+++ b/src/elliptical.cpp
@@ -108,6 +108,7 @@ void elliptical::clearStats() {
Speed.clear(false);
KCal.clear(true);
Distance.clear(true);
+ Distance1s.clear(true);
Heart.clear(false);
m_jouls.clear(true);
elevationAcc = 0;
@@ -124,6 +125,7 @@ void elliptical::setPaused(bool p) {
Speed.setPaused(p);
KCal.setPaused(p);
Distance.setPaused(p);
+ Distance1s.setPaused(p);
Heart.setPaused(p);
m_jouls.setPaused(p);
m_watt.setPaused(p);
@@ -138,6 +140,7 @@ void elliptical::setLap() {
Speed.setLap(false);
KCal.setLap(true);
Distance.setLap(true);
+ Distance1s.setLap(true);
Heart.setLap(false);
m_jouls.setLap(true);
m_watt.setLap(false);
diff --git a/src/fakebike.cpp b/src/fakebike.cpp
index 6bc0f6e09..6c3fcb1ac 100644
--- a/src/fakebike.cpp
+++ b/src/fakebike.cpp
@@ -49,6 +49,7 @@ void fakebike::update() {
if (requestPower != -1) {
// bepo70: don't know if this conversion is really needed, i would do it anyway.
m_watt = (double)requestPower;
+ Cadence = requestPower;
emit debug(QStringLiteral("writing power ") + QString::number(requestPower));
requestPower = -1;
// bepo70: Disregard the current inclination for calculating speed. When the video
@@ -60,6 +61,11 @@ void fakebike::update() {
m_watt.value(), 0, Speed.value(), fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0),
speedLimit());
}
+
+ if (Cadence.value() > 0) {
+ CrankRevs++;
+ LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
+ }
if (requestInclination != -100) {
Inclination = requestInclination;
diff --git a/src/faketreadmill.cpp b/src/faketreadmill.cpp
index 5396164f1..9cacef88a 100644
--- a/src/faketreadmill.cpp
+++ b/src/faketreadmill.cpp
@@ -32,6 +32,7 @@ void faketreadmill::update() {
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
+ QDateTime now = QDateTime::currentDateTime();
update_metrics(true, watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()));
@@ -50,8 +51,8 @@ void faketreadmill::update() {
cadenceFromAppleWatch();
Distance += ((Speed.value() / (double)3600.0) /
- ((double)1000.0 / (double)(lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ ((double)1000.0 / (double)(lastRefreshCharacteristicChanged.msecsTo(now))));
+ lastRefreshCharacteristicChanged = now;
// ******************************************* virtual treadmill init *************************************
if (!firstStateChanged && !this->hasVirtualDevice()) {
diff --git a/src/fitplusbike.cpp b/src/fitplusbike.cpp
index 77cacf2b4..2b306c452 100644
--- a/src/fitplusbike.cpp
+++ b/src/fitplusbike.cpp
@@ -376,6 +376,7 @@ void fitplusbike::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -428,7 +429,7 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
index += 2;
qDebug() << QStringLiteral("Current Speed: ") + QString::number(Speed.value());
@@ -525,7 +526,7 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
200.0) /
(60000.0 /
((double)lastRefreshCharacteristicChanged.msecsTo(
- QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
}
@@ -538,7 +539,7 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
#endif
{
if (Flags.heartRate && !disable_hr_frommachinery && newValue.length() > index) {
- Heart = ((double)((newValue.at(index))));
+ Heart = ((double)(((uint8_t)newValue.at(index))));
// index += 1; // NOTE: clang-analyzer-deadcode.DeadStores
qDebug() << (QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
} else {
@@ -597,7 +598,7 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
else*/
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
} else if (newValue.length() == 13) {
@@ -625,7 +626,7 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
else
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
if (watts())
@@ -634,17 +635,17 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
diff --git a/src/fitshowtreadmill.cpp b/src/fitshowtreadmill.cpp
index 6b5645be9..a6dc49933 100644
--- a/src/fitshowtreadmill.cpp
+++ b/src/fitshowtreadmill.cpp
@@ -158,7 +158,7 @@ void fitshowtreadmill::update() {
if (initRequest) {
initRequest = false;
- btinit((lastSpeed > 0 ? true : false));
+ btinit(true);
} else if (bluetoothDevice.isValid() && m_control->state() == QLowEnergyController::DiscoveredState &&
gattCommunicationChannelService && gattWriteCharacteristic.isValid() &&
gattNotifyCharacteristic.isValid() && initDone) {
@@ -220,7 +220,22 @@ void fitshowtreadmill::update() {
if (lastSpeed == 0.0) {
lastSpeed = 0.5;
}
- btinit(true);
+
+ uint8_t startTape1[] = {
+ FITSHOW_SYS_CONTROL,
+ FITSHOW_CONTROL_READY_OR_START,
+ (FITSHOW_TREADMILL_SPORT_ID >> 0) & 0xFF,
+ (FITSHOW_TREADMILL_SPORT_ID >> 8) & 0xFF,
+ (FITSHOW_TREADMILL_SPORT_ID >> 16) & 0xFF,
+ (FITSHOW_TREADMILL_SPORT_ID >> 24) & 0xFF,
+ FITSHOW_SYS_MODE_NORMAL,
+ 0x00, // number of blocks (u8)
+ 0x00,
+ 0x00 // mode-dependent value (u16le)
+ }; // to verify
+ scheduleWrite(startTape1, sizeof(startTape1), QStringLiteral("init_start"));
+ forceSpeedOrIncline(lastSpeed, lastInclination);
+
lastStart = QDateTime::currentMSecsSinceEpoch();
requestStart = -1;
emit tapeStarted();
diff --git a/src/flywheelbike.cpp b/src/flywheelbike.cpp
index e461ace45..56959bbbf 100644
--- a/src/flywheelbike.cpp
+++ b/src/flywheelbike.cpp
@@ -240,6 +240,7 @@ void flywheelbike::updateStats() {
}
void flywheelbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
static uint8_t zero_fix_filter = 0;
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
@@ -293,7 +294,7 @@ void flywheelbike::characteristicChanged(const QLowEnergyCharacteristic &charact
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
// https://www.facebook.com/groups/149984563348738/permalink/174268944253633/?comment_id=174366620910532&reply_comment_id=174666314213896
diff --git a/src/ftmsbike.cpp b/src/ftmsbike.cpp
index 923066d21..4657b1f41 100644
--- a/src/ftmsbike.cpp
+++ b/src/ftmsbike.cpp
@@ -197,6 +197,7 @@ void ftmsbike::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -246,7 +247,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
index += 2;
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
@@ -295,7 +296,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
}
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
@@ -367,7 +368,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
200.0) /
(60000.0 /
((double)lastRefreshCharacteristicChanged.msecsTo(
- QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
}
@@ -438,7 +439,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
index += 2;
@@ -461,7 +462,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
index += 3;
} else {
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
}
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
@@ -557,7 +558,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
200.0) /
(60000.0 /
((double)lastRefreshCharacteristicChanged.msecsTo(
- QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
}
@@ -599,7 +600,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
if (heartRateBeltName.startsWith(QStringLiteral("Disabled")) &&
(!heart || Heart.value() == 0 || disable_hr_frommachinery)) {
diff --git a/src/ftmsrower.cpp b/src/ftmsrower.cpp
index c1a6d45f8..7cd114858 100644
--- a/src/ftmsrower.cpp
+++ b/src/ftmsrower.cpp
@@ -136,6 +136,7 @@ void ftmsrower::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
@@ -185,7 +186,7 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
if (!Flags.moreData) {
- if ((WATER_ROWER || DFIT_L_R) && lastStroke.secsTo(QDateTime::currentDateTime()) > 3) {
+ if ((WATER_ROWER || DFIT_L_R) && lastStroke.secsTo(now) > 3) {
qDebug() << "Resetting cadence!";
Cadence = 0;
m_watt = 0;
@@ -198,7 +199,7 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
(((uint16_t)((uint8_t)newValue.at(index + 2)) << 8) | (uint16_t)((uint8_t)newValue.at(index + 1)));
if (lastStrokesCount != StrokesCount.value()) {
- lastStroke = QDateTime::currentDateTime();
+ lastStroke = now;
}
lastStrokesCount = StrokesCount.value();
@@ -230,7 +231,7 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
index += 3;
} else {
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
}
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
@@ -303,7 +304,7 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
}
@@ -346,7 +347,7 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
update_hr_from_external();
diff --git a/src/gpx.cpp b/src/gpx.cpp
index cd33f7c66..691533c0e 100644
--- a/src/gpx.cpp
+++ b/src/gpx.cpp
@@ -7,12 +7,16 @@
gpx::gpx(QObject *parent) : QObject(parent) {}
-QList gpx::open(const QString &gpx) {
+QList gpx::open(const QString &gpx, bluetoothdevice::BLUETOOTH_TYPE device_type) {
QSettings settings;
const double meter_limit_for_auto_loop = 300;
bool treadmill_force_speed =
settings.value(QZSettings::treadmill_force_speed, QZSettings::default_treadmill_force_speed).toBool();
bool gpx_loop = settings.value(QZSettings::gpx_loop, QZSettings::default_gpx_loop).toBool();
+
+ if(device_type == bluetoothdevice::BIKE)
+ treadmill_force_speed = false;
+
QFile input(gpx);
input.open(QIODevice::ReadOnly);
QDomDocument doc;
diff --git a/src/gpx.h b/src/gpx.h
index 9ed56f7bc..c0c214c06 100644
--- a/src/gpx.h
+++ b/src/gpx.h
@@ -29,7 +29,7 @@ class gpx : public QObject {
Q_OBJECT
public:
explicit gpx(QObject *parent = nullptr);
- QList open(const QString &gpx);
+ QList open(const QString &gpx, bluetoothdevice::BLUETOOTH_TYPE device_type);
static void save(const QString &filename, QList session, bluetoothdevice::BLUETOOTH_TYPE type);
QString getVideoURL() {return videoUrl;}
diff --git a/src/homeform.cpp b/src/homeform.cpp
index fd96b20bb..ccd21a1bf 100644
--- a/src/homeform.cpp
+++ b/src/homeform.cpp
@@ -5,6 +5,7 @@
#include "localipaddress.h"
#ifdef Q_OS_ANDROID
#include "keepawakehelper.h"
+#include
#endif
#include "material.h"
#include "qfit.h"
@@ -52,7 +53,7 @@ using namespace std::chrono_literals;
#if defined(WIN32)
#pragma message("DEFINE STRAVA_CLIENT_ID!!!")
#else
-#warning "DEFINE STRAVA_CLIENT_ID!!!"
+#pragma message "DEFINE STRAVA_CLIENT_ID!!!"
#endif
#endif
#define _STR(x) #x
@@ -474,6 +475,8 @@ 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(trainprogram_open_other_folder(QUrl)), this, SLOT(trainprogram_open_other_folder(QUrl)));
+ QObject::connect(stack, SIGNAL(gpx_open_other_folder(QUrl)), this, SLOT(gpx_open_other_folder(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)));
@@ -500,6 +503,8 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) {
QObject::connect(stack, SIGNAL(floatingOpen()), this, SLOT(floatingOpen()));
QObject::connect(stack, SIGNAL(openFloatingWindowBrowser()), this, SLOT(openFloatingWindowBrowser()));
+ qDebug() << "homeform constructor events linked";
+
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
QObject::connect(engine, &QQmlApplicationEngine::quit, &QGuiApplication::quit);
#endif
@@ -549,7 +554,8 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) {
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(), ""));
+ if(QFile::copy(itAndroid.filePath(), getWritableAppDir() + itAndroid.filePath().replace(itAndroid.path(), "")))
+ QFile::remove(itAndroid.filePath());
}
}
}
@@ -649,6 +655,8 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) {
});
});
#endif
+
+ bluetoothManager->homeformLoaded = true;
}
void homeform::setActivityDescription(QString desc) { activityDescription = desc; }
@@ -657,8 +665,12 @@ void homeform::chartSaved(QString fileName) {
if (!stopped)
return;
chartImagesFilenames.append(fileName);
- if (chartImagesFilenames.length() >= 8) {
+ if (chartImagesFilenames.length() >= 9) {
sendMail();
+ qDebug() << "removing chart images";
+ for (const QString &f : qAsConst(chartImagesFilenames)) {
+ QFile::remove(f);
+ }
chartImagesFilenames.clear();
}
}
@@ -3344,6 +3356,10 @@ void homeform::Stop() {
emit startIconChanged(startIcon());
emit startTextChanged(startText());
emit startColorChanged(startColor());
+
+ // clearing the label on top because if it was running a training program, with stop the program will be terminated
+ m_info = workoutName();
+ emit infoChanged(m_info);
}
if (trainProgram) {
@@ -3769,7 +3785,6 @@ void homeform::update() {
speed->setValueFontColor(QStringLiteral("red"));
this->pace->setValueFontColor(QStringLiteral("red"));
}
- bluetoothManager->device()->currentSpeed().setColor(speed->valueFontColor());
} else {
if (bluetoothManager->device()->currentSpeed().value() <= trainProgram->currentRow().upper_speed &&
bluetoothManager->device()->currentSpeed().value() >= trainProgram->currentRow().lower_speed) {
@@ -3785,7 +3800,6 @@ void homeform::update() {
this->target_zone->setValueFontColor(QStringLiteral("red"));
this->pace->setValueFontColor(QStringLiteral("red"));
}
- bluetoothManager->device()->currentSpeed().setColor(speed->valueFontColor());
}
this->target_pace->setValue(
@@ -4037,7 +4051,6 @@ void homeform::update() {
speed->setValueFontColor(QStringLiteral("red"));
this->pace->setValueFontColor(QStringLiteral("red"));
}
- bluetoothManager->device()->currentSpeed().setColor(speed->valueFontColor());
}
} else if (bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL) {
@@ -4139,10 +4152,6 @@ void homeform::update() {
} else {
this->peloton_resistance->setValueFontColor(QStringLiteral("orange"));
}
- if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
- ((bike *)bluetoothManager->device())
- ->pelotonResistance()
- .setColor(this->peloton_resistance->valueFontColor());
}
int16_t lower_cadence = trainProgram->currentRow().lower_cadence;
@@ -4165,7 +4174,6 @@ void homeform::update() {
} else {
this->cadence->setValueFontColor(QStringLiteral("orange"));
}
- bluetoothManager->device()->currentCadence().setColor(this->cadence->valueFontColor());
}
}
@@ -4261,7 +4269,6 @@ void homeform::update() {
ftp->setValueFontColor(QStringLiteral("red"));
watt->setValueFontColor(QStringLiteral("red"));
}
- bluetoothManager->device()->wattsMetric().setColor(watt->valueFontColor());
bluetoothManager->device()->setPowerZone(ftpZone);
ftp->setValue(QStringLiteral("Z") + QString::number(ftpZone, 'f', 1));
ftp->setSecondLine(ftpMinW + QStringLiteral("-") + ftpMaxW + QStringLiteral("W ") +
@@ -4459,7 +4466,6 @@ void homeform::update() {
pidHR->setValueFontColor(QStringLiteral("white"));
break;
}
- bluetoothManager->device()->currentHeart().setColor(heart->valueFontColor());
bluetoothManager->device()->setHeartZone(currentHRZone);
Z = QStringLiteral("Z") + QString::number(currentHRZone, 'f', 1);
heart->setSecondLine(Z + QStringLiteral(" AVG: ") +
@@ -4909,6 +4915,16 @@ void homeform::update() {
}
if (!stopped && !paused) {
+ if(settings.value(QZSettings::autolap_distance, QZSettings::default_autolap_distance).toDouble() != 0) {
+ if (bluetoothManager->device()->currentDistance().lapValue() >=
+ settings.value(QZSettings::autolap_distance, QZSettings::default_autolap_distance).toDouble()) {
+ qDebug() << QStringLiteral("Autolap based on distance");
+ Lap();
+ setToastRequested("AutoLap " + QString::number(settings.value(QZSettings::autolap_distance, QZSettings::default_autolap_distance).toDouble(), 'f', 1));
+ emit toastRequestedChanged(toastRequested());
+ }
+ }
+
if (settings.value(QZSettings::tts_enabled, QZSettings::default_tts_enabled).toBool()) {
static double tts_speed_played = 0;
bool description =
@@ -5147,8 +5163,13 @@ void homeform::update() {
}
}
+ if(bluetoothManager->device()->currentSpeed().value() > 0 && !isinf(bluetoothManager->device()->currentSpeed().value()))
+ bluetoothManager->device()->addCurrentDistance1s((bluetoothManager->device()->currentSpeed().value() / 3600.0));
+
+ qDebug() << "Current Distance 1s:" << bluetoothManager->device()->currentDistance1s().value() << bluetoothManager->device()->currentSpeed().value();
+
SessionLine s(
- bluetoothManager->device()->currentSpeed().value(), inclination, bluetoothManager->device()->odometer(),
+ bluetoothManager->device()->currentSpeed().value(), inclination, bluetoothManager->device()->currentDistance1s().value(),
watts, resistance, peloton_resistance, (uint8_t)bluetoothManager->device()->currentHeart().value(),
pace, cadence, bluetoothManager->device()->calories().value(),
bluetoothManager->device()->elevationGain().value(),
@@ -5207,30 +5228,78 @@ 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);
+QString homeform::getFileNameFromContentUri(const QString &uriString) {
+ qDebug() << "getFileNameFromContentUri" << uriString;
+ if(!uriString.startsWith("content")) {
+ return uriString;
+ }
+#ifdef Q_OS_ANDROID
+
+ QAndroidJniObject jUriString = QAndroidJniObject::fromString(uriString);
+ QAndroidJniObject jUri = QAndroidJniObject::callStaticObjectMethod("android/net/Uri", "parse", "(Ljava/lang/String;)Landroid/net/Uri;", jUriString.object());
+ QAndroidJniObject result = QAndroidJniObject::callStaticObjectMethod(
+ "org/cagnulen/qdomyoszwift/ContentHelper",
+ "getFileName",
+ "(Landroid/content/Context;Landroid/net/Uri;)Ljava/lang/String;",
+ QtAndroid::androidContext().object(),
+ jUri.object());
+ return result.toString();
+#else
+ return uriString;
+#endif
+}
+
+QString homeform::copyAndroidContentsURI(QUrl file, QString subfolder) {
+#ifdef Q_OS_ANDROID
+ QString fileNameLocal = "";
+ qDebug() << "Android Version:" << QOperatingSystemVersion::current();
+ if (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::Android, 13))
+ fileNameLocal = getFileNameFromContentUri(file.toString());
+ if(fileNameLocal.contains(getWritableAppDir() + subfolder + "/")) {
+ qDebug() << "no need to copy file, the file is already in QZ subfolder" << file << subfolder;
+ return file.toString();
+ }
+
+ QString filename = "";
+ QFile fileFile(QQmlFile::urlToLocalFileOrQrc(file));
+ // android <14 fallback
+ if(fileNameLocal.length() == 0) {
+ qDebug() << "android <14 fallback" << fileNameLocal << filename << file.fileName();
+ filename = file.fileName();
+ } else {
+ QFileInfo f(fileNameLocal);
+ filename = f.fileName();
}
- bool copy = file->copy(getWritableAppDir() + subfolder + "/" + filename);
- qDebug() << "copy" << getWritableAppDir() + subfolder + "/" + filename << copy;
+ QString dest = getWritableAppDir() + subfolder + "/" + filename;
+ qDebug() << file.fileName() << fileNameLocal << filename;
+ QFile::remove(dest);
+ bool copy = fileFile.copy(dest);
+ qDebug() << "copy" << dest << copy << fileFile.exists() << fileFile.isReadable();
+ return dest;
#endif
+ return file.toString();
}
void homeform::profile_open_clicked(const QUrl &fileName) {
QFile file(QQmlFile::urlToLocalFileOrQrc(fileName));
- copyAndroidContentsURI(&file, "profiles");
+ copyAndroidContentsURI(fileName, "profiles");
+}
+
+void homeform::trainprogram_open_other_folder(const QUrl &fileName) {
+ QFile file(QQmlFile::urlToLocalFileOrQrc(fileName));
+ copyAndroidContentsURI(fileName, "training");
+}
+
+void homeform::gpx_open_other_folder(const QUrl &fileName) {
+ QFile file(QQmlFile::urlToLocalFileOrQrc(fileName));
+ copyAndroidContentsURI(fileName, "gpx");
}
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()) {
{
if (previewTrainProgram) {
@@ -5241,7 +5310,7 @@ void homeform::trainprogram_open_clicked(const QUrl &fileName) {
delete trainProgram;
}
- trainProgram = trainprogram::load(file.fileName(), bluetoothManager);
+ trainProgram = trainprogram::load(file.fileName(), bluetoothManager, file.fileName().right(3).toUpper());
QString movieName = file.fileName().left(file.fileName().length() - 3) + "mp4";
if (QFile::exists(movieName)) {
@@ -5258,7 +5327,7 @@ void homeform::trainprogram_open_clicked(const QUrl &fileName) {
trainingProgram()->setVideoAvailable(false);
}
- stravaWorkoutName = QFileInfo(fileName.fileName()).baseName();
+ stravaWorkoutName = QFileInfo(file.fileName()).baseName();
stravaPelotonInstructorName = QStringLiteral("");
emit workoutNameChanged(workoutName());
emit instructorNameChanged(instructorName());
@@ -5278,14 +5347,15 @@ void homeform::trainprogram_preview(const QUrl &fileName) {
qDebug() << QStringLiteral("trainprogram_preview") << fileName;
QFile file(QQmlFile::urlToLocalFileOrQrc(fileName));
- qDebug() << file.fileName();
- if (!file.fileName().isEmpty()) {
+ QString fileNameLocal = getFileNameFromContentUri(file.fileName());
+ qDebug() << fileNameLocal;
+ if (!fileNameLocal.isEmpty()) {
{
if (previewTrainProgram) {
delete previewTrainProgram;
previewTrainProgram = 0;
}
- previewTrainProgram = trainprogram::load(file.fileName(), bluetoothManager);
+ previewTrainProgram = trainprogram::load(file.fileName(), bluetoothManager, fileNameLocal.right(3).toUpper());
emit previewWorkoutPointsChanged(preview_workout_points());
emit previewWorkoutDescriptionChanged(previewWorkoutDescription());
emit previewWorkoutTagsChanged(previewWorkoutTags());
@@ -5365,9 +5435,7 @@ 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()) {
{
@@ -5379,7 +5447,7 @@ void homeform::gpx_open_clicked(const QUrl &fileName) {
// KML to GPX https://www.gpsvisualizer.com/elevation
gpx g;
QList list;
- auto g_list = g.open(file.fileName());
+ auto g_list = g.open(file.fileName(), bluetoothManager->device() ? bluetoothManager->device()->deviceType() : bluetoothdevice::BIKE);
if (bluetoothManager->device())
bluetoothManager->device()->setGPXFile(file.fileName());
gpx_altitude_point_for_treadmill last;
@@ -5449,7 +5517,7 @@ void homeform::gpxpreview_open_clicked(const QUrl &fileName) {
if (!file.fileName().isEmpty()) {
gpx g;
- auto g_list = g.open(file.fileName());
+ auto g_list = g.open(file.fileName(), bluetoothManager->device() ? bluetoothManager->device()->deviceType() : bluetoothdevice::BIKE);
gpx_preview.clearPath();
for (const auto &p : g_list) {
gpx_preview.addCoordinate(QGeoCoordinate(p.latitude, p.longitude, p.elevation));
@@ -5874,7 +5942,7 @@ QOAuth2AuthorizationCodeFlow *homeform::strava_connect() {
#elif defined(WIN32)
#pragma message("DEFINE STRAVA_SECRET_KEY!!!")
#else
-#warning "DEFINE STRAVA_SECRET_KEY!!!"
+#pragma message "DEFINE STRAVA_SECRET_KEY!!!"
#endif
strava->setModifyParametersFunction(
buildModifyParametersFunction(QUrl(QLatin1String("")), QUrl(QLatin1String(""))));
@@ -5995,7 +6063,7 @@ void homeform::sendMail() {
SmtpClient smtp(STRINGIFY(SMTP_SERVER), 587, SmtpClient::TlsConnection);
connect(&smtp, SIGNAL(smtpError(SmtpClient::SmtpError)), this, SLOT(smtpError(SmtpClient::SmtpError)));
#else
-#warning "stmp server is unset!"
+#pragma message "stmp server is unset!"
SmtpClient smtp(QLatin1String(""), 25, SmtpClient::TlsConnection);
return;
#endif
@@ -6007,7 +6075,7 @@ void homeform::sendMail() {
#define STRINGIFY(x) _STR(x)
smtp.setUser(STRINGIFY(SMTP_USERNAME));
#else
-#warning "smtp username is unset!"
+#pragma message "smtp username is unset!"
return;
#endif
#ifdef SMTP_PASSWORD
@@ -6015,7 +6083,7 @@ void homeform::sendMail() {
#define STRINGIFY(x) _STR(x)
smtp.setPassword(STRINGIFY(SMTP_PASSWORD));
#else
-#warning "smtp password is unset!"
+#pragma message "smtp password is unset!"
return;
#endif
@@ -6367,7 +6435,7 @@ void homeform::saveSettings(const QUrl &filename) {
void homeform::loadSettings(const QUrl &filename) {
QFile file(QQmlFile::urlToLocalFileOrQrc(filename));
- copyAndroidContentsURI(&file, "settings");
+ copyAndroidContentsURI(filename, "settings");
qDebug() << "homeform::loadSettings" << file.fileName();
diff --git a/src/homeform.h b/src/homeform.h
index e44b07f7f..72587b2d5 100644
--- a/src/homeform.h
+++ b/src/homeform.h
@@ -199,6 +199,7 @@ class homeform : public QObject {
QObject *stack = rootObject;
screenCapture s(reinterpret_cast(stack));
s.capture(filenameScreenshot);
+ chartImagesFilenames.append(filenameScreenshot);
}
Q_INVOKABLE void save_screenshot_chart(QQuickItem *item, QString filename) {
@@ -554,6 +555,68 @@ class homeform : public QObject {
QString getStravaAuthUrl() { return stravaAuthUrl; }
bool stravaWebVisible() { return stravaAuthWebVisible; }
trainprogram *trainingProgram() { return trainProgram; }
+
+ DataObject *speed;
+ DataObject *inclination;
+ DataObject *cadence;
+ DataObject *elevation;
+ DataObject *calories;
+ DataObject *odometer;
+ DataObject *pace;
+ DataObject *datetime;
+ DataObject *resistance;
+ DataObject *watt;
+ DataObject *avgWatt;
+ DataObject *avgWattLap;
+ DataObject *heart;
+ DataObject *fan;
+ DataObject *jouls;
+ DataObject *peloton_offset;
+ DataObject *peloton_remaining;
+ DataObject *elapsed;
+ DataObject *moving_time;
+ DataObject *peloton_resistance;
+ DataObject *target_resistance;
+ DataObject *target_peloton_resistance;
+ DataObject *target_cadence;
+ DataObject *target_power;
+ DataObject *target_zone;
+ DataObject *target_speed;
+ DataObject *target_pace;
+ DataObject *target_incline;
+ DataObject *ftp;
+ DataObject *lapElapsed;
+ DataObject *weightLoss;
+ DataObject *strokesLength;
+ DataObject *strokesCount;
+ DataObject *wattKg;
+ DataObject *gears;
+ DataObject *remaningTimeTrainingProgramCurrentRow;
+ DataObject *nextRows;
+ DataObject *mets;
+ DataObject *targetMets;
+ DataObject *steeringAngle;
+ DataObject *pidHR;
+ DataObject *extIncline;
+ DataObject *instantaneousStrideLengthCM;
+ DataObject *groundContactMS;
+ DataObject *verticalOscillationMM;
+ DataObject *preset_resistance_1;
+ DataObject *preset_resistance_2;
+ DataObject *preset_resistance_3;
+ DataObject *preset_resistance_4;
+ DataObject *preset_resistance_5;
+ DataObject *preset_speed_1;
+ DataObject *preset_speed_2;
+ DataObject *preset_speed_3;
+ DataObject *preset_speed_4;
+ DataObject *preset_speed_5;
+ DataObject *preset_inclination_1;
+ DataObject *preset_inclination_2;
+ DataObject *preset_inclination_3;
+ DataObject *preset_inclination_4;
+ DataObject *preset_inclination_5;
+ DataObject *pace_last500m;
private:
static homeform *m_singleton;
@@ -617,68 +680,6 @@ class homeform : public QObject {
bool m_startRequested = false;
bool m_overridePower = false;
- DataObject *speed;
- DataObject *inclination;
- DataObject *cadence;
- DataObject *elevation;
- DataObject *calories;
- DataObject *odometer;
- DataObject *pace;
- DataObject *datetime;
- DataObject *resistance;
- DataObject *watt;
- DataObject *avgWatt;
- DataObject *avgWattLap;
- DataObject *heart;
- DataObject *fan;
- DataObject *jouls;
- DataObject *peloton_offset;
- DataObject *peloton_remaining;
- DataObject *elapsed;
- DataObject *moving_time;
- DataObject *peloton_resistance;
- DataObject *target_resistance;
- DataObject *target_peloton_resistance;
- DataObject *target_cadence;
- DataObject *target_power;
- DataObject *target_zone;
- DataObject *target_speed;
- DataObject *target_pace;
- DataObject *target_incline;
- DataObject *ftp;
- DataObject *lapElapsed;
- DataObject *weightLoss;
- DataObject *strokesLength;
- DataObject *strokesCount;
- DataObject *wattKg;
- DataObject *gears;
- DataObject *remaningTimeTrainingProgramCurrentRow;
- DataObject *nextRows;
- DataObject *mets;
- DataObject *targetMets;
- DataObject *steeringAngle;
- DataObject *pidHR;
- DataObject *extIncline;
- DataObject *instantaneousStrideLengthCM;
- DataObject *groundContactMS;
- DataObject *verticalOscillationMM;
- DataObject *preset_resistance_1;
- DataObject *preset_resistance_2;
- DataObject *preset_resistance_3;
- DataObject *preset_resistance_4;
- DataObject *preset_resistance_5;
- DataObject *preset_speed_1;
- DataObject *preset_speed_2;
- DataObject *preset_speed_3;
- DataObject *preset_speed_4;
- DataObject *preset_speed_5;
- DataObject *preset_inclination_1;
- DataObject *preset_inclination_2;
- DataObject *preset_inclination_3;
- DataObject *preset_inclination_4;
- DataObject *preset_inclination_5;
- DataObject *pace_last500m;
-
QTimer *timer;
QTimer *backupTimer;
@@ -694,7 +695,8 @@ class homeform : public QObject {
static quint64 cryptoKeySettingsProfiles();
- static void copyAndroidContentsURI(QFile* file, QString subfolder);
+ static QString copyAndroidContentsURI(QUrl file, QString subfolder);
+ static QString getFileNameFromContentUri(const QString &uriString);
int16_t fanOverride = 0;
@@ -760,6 +762,8 @@ class homeform : public QObject {
void deviceConnected(QBluetoothDeviceInfo b);
void ftmsAccessoryConnected(smartspin2k *d);
void trainprogram_open_clicked(const QUrl &fileName);
+ void trainprogram_open_other_folder(const QUrl &fileName);
+ void gpx_open_other_folder(const QUrl &fileName);
void profile_open_clicked(const QUrl &fileName);
void trainprogram_preview(const QUrl &fileName);
void gpxpreview_open_clicked(const QUrl &fileName);
diff --git a/src/horizongr7bike.cpp b/src/horizongr7bike.cpp
index 00806a947..b55afd86b 100644
--- a/src/horizongr7bike.cpp
+++ b/src/horizongr7bike.cpp
@@ -144,6 +144,7 @@ void horizongr7bike::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void horizongr7bike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -195,7 +196,7 @@ void horizongr7bike::characteristicChanged(const QLowEnergyCharacteristic &chara
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
@@ -206,12 +207,12 @@ void horizongr7bike::characteristicChanged(const QLowEnergyCharacteristic &chara
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
return;
@@ -231,7 +232,7 @@ void horizongr7bike::characteristicChanged(const QLowEnergyCharacteristic &chara
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
index += 2;
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
@@ -286,13 +287,13 @@ void horizongr7bike::characteristicChanged(const QLowEnergyCharacteristic &chara
1000.0;*/
if (firstPacket)
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
index += 3;
} else {
if (firstPacket)
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
}
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
@@ -341,7 +342,7 @@ void horizongr7bike::characteristicChanged(const QLowEnergyCharacteristic &chara
200.0) /
(60000.0 /
((double)lastRefreshCharacteristicChanged.msecsTo(
- QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
}
@@ -354,7 +355,7 @@ void horizongr7bike::characteristicChanged(const QLowEnergyCharacteristic &chara
#endif
{
if (Flags.heartRate && !disable_hr_frommachinery && newValue.length() > index) {
- Heart = ((double)((newValue.at(index))));
+ Heart = ((double)(((uint8_t)newValue.at(index))));
// index += 1; // NOTE: clang-analyzer-deadcode.DeadStores
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
} else {
@@ -380,7 +381,7 @@ void horizongr7bike::characteristicChanged(const QLowEnergyCharacteristic &chara
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
if (heartRateBeltName.startsWith(QStringLiteral("Disabled")) &&
(!Flags.heartRate || Heart.value() == 0 || disable_hr_frommachinery)) {
diff --git a/src/horizontreadmill.cpp b/src/horizontreadmill.cpp
index c1b03f68d..e309e9fec 100644
--- a/src/horizontreadmill.cpp
+++ b/src/horizontreadmill.cpp
@@ -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);
@@ -840,10 +836,31 @@ void horizontreadmill::update() {
.startsWith(QStringLiteral("Disabled")));
update_metrics(!power_sensor, watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()));
+ if (firstDistanceCalculated) {
+ QDateTime now = QDateTime::currentDateTime();
+ KCal +=
+ ((((0.048 * ((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) +
+ 1.19) *
+ settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
+ 200.0) /
+ (60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
+ // kg * 3.5) / 200 ) / 60
+ Distance += ((Speed.value() / 3600000.0) *
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
+
+ lastRefreshCharacteristicChanged = now;
+ }
+
// updating the treadmill console every second
- if (sec1Update++ == (500 / refresh->interval())) {
+ if (sec1Update++ == (1000 / refresh->interval())) {
sec1Update = 0;
+ if(trx3500_treadmill) {
+ uint8_t write[] = {FTMS_START_RESUME};
+ writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write),
+ "start simulation", false, false);
+ }
// updateDisplay(elapsed);
}
@@ -1103,12 +1120,14 @@ void horizontreadmill::forceSpeed(double requestSpeed) {
}
} else if (gattFTMSService) {
// for the Tecnogym Myrun
- uint8_t write[] = {FTMS_REQUEST_CONTROL};
- writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
- false);
- write[0] = {FTMS_START_RESUME};
- writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "start simulation",
- false, false);
+ if(!anplus_treadmill && !trx3500_treadmill) {
+ uint8_t write[] = {FTMS_REQUEST_CONTROL};
+ writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
+ false);
+ write[0] = {FTMS_START_RESUME};
+ writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "start simulation",
+ false, false);
+ }
uint8_t writeS[] = {FTMS_SET_TARGET_SPEED, 0x00, 0x00};
writeS[1] = ((uint16_t)(requestSpeed * 100)) & 0xFF;
@@ -1125,6 +1144,9 @@ void horizontreadmill::forceIncline(double requestIncline) {
bool horizon_paragon_x =
settings.value(QZSettings::horizon_paragon_x, QZSettings::default_horizon_paragon_x).toBool();
+ if(tunturi_t60_treadmill)
+ Inclination = requestIncline;
+
if (gattCustomService) {
if (!horizon_paragon_x) {
messageID++;
@@ -1164,12 +1186,14 @@ void horizontreadmill::forceIncline(double requestIncline) {
}
} else if (gattFTMSService) {
// for the Tecnogym Myrun
- uint8_t write[] = {FTMS_REQUEST_CONTROL};
- writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
- false);
- write[0] = {FTMS_START_RESUME};
- writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "start simulation",
- false, false);
+ if(!anplus_treadmill && !trx3500_treadmill) {
+ uint8_t write[] = {FTMS_REQUEST_CONTROL};
+ writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
+ false);
+ write[0] = {FTMS_START_RESUME};
+ writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "start simulation",
+ false, false);
+ }
uint8_t writeS[] = {FTMS_SET_TARGET_INCLINATION, 0x00, 0x00};
if (kettler_treadmill) {
@@ -1287,6 +1311,8 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
+ QDateTime now = QDateTime::currentDateTime();
+
emit debug(QStringLiteral(" << ") + characteristic.uuid().toString() + " " + QString::number(newValue.length()) +
" " + newValue.toHex(' '));
@@ -1334,14 +1360,14 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
if (firstDistanceCalculated)
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
distanceEval = true;
} else if (characteristic.uuid() == QBluetoothUuid((quint16)0xFFF4) && newValue.length() > 70 &&
@@ -1361,14 +1387,14 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
if (firstDistanceCalculated)
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
distanceEval = true;
} else if (characteristic.uuid() == QBluetoothUuid((quint16)0xFFF4) && newValue.length() == 29 &&
@@ -1385,14 +1411,14 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
if (firstDistanceCalculated)
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
distanceEval = true;
} else if (characteristic.uuid() == QBluetoothUuid((quint16)0xFFF4) && newValue.length() > 10 &&
@@ -1463,16 +1489,17 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
{
if (firstDistanceCalculated)
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
distanceEval = true;
}
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
if (Flags.inclination) {
- Inclination = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
- (uint16_t)((uint8_t)newValue.at(index)))) /
- 10.0;
+ if(!tunturi_t60_treadmill)
+ Inclination = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
+ (uint16_t)((uint8_t)newValue.at(index)))) /
+ 10.0;
index += 4; // the ramo value is useless
emit debug(QStringLiteral("Current Inclination: ") + QString::number(Inclination.value()));
}
@@ -1510,7 +1537,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
200.0) /
(60000.0 /
((double)lastRefreshCharacteristicChanged.msecsTo(
- QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
distanceEval = true;
}
@@ -1526,7 +1553,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
if (Flags.heartRate) {
if (index < newValue.length()) {
- heart = ((double)((newValue.at(index))));
+ heart = ((double)(((uint8_t)newValue.at(index))));
emit debug(QStringLiteral("Current Heart: ") + QString::number(heart));
} else {
emit debug(QStringLiteral("Error on parsing heart!"));
@@ -1606,7 +1633,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
} else {
if (firstDistanceCalculated)
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
distanceEval = true;
}
@@ -1685,7 +1712,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
200.0) /
(60000.0 /
((double)lastRefreshCharacteristicChanged.msecsTo(
- QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
distanceEval = true;
}
@@ -1699,7 +1726,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
#endif
{
if (Flags.heartRate && !disable_hr_frommachinery && newValue.length() > index) {
- Heart = ((double)((newValue.at(index))));
+ Heart = ((double)(((uint8_t)newValue.at(index))));
// index += 1; // NOTE: clang-analyzer-deadcode.DeadStores
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
} else {
@@ -1740,7 +1767,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
if (distanceEval) {
firstDistanceCalculated = true;
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
}
if (m_control->error() != QLowEnergyController::NoError) {
@@ -1960,6 +1987,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() + ')');
{
@@ -1971,6 +1999,25 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if (device.name().toUpper().startsWith(QStringLiteral("KETTLER TREADMILL"))) {
kettler_treadmill = true;
qDebug() << QStringLiteral("KETTLER TREADMILL workaround ON!");
+ } else if (device.name().toUpper().startsWith(QStringLiteral("ANPLUS-"))) {
+ anplus_treadmill = true;
+ qDebug() << QStringLiteral("ANPLUS TREADMILL workaround ON!");
+ } else if (device.name().toUpper().startsWith(QStringLiteral("TUNTURI T60-"))) {
+ tunturi_t60_treadmill = true;
+ qDebug() << QStringLiteral("TUNTURI T60 TREADMILL workaround ON!");
+ }
+
+
+ if (device.name().toUpper().startsWith(QStringLiteral("TRX3500"))) {
+ trx3500_treadmill = true;
+ qDebug() << QStringLiteral("TRX3500 TREADMILL workaround ON!");
+#ifdef Q_OS_IOS
+ QZ_EnableDiscoveryCharsAndDescripttors = false;
+#endif
+ } else {
+#ifdef Q_OS_IOS
+ QZ_EnableDiscoveryCharsAndDescripttors = true;
+#endif
}
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
@@ -2674,7 +2721,7 @@ void horizontreadmill::testProfileCRC() {
}
double horizontreadmill::minStepInclination() {
- if (kettler_treadmill)
+ if (kettler_treadmill || trx3500_treadmill)
return 1.0;
else
return 0.5;
diff --git a/src/horizontreadmill.h b/src/horizontreadmill.h
index 1ec9664c3..dd1a26de0 100644
--- a/src/horizontreadmill.h
+++ b/src/horizontreadmill.h
@@ -88,6 +88,9 @@ class horizontreadmill : public treadmill {
bool mobvoi_treadmill = false;
bool kettler_treadmill = false;
+ bool anplus_treadmill = false;
+ bool tunturi_t60_treadmill = false;
+ bool trx3500_treadmill = false;
void testProfileCRC();
void updateProfileCRC();
diff --git a/src/inner_templates/chartjs/dochart.js b/src/inner_templates/chartjs/dochart.js
index beb922c88..fa475824d 100644
--- a/src/inner_templates/chartjs/dochart.js
+++ b/src/inner_templates/chartjs/dochart.js
@@ -177,7 +177,7 @@ function process_arr(arr) {
$('.summary_watts_avg').text(Math.floor(watts_avg) + ' W');
$('.summary_jouls').text(Math.floor(jouls / 1000.0) + ' kJ');
$('.summary_calories').text(Math.floor(calories) + ' kcal');
- $('.summary_distance').text(Math.floor(distance * miles) + (miles === 1 ? ' km' : ' mi'));
+ $('.summary_distance').text((distance * miles).toFixed(1) + (miles === 1 ? ' km' : ' mi'));
$('.summary_cadence_avg').text(Math.floor(cadence_avg) + ' rpm');
$('.summary_resistance_avg').text(Math.floor(peloton_resistance_avg) + ' lvl');
diff --git a/src/inner_templates/floating/floating.htm b/src/inner_templates/floating/floating.htm
index b6f3e3599..a6723a616 100644
--- a/src/inner_templates/floating/floating.htm
+++ b/src/inner_templates/floating/floating.htm
@@ -590,11 +590,11 @@ Peloton Workout in progress!
keys_arr = ['speed', 'speed_lapavg', 'cadence', 'cadence_lapavg', 'heart', 'heart_lapavg', 'calories', 'distance', 'watts', 'watts_lapavg',
'elapsed_h', 'elapsed_m', 'elapsed_s', 'resistance', 'resistance_lapavg', 'peloton_resistance', 'peloton_resistance_lapavg',
'speed_lapmax', 'cadence_lapmax', 'heart_lapmax', 'watts_lapmax', 'resistance_lapmax', 'peloton_resistance_lapmax',
- 'speed_color', 'cadence_color', 'heart_color', 'watts_color', 'peloton_resistance_color', 'target_resistance', 'target_peloton_resistance',
+ 'speed_color', 'power_zone_color', 'cadence_color', 'heart_color', 'watts_color', 'peloton_resistance_color', 'target_resistance', 'target_peloton_resistance',
'target_cadence', 'target_power', 'peloton_offset', 'peloton_ask_start', 'target_speed', 'target_pace', 'inclination', 'inclination_lapavg',
'inclination_lapmax', 'target_inclination', 'power_zone', 'power_zone_lapavg', 'power_zone_lapmax', 'target_power_zone', 'jouls',
'row_remaining_time_s', 'row_remaining_time_m', 'row_remaining_time_h' , 'autoresistance', 'gears', 'elevation', 'pace_s' , 'pace_m',
- 'avgpace_s', 'avgpace_m', 'maxpace_s' , 'maxpace_m',]
+ 'avgpace_s', 'avgpace_m', 'maxpace_s' , 'maxpace_m', 'remaining_time_s', 'remaining_time_m', 'remaining_time_h']
let ell = new MainWSQueueElement(null, function (msg) {
if (msg.msg === 'workout') {
var speed = 0;
@@ -628,6 +628,9 @@ Peloton Workout in progress!
var row_remaining_time_s = 0;
var row_remaining_time_m = 0;
var row_remaining_time_h = 0;
+ var remaining_time_s = 0;
+ var remaining_time_m = 0;
+ var remaining_time_h = 0;
var resistance = 0;
var resistance_lapavg = 0;
var peloton_resistance = 0;
@@ -721,6 +724,12 @@ Peloton Workout in progress!
row_remaining_time_m = msg.content[key];
} else if (key === 'row_remaining_time_s') {
row_remaining_time_s = msg.content[key];
+ } else if (key === 'remaining_time_h') {
+ remaining_time_h = msg.content[key];
+ } else if (key === 'remaining_time_m') {
+ remaining_time_m = msg.content[key];
+ } else if (key === 'remaining_time_s') {
+ remaining_time_s = msg.content[key];
} else if (key === 'target_pace_h') {
target_pace_h = msg.content[key];
} else if (key === 'target_pace_m') {
@@ -758,7 +767,7 @@ Peloton Workout in progress!
} else if (key === 'gears') {
gears = msg.content[key];
} else if (key === 'peloton_resistance_color') {
- $('.peloton_resistance-value').css('color', msg.content[key]);
+ $('.pelotonresistance-value').css('color', msg.content[key]);
} else if (key === 'heart_color') {
$('.heart-value').css('color', msg.content[key]);
} else if (key === 'cadence_color') {
@@ -767,6 +776,8 @@ Peloton Workout in progress!
$('.watt-value').css('color', msg.content[key]);
} else if (key === 'speed_color') {
$('.speed-value').css('color', msg.content[key]);
+ } else if (key === 'power_zone_color') {
+ $('.powerzone-value').css('color', msg.content[key]);
} else if (key === 'peloton_ask_start' && !peloton_ask_already_running && (msg.content[key] === true || msg.content[key] === 'true')) {
peloton_ask_already_running = true;
document.getElementById("overlay").hidden = false;
@@ -822,7 +833,7 @@ Peloton Workout in progress!
$('.pelotonresistance-max').html(peloton_resistance_lapmax.toFixed(0));
$('.distance-value').html("" + odometer.toFixed(2) + "");
$('.rowremainingtime-value').html("" + row_remaining_time_h.toString().padStart(2, "0") + ":" + row_remaining_time_m.toString().padStart(2, "0") + ":" + row_remaining_time_s.toString().padStart(2, "0") + "");
- $('.elapsed-value').html("" + elapsed_h.toString().padStart(2, "0") + ":" + elapsed_m.toString().padStart(2, "0") + ":" + elapsed_s.toString().padStart(2, "0") + "");
+ $('.elapsed-value').html("" + elapsed_h.toString().padStart(2, "0") + ":" + elapsed_m.toString().padStart(2, "0") + ":" + elapsed_s.toString().padStart(2, "0") + "" + (remaining_time_h > 0 || remaining_time_m > 0 || remaining_time_s > 0 ? " / " + "" + remaining_time_h.toString().padStart(2, "0") + ":" + remaining_time_m.toString().padStart(2, "0") + ":" + remaining_time_s.toString().padStart(2, "0") + "" : ""));
$('.gears-value').html("" + gears.toFixed(0) + "");
if(pace_s.toString() === "-1" || (pace_s.toString() === "0" && pace_m.toString() === "0"))
$('.pace-value').html("N/A");
diff --git a/src/inspirebike.cpp b/src/inspirebike.cpp
index 52c1fec51..706c80695 100644
--- a/src/inspirebike.cpp
+++ b/src/inspirebike.cpp
@@ -127,6 +127,7 @@ void inspirebike::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void inspirebike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -151,17 +152,17 @@ void inspirebike::characteristicChanged(const QLowEnergyCharacteristic &characte
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
Speed = 0.37497622 * ((double)Cadence.value());
} else {
- Speed = metric::calculateSpeedFromPower(watts(), Inclination.value(), Speed.value(),fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ Speed = metric::calculateSpeedFromPower(watts(), Inclination.value(), Speed.value(),fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
if (settings.value(QZSettings::inspire_peloton_formula2, QZSettings::default_inspire_peloton_formula2).toBool()) {
// y = 0,0002x^3 - 0.1478x^2 + 4.2412x + 1.8102
@@ -184,7 +185,7 @@ void inspirebike::characteristicChanged(const QLowEnergyCharacteristic &characte
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
diff --git a/src/ios/AppDelegate.swift b/src/ios/AppDelegate.swift
index 7e7420364..651f3248c 100644
--- a/src/ios/AppDelegate.swift
+++ b/src/ios/AppDelegate.swift
@@ -17,9 +17,11 @@ var pedometer = CMPedometer()
@objc public class healthkit:NSObject {
let w = watchAppStart()
+ let SwiftDebug = swiftDebug()
@objc public func request()
{
+ SwiftDebug.qtDebug("swift debug test")
if #available(iOS 13.0, *) {
Client.client.start()
} else {
diff --git a/src/ios/GarminConnect.swift b/src/ios/GarminConnect.swift
index 76b0272aa..eedf5de4a 100644
--- a/src/ios/GarminConnect.swift
+++ b/src/ios/GarminConnect.swift
@@ -45,6 +45,8 @@ class GarminConnectSwift: NSObject, IQDeviceEventDelegate, IQAppMessageDelegate
public var HR: Int = 0
public var FootCad: Int = 0
+
+ let SwiftDebug = swiftDebug()
private let formatter: NumberFormatter = {
let formatter = NumberFormatter()
@@ -65,6 +67,7 @@ class GarminConnectSwift: NSObject, IQDeviceEventDelegate, IQAppMessageDelegate
guard url.scheme == GarminConnectSwift.urlScheme,
let devices = ConnectIQ.shared?.parseDeviceSelectionResponse(from: url) as? [IQDevice] else { return false }
registerForDeviceEvents(devices: devices)
+ SwiftDebug.qtDebug("GarminConnect: urlParser: return true")
return true
}
@@ -75,6 +78,7 @@ class GarminConnectSwift: NSObject, IQDeviceEventDelegate, IQAppMessageDelegate
}
public func deviceStatusChanged(_ device: IQDevice!, status: IQDeviceStatus) {
+ let descriptionStatus: String
switch status {
case .connected:
// The `store` is not necessary for sending messages, I suppose it's for when you want the user to download the app.
@@ -82,6 +86,7 @@ class GarminConnectSwift: NSObject, IQDeviceEventDelegate, IQAppMessageDelegate
let app = IQApp(uuid: GarminConnectSwift.watchAppUuid, store: nil, device: device)
apps[device.uuid] = app
print(device.uuid)
+ descriptionStatus = "connected"
ConnectIQ.shared?.register(forAppMessages: app, delegate: self)
@@ -93,15 +98,20 @@ class GarminConnectSwift: NSObject, IQDeviceEventDelegate, IQAppMessageDelegate
case .bluetoothNotReady, .invalidDevice, .notConnected, .notFound:
print(device.uuid)
+ descriptionStatus = "invalidDevice"
apps.removeValue(forKey: device.uuid)
@unknown default:
+ descriptionStatus = "case not handled"
print("New case, still unhandled. \(status.rawValue)")
}
+
+ SwiftDebug.qtDebug("GarminConnect: deviceStatusChanged: status " + descriptionStatus + " " + device.uuid.uuidString)
}
func receivedMessage(_ message: Any!, from app: IQApp!) {
print("Received message from ConnectIQ: \(message.debugDescription)")
+ SwiftDebug.qtDebug("GarminConnect: receivedMessage: " + message.debugDescription)
guard let array = message as? [Any] else {
print("Failed to parse message sent from ConnectIQ.")
diff --git a/src/ios/ios_eliteariafan.h b/src/ios/ios_eliteariafan.h
new file mode 100644
index 000000000..0684e9aa0
--- /dev/null
+++ b/src/ios/ios_eliteariafan.h
@@ -0,0 +1,20 @@
+#ifndef IOSELITEARIAFAN_H
+#define IOSELITEARIAFAN_H
+
+#import
+#import
+
+@interface ios_eliteariafan : NSObject
+
+@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
diff --git a/src/ios/ios_eliteariafan.mm b/src/ios/ios_eliteariafan.mm
new file mode 100644
index 000000000..d6ccd77cf
--- /dev/null
+++ b/src/ios/ios_eliteariafan.mm
@@ -0,0 +1,91 @@
+#import
+#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 *)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
diff --git a/src/ios/lockscreen.h b/src/ios/lockscreen.h
index 9222a8c2d..8b8dc9466 100644
--- a/src/ios/lockscreen.h
+++ b/src/ios/lockscreen.h
@@ -18,7 +18,7 @@ class lockscreen {
void virtualbike_setHeartRate(unsigned char heartRate);
void virtualbike_setCadence(unsigned short crankRevolutions, unsigned short lastCrankEventTime);
- void virtualbike_zwift_ios(bool disable_hr);
+ void virtualbike_zwift_ios(bool disable_hr, bool garmin_bluetooth_compatibility);
double virtualbike_getCurrentSlope();
double virtualbike_getCurrentCRR();
double virtualbike_getCurrentCW();
@@ -46,7 +46,7 @@ class lockscreen {
double virtualtreadmill_getPowerRequested();
bool virtualtreadmill_updateFTMS(unsigned short normalizeSpeed, unsigned char currentResistance,
unsigned short currentCadence, unsigned short currentWatt,
- unsigned short currentInclination);
+ unsigned short currentInclination, unsigned long long currentDistance);
// volume
double getVolume();
@@ -63,6 +63,17 @@ 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);
+
+ // Zwift API
+ void zwift_api_decodemessage_player(const char* data, int len);
+ float zwift_api_getaltitude();
+ int zwift_api_getdistance();
+ float zwift_api_getlatitude();
+ float zwift_api_getlongitude();
};
#endif // LOCKSCREEN_H
diff --git a/src/ios/lockscreen.mm b/src/ios/lockscreen.mm
index da616415d..19894b11c 100644
--- a/src/ios/lockscreen.mm
+++ b/src/ios/lockscreen.mm
@@ -9,6 +9,7 @@
#include "ios/lockscreen.h"
#include
#include "ios/AdbClient.h"
+#include "ios/ios_eliteariafan.h"
@class virtualbike_ios_swift;
@class virtualbike_zwift;
@@ -26,6 +27,10 @@
static AdbClient *_adb = 0;
+static ios_eliteariafan* ios_eliteAriaFan = nil;
+
+static zwift_protobuf_layer* zwiftProtobufLayer = nil;
+
void lockscreen::setTimerDisabled() {
[[UIApplication sharedApplication] setIdleTimerDisabled: YES];
}
@@ -34,6 +39,7 @@
{
h = [[healthkit alloc] init];
[h request];
+ zwiftProtobufLayer = [[zwift_protobuf_layer alloc] init];
if (@available(iOS 13, *)) {
Garmin = [[GarminConnect alloc] init];
}
@@ -96,9 +102,9 @@
[_virtualbike updateCadenceWithCrankRevolutions:crankRevolutions LastCrankEventTime:lastCrankEventTime];
}
-void lockscreen::virtualbike_zwift_ios(bool disable_hr)
+void lockscreen::virtualbike_zwift_ios(bool disable_hr, bool garmin_bluetooth_compatibility)
{
- _virtualbike_zwift = [[virtualbike_zwift alloc] initWithDisable_hr: disable_hr];
+ _virtualbike_zwift = [[virtualbike_zwift alloc] initWithDisable_hr:disable_hr garmin_bluetooth_compatibility:garmin_bluetooth_compatibility];
}
void lockscreen::virtualrower_ios()
@@ -202,10 +208,10 @@
return 0;
}
-bool lockscreen::virtualtreadmill_updateFTMS(UInt16 normalizeSpeed, UInt8 currentResistance, UInt16 currentCadence, UInt16 currentWatt, UInt16 currentInclination)
+bool lockscreen::virtualtreadmill_updateFTMS(UInt16 normalizeSpeed, UInt8 currentResistance, UInt16 currentCadence, UInt16 currentWatt, UInt16 currentInclination, UInt64 currentDistance)
{
if(_virtualtreadmill_zwift != nil)
- return [_virtualtreadmill_zwift updateFTMSWithNormalizeSpeed:normalizeSpeed currentCadence:currentCadence currentResistance:currentResistance currentWatt:currentWatt currentInclination:currentInclination];
+ return [_virtualtreadmill_zwift updateFTMSWithNormalizeSpeed:normalizeSpeed currentCadence:currentCadence currentResistance:currentResistance currentWatt:currentWatt currentInclination:currentInclination currentDistance:currentDistance];
return 0;
}
@@ -285,4 +291,35 @@
}];
}
+
+void lockscreen::eliteAriaFan() {
+ ios_eliteAriaFan = [[ios_eliteariafan alloc] init];
+}
+
+void lockscreen::eliteAriaFan_fanSpeedRequest(unsigned char speed) {
+ if(ios_eliteAriaFan) {
+ [ios_eliteAriaFan fanSpeedRequest:speed];
+ }
+}
+
+void lockscreen::zwift_api_decodemessage_player(const char* data, int len) {
+ NSData *d = [NSData dataWithBytes:data length:len];
+ [zwiftProtobufLayer getPlayerStateWithValue:d];
+}
+
+float lockscreen::zwift_api_getaltitude() {
+ return [zwiftProtobufLayer getAltitude];
+}
+
+int lockscreen::zwift_api_getdistance() {
+ return [zwiftProtobufLayer getDistance];
+}
+
+float lockscreen::zwift_api_getlatitude() {
+ return [zwiftProtobufLayer getLatitude];
+}
+
+float lockscreen::zwift_api_getlongitude() {
+ return [zwiftProtobufLayer getLongitude];
+}
#endif
diff --git a/src/ios/swiftDebug.h b/src/ios/swiftDebug.h
new file mode 100644
index 000000000..a53939e58
--- /dev/null
+++ b/src/ios/swiftDebug.h
@@ -0,0 +1,20 @@
+//
+// swiftDebug.h
+// qdomyoszwift
+//
+// Created by Roberto Viola on 14/12/23.
+//
+
+#ifndef swiftDebug_h
+#define swiftDebug_h
+
+#import
+
+
+@interface swiftDebug : NSObject
+
+- (void)qtDebug:(NSString *)inputString;;
+
+@end
+
+#endif /* swiftDebug_h */
diff --git a/src/ios/swiftDebug.mm b/src/ios/swiftDebug.mm
new file mode 100644
index 000000000..1c8db0e08
--- /dev/null
+++ b/src/ios/swiftDebug.mm
@@ -0,0 +1,16 @@
+//
+// swiftDebug.m
+// qdomyoszwift
+//
+// Created by Roberto Viola on 14/12/23.
+//
+#import "swiftDebug.h"
+#include
+
+@implementation swiftDebug
+
+- (void)qtDebug:(NSString *)inputString {
+ qDebug() << inputString;
+}
+
+@end
diff --git a/src/ios/virtualbike_zwift.swift b/src/ios/virtualbike_zwift.swift
index 9ca88f1b3..7315efdc6 100644
--- a/src/ios/virtualbike_zwift.swift
+++ b/src/ios/virtualbike_zwift.swift
@@ -11,9 +11,9 @@ let TrainingStatusUuid = CBUUID(string: "0x2AD3");
@objc public class virtualbike_zwift: NSObject {
private var peripheralManager: BLEPeripheralManagerZwift!
- @objc public init(disable_hr: Bool) {
+ @objc public init(disable_hr: Bool, garmin_bluetooth_compatibility: Bool) {
super.init()
- peripheralManager = BLEPeripheralManagerZwift(disable_hr: disable_hr)
+ peripheralManager = BLEPeripheralManagerZwift(disable_hr: disable_hr, garmin_bluetooth_compatibility: garmin_bluetooth_compatibility)
}
@objc public func updateHeartRate(HeartRate: UInt8)
@@ -61,6 +61,7 @@ let TrainingStatusUuid = CBUUID(string: "0x2AD3");
}
class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
+ private var garmin_bluetooth_compatibility: Bool = false
private var disable_hr: Bool = false
private var peripheralManager: CBPeripheralManager!
@@ -108,9 +109,11 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
private var notificationTimer: Timer! = nil
//var delegate: BLEPeripheralManagerDelegate?
- init(disable_hr: Bool) {
+ init(disable_hr: Bool, garmin_bluetooth_compatibility: Bool) {
super.init()
self.disable_hr = disable_hr
+ self.garmin_bluetooth_compatibility = garmin_bluetooth_compatibility
+
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
}
@@ -119,107 +122,107 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
case .poweredOn:
print("Peripheral manager is up and running")
-
- self.heartRateService = CBMutableService(type: heartRateServiceUUID, primary: true)
- let characteristicProperties: CBCharacteristicProperties = [.notify, .read, .write]
- let characteristicPermissions: CBAttributePermissions = [.readable]
- self.heartRateCharacteristic = CBMutableCharacteristic(type: heartRateCharacteristicUUID,
- properties: characteristicProperties,
- value: nil,
- permissions: characteristicPermissions)
-
- heartRateService.characteristics = [heartRateCharacteristic]
- self.peripheralManager.add(heartRateService)
-
- self.FitnessMachineService = CBMutableService(type: FitnessMachineServiceUuid, primary: true)
-
- let FitnessMachineFeatureProperties: CBCharacteristicProperties = [.read]
- let FitnessMachineFeaturePermissions: CBAttributePermissions = [.readable]
- self.FitnessMachineFeatureCharacteristic = CBMutableCharacteristic(type: FitnessMachineFeatureCharacteristicUuid,
- properties: FitnessMachineFeatureProperties,
- value: Data (bytes: [0x83, 0x14, 0x00, 0x00, 0x0c, 0xe0, 0x00, 0x00]),
- permissions: FitnessMachineFeaturePermissions)
-
- let supported_resistance_level_rangeProperties: CBCharacteristicProperties = [.read]
- let supported_resistance_level_rangePermissions: CBAttributePermissions = [.readable]
- self.supported_resistance_level_rangeCharacteristic = CBMutableCharacteristic(type: supported_resistance_level_rangeCharacteristicUuid,
- properties: supported_resistance_level_rangeProperties,
- value: Data (bytes: [0x0A, 0x00, 0x96, 0x00, 0x0A, 0x00]),
- permissions: supported_resistance_level_rangePermissions)
-
- let FitnessMachineControlPointProperties: CBCharacteristicProperties = [.indicate, .notify, .write]
- let FitnessMachineControlPointPermissions: CBAttributePermissions = [.writeable]
- self.FitnessMachineControlPointCharacteristic = CBMutableCharacteristic(type: FitnessMachineControlPointUuid,
- properties: FitnessMachineControlPointProperties,
- value: nil,
- permissions: FitnessMachineControlPointPermissions)
-
- let indoorbikeProperties: CBCharacteristicProperties = [.notify, .read]
- let indoorbikePermissions: CBAttributePermissions = [.readable]
- self.indoorbikeCharacteristic = CBMutableCharacteristic(type: indoorbikeUuid,
- properties: indoorbikeProperties,
- value: nil,
- permissions: indoorbikePermissions)
-
- let FitnessMachinestatusProperties: CBCharacteristicProperties = [.notify]
- let FitnessMachinestatusPermissions: CBAttributePermissions = [.readable]
- self.FitnessMachinestatusCharacteristic = CBMutableCharacteristic(type: FitnessMachinestatusUuid,
- properties: FitnessMachinestatusProperties,
- value: nil,
- permissions: FitnessMachinestatusPermissions)
-
- let TrainingStatusProperties: CBCharacteristicProperties = [.read]
- let TrainingStatusPermissions: CBAttributePermissions = [.readable]
- self.TrainingStatusCharacteristic = CBMutableCharacteristic(type: TrainingStatusUuid,
- properties: TrainingStatusProperties,
- value: Data (bytes: [0x00, 0x01]),
- permissions: TrainingStatusPermissions)
-
- FitnessMachineService.characteristics = [FitnessMachineFeatureCharacteristic,
- supported_resistance_level_rangeCharacteristic,
- FitnessMachineControlPointCharacteristic,
- indoorbikeCharacteristic,
- FitnessMachinestatusCharacteristic,
- TrainingStatusCharacteristic ]
-
- self.peripheralManager.add(FitnessMachineService)
-
- self.CSCService = CBMutableService(type: CSCServiceUUID, primary: true)
-
- let CSCFeatureProperties: CBCharacteristicProperties = [.read]
- let CSCFeaturePermissions: CBAttributePermissions = [.readable]
- self.CSCFeatureCharacteristic = CBMutableCharacteristic(type: CSCFeatureCharacteristicUUID,
- properties: CSCFeatureProperties,
- value: Data (bytes: [0x02, 0x00]),
- permissions: CSCFeaturePermissions)
-
- let SensorLocationProperties: CBCharacteristicProperties = [.read]
- let SensorLocationPermissions: CBAttributePermissions = [.readable]
- self.SensorLocationCharacteristic = CBMutableCharacteristic(type: SensorLocationCharacteristicUUID,
- properties: SensorLocationProperties,
- value: Data (bytes: [0x13]),
- permissions: SensorLocationPermissions)
-
- let CSCMeasurementProperties: CBCharacteristicProperties = [.notify, .read]
- let CSCMeasurementPermissions: CBAttributePermissions = [.readable]
- self.CSCMeasurementCharacteristic = CBMutableCharacteristic(type: CSCMeasurementCharacteristicUUID,
- properties: CSCMeasurementProperties,
+ if(!self.garmin_bluetooth_compatibility) {
+ self.heartRateService = CBMutableService(type: heartRateServiceUUID, primary: true)
+ let characteristicProperties: CBCharacteristicProperties = [.notify, .read, .write]
+ let characteristicPermissions: CBAttributePermissions = [.readable]
+ self.heartRateCharacteristic = CBMutableCharacteristic(type: heartRateCharacteristicUUID,
+ properties: characteristicProperties,
value: nil,
- permissions: CSCMeasurementPermissions)
-
- let SCControlPointProperties: CBCharacteristicProperties = [.indicate, .write]
- let SCControlPointPermissions: CBAttributePermissions = [.writeable]
- self.SCControlPointCharacteristic = CBMutableCharacteristic(type: SCControlPointCharacteristicUUID,
- properties: SCControlPointProperties,
- value: nil,
- permissions: SCControlPointPermissions)
-
- CSCService.characteristics = [CSCFeatureCharacteristic,
- SensorLocationCharacteristic,
- CSCMeasurementCharacteristic,
- SCControlPointCharacteristic]
- self.peripheralManager.add(CSCService)
-
+ permissions: characteristicPermissions)
+
+ heartRateService.characteristics = [heartRateCharacteristic]
+ self.peripheralManager.add(heartRateService)
+
+ self.FitnessMachineService = CBMutableService(type: FitnessMachineServiceUuid, primary: true)
+
+ let FitnessMachineFeatureProperties: CBCharacteristicProperties = [.read]
+ let FitnessMachineFeaturePermissions: CBAttributePermissions = [.readable]
+ self.FitnessMachineFeatureCharacteristic = CBMutableCharacteristic(type: FitnessMachineFeatureCharacteristicUuid,
+ properties: FitnessMachineFeatureProperties,
+ value: Data (bytes: [0x83, 0x14, 0x00, 0x00, 0x0c, 0xe0, 0x00, 0x00]),
+ permissions: FitnessMachineFeaturePermissions)
+
+ let supported_resistance_level_rangeProperties: CBCharacteristicProperties = [.read]
+ let supported_resistance_level_rangePermissions: CBAttributePermissions = [.readable]
+ self.supported_resistance_level_rangeCharacteristic = CBMutableCharacteristic(type: supported_resistance_level_rangeCharacteristicUuid,
+ properties: supported_resistance_level_rangeProperties,
+ value: Data (bytes: [0x0A, 0x00, 0x96, 0x00, 0x0A, 0x00]),
+ permissions: supported_resistance_level_rangePermissions)
+
+ let FitnessMachineControlPointProperties: CBCharacteristicProperties = [.indicate, .notify, .write]
+ let FitnessMachineControlPointPermissions: CBAttributePermissions = [.writeable]
+ self.FitnessMachineControlPointCharacteristic = CBMutableCharacteristic(type: FitnessMachineControlPointUuid,
+ properties: FitnessMachineControlPointProperties,
+ value: nil,
+ permissions: FitnessMachineControlPointPermissions)
+
+ let indoorbikeProperties: CBCharacteristicProperties = [.notify, .read]
+ let indoorbikePermissions: CBAttributePermissions = [.readable]
+ self.indoorbikeCharacteristic = CBMutableCharacteristic(type: indoorbikeUuid,
+ properties: indoorbikeProperties,
+ value: nil,
+ permissions: indoorbikePermissions)
+
+ let FitnessMachinestatusProperties: CBCharacteristicProperties = [.notify]
+ let FitnessMachinestatusPermissions: CBAttributePermissions = [.readable]
+ self.FitnessMachinestatusCharacteristic = CBMutableCharacteristic(type: FitnessMachinestatusUuid,
+ properties: FitnessMachinestatusProperties,
+ value: nil,
+ permissions: FitnessMachinestatusPermissions)
+
+ let TrainingStatusProperties: CBCharacteristicProperties = [.read]
+ let TrainingStatusPermissions: CBAttributePermissions = [.readable]
+ self.TrainingStatusCharacteristic = CBMutableCharacteristic(type: TrainingStatusUuid,
+ properties: TrainingStatusProperties,
+ value: Data (bytes: [0x00, 0x01]),
+ permissions: TrainingStatusPermissions)
+
+ FitnessMachineService.characteristics = [FitnessMachineFeatureCharacteristic,
+ supported_resistance_level_rangeCharacteristic,
+ FitnessMachineControlPointCharacteristic,
+ indoorbikeCharacteristic,
+ FitnessMachinestatusCharacteristic,
+ TrainingStatusCharacteristic ]
+
+ self.peripheralManager.add(FitnessMachineService)
+
+ self.CSCService = CBMutableService(type: CSCServiceUUID, primary: true)
+
+ let CSCFeatureProperties: CBCharacteristicProperties = [.read]
+ let CSCFeaturePermissions: CBAttributePermissions = [.readable]
+ self.CSCFeatureCharacteristic = CBMutableCharacteristic(type: CSCFeatureCharacteristicUUID,
+ properties: CSCFeatureProperties,
+ value: Data (bytes: [0x02, 0x00]),
+ permissions: CSCFeaturePermissions)
+
+ let SensorLocationProperties: CBCharacteristicProperties = [.read]
+ let SensorLocationPermissions: CBAttributePermissions = [.readable]
+ self.SensorLocationCharacteristic = CBMutableCharacteristic(type: SensorLocationCharacteristicUUID,
+ properties: SensorLocationProperties,
+ value: Data (bytes: [0x0D]),
+ permissions: SensorLocationPermissions)
+
+ let CSCMeasurementProperties: CBCharacteristicProperties = [.notify, .read]
+ let CSCMeasurementPermissions: CBAttributePermissions = [.readable]
+ self.CSCMeasurementCharacteristic = CBMutableCharacteristic(type: CSCMeasurementCharacteristicUUID,
+ properties: CSCMeasurementProperties,
+ value: nil,
+ permissions: CSCMeasurementPermissions)
+
+ let SCControlPointProperties: CBCharacteristicProperties = [.indicate, .write]
+ let SCControlPointPermissions: CBAttributePermissions = [.writeable]
+ self.SCControlPointCharacteristic = CBMutableCharacteristic(type: SCControlPointCharacteristicUUID,
+ properties: SCControlPointProperties,
+ value: nil,
+ permissions: SCControlPointPermissions)
+
+ CSCService.characteristics = [CSCFeatureCharacteristic,
+ SensorLocationCharacteristic,
+ CSCMeasurementCharacteristic,
+ SCControlPointCharacteristic]
+ self.peripheralManager.add(CSCService)
+ }
self.PowerService = CBMutableService(type: PowerServiceUUID, primary: true)
let PowerFeatureProperties: CBCharacteristicProperties = [.read]
@@ -260,8 +263,13 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
print("Failed to add service with error: \(uwError.localizedDescription)")
return
}
-
- if(disable_hr) {
+
+
+ if(garmin_bluetooth_compatibility) {
+ let advertisementData = [CBAdvertisementDataLocalNameKey: "QZ",
+ CBAdvertisementDataServiceUUIDsKey: [PowerServiceUUID]] as [String : Any]
+ peripheralManager.startAdvertising(advertisementData)
+ } else if(disable_hr) {
// useful in order to hide HR from Garmin devices
let advertisementData = [CBAdvertisementDataLocalNameKey: "QZ",
CBAdvertisementDataServiceUUIDsKey: [FitnessMachineServiceUuid, CSCServiceUUID, PowerServiceUUID]] as [String : Any]
@@ -272,7 +280,6 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
peripheralManager.startAdvertising(advertisementData)
}
-
print("Successfully added service")
}
@@ -371,13 +378,84 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
return cadenceData
}
+
+ private var revolutions: UInt16 = 0
+ private var timestamp: UInt16 = 0
+ private var lastRevolution: UInt64 = UInt64(Date().timeIntervalSince1970 * 1000)
+
func calculatePower() -> Data {
- let flags:UInt8 = 0x20
- //self.delegate?.BLEPeripheralManagerCSCDidSendValue(flags, crankRevolutions: self.crankRevolutions, lastCrankEventTime: self.lastCrankEventTime)
- var power: [UInt8] = [flags, 0x00, (UInt8)(self.CurrentWatt & 0xFF), (UInt8)((self.CurrentWatt >> 8) & 0xFF), (UInt8)(crankRevolutions & 0xFF), (UInt8)((crankRevolutions >> 8) & 0xFF), (UInt8)(lastCrankEventTime & 0xFF), (UInt8)((lastCrankEventTime >> 8) & 0xFF)]
- let powerData = Data(bytes: &power, count: MemoryLayout.size(ofValue: power))
- return powerData
- }
+ if(garmin_bluetooth_compatibility) {
+ /*
+ // convert RPM to timestamp
+ if (cadenceInstantaneous != 0 && (millis()) >= (lastRevolution + (60000 / cadenceInstantaneous)))
+ {
+ revolutions++; // One crank revolution should have passed, add one revolution
+ timestamp = (unsigned short)(((millis() * 1024) / 1000) % 65536); // create timestamp and format
+ lastRevolution = millis();
+ }
+ */
+
+ let millis : UInt64 = UInt64(Date().timeIntervalSince1970 * 1000)
+ if CurrentCadence != 0 && (millis >= lastRevolution + (60000 / UInt64(CurrentCadence / 2))) {
+ revolutions = revolutions + 1
+ var newT: UInt64 = ((60000 / (UInt64(CurrentCadence / 2)) * 1024) / 1000)
+ newT = newT + UInt64(timestamp)
+ newT = newT % 65536
+ timestamp = UInt16(newT)
+ lastRevolution = millis
+ }
+
+ let flags:UInt8 = 0x20
+ //self.delegate?.BLEPeripheralManagerCSCDidSendValue(flags, crankRevolutions: self.crankRevolutions, lastCrankEventTime: self.lastCrankEventTime)
+ var power: [UInt8] = [flags, 0x00, (UInt8)(self.CurrentWatt & 0xFF), (UInt8)((self.CurrentWatt >> 8) & 0xFF), (UInt8)(revolutions & 0xFF), (UInt8)((revolutions >> 8) & 0xFF), (UInt8)(timestamp & 0xFF), (UInt8)((timestamp >> 8) & 0xFF)]
+ let powerData = Data(bytes: &power, count: MemoryLayout.size(ofValue: power))
+ return powerData
+ } else {
+ let flags:UInt8 = 0x30
+
+ /*
+ // set measurement
+ measurement[2] = power & 0xFF;
+ measurement[3] = (power >> 8) & 0xFF;
+ measurement[4] = wheelrev & 0xFF;
+ measurement[5] = (wheelrev >> 8) & 0xFF;
+ measurement[6] = (wheelrev >> 16) & 0xFF;
+ measurement[7] = (wheelrev >> 24) & 0xFF;
+ measurement[8] = lastwheel & 0xFF;
+ measurement[9] = (lastwheel >> 8) & 0xFF;
+ measurement[10] = crankrev & 0xFF;
+ measurement[11] = (crankrev >> 8) & 0xFF;
+ measurement[12] = lastcrank & 0xFF;
+ measurement[13] = (lastcrank >> 8) & 0xFF;
+
+ // speed & distance
+ // NOTE : based on Apple Watch default wheel dimension 700c x 2.5mm
+ // NOTE : 3 is theoretical crank:wheel gear ratio
+ // NOTE : 2.13 is circumference of 700c in meters
+
+ wheelCount = crankCount * 3;
+ speed = cadence * 3 * 2.13 * 60 / 1000;
+ distance = wheelCount * 2.13 / 1000;
+ #if defined(USEPOWER)
+ lastWheelK = lastCrankK * 2; // 1/2048 s granularity
+ #else
+ lastWheelK = lastCrankK * 1; // 1/1024 s granularity
+ #endif
+
+ */
+
+ //self.delegate?.BLEPeripheralManagerCSCDidSendValue(flags, crankRevolutions: self.crankRevolutions, lastCrankEventTime: self.lastCrankEventTime)
+ let wheelrev: UInt32 = ((UInt32)(crankRevolutions)) * 3;
+ let lastWheel: UInt16 = (UInt16)((((UInt32)(lastCrankEventTime)) * 2) & 0xFFFF);
+ var power: [UInt8] = [flags, 0x00, (UInt8)(self.CurrentWatt & 0xFF), (UInt8)((self.CurrentWatt >> 8) & 0xFF),
+ (UInt8)(wheelrev & 0xFF), (UInt8)((wheelrev >> 8) & 0xFF), (UInt8)((wheelrev >> 16) & 0xFF), (UInt8)((wheelrev >> 24) & 0xFF),
+ (UInt8)(lastWheel & 0xFF), (UInt8)((lastWheel >> 8) & 0xFF),
+ (UInt8)(crankRevolutions & 0xFF), (UInt8)((crankRevolutions >> 8) & 0xFF),
+ (UInt8)(lastCrankEventTime & 0xFF), (UInt8)((lastCrankEventTime >> 8) & 0xFF)]
+ let powerData = Data(bytes: &power, count: MemoryLayout.size(ofValue: power))
+ return powerData
+ }
+ }
func calculateHeartRate() -> Data {
//self.delegate?.BLEPeripheralManagerDidSendValue(self.heartRate)
@@ -397,35 +475,27 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
}
@objc func updateSubscribers() {
- let heartRateData = self.calculateHeartRate()
- let indoorBikeData = self.calculateIndoorBike()
- let cadenceData = self.calculateCadence()
- let powerData = self.calculatePower()
-
- if(self.serviceToggle == 3)
+ if(self.serviceToggle == 3 || garmin_bluetooth_compatibility)
{
+ let powerData = self.calculatePower()
let ok = self.peripheralManager.updateValue(powerData, for: self.PowerMeasurementCharacteristic, onSubscribedCentrals: nil)
if(ok) {
self.serviceToggle = 0
}
- }
-
- if(self.serviceToggle == 2)
- {
+ } else if(self.serviceToggle == 2) {
+ let cadenceData = self.calculateCadence()
let ok = self.peripheralManager.updateValue(cadenceData, for: self.CSCMeasurementCharacteristic, onSubscribedCentrals: nil)
if(ok) {
self.serviceToggle = self.serviceToggle + 1
}
- }
- else if(self.serviceToggle == 1)
- {
+ } else if(self.serviceToggle == 1) {
+ let heartRateData = self.calculateHeartRate()
let ok = self.peripheralManager.updateValue(heartRateData, for: self.heartRateCharacteristic, onSubscribedCentrals: nil)
if(ok) {
self.serviceToggle = self.serviceToggle + 1
}
- }
- else
- {
+ } else {
+ let indoorBikeData = self.calculateIndoorBike()
let ok = self.peripheralManager.updateValue(indoorBikeData, for: self.indoorbikeCharacteristic, onSubscribedCentrals: nil)
if(ok) {
self.serviceToggle = self.serviceToggle + 1
diff --git a/src/ios/virtualtreadmill_zwift.swift b/src/ios/virtualtreadmill_zwift.swift
index c0a05068e..9a341d299 100644
--- a/src/ios/virtualtreadmill_zwift.swift
+++ b/src/ios/virtualtreadmill_zwift.swift
@@ -36,13 +36,14 @@ let treadmilldataUuid = CBUUID(string: "0x2ACD");
return peripheralManager.PowerRequested;
}
- @objc public func updateFTMS(normalizeSpeed: UInt16, currentCadence: UInt16, currentResistance: UInt8, currentWatt: UInt16, currentInclination: UInt16) -> Bool
+ @objc public func updateFTMS(normalizeSpeed: UInt16, currentCadence: UInt16, currentResistance: UInt8, currentWatt: UInt16, currentInclination: UInt16, currentDistance: UInt64) -> Bool
{
peripheralManager.NormalizeSpeed = normalizeSpeed
peripheralManager.CurrentCadence = currentCadence
peripheralManager.CurrentResistance = currentResistance
peripheralManager.CurrentWatt = currentWatt
peripheralManager.CurrentInclination = currentInclination
+ peripheralManager.CurrentDistance = currentDistance
return peripheralManager.connected;
}
@@ -70,6 +71,7 @@ class BLEPeripheralManagerTreadmillZwift: NSObject, CBPeripheralManagerDelegate
public var CurrentResistance: UInt8! = 0
public var CurrentWatt: UInt16! = 0
public var CurrentInclination: UInt16! = 0
+ public var CurrentDistance: UInt64! = 0
public var lastCurrentSlope: UInt64! = 0;
public var serviceToggle: UInt8 = 0
@@ -314,14 +316,15 @@ class BLEPeripheralManagerTreadmillZwift: NSObject, CBPeripheralManagerDelegate
}
func calculateTreadmillData() -> Data {
- let flags0:UInt8 = 0x08
+ let flags0:UInt8 = 0x0C
let flags1:UInt8 = 0x01
var treadmillData: [UInt8] = [flags0, flags1, (UInt8)(self.NormalizeSpeed & 0xFF), (UInt8)((self.NormalizeSpeed >> 8) & 0xFF),
+ (UInt8)(self.CurrentDistance & 0xFF), (UInt8)((self.CurrentDistance >> 8) & 0xFF), (UInt8)((self.CurrentDistance >> 16) & 0xFF),
(UInt8)(self.CurrentInclination & 0xFF), (UInt8)((self.CurrentInclination >> 8) & 0xFF),
(UInt8)(self.CurrentInclination & 0xFF), (UInt8)((self.CurrentInclination >> 8) & 0xFF),
self.heartRate]
- let treadmillDataData = Data(bytes: &treadmillData, count: 10)
+ let treadmillDataData = Data(bytes: &treadmillData, count: 13)
return treadmillDataData
}
diff --git a/src/keepbike.cpp b/src/keepbike.cpp
index 2138b660d..278a15fe5 100644
--- a/src/keepbike.cpp
+++ b/src/keepbike.cpp
@@ -186,6 +186,7 @@ double keepbike::bikeResistanceToPeloton(double resistance) {
}
void keepbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -220,7 +221,7 @@ void keepbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
{
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
m_watt = GetWattFromPacket(newValue);
@@ -231,17 +232,17 @@ void keepbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) {
diff --git a/src/kingsmithr2treadmill.cpp b/src/kingsmithr2treadmill.cpp
index 22d18a34c..c06d2367f 100644
--- a/src/kingsmithr2treadmill.cpp
+++ b/src/kingsmithr2treadmill.cpp
@@ -67,6 +67,8 @@ void kingsmithr2treadmill::writeCharacteristic(const QString &data, const QStrin
encrypted.append(ENCRYPT_TABLE_v4[idx]);
else if (settings.value(QZSettings::kingsmith_encrypt_v5, QZSettings::default_kingsmith_encrypt_v5).toBool())
encrypted.append(ENCRYPT_TABLE_v5[idx]);
+ else if (settings.value(QZSettings::kingsmith_encrypt_g1_walking_pad, QZSettings::default_kingsmith_encrypt_g1_walking_pad).toBool())
+ encrypted.append(ENCRYPT_TABLE_v6[idx]);
else
encrypted.append(ENCRYPT_TABLE[idx]);
}
@@ -269,6 +271,8 @@ void kingsmithr2treadmill::characteristicChanged(const QLowEnergyCharacteristic
idx = ENCRYPT_TABLE_v4.indexOf(ch);
else if (settings.value(QZSettings::kingsmith_encrypt_v5, QZSettings::default_kingsmith_encrypt_v5).toBool())
idx = ENCRYPT_TABLE_v5.indexOf(ch);
+ else if (settings.value(QZSettings::kingsmith_encrypt_g1_walking_pad, QZSettings::default_kingsmith_encrypt_g1_walking_pad).toBool())
+ idx = ENCRYPT_TABLE_v6.indexOf(ch);
else
idx = ENCRYPT_TABLE.indexOf(ch);
decrypted.append(PLAINTEXT_TABLE[idx]);
@@ -407,6 +411,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();
@@ -460,6 +467,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 +497,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);
diff --git a/src/kingsmithr2treadmill.h b/src/kingsmithr2treadmill.h
index 0c63b8d63..90ec55f6b 100644
--- a/src/kingsmithr2treadmill.h
+++ b/src/kingsmithr2treadmill.h
@@ -56,6 +56,8 @@ class kingsmithr2treadmill : public treadmill {
QStringLiteral("ZaCw4FGHIJqLhN9P+RVTU/WcY6ObDdefgEijklmnopQrsBuvMxXz1yA2t5078KS3=").toUtf8();
const QByteArray ENCRYPT_TABLE_v5 =
QStringLiteral("iaCw4FGHIJqLhN+P9RVTU/WcY6ObDdefgEZjklmnopQrsBuvMxXz1yA2t5078KS3=").toUtf8();
+ const QByteArray ENCRYPT_TABLE_v6 =
+ QStringLiteral("ZaCw4FGHIJqLhN+P8RVTU/WcY6ObDdefgEijklmnopQrsBuvMxXz1yA2t5079KS3=").toUtf8();
double GetInclinationFromPacket(const QByteArray &packet);
double GetKcalFromPacket(const QByteArray &packet);
@@ -88,6 +90,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;
diff --git a/src/lifefitnesstreadmill.cpp b/src/lifefitnesstreadmill.cpp
index b576da836..e720b5d3f 100644
--- a/src/lifefitnesstreadmill.cpp
+++ b/src/lifefitnesstreadmill.cpp
@@ -398,7 +398,7 @@ void lifefitnesstreadmill::characteristicChanged(const QLowEnergyCharacteristic
if (Flags.heartRate) {
if (index < newValue.length()) {
- heart = ((double)((newValue.at(index))));
+ heart = ((double)(((uint8_t)newValue.at(index))));
emit debug(QStringLiteral("Current Heart: ") + QString::number(heart));
} else {
emit debug(QStringLiteral("Error on parsing heart!"));
@@ -573,7 +573,7 @@ void lifefitnesstreadmill::characteristicChanged(const QLowEnergyCharacteristic
#endif
{
if (Flags.heartRate && !disable_hr_frommachinery && newValue.length() > index) {
- Heart = ((double)((newValue.at(index))));
+ Heart = ((double)(((uint8_t)newValue.at(index))));
// index += 1; // NOTE: clang-analyzer-deadcode.DeadStores
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
} else {
diff --git a/src/main.cpp b/src/main.cpp
index ad68224b7..d317af74f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -282,7 +282,6 @@ void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QS
}
int main(int argc, char *argv[]) {
-
#ifdef Q_OS_WIN32
qputenv("QT_MULTIMEDIA_PREFERRED_PLUGINS", "windowsmediafoundation");
#endif
diff --git a/src/main.qml b/src/main.qml
index bd750d059..8d30b837d 100644
--- a/src/main.qml
+++ b/src/main.qml
@@ -22,6 +22,8 @@ ApplicationWindow {
signal gpxpreview_open_clicked(url name)
signal profile_open_clicked(url name)
signal trainprogram_open_clicked(url name)
+ signal trainprogram_open_other_folder(url name)
+ signal gpx_open_other_folder(url name)
signal trainprogram_preview(url name)
signal trainprogram_zwo_loaded(string s)
signal gpx_save_clicked()
@@ -658,6 +660,7 @@ ApplicationWindow {
onClicked: {
stackView.push("GPXList.qml")
stackView.currentItem.trainprogram_open_clicked.connect(gpx_open_clicked)
+ stackView.currentItem.trainprogram_open_other_folder.connect(gpx_open_other_folder)
stackView.currentItem.trainprogram_preview.connect(gpxpreview_open_clicked)
stackView.currentItem.trainprogram_open_clicked.connect(function(url) {
stackView.pop();
@@ -673,6 +676,7 @@ ApplicationWindow {
onClicked: {
stackView.push("TrainingProgramsList.qml")
stackView.currentItem.trainprogram_open_clicked.connect(trainprogram_open_clicked)
+ stackView.currentItem.trainprogram_open_other_folder.connect(trainprogram_open_other_folder)
stackView.currentItem.trainprogram_preview.connect(trainprogram_preview)
stackView.currentItem.trainprogram_open_clicked.connect(function(url) {
stackView.pop();
@@ -747,7 +751,7 @@ ApplicationWindow {
}
ItemDelegate {
- text: "version 2.16.24"
+ text: "version 2.16.32"
width: parent.width
}
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index cc21774ec..4e24e4d26 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -356,14 +356,14 @@ void MainWindow::loadTrainProgram(const QString &fileName) {
delete trainProgram;
}
- trainProgram = trainprogram::load(fileName, bluetoothManager);
+ trainProgram = trainprogram::load(fileName, bluetoothManager, fileName.right(3).toUpper());
} else if (fileName.endsWith(QStringLiteral("gpx"))) {
if (trainProgram) {
delete trainProgram;
}
gpx g;
QList list;
- auto g_list = g.open(fileName);
+ auto g_list = g.open(fileName, bluetoothManager->device() ? bluetoothManager->device()->deviceType() : bluetoothdevice::BIKE);
list.reserve(g_list.count() + 1);
for (const auto &p : qAsConst(g_list)) {
trainrow r;
diff --git a/src/mcfbike.cpp b/src/mcfbike.cpp
index 3fe120d43..96b97afbd 100644
--- a/src/mcfbike.cpp
+++ b/src/mcfbike.cpp
@@ -177,6 +177,7 @@ double mcfbike::bikeResistanceToPeloton(double resistance) {
}
void mcfbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -210,11 +211,11 @@ void mcfbike::characteristicChanged(const QLowEnergyCharacteristic &characterist
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
m_watt = (((uint16_t)newValue.at(9) << 8) | (uint16_t)((uint8_t)newValue.at(10)));
@@ -222,14 +223,14 @@ void mcfbike::characteristicChanged(const QLowEnergyCharacteristic &characterist
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()))));
+ (60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(now))));
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
qDebug() << QStringLiteral("Current Speed: ") + QString::number(Speed.value());
qDebug() << QStringLiteral("Current Calculate Distance: ") + QString::number(Distance.value());
diff --git a/src/mepanelbike.cpp b/src/mepanelbike.cpp
index e0d51ec8a..891ca38af 100644
--- a/src/mepanelbike.cpp
+++ b/src/mepanelbike.cpp
@@ -130,6 +130,7 @@ void mepanelbike::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void mepanelbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -156,7 +157,7 @@ void mepanelbike::characteristicChanged(const QLowEnergyCharacteristic &characte
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
break;
@@ -233,17 +234,17 @@ void mepanelbike::characteristicChanged(const QLowEnergyCharacteristic &characte
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool() && disable_hr_frommachinery) {
diff --git a/src/metric.cpp b/src/metric.cpp
index 31c174567..426920949 100644
--- a/src/metric.cpp
+++ b/src/metric.cpp
@@ -323,7 +323,10 @@ double metric::powerPeak(QList *session, int seconds) {
std::sort(bests.begin(), bests.end(), CompareBests());
- return bests.first().avg;
+ if(bests.length() > 0)
+ return bests.first().avg;
+ else
+ return 0;
}
// VO2 (L/min) = 0.0108 x power (W) + 0.007 x body mass (kg)
diff --git a/src/metric.h b/src/metric.h
index c13be8668..19e13125f 100644
--- a/src/metric.h
+++ b/src/metric.h
@@ -42,8 +42,6 @@ class metric {
void operator+=(double);
void setPaused(bool p);
void setLap(bool accumulator);
- void setColor(QString color) { m_color = color; }
- QString color() { return m_color; }
static double calculateMaxSpeedFromPower(double power, double inclination);
static double calculatePowerFromSpeed(double speed, double inclination);
@@ -54,7 +52,7 @@ class metric {
static double calculateKCalfromHR(double HR_AVG, double elapsed);
static double powerPeak(QList *session, int seconds);
-
+
private:
double m_value = 0;
double m_totValue = 0;
@@ -77,7 +75,6 @@ class metric {
_metric_type m_type = METRIC_OTHER;
bool paused = false;
- QString m_color;
};
#endif // METRIC_H
diff --git a/src/nautilusbike.cpp b/src/nautilusbike.cpp
index e6c146ef4..360e9ac16 100644
--- a/src/nautilusbike.cpp
+++ b/src/nautilusbike.cpp
@@ -132,6 +132,7 @@ void nautilusbike::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void nautilusbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
@@ -172,7 +173,7 @@ void nautilusbike::characteristicChanged(const QLowEnergyCharacteristic &charact
if (watts())
KCal += ((((0.048 * ((double)watts()) + 1.19) * weight * 3.5) / 200.0) /
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
- QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
// double kcal = GetKcalFromPacket(newValue);
// double distance = GetDistanceFromPacket(newValue) *
@@ -189,11 +190,11 @@ void nautilusbike::characteristicChanged(const QLowEnergyCharacteristic &charact
Speed = speed;
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
emit debug(QStringLiteral("Current speed: ") + QString::number(speed));
emit debug(QStringLiteral("Current cadence: ") + QString::number(Cadence.value()));
diff --git a/src/nordictrackifitadbbike.cpp b/src/nordictrackifitadbbike.cpp
index eeb9868e4..839037346 100644
--- a/src/nordictrackifitadbbike.cpp
+++ b/src/nordictrackifitadbbike.cpp
@@ -124,6 +124,8 @@ nordictrackifitadbbike::nordictrackifitadbbike(bool noWriteResistance, bool noHe
processPendingDatagrams();
connect(socket, SIGNAL(readyRead()), this, SLOT(processPendingDatagrams()));
+ initRequest = true;
+
// ******************************************* virtual treadmill init *************************************
if (!firstStateChanged && !this->hasVirtualDevice()) {
bool virtual_device_enabled =
@@ -200,6 +202,7 @@ void nordictrackifitadbbike::processPendingDatagrams() {
QHostAddress sender;
QSettings settings;
uint16_t port;
+ bool freemotion_coachbike_b22_7 = settings.value(QZSettings::freemotion_coachbike_b22_7, QZSettings::default_freemotion_coachbike_b22_7).toBool();
while (socket->hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(socket->pendingDatagramSize());
@@ -247,7 +250,10 @@ void nordictrackifitadbbike::processPendingDatagrams() {
QStringList aValues = line.split(" ");
if (aValues.length()) {
resistance = getDouble(aValues.last());
- m_pelotonResistance = (100 / 32) * resistance;
+ if(freemotion_coachbike_b22_7)
+ m_pelotonResistance = (100 / 24) * resistance;
+ else
+ m_pelotonResistance = (100 / 32) * resistance;
qDebug() << QStringLiteral("Current Peloton Resistance: ") << m_pelotonResistance.value()
<< resistance;
if(!gearsAvailable)
@@ -322,15 +328,15 @@ void nordictrackifitadbbike::processPendingDatagrams() {
if (requestInclination != -100) {
double inc = qRound(requestInclination / 0.5) * 0.5;
if (inc != currentInclination().value()) {
- bool proform_studio = settings.value(QZSettings::proform_studio, QZSettings::default_proform_studio).toBool();
+ bool proform_studio = settings.value(QZSettings::proform_studio, QZSettings::default_proform_studio).toBool();
int x1 = 75;
int y2 = (int)(616.18 - (17.223 * (inc + gears())));
int y1Resistance = (int)(616.18 - (17.223 * currentInclination().value()));
- if(proform_studio) {
- int x1 = 1827;
- int y2 = (int)(806 - (21.375 * (inc + gears())));
- int y1Resistance = (int)(806 - (21.375 * currentInclination().value()));
+ if(proform_studio || freemotion_coachbike_b22_7) {
+ x1 = 1827;
+ y2 = (int)(806 - (21.375 * (inc + gears())));
+ y1Resistance = (int)(806 - (21.375 * currentInclination().value()));
}
lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Resistance) + " " +
@@ -349,6 +355,32 @@ void nordictrackifitadbbike::processPendingDatagrams() {
h->adb_sendcommand(lastCommand.toStdString().c_str());
#endif
#endif
+ // this bike has both inclination and resistance, let's try to handle both
+ // the Original Poster doesn't want anymore, but maybe it will be useful in the future
+ /*
+ if(freemotion_coachbike_b22_7) {
+ int x1 = 75;
+ int y2 = (int)(616.18 - (17.223 * (inc + gears())));
+ int y1Resistance = (int)(616.18 - (17.223 * currentInclination().value()));
+
+ lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Resistance) + " " +
+ QString::number(x1) + " " + QString::number(y2) + " 200";
+ qDebug() << " >> " + lastCommand;
+ #ifdef Q_OS_ANDROID
+ QAndroidJniObject command = QAndroidJniObject::fromString(lastCommand).object();
+ QAndroidJniObject::callStaticMethod("org/cagnulen/qdomyoszwift/QZAdbRemote",
+ "sendCommand", "(Ljava/lang/String;)V",
+ command.object());
+ #elif defined(Q_OS_WIN)
+ if (logcatAdbThread)
+ logcatAdbThread->runCommand("shell " + lastCommand);
+ #elif defined Q_OS_IOS
+ #ifndef IO_UNDER_QT
+ h->adb_sendcommand(lastCommand.toStdString().c_str());
+ #endif
+ #endif
+ }
+ */
}
}
@@ -490,6 +522,11 @@ void nordictrackifitadbbike::update() {
QSettings settings;
update_metrics(false, 0);
+ if (initRequest) {
+ initRequest = false;
+ emit connectedAndDiscovered();
+ }
+
// updating the treadmill console every second
if (sec1Update++ == (500 / refresh->interval())) {
sec1Update = 0;
diff --git a/src/nordictrackifitadbtreadmill.cpp b/src/nordictrackifitadbtreadmill.cpp
index 052efe348..b7118176f 100644
--- a/src/nordictrackifitadbtreadmill.cpp
+++ b/src/nordictrackifitadbtreadmill.cpp
@@ -22,9 +22,18 @@ void nordictrackifitadbtreadmillLogcatAdbThread::run() {
QString ip = settings.value(QZSettings::nordictrack_2950_ip, QZSettings::default_nordictrack_2950_ip).toString();
runAdbCommand("connect " + ip);
- while (1) {
+ while (!stop)
+ {
runAdbTailCommand("logcat");
+#ifndef Q_OS_WINDOWS
+ if(adbCommandPending.length() != 0) {
+ runAdbCommand(adbCommandPending);
+ adbCommandPending = "";
+ }
+ msleep(100);
+#endif
}
+
}
QString nordictrackifitadbtreadmillLogcatAdbThread::runAdbCommand(QString command) {
@@ -45,10 +54,22 @@ QString nordictrackifitadbtreadmillLogcatAdbThread::runAdbCommand(QString comman
return out;
}
+bool nordictrackifitadbtreadmillLogcatAdbThread::runCommand(QString command) {
+ if(adbCommandPending.length() == 0) {
+ adbCommandPending = command;
+ return true;
+ }
+ return false;
+}
+
void nordictrackifitadbtreadmillLogcatAdbThread::runAdbTailCommand(QString command) {
#ifdef Q_OS_WINDOWS
auto process = new QProcess;
QObject::connect(process, &QProcess::readyReadStandardOutput, [process, this]() {
+ if(stop) {
+ process->close();
+ return;
+ }
QString output = process->readAllStandardOutput();
qDebug() << "adbLogCat STDOUT << " << output;
QStringList lines = output.split('\n', Qt::SplitBehaviorFlags::SkipEmptyParts);
@@ -69,6 +90,12 @@ void nordictrackifitadbtreadmillLogcatAdbThread::runAdbTailCommand(QString comma
emit onSpeedInclination(speed, inclination);
if (wattFound)
emit onWatt(watt);
+#ifdef Q_OS_WINDOWS
+ if(adbCommandPending.length() != 0) {
+ runAdbCommand(adbCommandPending);
+ adbCommandPending = "";
+ }
+#endif
});
QObject::connect(process, &QProcess::readyReadStandardError, [process, this]() {
auto output = process->readAllStandardError();
@@ -100,12 +127,10 @@ nordictrackifitadbtreadmill::nordictrackifitadbtreadmill(bool noWriteResistance,
this->noHeartService = noHeartService;
initDone = false;
connect(refresh, &QTimer::timeout, this, &nordictrackifitadbtreadmill::update);
+ connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &nordictrackifitadbtreadmill::stopLogcatAdbThread);
QString ip = settings.value(QZSettings::nordictrack_2950_ip, QZSettings::default_nordictrack_2950_ip).toString();
refresh->start(200ms);
-#ifdef Q_OS_WIN32
- if (!nordictrack_ifit_adb_remote)
-#endif
{
socket = new QUdpSocket(this);
bool result = socket->bind(QHostAddress::AnyIPv4, 8002);
@@ -114,7 +139,7 @@ nordictrackifitadbtreadmill::nordictrackifitadbtreadmill(bool noWriteResistance,
connect(socket, SIGNAL(readyRead()), this, SLOT(processPendingDatagrams()));
}
#ifdef Q_OS_WIN32
- else {
+ {
logcatAdbThread = new nordictrackifitadbtreadmillLogcatAdbThread("logcatAdbThread");
connect(logcatAdbThread, &nordictrackifitadbtreadmillLogcatAdbThread::onSpeedInclination, this,
&nordictrackifitadbtreadmill::onSpeedInclination);
@@ -223,6 +248,21 @@ void nordictrackifitadbtreadmill::processPendingDatagrams() {
}
}
+ double inc = qRound(requestInclination / 0.5) * 0.5;
+ if(inc == currentInclination().value()) {
+ qDebug() << "ignoring inclination" << requestInclination;
+ requestInclination = -100;
+ }
+
+ int currentRequestInclination = requestInclination;
+
+ // since the motor of the treadmill is slow, let's filter the inclination changes to more than 1 second
+ if (requestInclination != -100 && lastInclinationChanged.secsTo(QDateTime::currentDateTime()) > 1) {
+ lastInclinationChanged = QDateTime::currentDateTime();
+ } else {
+ currentRequestInclination = -100;
+ }
+
bool nordictrack_ifit_adb_remote =
settings.value(QZSettings::nordictrack_ifit_adb_remote, QZSettings::default_nordictrack_ifit_adb_remote)
.toBool();
@@ -231,9 +271,9 @@ void nordictrackifitadbtreadmill::processPendingDatagrams() {
settings.value(QZSettings::nordictrack_x22i, QZSettings::default_nordictrack_x22i).toBool();
if (requestSpeed != -1) {
int x1 = 1845;
- int y1Speed = 807 - (int)((Speed.value() - 1) * 29.78);
+ int y1Speed = 807 - (int)((Speed.value() - 1) * 31);
// set speed slider to target position
- int y2 = y1Speed - (int)((requestSpeed - Speed.value()) * 29.78);
+ int y2 = y1Speed - (int)((requestSpeed - Speed.value()) * 31);
if(nordictrack_x22i) {
x1 = 1845;
y1Speed = (int) (785 - (23.636 * (Speed.value() - 1)));
@@ -247,51 +287,56 @@ void nordictrackifitadbtreadmill::processPendingDatagrams() {
QAndroidJniObject command = QAndroidJniObject::fromString(lastCommand).object();
QAndroidJniObject::callStaticMethod("org/cagnulen/qdomyoszwift/QZAdbRemote", "sendCommand",
"(Ljava/lang/String;)V", command.object());
+#elif defined(Q_OS_WIN)
+ if (logcatAdbThread)
+ logcatAdbThread->runCommand("shell " + lastCommand);
#elif defined Q_OS_IOS
#ifndef IO_UNDER_QT
h->adb_sendcommand(lastCommand.toStdString().c_str());
#endif
#endif
requestSpeed = -1;
- } else if (requestInclination != -100) {
- 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);
- }
-
- lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Inclination) + " " +
- QString::number(x1) + " " + QString::number(y2) + " 200";
- qDebug() << " >> " + lastCommand;
+ } else if (currentRequestInclination != -100) {
+ requestInclination = inc;
+ int x1 = 75;
+ int y1Inclination = 807 - (int)((currentInclination().value() + 3) * 31.1);
+ // set speed slider to target position
+ int y2 = y1Inclination - (int)((requestInclination - currentInclination().value()) * 31.1);
+
+ 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;
#ifdef Q_OS_ANDROID
- QAndroidJniObject command = QAndroidJniObject::fromString(lastCommand).object();
- QAndroidJniObject::callStaticMethod("org/cagnulen/qdomyoszwift/QZAdbRemote", "sendCommand",
- "(Ljava/lang/String;)V", command.object());
+ QAndroidJniObject command = QAndroidJniObject::fromString(lastCommand).object();
+ QAndroidJniObject::callStaticMethod("org/cagnulen/qdomyoszwift/QZAdbRemote", "sendCommand",
+ "(Ljava/lang/String;)V", command.object());
+#elif defined(Q_OS_WIN)
+ if (logcatAdbThread)
+ logcatAdbThread->runCommand("shell " + lastCommand);
#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;
}
}
- QByteArray message = (QString::number(requestSpeed) + ";" + QString::number(requestInclination)).toLocal8Bit();
- // we have to separate the 2 commands
- if (requestSpeed == -1)
- requestInclination = -100;
- requestSpeed = -1;
- int ret = socket->writeDatagram(message, message.size(), sender, 8003);
- qDebug() << QString::number(ret) + " >> " + message;
+ if(!nordictrack_ifit_adb_remote) {
+ QByteArray message = (QString::number(requestSpeed) + ";" + QString::number(currentRequestInclination)).toLocal8Bit();
+ // we have to separate the 2 commands
+ if (requestSpeed == -1)
+ requestInclination = -100;
+ requestSpeed = -1;
+ int ret = socket->writeDatagram(message, message.size(), sender, 8003);
+ qDebug() << QString::number(ret) + " >> " + message;
+ }
if (watts(weight))
KCal +=
@@ -439,3 +484,24 @@ void nordictrackifitadbtreadmill::changeInclinationRequested(double grade, doubl
}
bool nordictrackifitadbtreadmill::connected() { return true; }
+
+void nordictrackifitadbtreadmill::stopLogcatAdbThread() {
+ qDebug() << "stopLogcatAdbThread()";
+
+ initiateThreadStop();
+ logcatAdbThread->quit();
+ logcatAdbThread->terminate();
+
+#ifdef Q_OS_WIN32
+ QProcess process;
+ QString command = "/c wmic process where name='adb.exe' delete";
+ process.start("cmd.exe", QStringList(command.split(' ')));
+ process.waitForFinished(-1); // will wait forever until finished
+#endif
+
+ logcatAdbThread->wait();
+}
+
+void nordictrackifitadbtreadmill::initiateThreadStop() {
+ logcatAdbThread->stop = true;
+}
diff --git a/src/nordictrackifitadbtreadmill.h b/src/nordictrackifitadbtreadmill.h
index 990e7c035..ec50e3963 100644
--- a/src/nordictrackifitadbtreadmill.h
+++ b/src/nordictrackifitadbtreadmill.h
@@ -30,8 +30,10 @@ class nordictrackifitadbtreadmillLogcatAdbThread : public QThread {
public:
explicit nordictrackifitadbtreadmillLogcatAdbThread(QString s);
+ bool runCommand(QString command);
void run() override;
+ bool stop = false;
signals:
void onSpeedInclination(double speed, double inclination);
@@ -39,6 +41,7 @@ class nordictrackifitadbtreadmillLogcatAdbThread : public QThread {
void onWatt(double watt);
private:
+ QString adbCommandPending = "";
double speed = 0;
double inclination = 0;
double watt = 0;
@@ -63,11 +66,13 @@ class nordictrackifitadbtreadmill : public treadmill {
void forceIncline(double incline);
void forceSpeed(double speed);
double getDouble(QString v);
+ void initiateThreadStop();
QTimer *refresh;
uint8_t sec1Update = 0;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ QDateTime lastInclinationChanged = QDateTime::currentDateTime();
uint8_t firstStateChanged = 0;
uint16_t m_watts = 0;
bool wattReadFromTM = false;
@@ -102,6 +107,9 @@ class nordictrackifitadbtreadmill : public treadmill {
void changeInclinationRequested(double grade, double percentage);
void update();
+
+ public slots:
+ void stopLogcatAdbThread();
};
-#endif // NORDICTRACKIFITADBTREADMILL_H
+#endif // NORDICTRACKIFITADBTREADMILL_H
\ No newline at end of file
diff --git a/src/npecablebike.cpp b/src/npecablebike.cpp
index 44ce83f1b..f76250f00 100644
--- a/src/npecablebike.cpp
+++ b/src/npecablebike.cpp
@@ -111,6 +111,7 @@ void npecablebike::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void npecablebike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -167,8 +168,8 @@ void npecablebike::characteristicChanged(const QLowEnergyCharacteristic &charact
if (cadence >= 0) {
Cadence = cadence;
}
- lastGoodCadence = QDateTime::currentDateTime();
- } else if (lastGoodCadence.msecsTo(QDateTime::currentDateTime()) > 2000) {
+ lastGoodCadence = now;
+ } else if (lastGoodCadence.msecsTo(now) > 2000) {
Cadence = 0;
}
}
@@ -181,12 +182,12 @@ void npecablebike::characteristicChanged(const QLowEnergyCharacteristic &charact
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
Speed = Cadence.value() * settings.value(QZSettings::cadence_sensor_speed_ratio, QZSettings::default_cadence_sensor_speed_ratio).toDouble();
} else {
- Speed = metric::calculateSpeedFromPower(watts(), Inclination.value(), Speed.value(),fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ Speed = metric::calculateSpeedFromPower(watts(), Inclination.value(), Speed.value(),fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
// Resistance = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
@@ -200,7 +201,7 @@ void npecablebike::characteristicChanged(const QLowEnergyCharacteristic &charact
3.5) /
200.0) /
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
- QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
} else if (characteristic.uuid() == QBluetoothUuid::HeartRateMeasurement) {
@@ -250,7 +251,7 @@ void npecablebike::characteristicChanged(const QLowEnergyCharacteristic &charact
(uint16_t)((uint8_t)newValue.at(index)))) /
100.0;
} else {
- Speed = metric::calculateSpeedFromPower(watts(), Inclination.value(), Speed.value(),fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ Speed = metric::calculateSpeedFromPower(watts(), Inclination.value(), Speed.value(),fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
index += 2;
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
@@ -294,7 +295,7 @@ void npecablebike::characteristicChanged(const QLowEnergyCharacteristic &charact
index += 3;
} else {
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
}
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
@@ -362,7 +363,7 @@ void npecablebike::characteristicChanged(const QLowEnergyCharacteristic &charact
3.5) /
200.0) /
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
- QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
}
@@ -375,7 +376,7 @@ void npecablebike::characteristicChanged(const QLowEnergyCharacteristic &charact
#endif
{
if (Flags.heartRate && !disable_hr_frommachinery && newValue.length() > index) {
- Heart = ((double)((newValue.at(index))));
+ Heart = ((double)(((uint8_t)newValue.at(index))));
// index += 1; // NOTE: clang-analyzer-deadcode.DeadStores
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
} else {
@@ -411,7 +412,7 @@ void npecablebike::characteristicChanged(const QLowEnergyCharacteristic &charact
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
diff --git a/src/octaneelliptical.cpp b/src/octaneelliptical.cpp
index 2257de163..1b0f2a858 100644
--- a/src/octaneelliptical.cpp
+++ b/src/octaneelliptical.cpp
@@ -170,10 +170,18 @@ octaneelliptical::octaneelliptical(uint32_t pollDeviceTime, bool noConsole, bool
actualPace2Sign.clear();
// SPEED
+ actualPaceSign.append(0x01);
actualPaceSign.append(0x07);
- actualPaceSign.append(0x03);
+ actualPace2Sign.append((char)0x00);
actualPace2Sign.append(0x07);
- actualPace2Sign.append(0x03);
+
+ actualHR.append((char)0x02);
+ actualHR.append((char)0x11);
+
+ actualResistance.append((char)0x01);
+ actualResistance.append((char)0x09);
+
+ actualOdometer.append((char)0x0b);
m_watt.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
@@ -340,41 +348,66 @@ void octaneelliptical::characteristicChanged(const QLowEnergyCharacteristic &cha
if ((newValue.length() != 20))
return;
- if ((uint8_t)newValue[0] == 0xa5 && newValue[1] == 0x09) {
- Resistance = (uint8_t)newValue[4];
- emit debug(QStringLiteral("Current resistance: ") + QString::number(Resistance.value()));
- return;
- }
-
- if (!newValue.contains(actualPaceSign) && !newValue.contains(actualPace2Sign))
- return;
-
- int16_t i = newValue.indexOf(actualPaceSign) + 2;
- if (i <= 1)
- i = newValue.indexOf(actualPace2Sign) + 2;
+ if (newValue.contains(actualResistance)) {
+ int16_t i = newValue.indexOf(actualResistance) + 2;
- if (i + 1 >= newValue.length())
- return;
+ if (i + 1 < newValue.length()) {
+ Resistance = ((uint8_t)value.at(i));
+ emit debug(QStringLiteral("Current resistance: ") + QString::number(Resistance.value()));
+ }
+ }
- double speed = GetSpeedFromPacket(value, i);
- if (isinf(speed))
- return;
+ if (newValue.contains(actualHR)) {
+ bool disable_hr_frommachinery = settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
#ifdef Q_OS_ANDROID
- if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
- Heart = (uint8_t)KeepAwakeHelper::heart();
- else
+ if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
+ Heart = (uint8_t)KeepAwakeHelper::heart();
+ else
#endif
- {
- /*if(heartRateBeltName.startsWith("Disabled"))
- Heart = value.at(18);*/
+ {
+ if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
+ int16_t i = newValue.indexOf(actualHR) + 2;
+
+ if (i + 1 < newValue.length()) {
+ uint8_t heart = ((uint8_t)value.at(i));
+ if (heart == 0 || disable_hr_frommachinery) {
+ update_hr_from_external();
+ } else {
+ Heart = heart;
+ }
+ }
+ }
+ }
+ emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
}
- emit debug(QStringLiteral("Current speed: ") + QString::number(speed));
- if (Speed.value() != speed) {
- emit speedChanged(speed);
- }
- Speed = speed;
+ int16_t i = newValue.indexOf(actualPaceSign) + 2;
+ /*if (i <= 1)
+ i = newValue.indexOf(actualPace2Sign) + 1;*/
+
+ if (i + 1 >= newValue.length() || i <= 1) {
+ // fallback for a previous firmware version
+ if(newValue.length() >= 20 && (uint8_t)newValue.at(0) == 0xa5) {
+ if(newValue.at(6) == 0x07) {
+ i = 7;
+ } else if(newValue.at(15) == 0x07) {
+ i = 16;
+ } else {
+ return;
+ }
+ } else {
+ return;
+ }
+ }
+
+ Cadence = ((uint8_t)value.at(i));
+ emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
+
+ // Q37xi has a fixed stride length of 20.5 inches (52cm).
+ Speed = (((Cadence.value() / 2.0) * 52.07 * 60) / 10000) * 0.84135;
+ emit speedChanged(speed.value());
+ emit debug(QStringLiteral("Current speed: ") + QString::number(Speed.value()));
if (!firstCharacteristicChanged) {
if (watts())
@@ -385,13 +418,12 @@ void octaneelliptical::characteristicChanged(const QLowEnergyCharacteristic &cha
(60000.0 / ((double)lastTimeCharacteristicChanged.msecsTo(
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
-
- Distance += ((Speed.value() / 3600.0) /
- (1000.0 / (lastTimeCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
+ Distance += ((Speed.value() / 3600000.0) *
+ ((double)lastTimeCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
lastTimeCharacteristicChanged = QDateTime::currentDateTime();
}
- emit debug(QStringLiteral("Current Distance Calculated: ") + QString::number(Distance.value()));
+ emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
if (m_control->error() != QLowEnergyController::NoError) {
@@ -402,8 +434,8 @@ void octaneelliptical::characteristicChanged(const QLowEnergyCharacteristic &cha
}
double octaneelliptical::GetSpeedFromPacket(const QByteArray &packet, int index) {
- uint16_t convertedData = (packet.at(index + 4) << 8) | ((uint8_t)packet.at(index + 5));
- return ((double)convertedData) / 100.0;
+ uint16_t convertedData = ((uint8_t)packet.at(index));
+ return ((double)convertedData) / 10.0;
}
void octaneelliptical::btinit(bool startTape) {
diff --git a/src/octaneelliptical.h b/src/octaneelliptical.h
index 6f590dede..ef565c047 100644
--- a/src/octaneelliptical.h
+++ b/src/octaneelliptical.h
@@ -62,6 +62,12 @@ class octaneelliptical : public elliptical {
QByteArray actualPaceSign;
QByteArray actualPace2Sign;
+ QByteArray actualHR;
+ QByteArray actualResistance;
+ QByteArray actualOdometer;
+ int distance = 0;
+ QDateTime lastTimeDistance = QDateTime::currentDateTime();
+ metric speed;
QTimer *refresh;
diff --git a/src/pafersbike.cpp b/src/pafersbike.cpp
index 7e698fbe4..d7c247469 100644
--- a/src/pafersbike.cpp
+++ b/src/pafersbike.cpp
@@ -192,6 +192,7 @@ double pafersbike::GetWattFromPacket(const QByteArray &packet) {
}
void pafersbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -216,7 +217,7 @@ void pafersbike::characteristicChanged(const QLowEnergyCharacteristic &character
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
Resistance = ((uint8_t)newValue.at(5));
@@ -230,17 +231,17 @@ void pafersbike::characteristicChanged(const QLowEnergyCharacteristic &character
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) {
diff --git a/src/proformbike.cpp b/src/proformbike.cpp
index 78c44066b..8dc2d2500 100644
--- a/src/proformbike.cpp
+++ b/src/proformbike.cpp
@@ -162,6 +162,10 @@ void proformbike::forceResistance(resistance_t requestResistance) {
.value(QZSettings::proform_hybrid_trainer_PFEL03815, QZSettings::default_proform_hybrid_trainer_PFEL03815)
.toBool();
bool proform_bike_sb = settings.value(QZSettings::proform_bike_sb, QZSettings::default_proform_bike_sb).toBool();
+ bool proform_cycle_trainer_300_ci =
+ settings.value(QZSettings::proform_cycle_trainer_300_ci, QZSettings::default_proform_cycle_trainer_300_ci)
+ .toBool();
+ bool proform_bike_225_csx = settings.value(QZSettings::proform_bike_225_csx, QZSettings::default_proform_bike_225_csx).toBool();
if (proform_studio || proform_tdf_10) {
const uint8_t res1[] = {0xfe, 0x02, 0x16, 0x03};
@@ -265,7 +269,7 @@ void proformbike::forceResistance(resistance_t requestResistance) {
writeCharacteristic((uint8_t *)res16, sizeof(res16), QStringLiteral("resistance16"), false, true);
break;
}
- } else if (nordictrack_gx_2_7) {
+ } else if (nordictrack_gx_2_7 || proform_bike_225_csx) {
const uint8_t res1[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01,
0x04, 0xc2, 0x01, 0x00, 0xda, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t res2[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01,
@@ -406,6 +410,11 @@ void proformbike::forceResistance(resistance_t requestResistance) {
const uint8_t res16[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01,
0x04, 0xd1, 0x26, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00};
+ if(proform_cycle_trainer_300_ci) {
+ uint8_t noOpData7[] = {0xfe, 0x02, 0x0d, 0x02};
+ writeCharacteristic((uint8_t *)noOpData7, sizeof(noOpData7), QStringLiteral("resrequest"), false, false);
+ }
+
switch (requestResistance) {
case 1:
writeCharacteristic((uint8_t *)res1, sizeof(res1), QStringLiteral("resistance1"), false, true);
@@ -509,6 +518,9 @@ void proformbike::update() {
bool proform_tdf_10 = settings.value(QZSettings::proform_tdf_10, QZSettings::default_proform_tdf_10).toBool();
bool nordictrack_gx_2_7 =
settings.value(QZSettings::nordictrack_gx_2_7, QZSettings::default_nordictrack_gx_2_7).toBool();
+ bool proform_cycle_trainer_300_ci =
+ settings.value(QZSettings::proform_cycle_trainer_300_ci, QZSettings::default_proform_cycle_trainer_300_ci)
+ .toBool();
bool proform_cycle_trainer_400 =
settings.value(QZSettings::proform_cycle_trainer_400, QZSettings::default_proform_cycle_trainer_400)
.toBool();
@@ -521,6 +533,7 @@ void proformbike::update() {
bool proform_bike_PFEVEX71316_1 =
settings.value(QZSettings::proform_bike_PFEVEX71316_1, QZSettings::default_proform_bike_PFEVEX71316_1)
.toBool();
+ bool proform_bike_225_csx = settings.value(QZSettings::proform_bike_225_csx, QZSettings::default_proform_bike_225_csx).toBool();
uint8_t noOpData1[] = {0xfe, 0x02, 0x19, 0x03};
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x07, 0x15, 0x02, 0x00,
@@ -534,6 +547,13 @@ void proformbike::update() {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData7[] = {0xfe, 0x02, 0x0d, 0x02};
+ // proform_bike_225_csx
+ uint8_t noOpData2_proform_bike_225_csx[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x00, 0x0d, 0x3c, 0x96, 0x31, 0x00, 0x00, 0x40, 0x40, 0x00, 0x80};
+ uint8_t noOpData3_proform_bike_225_csx[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x85, 0xb1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData5_proform_bike_225_csx[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x07, 0x15, 0x02, 0x00, 0x0f, 0x80, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData6_proform_bike_225_csx[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+
// proform_bike_sb
uint8_t noOpData2_proform_bike_sb[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x00,
0x0d, 0x3c, 0x9e, 0x31, 0x00, 0x00, 0x40, 0x40, 0x00, 0x80};
@@ -601,7 +621,7 @@ void proformbike::update() {
switch (counterPoll) {
case 0:
- if (nordictrack_gx_2_7 || proform_hybrid_trainer_PFEL03815 || proform_bike_sb) {
+ if (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci || proform_hybrid_trainer_PFEL03815 || proform_bike_sb || proform_bike_225_csx) {
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"));
} else {
writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp"));
@@ -610,15 +630,18 @@ void proformbike::update() {
case 1:
if (proform_studio || proform_tdf_10)
writeCharacteristic(noOpData2_proform_studio, sizeof(noOpData2_proform_studio), QStringLiteral("noOp"));
- else if (nordictrack_gx_2_7) {
+ else if (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci) {
writeCharacteristic(noOpData2_nordictrack_gx_2_7, sizeof(noOpData2_nordictrack_gx_2_7),
QStringLiteral("noOp"));
} else if (proform_hybrid_trainer_PFEL03815) {
writeCharacteristic(noOpData2_proform_hybrid_trainer_PFEL03815,
sizeof(noOpData2_proform_hybrid_trainer_PFEL03815), QStringLiteral("noOp"));
- } else if (proform_tour_de_france_clc)
+ } else if (proform_tour_de_france_clc) {
writeCharacteristic(noOpData2_proform_tour_de_france_clc, sizeof(noOpData2_proform_tour_de_france_clc),
QStringLiteral("noOp"));
+ } else if (proform_bike_225_csx)
+ writeCharacteristic(noOpData2_proform_bike_225_csx, sizeof(noOpData2_proform_bike_225_csx),
+ QStringLiteral("noOp"));
else if (proform_cycle_trainer_400)
writeCharacteristic(noOpData2_proform_cycle_trainer_400, sizeof(noOpData2_proform_cycle_trainer_400),
QStringLiteral("noOp"));
@@ -637,15 +660,18 @@ void proformbike::update() {
else if (proform_tour_de_france_clc)
writeCharacteristic(noOpData3_proform_tour_de_france_clc, sizeof(noOpData3_proform_tour_de_france_clc),
QStringLiteral("noOp"));
- else if (nordictrack_gx_2_7) {
+ else if (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci) {
writeCharacteristic(noOpData3_nordictrack_gx_2_7, sizeof(noOpData3_nordictrack_gx_2_7),
QStringLiteral("noOp"));
} else if (proform_hybrid_trainer_PFEL03815) {
writeCharacteristic(noOpData3_proform_hybrid_trainer_PFEL03815,
sizeof(noOpData3_proform_hybrid_trainer_PFEL03815), QStringLiteral("noOp"));
- } else if (proform_cycle_trainer_400)
+ } else if (proform_cycle_trainer_400) {
writeCharacteristic(noOpData3_proform_cycle_trainer_400, sizeof(noOpData3_proform_cycle_trainer_400),
QStringLiteral("noOp"));
+ } else if (proform_bike_225_csx)
+ writeCharacteristic(noOpData3_proform_bike_225_csx, sizeof(noOpData3_proform_bike_225_csx),
+ QStringLiteral("noOp"));
else if (proform_bike_sb)
writeCharacteristic(noOpData3_proform_bike_sb, sizeof(noOpData3_proform_bike_sb),
QStringLiteral("noOp"));
@@ -658,7 +684,7 @@ void proformbike::update() {
case 3:
if (proform_studio || proform_tdf_10)
writeCharacteristic(noOpData4_proform_studio, sizeof(noOpData4_proform_studio), QStringLiteral("noOp"));
- else if (nordictrack_gx_2_7) {
+ else if (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci) {
innerWriteResistance();
writeCharacteristic(noOpData7, sizeof(noOpData7), QStringLiteral("noOp"));
} else if (proform_hybrid_trainer_PFEL03815) {
@@ -668,18 +694,23 @@ void proformbike::update() {
} else if (proform_bike_sb) {
innerWriteResistance();
writeCharacteristic(noOpData7, sizeof(noOpData7), QStringLiteral("noOp"));
+ } else if(proform_bike_225_csx) {
+ writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp"));
} else
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"));
break;
case 4:
if (proform_studio || proform_tdf_10)
writeCharacteristic(noOpData5_proform_studio, sizeof(noOpData5_proform_studio), QStringLiteral("noOp"));
- else if (nordictrack_gx_2_7) {
+ else if (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci) {
writeCharacteristic(noOpData5_nordictrack_gx_2_7, sizeof(noOpData5_nordictrack_gx_2_7),
QStringLiteral("noOp"));
} else if (proform_hybrid_trainer_PFEL03815) {
writeCharacteristic(noOpData5_proform_hybrid_trainer_PFEL03815,
sizeof(noOpData5_proform_hybrid_trainer_PFEL03815), QStringLiteral("noOp"));
+ } else if (proform_bike_225_csx) {
+ writeCharacteristic(noOpData5_proform_bike_225_csx, sizeof(noOpData5_proform_bike_225_csx),
+ QStringLiteral("noOp"));
} else if (proform_bike_sb)
writeCharacteristic(noOpData5_proform_bike_sb, sizeof(noOpData5_proform_bike_sb),
QStringLiteral("noOp"));
@@ -692,9 +723,14 @@ void proformbike::update() {
case 5:
if (proform_studio || proform_tdf_10)
writeCharacteristic(noOpData6_proform_studio, sizeof(noOpData6_proform_studio), QStringLiteral("noOp"));
- else if (proform_tour_de_france_clc)
+ else if (proform_tour_de_france_clc) {
writeCharacteristic(noOpData6_proform_tour_de_france_clc, sizeof(noOpData6_proform_tour_de_france_clc),
QStringLiteral("noOp"));
+ } else if (proform_bike_225_csx) {
+ writeCharacteristic(noOpData6_proform_bike_225_csx, sizeof(noOpData6_proform_bike_225_csx),
+ QStringLiteral("noOp"));
+ innerWriteResistance();
+ }
else if (proform_cycle_trainer_400)
writeCharacteristic(noOpData6_proform_cycle_trainer_400, sizeof(noOpData6_proform_cycle_trainer_400),
QStringLiteral("noOp"));
@@ -728,12 +764,14 @@ void proformbike::update() {
counterPoll++;
if (counterPoll > 6) {
counterPoll = 0;
+ } else if(counterPoll == 6 && proform_bike_225_csx) {
+ counterPoll = 0;
} else if (counterPoll == 6 &&
(proform_tour_de_france_clc || proform_cycle_trainer_400 || proform_bike_PFEVEX71316_1) &&
requestResistance == -1) {
// this bike sends the frame noOpData7 only when it needs to change the resistance
counterPoll = 0;
- } else if (counterPoll == 5 && (nordictrack_gx_2_7 || proform_hybrid_trainer_PFEL03815 || proform_bike_sb)) {
+ } else if (counterPoll == 5 && (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci || proform_hybrid_trainer_PFEL03815 || proform_bike_sb)) {
counterPoll = 0;
}
@@ -827,6 +865,7 @@ void proformbike::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void proformbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -845,6 +884,7 @@ void proformbike::characteristicChanged(const QLowEnergyCharacteristic &characte
bool proform_bike_sb = settings.value(QZSettings::proform_bike_sb, QZSettings::default_proform_bike_sb).toBool();
bool proform_bike_PFEVEX71316_1 =
settings.value(QZSettings::proform_bike_PFEVEX71316_1, QZSettings::default_proform_bike_PFEVEX71316_1).toBool();
+ bool proform_bike_225_csx = settings.value(QZSettings::proform_bike_225_csx, QZSettings::default_proform_bike_225_csx).toBool();
emit debug(QStringLiteral(" << ") + newValue.toHex(' '));
@@ -867,7 +907,7 @@ void proformbike::characteristicChanged(const QLowEnergyCharacteristic &characte
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
double incline =
@@ -909,7 +949,7 @@ void proformbike::characteristicChanged(const QLowEnergyCharacteristic &characte
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
double incline =
@@ -1025,6 +1065,7 @@ void proformbike::characteristicChanged(const QLowEnergyCharacteristic &characte
}
} else if (!nordictrack_gx_2_7) {
switch ((uint8_t)newValue.at(11)) {
+ case 0x00:
case 0x02:
Resistance = 1;
m_pelotonResistance = 10;
@@ -1204,7 +1245,7 @@ void proformbike::characteristicChanged(const QLowEnergyCharacteristic &characte
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
}
}
@@ -1213,18 +1254,18 @@ void proformbike::characteristicChanged(const QLowEnergyCharacteristic &characte
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
// KCal = (((uint16_t)((uint8_t)newValue.at(15)) << 8) + (uint16_t)((uint8_t) newValue.at(14)));
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
@@ -1270,11 +1311,14 @@ void proformbike::btinit() {
settings.value(QZSettings::nordictrack_gx_2_7, QZSettings::default_nordictrack_gx_2_7).toBool();
bool proform_cycle_trainer_400 =
settings.value(QZSettings::proform_cycle_trainer_400, QZSettings::default_proform_cycle_trainer_400).toBool();
+ bool proform_cycle_trainer_300_ci =
+ settings.value(QZSettings::proform_cycle_trainer_300_ci, QZSettings::default_proform_cycle_trainer_300_ci).toBool();
bool proform_hybrid_trainer_PFEL03815 =
settings
.value(QZSettings::proform_hybrid_trainer_PFEL03815, QZSettings::default_proform_hybrid_trainer_PFEL03815)
.toBool();
bool proform_bike_sb = settings.value(QZSettings::proform_bike_sb, QZSettings::default_proform_bike_sb).toBool();
+ bool proform_bike_225_csx = settings.value(QZSettings::proform_bike_225_csx, QZSettings::default_proform_bike_225_csx).toBool();
if (settings.value(QZSettings::proform_studio, QZSettings::default_proform_studio).toBool()) {
@@ -1517,6 +1561,108 @@ void proformbike::btinit() {
uint8_t initData12[] = {0xff, 0x08, 0x4a, 0xb6, 0x20, 0x98, 0x02, 0x00, 0x00, 0x93,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+ writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+ writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+ } else if (proform_cycle_trainer_300_ci) {
+ max_resistance = 16;
+
+ uint8_t initData10[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x07, 0x28, 0x90, 0x07,
+ 0x01, 0x10, 0xcc, 0x7a, 0x3e, 0xf4, 0xb8, 0x66, 0x3a, 0xf8};
+ uint8_t initData11[] = {0x01, 0x12, 0xb4, 0x72, 0x46, 0x1c, 0xf0, 0xbe, 0x92, 0x40,
+ 0x3c, 0xea, 0xce, 0xa4, 0x88, 0x76, 0x4a, 0x28, 0x04, 0xe2};
+ uint8_t initData12[] = {0xff, 0x08, 0xf6, 0xcc, 0xe0, 0x80, 0x02, 0x00, 0x00, 0xb5,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+ writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+ writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+ writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+
+ uint8_t noOpData0[] = {0xfe, 0x02, 0x17, 0x03};
+ uint8_t noOpData1[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData2[] = {0xff, 0x05, 0x00, 0x80, 0x01, 0x00, 0xa9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData3[] = {0xfe, 0x02, 0x19, 0x03};
+ uint8_t noOpData4[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x07, 0x15, 0x02, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData5[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData6[] = {0xfe, 0x02, 0x17, 0x03};
+ uint8_t noOpData7[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x00, 0x0d, 0x00, 0x10, 0x00, 0xc0, 0x1c, 0x4c, 0x00, 0x00, 0xe0};
+ uint8_t noOpData8[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x10, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData9[] = {0xfe, 0x02, 0x17, 0x03};
+ uint8_t noOpData10[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData11[] = {0xff, 0x05, 0x00, 0x80, 0x01, 0x00, 0xa9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData12[] = {0xfe, 0x02, 0x17, 0x03};
+ uint8_t noOpData13[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData14[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData15[] = {0xfe, 0x02, 0x17, 0x03};
+ uint8_t noOpData16[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData17[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+ writeCharacteristic(noOpData0, sizeof(noOpData0), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+
+ writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+
+ writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+
+ writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+
+ writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+
+ writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+
+ writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+
+ writeCharacteristic(noOpData7, sizeof(noOpData7), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+
+ writeCharacteristic(noOpData8, sizeof(noOpData8), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+
+ writeCharacteristic(noOpData9, sizeof(noOpData9), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+
+ writeCharacteristic(noOpData10, sizeof(noOpData10), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+
+ writeCharacteristic(noOpData11, sizeof(noOpData11), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+
+ writeCharacteristic(noOpData12, sizeof(noOpData12), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+
+ writeCharacteristic(noOpData13, sizeof(noOpData13), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+
+ writeCharacteristic(noOpData14, sizeof(noOpData14), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+
+ writeCharacteristic(noOpData15, sizeof(noOpData15), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+
+ writeCharacteristic(noOpData16, sizeof(noOpData16), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+
+ writeCharacteristic(noOpData17, sizeof(noOpData17), QStringLiteral("init"), false, false);
+ QThread::msleep(400);
+
+ } else if (proform_bike_225_csx) {
+ max_resistance = 10;
+ uint8_t initData10[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x07, 0x28, 0x90, 0x07, 0x01, 0xd2, 0x74, 0x14, 0xb2, 0x5e, 0x08, 0xa0, 0x5e, 0x0a};
+ uint8_t initData11[] = {0x01, 0x12, 0xbc, 0x6c, 0x1a, 0xc6, 0x90, 0x28, 0xe6, 0xa2, 0x64, 0x24, 0xe2, 0xae, 0x98, 0x50, 0x0e, 0xfa, 0xac, 0x9c};
+ uint8_t initData12[] = {0xff, 0x08, 0x4a, 0x36, 0x20, 0x98, 0x02, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
diff --git a/src/proformelliptical.cpp b/src/proformelliptical.cpp
index 7c9950057..162b8a2b4 100644
--- a/src/proformelliptical.cpp
+++ b/src/proformelliptical.cpp
@@ -233,13 +233,9 @@ void proformelliptical::characteristicChanged(const QLowEnergyCharacteristic &ch
return;
}
- double w = (double)(((uint16_t)((uint8_t)newValue.at(13)) << 8) + (uint16_t)((uint8_t)newValue.at(12)));
- if(w == 0 && !filterWattHole) {
- filterWattHole = true;
- qDebug() << QStringLiteral("filtering watt hole");
+ if(newValue.at(11) == 0) {
+ qDebug() << QStringLiteral("filtering resistance hole");
return;
- } else if(w > 0) {
- filterWattHole = false;
}
Resistance = GetResistanceFromPacket(newValue);
diff --git a/src/proformelliptical.h b/src/proformelliptical.h
index 8f955c1c9..1ccfcedde 100644
--- a/src/proformelliptical.h
+++ b/src/proformelliptical.h
@@ -70,8 +70,6 @@ class proformelliptical : public elliptical {
bool noWriteResistance = false;
bool noHeartService = false;
- bool filterWattHole = false;
-
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif
diff --git a/src/proformrower.cpp b/src/proformrower.cpp
index 2c6d5613f..c96a63020 100644
--- a/src/proformrower.cpp
+++ b/src/proformrower.cpp
@@ -478,6 +478,7 @@ double proformrower::GetResistanceFromPacket(QByteArray packet) {
}
void proformrower::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -492,7 +493,7 @@ void proformrower::characteristicChanged(const QLowEnergyCharacteristic &charact
if (newValue.length() == 20 && (uint8_t)newValue.at(0) == 0xff && newValue.at(1) == 0x11) {
Cadence = (uint8_t)(newValue.at(12));
StrokesCount += (Cadence.value()) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())) / 60000;
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)) / 60000;
emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
emit debug(QStringLiteral("Strokes Count: ") + QString::number(StrokesCount.value()));
uint16_t s = (((uint16_t)((uint8_t)newValue.at(14)) << 8) + (uint16_t)((uint8_t)newValue.at(13)));
@@ -518,14 +519,14 @@ void proformrower::characteristicChanged(const QLowEnergyCharacteristic &charact
if (watts())
KCal += ((((0.048 * ((double)watts()) + 1.19) * weight * 3.5) / 200.0) /
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
- QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
// KCal = (((uint16_t)((uint8_t)newValue.at(15)) << 8) + (uint16_t)((uint8_t) newValue.at(14)));
// Distance += ((Speed.value() / 3600000.0) *
- // ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ // ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
Distance = (((uint16_t)(((uint8_t)newValue.at(15)) << 8) + (uint16_t)((uint8_t)newValue.at(14)))) / 1000.0;
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
diff --git a/src/proformtelnetbike.cpp b/src/proformtelnetbike.cpp
new file mode 100644
index 000000000..f428f7cd1
--- /dev/null
+++ b/src/proformtelnetbike.cpp
@@ -0,0 +1,487 @@
+#include "proformtelnetbike.h"
+#ifdef Q_OS_ANDROID
+#include "keepawakehelper.h"
+#endif
+#include "virtualbike.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace std::chrono_literals;
+
+proformtelnetbike::proformtelnetbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
+ double bikeResistanceGain) {
+ QSettings settings;
+ m_watt.setType(metric::METRIC_WATT);
+ target_watts.setType(metric::METRIC_WATT);
+ Speed.setType(metric::METRIC_SPEED);
+ refresh = new QTimer(this);
+ this->noWriteResistance = noWriteResistance;
+ this->noHeartService = noHeartService;
+ this->bikeResistanceGain = bikeResistanceGain;
+ this->bikeResistanceOffset = bikeResistanceOffset;
+ initDone = false;
+ connect(refresh, &QTimer::timeout, this, &proformtelnetbike::update);
+ refresh->start(200ms);
+
+ bool ok = connect(&telnet, &QTelnet::newData, this, &proformtelnetbike::characteristicChanged);
+
+ ergModeSupported = true; // IMPORTANT, only for this bike
+
+ connectToDevice();
+
+ initRequest = true;
+
+ // ******************************************* virtual bike init *************************************
+ if (!firstStateChanged && !this->hasVirtualDevice()
+#ifdef Q_OS_IOS
+#ifndef IO_UNDER_QT
+ && !h
+#endif
+#endif
+ ) {
+ QSettings settings;
+ bool virtual_device_enabled =
+ settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
+#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) {
+ qDebug() << "ios_peloton_workaround activated!";
+ h = new lockscreen();
+ h->virtualbike_ios();
+ } else
+#endif
+#endif
+ if (virtual_device_enabled) {
+ emit debug(QStringLiteral("creating virtual bike interface..."));
+ auto virtualBike =
+ new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
+ // connect(virtualBike,&virtualbike::debug ,this,& proformtelnetbike::debug);
+ connect(virtualBike, &virtualbike::changeInclination, this, &proformtelnetbike::changeInclination);
+ this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
+ }
+ }
+ firstStateChanged = 1;
+ // ********************************************************************************************************
+}
+
+void proformtelnetbike::connectToDevice() {
+ QSettings settings;
+ // https://github.com/dawsontoth/zwifit/blob/e846501149a6c8fbb03af8d7b9eab20474624883/src/ifit.js
+ telnet.connectToHost(settings.value(QZSettings::proformtdf1ip, QZSettings::default_proformtdf1ip).toString(), 23);
+ telnet.waitForConnected();
+ telnet.sendData("./utconfig\n");
+ QThread::sleep(1);
+ telnet.sendData("2\n"); // modify variables
+
+}
+
+/*
+void proformtelnetbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
+ bool wait_for_response) {
+ QEventLoop loop;
+ QTimer timeout;
+ if (wait_for_response) {
+ connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, &loop, &QEventLoop::quit);
+ timeout.singleShot(300ms, &loop, &QEventLoop::quit);
+ } else {
+ connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, &loop, &QEventLoop::quit);
+ timeout.singleShot(300ms, &loop, &QEventLoop::quit);
+ }
+
+ gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic,
+ QByteArray((const char *)data, data_len));
+
+ if (!disable_log) {
+ emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') +
+ QStringLiteral(" // ") + info);
+ }
+
+ loop.exec();
+}*/
+
+resistance_t proformtelnetbike::resistanceFromPowerRequest(uint16_t power) {
+ qDebug() << QStringLiteral("resistanceFromPowerRequest") << Cadence.value();
+
+ QSettings settings;
+
+ double watt_gain = settings.value(QZSettings::watt_gain, QZSettings::default_watt_gain).toDouble();
+ double watt_offset = settings.value(QZSettings::watt_offset, QZSettings::default_watt_offset).toDouble();
+
+ for (resistance_t i = 1; i < max_resistance; i++) {
+ if (((wattsFromResistance(i) * watt_gain) + watt_offset) <= power &&
+ ((wattsFromResistance(i + 1) * watt_gain) + watt_offset) >= power) {
+ qDebug() << QStringLiteral("resistanceFromPowerRequest")
+ << ((wattsFromResistance(i) * watt_gain) + watt_offset)
+ << ((wattsFromResistance(i + 1) * watt_gain) + watt_offset) << power;
+ return i;
+ }
+ }
+ if (power < ((wattsFromResistance(1) * watt_gain) + watt_offset))
+ return 1;
+ else
+ return max_resistance;
+}
+
+uint16_t proformtelnetbike::wattsFromResistance(resistance_t resistance) {
+
+ if (currentCadence().value() == 0)
+ return 0;
+
+ switch (resistance) {
+ case 0:
+ case 1:
+ // -13.5 + 0.999x + 0.00993x²
+ return (-13.5 + (0.999 * currentCadence().value()) + (0.00993 * pow(currentCadence().value(), 2)));
+ case 2:
+ // -17.7 + 1.2x + 0.0116x²
+ return (-17.7 + (1.2 * currentCadence().value()) + (0.0116 * pow(currentCadence().value(), 2)));
+
+ case 3:
+ // -17.5 + 1.24x + 0.014x²
+ return (-17.5 + (1.24 * currentCadence().value()) + (0.014 * pow(currentCadence().value(), 2)));
+
+ case 4:
+ // -20.9 + 1.43x + 0.016x²
+ return (-20.9 + (1.43 * currentCadence().value()) + (0.016 * pow(currentCadence().value(), 2)));
+
+ case 5:
+ // -27.9 + 1.75x+0.0172x²
+ return (-27.9 + (1.75 * currentCadence().value()) + (0.0172 * pow(currentCadence().value(), 2)));
+
+ case 6:
+ // -26.7 + 1.9x + 0.0201x²
+ return (-26.7 + (1.9 * currentCadence().value()) + (0.0201 * pow(currentCadence().value(), 2)));
+
+ case 7:
+ // -33.5 + 2.23x + 0.0225x²
+ return (-33.5 + (2.23 * currentCadence().value()) + (0.0225 * pow(currentCadence().value(), 2)));
+
+ case 8:
+ // -36.5+2.5x+0.0262x²
+ return (-36.5 + (2.5 * currentCadence().value()) + (0.0262 * pow(currentCadence().value(), 2)));
+
+ case 9:
+ // -38+2.62x+0.0305x²
+ return (-38.0 + (2.62 * currentCadence().value()) + (0.0305 * pow(currentCadence().value(), 2)));
+
+ case 10:
+ // -41.2+2.85x+0.0327x²
+ return (-41.2 + (2.85 * currentCadence().value()) + (0.0327 * pow(currentCadence().value(), 2)));
+
+ case 11:
+ // -43.4+3.01x+0.0359x²
+ return (-43.4 + (3.01 * currentCadence().value()) + (0.0359 * pow(currentCadence().value(), 2)));
+
+ case 12:
+ // -46.8+3.23x+0.0364x²
+ return (-46.8 + (3.23 * currentCadence().value()) + (0.0364 * pow(currentCadence().value(), 2)));
+
+ case 13:
+ // -49+3.39x+0.0371x²
+ return (-49.0 + (3.39 * currentCadence().value()) + (0.0371 * pow(currentCadence().value(), 2)));
+
+ case 14:
+ // -53.4+3.55x+0.0383x²
+ return (-53.4 + (3.55 * currentCadence().value()) + (0.0383 * pow(currentCadence().value(), 2)));
+
+ case 15:
+ // -49.9+3.37x+0.0429x²
+ return (-49.9 + (3.37 * currentCadence().value()) + (0.0429 * pow(currentCadence().value(), 2)));
+
+ case 16:
+ default:
+ // -47.1+3.25x+0.0464x²
+ return (-47.1 + (3.25 * currentCadence().value()) + (0.0464 * pow(currentCadence().value(), 2)));
+ }
+}
+
+void proformtelnetbike::sendFrame(QByteArray frame) {
+ telnet.sendData(frame);
+ qDebug() << " >> " << frame;
+}
+
+void proformtelnetbike::update() {
+ qDebug() << "websocket.state()" << telnet.isConnected();
+
+ if (initRequest) {
+ initRequest = false;
+ btinit();
+ emit connectedAndDiscovered();
+ } else if (telnet.isConnected()) {
+ update_metrics(false, watts());
+
+ // updating the treadmill console every second
+ if (sec1Update++ == (500 / refresh->interval())) {
+ sec1Update = 0;
+ // updateDisplay(elapsed);
+ }
+
+ if (requestStart != -1) {
+ emit debug(QStringLiteral("starting..."));
+
+ // btinit();
+
+ requestStart = -1;
+ emit bikeStarted();
+ }
+ if (requestStop != -1) {
+ emit debug(QStringLiteral("stopping..."));
+ // writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
+ requestStop = -1;
+ }
+ }
+}
+
+bool proformtelnetbike::inclinationAvailableByHardware() { return true; }
+
+resistance_t proformtelnetbike::pelotonToBikeResistance(int pelotonResistance) {
+ if (pelotonResistance <= 10) {
+ return 1;
+ }
+ if (pelotonResistance <= 20) {
+ return 2;
+ }
+ if (pelotonResistance <= 25) {
+ return 3;
+ }
+ if (pelotonResistance <= 30) {
+ return 4;
+ }
+ if (pelotonResistance <= 35) {
+ return 5;
+ }
+ if (pelotonResistance <= 40) {
+ return 6;
+ }
+ if (pelotonResistance <= 45) {
+ return 7;
+ }
+ if (pelotonResistance <= 50) {
+ return 8;
+ }
+ if (pelotonResistance <= 55) {
+ return 9;
+ }
+ if (pelotonResistance <= 60) {
+ return 10;
+ }
+ if (pelotonResistance <= 65) {
+ return 11;
+ }
+ if (pelotonResistance <= 70) {
+ return 12;
+ }
+ if (pelotonResistance <= 75) {
+ return 13;
+ }
+ if (pelotonResistance <= 80) {
+ return 14;
+ }
+ if (pelotonResistance <= 85) {
+ return 15;
+ }
+ if (pelotonResistance <= 100) {
+ return 16;
+ }
+ return Resistance.value();
+}
+
+void proformtelnetbike::serviceDiscovered(const QBluetoothUuid &gatt) {
+ emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString());
+}
+
+void proformtelnetbike::characteristicChanged(const char *buff, int len) {
+ // qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
+ QSettings settings;
+ QString heartRateBeltName =
+ 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 erg_mode = settings.value(QZSettings::zwift_erg, QZSettings::default_zwift_erg).toBool();
+
+ QByteArray newValue = QByteArray::fromRawData(buff, len);
+ emit debug(QStringLiteral(" << ") + newValue);
+
+ if(newValue.contains("Shared Memory Management Utility")) {
+ emit debug(QStringLiteral("Ready to start the poll"));
+ sendFrame("2\n"); // current watt
+ } else if(newValue.contains("Enter New Value")) {
+ if(poolIndex >= 4) {
+ if(!erg_mode) {
+ sendFrame((QString::number(requestInclination) + "\n").toLocal8Bit()); // target incline
+ qDebug() << "forceInclination" << requestInclination;
+ requestInclination = -100;
+ } else {
+ double r = requestPower;
+ if (settings.value(QZSettings::watt_gain, QZSettings::default_watt_gain).toDouble() <= 2.00) {
+ if (settings.value(QZSettings::watt_gain, QZSettings::default_watt_gain).toDouble() != 1.0) {
+ qDebug() << QStringLiteral("request watt value was ") << r
+ << QStringLiteral("but it will be transformed to")
+ << r / settings.value(QZSettings::watt_gain, QZSettings::default_watt_gain).toDouble();
+ }
+ r /= settings.value(QZSettings::watt_gain, QZSettings::default_watt_gain).toDouble();
+ }
+ if (settings.value(QZSettings::watt_offset, QZSettings::default_watt_offset).toDouble() < 0) {
+ if (settings.value(QZSettings::watt_offset, QZSettings::default_watt_offset).toDouble() != 0.0) {
+ qDebug() << QStringLiteral("request watt value was ") << r
+ << QStringLiteral("but it will be transformed to")
+ << r - settings.value(QZSettings::watt_offset, QZSettings::default_watt_offset).toDouble();
+ }
+ r -= settings.value(QZSettings::watt_offset, QZSettings::default_watt_offset).toDouble();
+ }
+ sendFrame((QString::number(r) + "\n").toLocal8Bit()); // target watt
+ qDebug() << "forceWatt" << r;
+ requestPower = -1;
+ }
+ poolIndex = 0;
+ } else {
+ sendFrame("q\n"); // quit
+ }
+ } else if(newValue.contains("Enter Variable Offset")) {
+ qDebug() << "poolIndex" << poolIndex;
+ bool done = false;
+ do {
+ switch (poolIndex)
+ {
+ case 0:
+ sendFrame("124\n"); // current watt
+ done = true;
+ break;
+ case 1:
+ sendFrame("40\n"); // current rpm
+ done = true;
+ break;
+ case 2:
+ sendFrame("34\n"); // current speed
+ done = true;
+ break;
+ case 3:
+ if(!erg_mode) {
+ if(requestInclination != -100) {
+ sendFrame("45\n"); // target incline
+ done = true;
+ }
+ else
+ poolIndex = 99;
+ } else {
+ if(requestPower != -1) {
+ sendFrame("125\n"); // target watt
+ done = true;
+ }
+ else
+ poolIndex = 99;
+ }
+ break;
+ default:
+ break;
+ }
+ poolIndex++;
+ if(poolIndex > 4)
+ poolIndex = 0;
+ } while(!done);
+ }
+
+ QStringList packet = QString::fromLocal8Bit(newValue).split(" ");
+ qDebug() << packet;
+ if (newValue.contains("Current Watts")) {
+ double watt = packet[3].toDouble();
+ if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
+ .toString()
+ .startsWith(QStringLiteral("Disabled")))
+ m_watt = watt;
+ emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
+ } else if (newValue.contains("Cur RPM")) {
+ double RPM = packet[3].toDouble();
+ Cadence = RPM;
+ emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
+
+ if (Cadence.value() > 0) {
+ CrankRevs++;
+ LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
+ }
+ } else if (newValue.contains("Cur KPH")) {
+ if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
+ double kph = packet[3].toDouble() / 10.0;
+ Speed = kph;
+ emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
+ } else {
+ Speed = metric::calculateSpeedFromPower(
+ watts(), Inclination.value(), Speed.value(),
+ fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ }
+ }
+
+ 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
+ Distance += ((Speed.value() / (double)3600.0) /
+ ((double)1000.0 / (double)(lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
+ }
+ /*
+ Resistance = resistance;
+ m_pelotonResistance = (100 / 32) * Resistance.value();
+ emit resistanceRead(Resistance.value()); */
+
+ /*
+ if (!disable_hr_frommachinery && !values[QStringLiteral("Chest Pulse")].isUndefined()) {
+ Heart = values[QStringLiteral("Chest Pulse")].toString().toDouble();
+ // index += 1; // NOTE: clang-analyzer-deadcode.DeadStores
+ emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
+ }*/
+
+#ifdef Q_OS_ANDROID
+ if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
+ Heart = (uint8_t)KeepAwakeHelper::heart();
+ else
+#endif
+ {
+ if (disable_hr_frommachinery && heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
+ update_hr_from_external();
+ }
+ }
+
+ lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+
+#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->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
+ h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
+ }
+#endif
+#endif
+
+ /*
+ emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
+ emit debug(QStringLiteral("Current Calculate Distance: ") + QString::number(Distance.value()));
+ emit debug(QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs));
+ emit debug(QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime)); */
+}
+
+void proformtelnetbike::btinit() { initDone = true; }
+
+void proformtelnetbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
+ emit debug(QStringLiteral("Found new device: ") + device.name() + " (" + device.address().toString() + ')');
+}
+
+bool proformtelnetbike::connected() { return telnet.isConnected(); }
+
+uint16_t proformtelnetbike::watts() { return m_watt.value(); }
diff --git a/src/proformtelnetbike.h b/src/proformtelnetbike.h
new file mode 100644
index 000000000..c62347695
--- /dev/null
+++ b/src/proformtelnetbike.h
@@ -0,0 +1,110 @@
+#ifndef PROFORMTELNETBIKE_H
+#define PROFORMTELNETBIKE_H
+
+
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifndef Q_OS_ANDROID
+#include
+#else
+#include
+#endif
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "bike.h"
+
+#include "QTelnet.h"
+
+#ifdef Q_OS_IOS
+#include "ios/lockscreen.h"
+#endif
+
+class proformtelnetbike : public bike {
+ Q_OBJECT
+ public:
+ proformtelnetbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
+ double bikeResistanceGain);
+ resistance_t pelotonToBikeResistance(int pelotonResistance) override;
+ resistance_t resistanceFromPowerRequest(uint16_t power) override;
+ resistance_t maxResistance() override { return max_resistance; }
+ bool inclinationAvailableByHardware() override;
+ bool connected() override;
+
+ private:
+ QTelnet telnet;
+ resistance_t max_resistance = 100;
+ resistance_t min_resistance = -20;
+ double max_incline_supported = 20;
+ void connectToDevice();
+ uint16_t wattsFromResistance(resistance_t resistance);
+ double GetDistanceFromPacket(QByteArray packet);
+ QTime GetElapsedFromPacket(QByteArray packet);
+ void btinit();
+ void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
+ bool wait_for_response = false);
+ void startDiscover();
+ void sendPoll();
+ uint16_t watts() override;
+ void sendFrame(QByteArray frame);
+
+ QTimer *refresh;
+ uint8_t counterPoll = 0;
+ uint8_t bikeResistanceOffset = 4;
+ double bikeResistanceGain = 1.0;
+
+ uint8_t sec1Update = 0;
+ QString lastPacket;
+ QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ uint8_t firstStateChanged = 0;
+ metric target_watts;
+
+ bool initDone = false;
+ bool initRequest = false;
+
+ bool noWriteResistance = false;
+ bool noHeartService = false;
+
+ uint8_t poolIndex = 0;
+
+#ifdef Q_OS_IOS
+ lockscreen *h = 0;
+#endif
+
+ signals:
+ void disconnected();
+ void debug(QString string);
+
+ public slots:
+ void deviceDiscovered(const QBluetoothDeviceInfo &device);
+
+ private slots:
+
+ void characteristicChanged(const char *buff, int len);
+
+ void serviceDiscovered(const QBluetoothUuid &gatt);
+ void update();
+};
+#endif // PROFORMTELNETBIKE_H
diff --git a/src/proformtreadmill.cpp b/src/proformtreadmill.cpp
index 0c8626fdb..e9f83b427 100644
--- a/src/proformtreadmill.cpp
+++ b/src/proformtreadmill.cpp
@@ -72,8 +72,15 @@ void proformtreadmill::forceIncline(double incline) {
settings.value(QZSettings::proform_treadmill_se, QZSettings::default_proform_treadmill_se).toBool();
bool norditrack_s25i_treadmill =
settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25i_treadmill).toBool();
+ bool norditrack_s25_treadmill =
+ settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25_treadmill).toBool();
bool proform_treadmill_z1300i =
settings.value(QZSettings::proform_treadmill_z1300i, QZSettings::default_proform_treadmill_z1300i).toBool();
+ bool nordictrack_s20_treadmill = settings.value(QZSettings::nordictrack_s20_treadmill,
+ QZSettings::default_nordictrack_s20_treadmill).toBool();
+ bool proform_treadmill_l6_0s = settings.value(QZSettings::proform_treadmill_l6_0s, QZSettings::default_proform_treadmill_l6_0s).toBool();
+ bool proform_8_5_treadmill = settings.value(QZSettings::proform_8_5_treadmill, QZSettings::default_proform_8_5_treadmill).toBool();
+ bool proform_2000_treadmill = settings.value(QZSettings::proform_2000_treadmill, QZSettings::default_proform_8_5_treadmill).toBool();
if (proform_treadmill_1800i) {
uint8_t i = abs(incline * 10);
@@ -94,9 +101,9 @@ void proformtreadmill::forceIncline(double incline) {
if (norditrack_s25i_treadmill) {
write[14] = write[11] + write[12] + 0x11;
- } else if (proform_treadmill_8_0 || proform_treadmill_9_0 || proform_treadmill_se || proform_treadmill_z1300i) {
+ } else if (proform_treadmill_8_0 || proform_treadmill_9_0 || proform_treadmill_se || proform_treadmill_z1300i || proform_treadmill_l6_0s || norditrack_s25_treadmill || proform_8_5_treadmill || proform_2000_treadmill) {
write[14] = write[11] + write[12] + 0x12;
- } else if (!nordictrack_t65s_treadmill && !nordictrack_s30_treadmill && !nordictrack_t65s_83_treadmill) {
+ } else if (!nordictrack_t65s_treadmill && !nordictrack_s30_treadmill && !nordictrack_s20_treadmill && !nordictrack_t65s_83_treadmill) {
for (uint8_t i = 0; i < 7; i++) {
write[14] += write[i + 6];
}
@@ -129,8 +136,15 @@ void proformtreadmill::forceSpeed(double speed) {
.toBool();
bool norditrack_s25i_treadmill =
settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25i_treadmill).toBool();
+ bool norditrack_s25_treadmill =
+ settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25_treadmill).toBool();
bool proform_treadmill_z1300i =
settings.value(QZSettings::proform_treadmill_z1300i, QZSettings::default_proform_treadmill_z1300i).toBool();
+ bool nordictrack_s20_treadmill = settings.value(QZSettings::nordictrack_s20_treadmill,
+ QZSettings::default_nordictrack_s20_treadmill).toBool();
+ bool proform_treadmill_l6_0s = settings.value(QZSettings::proform_treadmill_l6_0s, QZSettings::default_proform_treadmill_l6_0s).toBool();
+ bool proform_8_5_treadmill = settings.value(QZSettings::proform_8_5_treadmill, QZSettings::default_proform_8_5_treadmill).toBool();
+ bool proform_2000_treadmill = settings.value(QZSettings::proform_2000_treadmill, QZSettings::default_proform_8_5_treadmill).toBool();
uint8_t noOpData7[] = {0xfe, 0x02, 0x0d, 0x02};
uint8_t write[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x04, 0x09, 0x02, 0x01,
@@ -142,9 +156,9 @@ void proformtreadmill::forceSpeed(double speed) {
if (norditrack_s25i_treadmill) {
write[14] = write[11] + write[12] + 0x11;
} else if (proform_treadmill_8_0 || proform_treadmill_9_0 || proform_treadmill_se || proform_treadmill_cadence_lt ||
- proform_treadmill_z1300i) {
+ proform_treadmill_z1300i || proform_treadmill_l6_0s || norditrack_s25_treadmill || proform_8_5_treadmill || proform_2000_treadmill) {
write[14] = write[11] + write[12] + 0x11;
- } else if (!nordictrack_t65s_treadmill && !nordictrack_s30_treadmill && !nordictrack_t65s_83_treadmill) {
+ } else if (!nordictrack_t65s_treadmill && !nordictrack_s30_treadmill && !nordictrack_s20_treadmill && !nordictrack_t65s_83_treadmill) {
for (uint8_t i = 0; i < 7; i++) {
write[14] += write[i + 6];
}
@@ -199,6 +213,9 @@ void proformtreadmill::update() {
bool norditrack_s25i_treadmill =
settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25i_treadmill)
.toBool();
+ bool norditrack_s25_treadmill =
+ settings.value(QZSettings::norditrack_s25_treadmill, QZSettings::default_norditrack_s25_treadmill)
+ .toBool();
bool nordictrack_incline_trainer_x7i =
settings
.value(QZSettings::nordictrack_incline_trainer_x7i, QZSettings::default_nordictrack_incline_trainer_x7i)
@@ -207,6 +224,11 @@ void proformtreadmill::update() {
settings.value(QZSettings::proform_treadmill_z1300i, QZSettings::default_proform_treadmill_z1300i).toBool();
bool proform_pro_1000_treadmill =
settings.value(QZSettings::proform_pro_1000_treadmill, QZSettings::default_proform_pro_1000_treadmill).toBool();
+ bool nordictrack_s20_treadmill = settings.value(QZSettings::nordictrack_s20_treadmill,
+ QZSettings::default_nordictrack_s20_treadmill).toBool();
+ bool proform_treadmill_l6_0s = settings.value(QZSettings::proform_treadmill_l6_0s, QZSettings::default_proform_treadmill_l6_0s).toBool();
+ bool proform_8_5_treadmill = settings.value(QZSettings::proform_8_5_treadmill, QZSettings::default_proform_8_5_treadmill).toBool();
+ bool proform_2000_treadmill = settings.value(QZSettings::proform_2000_treadmill, QZSettings::default_proform_8_5_treadmill).toBool();
// bool proform_treadmill_995i = settings.value(QZSettings::proform_treadmill_995i,
// QZSettings::default_proform_treadmill_995i).toBool();
@@ -407,6 +429,66 @@ void proformtreadmill::update() {
if (counterPoll > 5) {
counterPoll = 0;
}
+ } else if (proform_2000_treadmill) {
+ uint8_t noOpData1[] = {0xfe, 0x02, 0x17, 0x03};
+ uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00, 0x0d, 0x1b, 0x94, 0x31, 0x00, 0x00, 0x40, 0x50, 0x00, 0x80};
+ uint8_t noOpData3[] = {0xff, 0x05, 0x18, 0x00, 0x00, 0x01, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData4[] = {0xfe, 0x02, 0x17, 0x03};
+ uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00, 0x0d, 0x80, 0x0a, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData6[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x84, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+ switch (counterPoll) {
+ case 0:
+ writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp"));
+ break;
+ case 1:
+ writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("noOp"));
+ break;
+ case 2:
+ writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("noOp"));
+ if (requestInclination != -100) {
+ if (requestInclination < 0)
+ requestInclination = 0;
+ if (requestInclination != currentInclination().value() && requestInclination >= 0 &&
+ requestInclination <= 15) {
+ emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
+ forceIncline(requestInclination);
+ }
+ requestInclination = -100;
+ }
+ if (requestSpeed != -1) {
+ if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) {
+ emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed));
+ forceSpeed(requestSpeed);
+ }
+ requestSpeed = -1;
+ }
+ break;
+ case 3:
+ writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"), true);
+ break;
+ case 4:
+ writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("noOp"));
+ break;
+ case 5:
+ writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("noOp"));
+ if (requestStart != -1) {
+ emit debug(QStringLiteral("starting..."));
+ requestStart = -1;
+ emit tapeStarted();
+ }
+ if (requestStop != -1) {
+ emit debug(QStringLiteral("stopping..."));
+ // writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
+ requestStop = -1;
+ }
+
+ break;
+ }
+ counterPoll++;
+ if (counterPoll > 5) {
+ counterPoll = 0;
+ }
} else if (nordictrack10) {
uint8_t noOpData1[] = {0xff, 0x05, 0x18, 0x00, 0x00, 0x01, 0x2f, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
@@ -542,6 +624,69 @@ void proformtreadmill::update() {
if (counterPoll > 5) {
counterPoll = 0;
}
+ } else if (proform_8_5_treadmill) {
+ uint8_t noOpData1[] = {0xfe, 0x02, 0x19, 0x03};
+ uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00, 0x0f, 0x80, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData3[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x80, 0x00, 0x10, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData4[] = {0xfe, 0x02, 0x17, 0x03};
+ uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00, 0x0d, 0x13, 0x96, 0x31, 0x00, 0x00, 0x40, 0x10, 0x00, 0x80};
+ uint8_t noOpData6[] = {0xff, 0x05, 0x18, 0x00, 0x00, 0x01, 0xe9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+ switch (counterPoll) {
+ case 0:
+ writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp"));
+ break;
+ case 1:
+ writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("noOp"));
+ break;
+ case 2:
+ writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("noOp"), false, true);
+ if (requestInclination != -100) {
+ if (requestInclination < 0)
+ requestInclination = 0;
+ if (requestInclination != currentInclination().value() && requestInclination >= 0 &&
+ requestInclination <= 15) {
+ emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
+ forceIncline(requestInclination);
+ }
+ requestInclination = -100;
+ }
+ if (requestSpeed != -1) {
+ if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) {
+ emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed));
+ forceSpeed(requestSpeed);
+ }
+ requestSpeed = -1;
+ }
+ break;
+ case 3:
+ writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"));
+ break;
+ case 4:
+ writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("noOp"));
+ break;
+ case 5:
+ writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("noOp"));
+
+ if (requestStart != -1) {
+ emit debug(QStringLiteral("starting..."));
+
+ // btinit();
+
+ requestStart = -1;
+ emit tapeStarted();
+ }
+ if (requestStop != -1) {
+ emit debug(QStringLiteral("stopping..."));
+ // writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
+ requestStop = -1;
+ }
+ break;
+ }
+ counterPoll++;
+ if (counterPoll > 5) {
+ counterPoll = 0;
+ }
} else if (norditrack_s25i_treadmill) {
uint8_t noOpData1[] = {0xfe, 0x02, 0x19, 0x03};
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00,
@@ -609,6 +754,164 @@ void proformtreadmill::update() {
if (counterPoll > 5) {
counterPoll = 0;
}
+ } else if (nordictrack_s20_treadmill) {
+ uint8_t noOpData1[] = {0xfe, 0x02, 0x19, 0x03};
+ uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00,
+ 0x0f, 0x80, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData3[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x80, 0x00, 0x10, 0x82, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData4[] = {0xfe, 0x02, 0x17, 0x03};
+ uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00,
+ 0x0d, 0x13, 0x96, 0x31, 0x00, 0x00, 0x40, 0x10, 0x00, 0x80};
+ uint8_t noOpData6[] = {0xff, 0x05, 0x18, 0x00, 0x00, 0x01, 0xe9, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+ switch (counterPoll) {
+ case 0:
+ writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp"));
+ break;
+ case 1:
+ writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("noOp"));
+ break;
+ case 2:
+ writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("noOp"), false, true);
+ break;
+ case 3:
+ writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"));
+ break;
+ case 4:
+ writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("noOp"));
+ break;
+ case 5:
+ writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("noOp"));
+ if (requestInclination != -100) {
+ if (requestInclination < 0)
+ requestInclination = 0;
+ if (requestInclination != currentInclination().value() && requestInclination >= 0 &&
+ requestInclination <= 15) {
+ emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
+ forceIncline(requestInclination);
+ }
+ requestInclination = -100;
+ }
+ if (requestSpeed != -1) {
+ if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) {
+ emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed));
+ forceSpeed(requestSpeed);
+ }
+ requestSpeed = -1;
+ }
+
+ if (requestStart != -1) {
+ emit debug(QStringLiteral("starting..."));
+
+ uint8_t start1[] = {0xfe, 0x02, 0x20, 0x03};
+ uint8_t start2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x1c, 0x04, 0x1c, 0x02, 0x09,
+ 0x00, 0x00, 0x40, 0x02, 0x18, 0x40, 0x00, 0x00, 0x80, 0x30};
+ uint8_t start3[] = {0xff, 0x0e, 0x2a, 0x00, 0x00, 0xef, 0x1a, 0x58, 0x02, 0x00,
+ 0xb4, 0x00, 0x58, 0x02, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00};
+ uint8_t start4[] = {0xfe, 0x02, 0x11, 0x02};
+ uint8_t start5[] = {0xff, 0x11, 0x02, 0x04, 0x02, 0x0d, 0x04, 0x0d, 0x02, 0x02,
+ 0x03, 0x10, 0xa0, 0x00, 0x00, 0x00, 0x0a, 0x00, 0xd2, 0x00};
+ writeCharacteristic(start1, sizeof(start1), QStringLiteral("start1"));
+ writeCharacteristic(start2, sizeof(start2), QStringLiteral("start2"));
+ writeCharacteristic(start3, sizeof(start3), QStringLiteral("start3"), false, true);
+ writeCharacteristic(start4, sizeof(start4), QStringLiteral("start4"));
+ writeCharacteristic(start5, sizeof(start5), QStringLiteral("start5"), false, true);
+
+ requestStart = -1;
+ emit tapeStarted();
+ }
+ if (requestStop != -1 || requestPause != -1) {
+ forceSpeed(0);
+
+ emit debug(QStringLiteral("stopping..."));
+ requestStop = -1;
+ requestPause = -1;
+ }
+ break;
+ }
+ counterPoll++;
+ if (counterPoll > 5) {
+ counterPoll = 0;
+ }
+ } else if (proform_treadmill_l6_0s) {
+ uint8_t noOpData1[] = {0xfe, 0x02, 0x17, 0x03};
+ uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00, 0x0d, 0x80, 0x0a, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData3[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x84, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData4[] = {0xfe, 0x02, 0x17, 0x03};
+ uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00, 0x0d, 0x1b, 0x94, 0x31, 0x00, 0x00, 0x40, 0x50, 0x00, 0x80};
+ uint8_t noOpData6[] = {0xff, 0x05, 0x18, 0x00, 0x00, 0x01, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+ switch (counterPoll) {
+ case 0:
+ writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp"));
+ break;
+ case 1:
+ writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("noOp"));
+ break;
+ case 2:
+ writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("noOp"), false, true);
+ break;
+ case 3:
+ writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"));
+ break;
+ case 4:
+ writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("noOp"));
+ break;
+ case 5:
+ writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("noOp"));
+ if (requestInclination != -100) {
+ if (requestInclination < 0)
+ requestInclination = 0;
+ if (requestInclination != currentInclination().value() && requestInclination >= 0 &&
+ requestInclination <= 15) {
+ emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
+ forceIncline(requestInclination);
+ }
+ requestInclination = -100;
+ }
+ if (requestSpeed != -1) {
+ if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) {
+ emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed));
+ forceSpeed(requestSpeed);
+ }
+ requestSpeed = -1;
+ }
+
+ if (requestStart != -1) {
+ emit debug(QStringLiteral("starting..."));
+
+ uint8_t start1[] = {0xfe, 0x02, 0x20, 0x03};
+ uint8_t start2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x1c, 0x04, 0x1c, 0x02, 0x09,
+ 0x00, 0x00, 0x40, 0x02, 0x18, 0x40, 0x00, 0x00, 0x80, 0x30};
+ uint8_t start3[] = {0xff, 0x0e, 0x2a, 0x00, 0x00, 0xef, 0x1a, 0x58, 0x02, 0x00,
+ 0xb4, 0x00, 0x58, 0x02, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00};
+ uint8_t start4[] = {0xfe, 0x02, 0x11, 0x02};
+ uint8_t start5[] = {0xff, 0x11, 0x02, 0x04, 0x02, 0x0d, 0x04, 0x0d, 0x02, 0x02,
+ 0x03, 0x10, 0xa0, 0x00, 0x00, 0x00, 0x0a, 0x00, 0xd2, 0x00};
+ writeCharacteristic(start1, sizeof(start1), QStringLiteral("start1"));
+ writeCharacteristic(start2, sizeof(start2), QStringLiteral("start2"));
+ writeCharacteristic(start3, sizeof(start3), QStringLiteral("start3"), false, true);
+ writeCharacteristic(start4, sizeof(start4), QStringLiteral("start4"));
+ writeCharacteristic(start5, sizeof(start5), QStringLiteral("start5"), false, true);
+
+ requestStart = -1;
+ emit tapeStarted();
+ }
+ if (requestStop != -1 || requestPause != -1) {
+ forceSpeed(0);
+
+ emit debug(QStringLiteral("stopping..."));
+ requestStop = -1;
+ requestPause = -1;
+ }
+ break;
+ }
+ counterPoll++;
+ if (counterPoll > 5) {
+ counterPoll = 0;
+ }
} else if (proform_treadmill_se) {
uint8_t noOpData1[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00,
@@ -1101,6 +1404,68 @@ void proformtreadmill::update() {
if (counterPoll > 5) {
counterPoll = 0;
}
+ } else if (norditrack_s25_treadmill) {
+ uint8_t noOpData1[] = {0xfe, 0x02, 0x19, 0x03};
+ uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00, 0x0f, 0x80, 0x0a, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData3[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x85, 0x00, 0x10, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData4[] = {0xfe, 0x02, 0x14, 0x03};
+ uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x10, 0x04, 0x10, 0x02, 0x00, 0x0a, 0x1b, 0x94, 0x30, 0x00, 0x00, 0x40, 0x50, 0x00, 0x80};
+ uint8_t noOpData6[] = {0xff, 0x02, 0x18, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+ switch (counterPoll) {
+ case 0:
+ writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp"));
+ break;
+ case 1:
+ writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("noOp"));
+ break;
+ case 2:
+ writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("noOp"));
+ break;
+ case 3:
+ writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"), false, true);
+ break;
+ case 4:
+ writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("noOp"));
+ break;
+ case 5:
+ writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("noOp"), false, true);
+ if (requestInclination != -100) {
+ if (requestInclination < 0)
+ requestInclination = 0;
+ if (requestInclination != currentInclination().value() && requestInclination >= 0 &&
+ requestInclination <= 15) {
+ emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
+ forceIncline(requestInclination);
+ }
+ requestInclination = -100;
+ }
+ if (requestSpeed != -1) {
+ if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) {
+ emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed));
+ forceSpeed(requestSpeed);
+ }
+ requestSpeed = -1;
+ }
+ if (requestStart != -1) {
+ emit debug(QStringLiteral("starting..."));
+
+ // btinit();
+
+ requestStart = -1;
+ emit tapeStarted();
+ }
+ if (requestStop != -1) {
+ emit debug(QStringLiteral("stopping..."));
+ // writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
+ requestStop = -1;
+ }
+ break;
+ }
+ counterPoll++;
+ if (counterPoll > 5) {
+ counterPoll = 0;
+ }
} else {
uint8_t noOpData1[] = {0xfe, 0x02, 0x19, 0x03};
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x07, 0x15, 0x02, 0x00,
@@ -1212,6 +1577,10 @@ void proformtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
.toBool();
bool proform_treadmill_z1300i =
settings.value(QZSettings::proform_treadmill_z1300i, QZSettings::default_proform_treadmill_z1300i).toBool();
+ bool nordictrack_s20_treadmill = settings.value(QZSettings::nordictrack_s20_treadmill,
+ QZSettings::default_nordictrack_s20_treadmill).toBool();
+ bool proform_8_5_treadmill = settings.value(QZSettings::proform_8_5_treadmill, QZSettings::default_proform_8_5_treadmill).toBool();
+ bool proform_2000_treadmill = settings.value(QZSettings::proform_2000_treadmill, QZSettings::default_proform_8_5_treadmill).toBool();
double weight = settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
@@ -1226,7 +1595,7 @@ void proformtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
(newValue.at(4) != 0x02 || (newValue.at(5) != 0x31 && newValue.at(5) != 0x34))) ||
((norditrack_s25i_treadmill) && (newValue.at(4) != 0x02 || (newValue.at(5) != 0x2f))) ||
((nordictrack_t65s_treadmill || proform_pro_1000_treadmill || nordictrack_t65s_83_treadmill || nordictrack_s30_treadmill ||
- proform_treadmill_se || proform_cadence_lt) &&
+ nordictrack_s20_treadmill || proform_treadmill_se || proform_cadence_lt || proform_8_5_treadmill) &&
(newValue.at(4) != 0x02 || newValue.at(5) != 0x2e)) ||
(((uint8_t)newValue.at(12)) == 0xFF && ((uint8_t)newValue.at(13)) == 0xFF &&
((uint8_t)newValue.at(14)) == 0xFF && ((uint8_t)newValue.at(15)) == 0xFF &&
@@ -1313,6 +1682,8 @@ void proformtreadmill::btinit() {
.toBool();
bool norditrack_s25i_treadmill =
settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25i_treadmill).toBool();
+ bool norditrack_s25_treadmill =
+ settings.value(QZSettings::norditrack_s25_treadmill, QZSettings::default_norditrack_s25_treadmill).toBool();
bool nordictrack_t65s_83_treadmill =
settings.value(QZSettings::nordictrack_t65s_83_treadmill, QZSettings::default_nordictrack_t65s_83_treadmill)
.toBool();
@@ -1323,6 +1694,12 @@ void proformtreadmill::btinit() {
settings.value(QZSettings::proform_treadmill_z1300i, QZSettings::default_proform_treadmill_z1300i).toBool();
bool proform_pro_1000_treadmill =
settings.value(QZSettings::proform_pro_1000_treadmill, QZSettings::default_proform_pro_1000_treadmill).toBool();
+ bool nordictrack_s20_treadmill = settings.value(QZSettings::nordictrack_s20_treadmill,
+ QZSettings::default_nordictrack_s20_treadmill).toBool();
+ bool proform_treadmill_l6_0s = settings.value(QZSettings::proform_treadmill_l6_0s, QZSettings::default_proform_treadmill_l6_0s).toBool();
+ bool proform_8_5_treadmill = settings.value(QZSettings::proform_8_5_treadmill, QZSettings::default_proform_8_5_treadmill).toBool();
+ bool proform_2000_treadmill = settings.value(QZSettings::proform_2000_treadmill, QZSettings::default_proform_8_5_treadmill).toBool();
+
// bool proform_treadmill_995i = settings.value(QZSettings::proform_treadmill_995i,
// QZSettings::default_proform_treadmill_995i).toBool();
@@ -1480,6 +1857,141 @@ void proformtreadmill::btinit() {
QThread::msleep(sleepms);
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
+ } else if (proform_8_5_treadmill) {
+ uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
+ uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData3[] = {0xfe, 0x02, 0x08, 0x02};
+ uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x80, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData5[] = {0xfe, 0x02, 0x08, 0x02};
+ uint8_t initData6[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x88, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData7[] = {0xfe, 0x02, 0x0a, 0x02};
+ uint8_t initData8[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x82, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData9[] = {0xfe, 0x02, 0x0a, 0x02};
+ uint8_t initData10[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData11[] = {0xfe, 0x02, 0x08, 0x02};
+ uint8_t initData12[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData13[] = {0xfe, 0x02, 0x2c, 0x04};
+ uint8_t initData14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x04, 0x28, 0x90, 0x07, 0x01, 0x72, 0xf4, 0x74, 0xf2, 0x7e, 0x08, 0x80, 0x1e, 0xaa};
+ uint8_t initData15[] = {0x01, 0x12, 0x3c, 0xcc, 0x5a, 0xe6, 0x90, 0x08, 0xa6, 0x42, 0xe4, 0x84, 0x22, 0xce, 0x98, 0x30, 0xce, 0x9a, 0x2c, 0xfc};
+ uint8_t initData16[] = {0xff, 0x08, 0x8a, 0x56, 0x20, 0x98, 0x02, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData17[] = {0xfe, 0x02, 0x19, 0x03};
+ uint8_t initData18[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00, 0x0f, 0x00, 0x10, 0x00, 0xd8, 0x1c, 0x48, 0x00, 0x00, 0xe0};
+ uint8_t initData19[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+ writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData13, sizeof(initData13), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData14, sizeof(initData14), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData15, sizeof(initData15), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData16, sizeof(initData16), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData18, sizeof(initData18), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData19, sizeof(initData19), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ } else if (proform_2000_treadmill) {
+ uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
+ writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+
+ uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+
+ uint8_t initData3[] = {0xfe, 0x02, 0x08, 0x02};
+ writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+
+ uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x80, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+
+ uint8_t initData5[] = {0xfe, 0x02, 0x08, 0x02};
+ writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+
+ uint8_t initData6[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x88, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+
+ uint8_t initData7[] = {0xfe, 0x02, 0x0a, 0x02};
+ writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+
+ uint8_t initData8[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x82, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+
+ uint8_t initData9[] = {0xfe, 0x02, 0x0a, 0x02};
+ writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+
+ uint8_t initData10[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+
+ uint8_t initData11[] = {0xfe, 0x02, 0x08, 0x02};
+ writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+
+ uint8_t initData12[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+
+ uint8_t initData13[] = {0xfe, 0x02, 0x2c, 0x04};
+ writeCharacteristic(initData13, sizeof(initData13), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+
+ uint8_t initData14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x04, 0x28, 0x90, 0x04, 0x00, 0xb9, 0xf8, 0x45, 0x80, 0xc9, 0x10, 0x6d, 0xb8, 0x09};
+ writeCharacteristic(initData14, sizeof(initData14), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+
+ uint8_t initData15[] = {0x01, 0x12, 0x58, 0xa5, 0xf0, 0x59, 0xa0, 0x1d, 0x78, 0xd9, 0x38, 0x85, 0xe0, 0x49, 0xd0, 0x2d, 0xb8, 0x09, 0x98, 0xe5};
+ writeCharacteristic(initData15, sizeof(initData15), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+
+ uint8_t initData16[] = {0xff, 0x08, 0x70, 0xf9, 0x40, 0x80, 0x02, 0x00, 0x00, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ writeCharacteristic(initData16, sizeof(initData16), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+
+ uint8_t initData17[] = {0xfe, 0x02, 0x17, 0x03};
+ writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+
+ uint8_t initData18[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00, 0x0d, 0x00, 0x10, 0x00, 0xd8, 0x1c, 0x48, 0x00, 0x00, 0xe0};
+ writeCharacteristic(initData18, sizeof(initData18), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+
+ uint8_t initData19[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x10, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ writeCharacteristic(initData19, sizeof(initData19), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
} else if (proform_pro_1000_treadmill) {
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
@@ -1549,7 +2061,7 @@ void proformtreadmill::btinit() {
writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
- QThread::msleep(sleepms);
+ QThread::msleep(sleepms);
} else if (proform_treadmill_z1300i) {
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
@@ -1704,6 +2216,73 @@ void proformtreadmill::btinit() {
QThread::msleep(sleepms);
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
+ } else if (norditrack_s25_treadmill) {
+ uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
+ uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData3[] = {0xfe, 0x02, 0x08, 0x02};
+ uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x80, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData5[] = {0xfe, 0x02, 0x08, 0x02};
+ uint8_t initData6[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x88, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData7[] = {0xfe, 0x02, 0x0a, 0x02};
+ uint8_t initData8[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x82, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData9[] = {0xfe, 0x02, 0x0a, 0x02};
+ uint8_t initData10[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData11[] = {0xfe, 0x02, 0x08, 0x02};
+ uint8_t initData12[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData13[] = {0xfe, 0x02, 0x2c, 0x04};
+ uint8_t initData14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x04, 0x28, 0x90, 0x07, 0x01, 0x38, 0xac, 0x12, 0x8e, 0xfc, 0x78, 0xee, 0x6a, 0xd0};
+ uint8_t initData15[] = {0x01, 0x12, 0x54, 0xda, 0x56, 0xd4, 0x70, 0xf6, 0x62, 0xe8, 0x9c, 0x02, 0xbe, 0x2c, 0xc8, 0x7e, 0x1a, 0x80, 0x24, 0xca};
+ uint8_t initData16[] = {0xff, 0x08, 0x66, 0x04, 0xe0, 0x98, 0x02, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData17[] = {0xfe, 0x02, 0x19, 0x03};
+ uint8_t initData18[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00, 0x0f, 0x00, 0x10, 0x00, 0xd8, 0x1c, 0x48, 0x00, 0x00, 0xe0};
+ uint8_t initData19[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+ writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData13, sizeof(initData13), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData14, sizeof(initData14), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData15, sizeof(initData15), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData16, sizeof(initData16), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData18, sizeof(initData18), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData19, sizeof(initData19), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
} else if (norditrack_s25i_treadmill) {
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
@@ -2125,6 +2704,78 @@ void proformtreadmill::btinit() {
QThread::msleep(sleepms);
writeCharacteristic(noOpData10, sizeof(noOpData10), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
+ } else if (proform_treadmill_l6_0s) {
+ uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
+ uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData3[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x80, 0x88,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x88, 0x90,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData5[] = {0xfe, 0x02, 0x0a, 0x02};
+ uint8_t initData6[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x82, 0x00,
+ 0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData7[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00,
+ 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData8[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData9[] = {0xfe, 0x02, 0x2c, 0x04};
+ uint8_t initData10[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x04, 0x28, 0x90, 0x04, 0x00, 0xee, 0x44, 0x90, 0xea, 0x42, 0xa8, 0xf4, 0x56, 0xb6};
+ uint8_t initData11[] = {0x01, 0x12, 0x2c, 0x88, 0xe2, 0x5a, 0xd0, 0x3c, 0x8e, 0x1e, 0x94, 0xe0, 0x7a, 0xf2, 0x78, 0xc4, 0x46, 0xc6, 0x7c, 0xf8};
+ uint8_t initData12[] = {0xff, 0x08, 0x72, 0xea, 0xa0, 0x80, 0x02, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData1[] = {0xfe, 0x02, 0x17, 0x03};
+ uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00, 0x0d, 0x00, 0x10, 0x00, 0xd8, 0x1c, 0x48, 0x00, 0x00, 0xe0};
+ uint8_t noOpData3[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x10, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData4[] = {0xfe, 0x02, 0x17, 0x03};
+ uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x0c,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData6[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+ writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
} else if (nordictrack_t65s_treadmill) {
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
@@ -2416,6 +3067,90 @@ void proformtreadmill::btinit() {
QThread::msleep(sleepms);
writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
+ } else if (nordictrack_s20_treadmill) {
+ uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
+ uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData3[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x80, 0x88,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x88, 0x90,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData5[] = {0xfe, 0x02, 0x0a, 0x02};
+ uint8_t initData6[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x82, 0x00,
+ 0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData7[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00,
+ 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData8[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t initData9[] = {0xfe, 0x02, 0x2c, 0x04};
+ uint8_t initData10[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x04, 0x28,
+ 0x90, 0x07, 0x01, 0x3b, 0xa0, 0x17, 0x84, 0xf3,
+ 0x60, 0xd7, 0x5c, 0xdb};
+ uint8_t initData11[] = {0x01, 0x12, 0x40, 0xc7, 0x44, 0xc3, 0x40, 0xc7,
+ 0x4c, 0xfb, 0x60, 0x17, 0x84, 0x33, 0xa0, 0x57,
+ 0xfc, 0x9b, 0x00, 0xa7};
+ uint8_t initData12[] = {0xff, 0x08, 0x44, 0xe3, 0x80, 0x88, 0x02, 0x00,
+ 0x00, 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData1[] = {0xfe, 0x02, 0x19, 0x03};
+ uint8_t noOpData2[] = { 0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15,
+ 0x02, 0x00, 0x0f, 0x00, 0x10, 0x00, 0xd8, 0x1c,
+ 0x48, 0x00, 0x00, 0xe0};
+ uint8_t noOpData3[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08,
+ 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData4[] = {0xfe, 0x02, 0x17, 0x03};
+ uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x0c,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t noOpData6[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+ writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
+ writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("init"), false, false);
+ QThread::msleep(sleepms);
} else if (nordictrack_s30_treadmill) {
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
@@ -2591,14 +3326,14 @@ void proformtreadmill::stateChanged(QLowEnergyService::ServiceState state) {
.toBool();
if (virtual_device_enabled) {
if (!virtual_device_force_bike) {
- debug("creating virtual treadmill interface...");
+ emit debug("creating virtual treadmill interface...");
auto virtualTreadmill = new virtualtreadmill(this, noHeartService);
connect(virtualTreadmill, &virtualtreadmill::debug, this, &proformtreadmill::debug);
connect(virtualTreadmill, &virtualtreadmill::changeInclination, this,
&proformtreadmill::changeInclinationRequested);
this->setVirtualDevice(virtualTreadmill, VIRTUAL_DEVICE_MODE::PRIMARY);
} else {
- debug("creating virtual bike interface...");
+ emit debug("creating virtual bike interface...");
auto virtualBike = new virtualbike(this);
connect(virtualBike, &virtualbike::changeInclination, this,
&proformtreadmill::changeInclinationRequested);
diff --git a/src/qdomyos-zwift.pri b/src/qdomyos-zwift.pri
index ec41c834e..28334c405 100644
--- a/src/qdomyos-zwift.pri
+++ b/src/qdomyos-zwift.pri
@@ -72,10 +72,13 @@ DEFINES += QT_DEPRECATED_WARNINGS IO_UNDER_QT SMTP_BUILD NOMINMAX
# include(../qtzeroconf/qtzeroconf.pri)
SOURCES += \
+ $$PWD/QTelnet.cpp \
$$PWD/bkoolbike.cpp \
$$PWD/csafe.cpp \
$$PWD/csaferower.cpp \
+ $$PWD/eliteariafan.cpp \
$$PWD/fakerower.cpp \
+ $$PWD/proformtelnetbike.cpp \
$$PWD/virtualdevice.cpp \
$$PWD/androidactivityresultreceiver.cpp \
$$PWD/androidadblog.cpp \
@@ -280,11 +283,16 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
INCLUDEPATH += fit-sdk/
HEADERS += \
+ $$PWD/QTelnet.h \
$$PWD/bkoolbike.h \
$$PWD/csafe.h \
$$PWD/csaferower.h \
+ $$PWD/eliteariafan.h \
+ $$PWD/proformtelnetbike.h \
$$PWD/windows_zwift_workout_paddleocr_thread.h \
$$PWD/fakerower.h \
+ $$PWD/zwift-api/PlayerStateWrapper.h \
+ $$PWD/zwift-api/zwift_client_auth.h \
virtualdevice.h \
$$PWD/androidactivityresultreceiver.h \
$$PWD/androidadblog.h \
@@ -714,6 +722,7 @@ DISTFILES += \
$$PWD/android/libs/connectiq-mobile-sdk-android-1.5.aar \
$$PWD/android/res/xml/device_filter.xml \
$$PWD/android/src/CSafeRowerUSBHID.java \
+ $$PWD/android/src/ContentHelper.java \
$$PWD/android/src/Garmin.java \
$$PWD/android/src/HidBridge.java \
$$PWD/android/src/IQMessageReceiverWrapper.java \
@@ -722,6 +731,8 @@ DISTFILES += \
$$PWD/android/src/ScreenCaptureService.java \
$$PWD/android/src/WearableController.java \
$$PWD/android/src/WearableMessageListenerService.java \
+ $$PWD/android/src/ZwiftAPI.java \
+ $$PWD/android/src/main/proto/zwift_messages.proto \
.clang-format \
AppxManifest.xml \
android/AndroidManifest.xml \
@@ -785,6 +796,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 +827,4 @@ INCLUDEPATH += purchasing/inapp
WINRT_MANIFEST = AppxManifest.xml
-VERSION = 2.16.24
+VERSION = 2.16.32
diff --git a/src/qfit.cpp b/src/qfit.cpp
index eeb81e678..5755b37a5 100644
--- a/src/qfit.cpp
+++ b/src/qfit.cpp
@@ -335,6 +335,7 @@ void qfit::save(const QString &filename, QList session, bluetoothde
}
}
+ lapMesg.SetTotalDistance((session.last().distance - lastLapOdometer) * 1000.0); // meters
lapMesg.SetTotalElapsedTime(session.last().elapsedTime - lastLapTimer);
lapMesg.SetTotalTimerTime(session.last().elapsedTime - lastLapTimer);
lapMesg.SetEvent(FIT_EVENT_LAP);
diff --git a/src/qthttpserver b/src/qthttpserver
new file mode 160000
index 000000000..983e93c3b
--- /dev/null
+++ b/src/qthttpserver
@@ -0,0 +1 @@
+Subproject commit 983e93c3b160c62e60b1755d075e959d4685d949
diff --git a/src/qzsettings.cpp b/src/qzsettings.cpp
index 9c3bf6302..691fee935 100644
--- a/src/qzsettings.cpp
+++ b/src/qzsettings.cpp
@@ -680,8 +680,28 @@ 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 QString QZSettings::autolap_distance = QStringLiteral("autolap_distance");
+const QString QZSettings::nordictrack_s20_treadmill = QStringLiteral("nordictrack_s20_treadmill");
+const QString QZSettings::freemotion_coachbike_b22_7 = QStringLiteral("freemotion_coachbike_b22_7");
+const QString QZSettings::proform_cycle_trainer_300_ci = QStringLiteral("proform_cycle_trainer_300_ci");
+const QString QZSettings::kingsmith_encrypt_g1_walking_pad = QStringLiteral("kingsmith_encrypt_g1_walking_pad");
+const QString QZSettings::proformtdf1ip = QStringLiteral("proformtdf1ip");
+const QString QZSettings::default_proformtdf1ip = QStringLiteral("");
+const QString QZSettings::proform_bike_225_csx = QStringLiteral("proform_bike_225_csx");
+const QString QZSettings::proform_treadmill_l6_0s = QStringLiteral("proform_treadmill_l6_0s");
+const QString QZSettings::zwift_username = QStringLiteral("zwift_username");
+const QString QZSettings::default_zwift_username = QStringLiteral("");
+const QString QZSettings::zwift_password = QStringLiteral("zwift_password");
+const QString QZSettings::default_zwift_password = QStringLiteral("");
+const QString QZSettings::garmin_bluetooth_compatibility = QStringLiteral("garmin_bluetooth_compatibility");
+const QString QZSettings::norditrack_s25_treadmill = QStringLiteral("norditrack_s25_treadmill");
+const QString QZSettings::proform_8_5_treadmill = QStringLiteral("proform_8_5_treadmill");
+const QString QZSettings::treadmill_incline_min = QStringLiteral("treadmill_incline_min");
+const QString QZSettings::treadmill_incline_max = QStringLiteral("treadmill_incline_max");
+const QString QZSettings::proform_2000_treadmill = QStringLiteral("proform_2000_treadmill");
-const uint32_t allSettingsCount = 570;
+const uint32_t allSettingsCount = 587;
QVariant allSettings[allSettingsCount][2] = {
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
@@ -1258,6 +1278,23 @@ 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},
+ {QZSettings::autolap_distance, QZSettings::default_autolap_distance},
+ {QZSettings::nordictrack_s20_treadmill, QZSettings::default_nordictrack_s20_treadmill},
+ {QZSettings::freemotion_coachbike_b22_7, QZSettings::default_freemotion_coachbike_b22_7},
+ {QZSettings::proform_cycle_trainer_300_ci, QZSettings::default_proform_cycle_trainer_300_ci},
+ {QZSettings::kingsmith_encrypt_g1_walking_pad, QZSettings::default_kingsmith_encrypt_g1_walking_pad},
+ {QZSettings::proform_bike_225_csx, QZSettings::default_proform_bike_225_csx},
+ {QZSettings::proform_treadmill_l6_0s, QZSettings::default_proform_treadmill_l6_0s},
+ {QZSettings::proformtdf1ip, QZSettings::default_proformtdf1ip},
+ {QZSettings::zwift_username, QZSettings::default_zwift_username},
+ {QZSettings::zwift_password, QZSettings::default_zwift_password},
+ {QZSettings::garmin_bluetooth_compatibility, QZSettings::default_garmin_bluetooth_compatibility},
+ {QZSettings::norditrack_s25_treadmill, QZSettings::default_norditrack_s25_treadmill},
+ {QZSettings::proform_8_5_treadmill, QZSettings::default_proform_8_5_treadmill},
+ {QZSettings::treadmill_incline_min, QZSettings::default_treadmill_incline_min},
+ {QZSettings::treadmill_incline_max, QZSettings::default_treadmill_incline_max},
+ {QZSettings::proform_2000_treadmill, QZSettings::default_proform_2000_treadmill},
};
void QZSettings::qDebugAllSettings(bool showDefaults) {
diff --git a/src/qzsettings.h b/src/qzsettings.h
index 9ba186d27..f2709003b 100644
--- a/src/qzsettings.h
+++ b/src/qzsettings.h
@@ -1911,6 +1911,57 @@ 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;
+
+ static const QString autolap_distance;
+ static constexpr float default_autolap_distance = 0;
+
+ static const QString nordictrack_s20_treadmill;
+ static constexpr bool default_nordictrack_s20_treadmill = false;
+
+ static const QString freemotion_coachbike_b22_7;
+ static constexpr bool default_freemotion_coachbike_b22_7 = false;
+
+ static const QString proform_cycle_trainer_300_ci;
+ static constexpr bool default_proform_cycle_trainer_300_ci = false;
+
+ static const QString kingsmith_encrypt_g1_walking_pad;
+ static constexpr bool default_kingsmith_encrypt_g1_walking_pad = false;
+
+ static const QString proform_bike_225_csx;
+ static constexpr bool default_proform_bike_225_csx = false;
+
+ static const QString proform_treadmill_l6_0s;
+ static constexpr bool default_proform_treadmill_l6_0s = false;
+
+ static const QString proformtdf1ip;
+ static const QString default_proformtdf1ip;
+
+ static const QString zwift_username;
+ static const QString default_zwift_username;
+
+ static const QString zwift_password;
+ static const QString default_zwift_password;
+
+ static const QString garmin_bluetooth_compatibility;
+ static constexpr bool default_garmin_bluetooth_compatibility = false;
+
+ static const QString norditrack_s25_treadmill;
+ static constexpr int default_norditrack_s25_treadmill = false;
+
+ static const QString proform_8_5_treadmill;
+ static constexpr int default_proform_8_5_treadmill = false;
+
+ static const QString treadmill_incline_min;
+ static constexpr float default_treadmill_incline_min = -100.0;
+
+ static const QString treadmill_incline_max;
+ static constexpr float default_treadmill_incline_max = 100.0;
+
+ static const QString proform_2000_treadmill;
+ static constexpr int default_proform_2000_treadmill = 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.
diff --git a/src/renphobike.cpp b/src/renphobike.cpp
index b01c2a675..64a39409a 100644
--- a/src/renphobike.cpp
+++ b/src/renphobike.cpp
@@ -175,6 +175,7 @@ void renphobike::update() {
void renphobike::serviceDiscovered(const QBluetoothUuid &gatt) { debug("serviceDiscovered " + gatt.toString()); }
void renphobike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -225,7 +226,7 @@ void renphobike::characteristicChanged(const QLowEnergyCharacteristic &character
else
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
index += 2;
debug("Current Speed: " + QString::number(Speed.value()));
}
@@ -267,7 +268,7 @@ void renphobike::characteristicChanged(const QLowEnergyCharacteristic &character
index += 3;
} else {
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
}
debug("Current Distance: " + QString::number(Distance.value()));
@@ -320,7 +321,7 @@ void renphobike::characteristicChanged(const QLowEnergyCharacteristic &character
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
}
@@ -333,7 +334,7 @@ void renphobike::characteristicChanged(const QLowEnergyCharacteristic &character
#endif
{
if (Flags.heartRate) {
- Heart = ((double)((newValue.at(index))));
+ Heart = ((double)(((uint8_t)newValue.at(index))));
index += 1;
debug("Current Heart: " + QString::number(Heart.value()));
}
@@ -356,7 +357,7 @@ void renphobike::characteristicChanged(const QLowEnergyCharacteristic &character
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
if (heartRateBeltName.startsWith("Disabled")) {
update_hr_from_external();
diff --git a/src/rower.cpp b/src/rower.cpp
index 44e6d38ca..02b0cc42e 100644
--- a/src/rower.cpp
+++ b/src/rower.cpp
@@ -53,6 +53,7 @@ void rower::clearStats() {
Speed.clear(false);
KCal.clear(true);
Distance.clear(true);
+ Distance1s.clear(true);
Heart.clear(false);
m_jouls.clear(true);
elevationAcc = 0;
@@ -81,6 +82,7 @@ void rower::setPaused(bool p) {
Speed.setPaused(p);
KCal.setPaused(p);
Distance.setPaused(p);
+ Distance1s.setPaused(p);
Heart.setPaused(p);
m_jouls.setPaused(p);
m_watt.setPaused(p);
@@ -104,6 +106,7 @@ void rower::setLap() {
Speed.setLap(false);
KCal.setLap(true);
Distance.setLap(true);
+ Distance1s.setLap(true);
Heart.setLap(false);
m_jouls.setLap(true);
m_watt.setLap(false);
diff --git a/src/schwinn170bike.cpp b/src/schwinn170bike.cpp
index e4a109c20..ab00311eb 100644
--- a/src/schwinn170bike.cpp
+++ b/src/schwinn170bike.cpp
@@ -115,6 +115,7 @@ void schwinn170bike::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void schwinn170bike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
double heart = 0.0;
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
@@ -140,6 +141,19 @@ void schwinn170bike::characteristicChanged(const QLowEnergyCharacteristic &chara
m_watt = ((double)(((uint16_t)((uint8_t)newValue.at(7)) << 8) | (uint16_t)((uint8_t)newValue.at(6)))) / 100.0;
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
+ double cadence_gain = settings.value(QZSettings::cadence_gain, QZSettings::default_cadence_gain).toDouble();
+ double cadence_offset = settings.value(QZSettings::cadence_offset, QZSettings::default_cadence_offset).toDouble();
+
+ if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
+ .toString()
+ .startsWith(QStringLiteral("Disabled"))) {
+ double c = ((double)(((uint16_t)((uint8_t)newValue.at(4)) << 8) | (uint16_t)((uint8_t)newValue.at(3)))) / 17.47;
+ if (c > 0)
+ Cadence = (c * cadence_gain) + cadence_offset;
+ else
+ Cadence = 0;
+ }
+
emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
@@ -147,12 +161,12 @@ void schwinn170bike::characteristicChanged(const QLowEnergyCharacteristic &chara
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
@@ -161,7 +175,7 @@ void schwinn170bike::characteristicChanged(const QLowEnergyCharacteristic &chara
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
@@ -218,7 +232,7 @@ void schwinn170bike::characteristicChanged(const QLowEnergyCharacteristic &chara
qDebug() << QStringLiteral("resistance not updated cause to schwinn_resistance_smooth setting");
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
if (heart == 0.0) {
diff --git a/src/schwinnic4bike.cpp b/src/schwinnic4bike.cpp
index 6166496bb..fd1a5ce9e 100644
--- a/src/schwinnic4bike.cpp
+++ b/src/schwinnic4bike.cpp
@@ -116,6 +116,7 @@ void schwinnic4bike::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void schwinnic4bike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
double heart = 0.0;
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
@@ -166,7 +167,7 @@ void schwinnic4bike::characteristicChanged(const QLowEnergyCharacteristic &chara
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
index += 2;
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
@@ -208,7 +209,7 @@ void schwinnic4bike::characteristicChanged(const QLowEnergyCharacteristic &chara
index += 3;
} else {
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
}
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
@@ -256,7 +257,7 @@ void schwinnic4bike::characteristicChanged(const QLowEnergyCharacteristic &chara
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
}
@@ -347,7 +348,7 @@ void schwinnic4bike::characteristicChanged(const QLowEnergyCharacteristic &chara
}
emit resistanceRead(Resistance.value());
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
if (heart == 0.0) {
diff --git a/src/settings.qml b/src/settings.qml
index caf8b1a67..46a7af886 100644
--- a/src/settings.qml
+++ b/src/settings.qml
@@ -842,6 +842,35 @@ 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
+ property real autolap_distance: 0
+ property bool nordictrack_s20_treadmill: false
+
+ // from version 2.16.28
+ property bool freemotion_coachbike_b22_7: false
+
+ // from version 2.16.29
+ property bool proform_cycle_trainer_300_ci: false
+ property bool kingsmith_encrypt_g1_walking_pad: false
+ property bool proform_bike_225_csx: false
+
+ // from version 2.16.30
+ property bool proform_treadmill_l6_0s: false
+ property string proformtdf1ip: ""
+ property string zwift_username: ""
+ property string zwift_password: ""
+
+ // from version 2.16.31
+ property bool garmin_bluetooth_compatibility: false
+ property bool norditrack_s25_treadmill: false
+ property bool proform_8_5_treadmill: false
+ property real treadmill_incline_min: -100
+ property real treadmill_incline_max: 100
+
+ // from version 2.16.32
+ property bool proform_2000_treadmill: false
}
function paddingZeros(text, limit) {
@@ -2911,6 +2940,19 @@ import QtQuick.Dialogs 1.0
Layout.fillWidth: true
onClicked: { settings.proform_studio_NTEX71021 = checked; window.settings_restart_to_apply = true; }
}
+ SwitchDelegate {
+ text: qsTr("Freemotion Coachbike B22.7")
+ spacing: 0
+ bottomPadding: 0
+ topPadding: 0
+ rightPadding: 0
+ leftPadding: 0
+ clip: false
+ checked: settings.freemotion_coachbike_b22_7
+ Layout.alignment: Qt.AlignLeft | Qt.AlignTop
+ Layout.fillWidth: true
+ onClicked: { settings.freemotion_coachbike_b22_7 = checked; window.settings_restart_to_apply = true; }
+ }
SwitchDelegate {
id: proformTDF10odelegate
text: qsTr("Proform TDF 1.0")
@@ -2966,6 +3008,19 @@ import QtQuick.Dialogs 1.0
Layout.fillWidth: true
onClicked: settings.proform_tdf_jonseed_watt = checked
}
+ SwitchDelegate {
+ text: qsTr("Cycle Trainer 300 CI")
+ spacing: 0
+ bottomPadding: 0
+ topPadding: 0
+ rightPadding: 0
+ leftPadding: 0
+ clip: false
+ checked: settings.proform_cycle_trainer_300_ci
+ Layout.alignment: Qt.AlignLeft | Qt.AlignTop
+ Layout.fillWidth: true
+ onClicked: { settings.proform_cycle_trainer_300_ci = checked; window.settings_restart_to_apply = true; }
+ }
SwitchDelegate {
id: proformCycleTrainerdelegate
text: qsTr("Cycle Trainer 400")
@@ -2979,7 +3034,20 @@ import QtQuick.Dialogs 1.0
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: { settings.proform_cycle_trainer_400 = checked; window.settings_restart_to_apply = true; }
- }
+ }
+ SwitchDelegate {
+ text: qsTr("Proform 225 CSX")
+ spacing: 0
+ bottomPadding: 0
+ topPadding: 0
+ rightPadding: 0
+ leftPadding: 0
+ clip: false
+ checked: settings.proform_bike_225_csx
+ Layout.alignment: Qt.AlignLeft | Qt.AlignTop
+ Layout.fillWidth: true
+ onClicked: { settings.proform_bike_225_csx = checked; window.settings_restart_to_apply = true; }
+ }
SwitchDelegate {
text: qsTr("Proform SB")
spacing: 0
@@ -2994,6 +3062,29 @@ import QtQuick.Dialogs 1.0
onClicked: { settings.proform_bike_sb = checked; window.settings_restart_to_apply = true; }
}
+ RowLayout {
+ spacing: 10
+ Label {
+ text: qsTr("TDF1 IP:")
+ Layout.fillWidth: true
+ }
+ TextField {
+ id: proformTDF1IPTextField
+ text: settings.proformtdf1ip
+ horizontalAlignment: Text.AlignRight
+ Layout.fillHeight: false
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ //inputMethodHints: Qt.ImhFormattedNumbersOnly
+ onAccepted: settings.proformtdf1ip = text
+ onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
+ }
+ Button {
+ text: "OK"
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ onClicked: { settings.proformtdf1ip = proformTDF1IPTextField.text; window.settings_restart_to_apply = true; toast.show("Setting saved!"); }
+ }
+ }
+
RowLayout {
spacing: 10
Label {
@@ -4337,7 +4428,6 @@ import QtQuick.Dialogs 1.0
}
AccordionElement {
- visible: OS_VERSION === "Other"
title: qsTr("Zwift Options") + "\uD83E\uDD47"
indicatRectColor: Material.color(Material.Grey)
textColor: Material.color(Material.Grey)
@@ -4345,6 +4435,84 @@ import QtQuick.Dialogs 1.0
accordionContent: ColumnLayout {
spacing: 0
+ RowLayout {
+ spacing: 10
+ Label {
+ text: qsTr("Username:")
+ Layout.fillWidth: true
+ }
+ TextField {
+ id: zwiftUsernameTextField
+ text: settings.zwift_username
+ horizontalAlignment: Text.AlignRight
+ Layout.fillHeight: false
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ onAccepted: settings.zwift_username = text
+ onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
+ }
+ Button {
+ id: okZwiftUsernameButton
+ text: "OK"
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ onClicked: { settings.zwift_username = zwiftUsernameTextField.text; window.settings_restart_to_apply = true; toast.show("Setting saved!"); }
+ }
+ }
+
+
+ Label {
+ text: qsTr("Enter the email address you use to login to Zwift. Ensure there are no spaces before or after your email. Click OK.")
+ font.bold: true
+ font.italic: true
+ font.pixelSize: 9
+ textFormat: Text.PlainText
+ wrapMode: Text.WordWrap
+ verticalAlignment: Text.AlignVCenter
+ Layout.alignment: Qt.AlignLeft | Qt.AlignTop
+ Layout.fillWidth: true
+ color: Material.color(Material.Lime)
+ }
+
+ RowLayout {
+ spacing: 10
+ Label {
+ id: labelZwiftPassword
+ //text: qsTr("Password:") + ((rootItem.zwiftLogin===-1)?"":(rootItem.zwiftLogin===1?"\u2705":"\u274c"))
+ text: qsTr("Password:")
+ Layout.fillWidth: true
+ }
+ TextField {
+ id: zwiftPasswordTextField
+ text: settings.zwift_password
+ horizontalAlignment: Text.AlignRight
+ Layout.fillHeight: false
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ inputMethodHints: Qt.ImhHiddenText
+ echoMode: TextInput.PasswordEchoOnEdit
+ onAccepted: settings.zwift_password = text
+ onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
+ }
+ Button {
+ id: okZwiftPasswordButton
+ text: "OK"
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ onClicked: { settings.zwift_password = zwiftPasswordTextField.text; window.settings_restart_to_apply = true; toast.show("Setting saved!"); }
+ }
+ }
+
+ Label {
+ //text: qsTr("Enter the password you use to login to Zwift. Click OK. If you have entered the correct login credentials and the QZ is able to access your account, you will see a when you reopen QZ. This is a secure login, not accessible by anyone but you.")
+ text: qsTr("Enter the password you use to login to Zwift. Click OK.")
+ font.bold: true
+ font.italic: true
+ font.pixelSize: 9
+ textFormat: Text.PlainText
+ wrapMode: Text.WordWrap
+ verticalAlignment: Text.AlignVCenter
+ Layout.alignment: Qt.AlignLeft | Qt.AlignTop
+ Layout.fillWidth: true
+ color: Material.color(Material.Lime)
+ }
+
SwitchDelegate {
text: qsTr("Zwift Treadmill Auto Inclination")
spacing: 0
@@ -4416,13 +4584,40 @@ import QtQuick.Dialogs 1.0
}
AccordionElement {
- title: qsTr("Garmin Companion Options") + "\uD83E\uDD47"
+ title: qsTr("Garmin Options") + "\uD83E\uDD47"
indicatRectColor: Material.color(Material.Grey)
textColor: Material.color(Material.Grey)
color: Material.backgroundColor
accordionContent: ColumnLayout {
spacing: 0
+ SwitchDelegate {
+ text: qsTr("Garmin Bluetooth Sensor")
+ spacing: 0
+ bottomPadding: 0
+ topPadding: 0
+ rightPadding: 0
+ leftPadding: 0
+ clip: false
+ checked: settings.garmin_bluetooth_compatibility
+ Layout.alignment: Qt.AlignLeft | Qt.AlignTop
+ Layout.fillWidth: true
+ onClicked: { settings.garmin_bluetooth_compatibility = checked; window.settings_restart_to_apply = true; }
+ }
+
+ Label {
+ text: qsTr("If you want to send metrics to your Garmin device from your Mac, enable this. Otherwise leave it disabled.")
+ font.bold: true
+ font.italic: true
+ font.pixelSize: 9
+ textFormat: Text.PlainText
+ wrapMode: Text.WordWrap
+ verticalAlignment: Text.AlignVCenter
+ Layout.alignment: Qt.AlignLeft | Qt.AlignTop
+ Layout.fillWidth: true
+ color: Material.color(Material.Lime)
+ }
+
SwitchDelegate {
text: qsTr("Enable Companion App")
spacing: 0
@@ -5236,6 +5431,78 @@ import QtQuick.Dialogs 1.0
color: Material.color(Material.Lime)
}
+ RowLayout {
+ spacing: 10
+ Label {
+ text: qsTr("Min. Inclination:")
+ Layout.fillWidth: true
+ }
+ TextField {
+ id: treadmillInclinationMinTextField
+ text: settings.treadmill_incline_min
+ horizontalAlignment: Text.AlignRight
+ Layout.fillHeight: false
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ //inputMethodHints: Qt.ImhDigitsOnly
+ onAccepted: settings.treadmill_incline_min = text
+ onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
+ }
+ Button {
+ text: "OK"
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ onClicked: { settings.treadmill_incline_min = treadmillInclinationMinTextField.text; toast.show("Setting saved!"); }
+ }
+ }
+
+ Label {
+ text: qsTr("This overrides the minimum inclination value of your treadmill (in order to reduce the inclination movement). Default is -100")
+ font.bold: true
+ font.italic: true
+ font.pixelSize: 9
+ textFormat: Text.PlainText
+ wrapMode: Text.WordWrap
+ verticalAlignment: Text.AlignVCenter
+ Layout.alignment: Qt.AlignLeft | Qt.AlignTop
+ Layout.fillWidth: true
+ color: Material.color(Material.Lime)
+ }
+
+ RowLayout {
+ spacing: 10
+ Label {
+ text: qsTr("Max. Inclination:")
+ Layout.fillWidth: true
+ }
+ TextField {
+ id: treadmillInclinationMaxTextField
+ text: settings.treadmill_incline_max
+ horizontalAlignment: Text.AlignRight
+ Layout.fillHeight: false
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ //inputMethodHints: Qt.ImhDigitsOnly
+ onAccepted: settings.treadmill_incline_max = text
+ onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
+ }
+ Button {
+ text: "OK"
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ onClicked: { settings.treadmill_incline_max = treadmillInclinationMaxTextField.text; toast.show("Setting saved!"); }
+ }
+ }
+
+ Label {
+ text: qsTr("This overrides the maximum inclination value of your treadmill (in order to reduce the inclination movement). Default is -100")
+ font.bold: true
+ font.italic: true
+ font.pixelSize: 9
+ textFormat: Text.PlainText
+ wrapMode: Text.WordWrap
+ verticalAlignment: Text.AlignVCenter
+ Layout.alignment: Qt.AlignLeft | Qt.AlignTop
+ Layout.fillWidth: true
+ color: Material.color(Material.Lime)
+ }
+
NewPageElement {
title: qsTr("Inclination Overrides")
indicatRectColor: Material.color(Material.Grey)
@@ -5343,7 +5610,21 @@ import QtQuick.Dialogs 1.0
textColor: Material.color(Material.Yellow)
color: Material.backgroundColor
accordionContent: ColumnLayout {
- spacing: 0
+ spacing: 0
+ SwitchDelegate {
+ text: qsTr("Nordictrack S25")
+ spacing: 0
+ bottomPadding: 0
+ topPadding: 0
+ rightPadding: 0
+ leftPadding: 0
+ clip: false
+ checked: settings.norditrack_s25_treadmill
+ Layout.alignment: Qt.AlignLeft | Qt.AlignTop
+ Layout.fillWidth: true
+ onClicked: { settings.norditrack_s25_treadmill = checked; window.settings_restart_to_apply = true; }
+ }
+
SwitchDelegate {
id: nordictrackS25iDelegate
text: qsTr("Nordictrack S25i")
@@ -5397,7 +5678,33 @@ import QtQuick.Dialogs 1.0
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: {settings.nordictrack_10_treadmill = checked; window.settings_restart_to_apply = true; }
- }
+ }
+ SwitchDelegate {
+ text: qsTr("Proform 2000 (not pro)")
+ spacing: 0
+ bottomPadding: 0
+ topPadding: 0
+ rightPadding: 0
+ leftPadding: 0
+ clip: false
+ checked: settings.proform_2000_treadmill
+ Layout.alignment: Qt.AlignLeft | Qt.AlignTop
+ Layout.fillWidth: true
+ onClicked: {settings.proform_2000_treadmill = checked; window.settings_restart_to_apply = true; }
+ }
+ SwitchDelegate {
+ text: qsTr("Proform 8.5")
+ spacing: 0
+ bottomPadding: 0
+ topPadding: 0
+ rightPadding: 0
+ leftPadding: 0
+ clip: false
+ checked: settings.proform_8_5_treadmill
+ Layout.alignment: Qt.AlignLeft | Qt.AlignTop
+ Layout.fillWidth: true
+ onClicked: {settings.proform_8_5_treadmill = checked; window.settings_restart_to_apply = true; }
+ }
SwitchDelegate {
text: qsTr("Proform Pro 1000")
spacing: 0
@@ -5410,7 +5717,21 @@ import QtQuick.Dialogs 1.0
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: {settings.proform_pro_1000_treadmill = checked; window.settings_restart_to_apply = true; }
- }
+ }
+ SwitchDelegate {
+ text: qsTr("Nordictrack L6.0S")
+ spacing: 0
+ bottomPadding: 0
+ topPadding: 0
+ rightPadding: 0
+ leftPadding: 0
+ clip: false
+ checked: settings.proform_treadmill_l6_0s
+ Layout.alignment: Qt.AlignLeft | Qt.AlignTop
+ Layout.fillWidth: true
+ onClicked: { settings.proform_treadmill_l6_0s = checked; window.settings_restart_to_apply = true; }
+ }
+
SwitchDelegate {
id: nordictrackT65SDelegate
text: qsTr("Nordictrack T6.5S v81")
@@ -5454,7 +5775,20 @@ import QtQuick.Dialogs 1.0
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: { settings.nordictrack_t70_treadmill = checked; window.settings_restart_to_apply = true; }
- }
+ }
+ SwitchDelegate {
+ text: qsTr("Nordictrack S20")
+ spacing: 0
+ bottomPadding: 0
+ topPadding: 0
+ rightPadding: 0
+ leftPadding: 0
+ clip: false
+ checked: settings.nordictrack_s20_treadmill
+ Layout.alignment: Qt.AlignLeft | Qt.AlignTop
+ Layout.fillWidth: true
+ onClicked: { settings.nordictrack_s20_treadmill = checked; window.settings_restart_to_apply = true; }
+ }
SwitchDelegate {
id: nordictrackS30Delegate
text: qsTr("Nordictrack S30")
@@ -5484,7 +5818,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
@@ -5753,7 +6087,7 @@ import QtQuick.Dialogs 1.0
checked: settings.kingsmith_encrypt_v2
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
- onClicked: { settings.kingsmith_encrypt_v2 = checked; settings.kingsmith_encrypt_v3 = false; settings.kingsmith_encrypt_v4 = false; settings.kingsmith_encrypt_v5 = false; window.settings_restart_to_apply = true; }
+ onClicked: { settings.kingsmith_encrypt_v2 = checked; settings.kingsmith_encrypt_g1_walking_pad = false; settings.kingsmith_encrypt_v3 = false; settings.kingsmith_encrypt_v4 = false; settings.kingsmith_encrypt_v5 = false; window.settings_restart_to_apply = true; }
}
SwitchDelegate {
@@ -5768,7 +6102,7 @@ import QtQuick.Dialogs 1.0
checked: settings.kingsmith_encrypt_v3
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
- onClicked: { settings.kingsmith_encrypt_v3 = checked; settings.kingsmith_encrypt_v2 = false; settings.kingsmith_encrypt_v4 = false; settings.kingsmith_encrypt_v5 = false; window.settings_restart_to_apply = true; }
+ onClicked: { settings.kingsmith_encrypt_v3 = checked; settings.kingsmith_encrypt_g1_walking_pad = false; settings.kingsmith_encrypt_v2 = false; settings.kingsmith_encrypt_v4 = false; settings.kingsmith_encrypt_v5 = false; window.settings_restart_to_apply = true; }
}
SwitchDelegate {
@@ -5783,7 +6117,7 @@ import QtQuick.Dialogs 1.0
checked: settings.kingsmith_encrypt_v4
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
- onClicked: { settings.kingsmith_encrypt_v4 = checked; settings.kingsmith_encrypt_v3 = false; settings.kingsmith_encrypt_v2 = false; settings.kingsmith_encrypt_v5 = false; window.settings_restart_to_apply = true; }
+ onClicked: { settings.kingsmith_encrypt_v4 = checked; settings.kingsmith_encrypt_g1_walking_pad = false; settings.kingsmith_encrypt_v3 = false; settings.kingsmith_encrypt_v2 = false; settings.kingsmith_encrypt_v5 = false; window.settings_restart_to_apply = true; }
}
SwitchDelegate {
@@ -5797,8 +6131,22 @@ import QtQuick.Dialogs 1.0
checked: settings.kingsmith_encrypt_v5
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
- onClicked: { settings.kingsmith_encrypt_v5 = checked; settings.kingsmith_encrypt_v3 = false; settings.kingsmith_encrypt_v2 = false; settings.kingsmith_encrypt_v4 = false; window.settings_restart_to_apply = true; }
+ onClicked: { settings.kingsmith_encrypt_v5 = checked; settings.kingsmith_encrypt_g1_walking_pad = false; settings.kingsmith_encrypt_v3 = false; settings.kingsmith_encrypt_v2 = false; settings.kingsmith_encrypt_v4 = false; window.settings_restart_to_apply = true; }
}
+
+ SwitchDelegate {
+ text: qsTr("WalkingPad G1")
+ spacing: 0
+ bottomPadding: 0
+ topPadding: 0
+ rightPadding: 0
+ leftPadding: 0
+ clip: false
+ checked: settings.kingsmith_encrypt_g1_walking_pad
+ Layout.alignment: Qt.AlignLeft | Qt.AlignTop
+ Layout.fillWidth: true
+ onClicked: { settings.kingsmith_encrypt_g1_walking_pad = checked; settings.kingsmith_encrypt_v5 = false; settings.kingsmith_encrypt_v3 = false; settings.kingsmith_encrypt_v2 = false; settings.kingsmith_encrypt_v4 = false; window.settings_restart_to_apply = true; }
+ }
}
}
@@ -6625,6 +6973,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; }
+ }
}
}
@@ -7044,7 +7405,7 @@ import QtQuick.Dialogs 1.0
horizontalAlignment: Text.AlignRight
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
- inputMethodHints: Qt.ImhDigitsOnly
+ //inputMethodHints: Qt.ImhDigitsOnly
onAccepted: settings.speed_offset = text
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
}
@@ -7525,6 +7886,42 @@ import QtQuick.Dialogs 1.0
Layout.fillWidth: true
color: Material.color(Material.Lime)
}
+
+ RowLayout {
+ spacing: 10
+ Label {
+ text: qsTr("AutoLap on Distance:")
+ Layout.fillWidth: true
+ }
+ TextField {
+ id: autoLapOnDistanceTextField
+ text: (settings.miles_unit?settings.autolap_distance * 0.621371:settings.autolap_distance).toFixed(1)
+ horizontalAlignment: Text.AlignRight
+ Layout.fillHeight: false
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ //inputMethodHints: Qt.ImhDigitsOnly
+ onAccepted: settings.autolap_distance = text
+ onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
+ }
+ Button {
+ text: "OK"
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ onClicked: { settings.autolap_distance = (settings.miles_unit?autoLapOnDistanceTextField.text * 1.60934:autoLapOnDistanceTextField.text); toast.show("Setting saved!"); }
+ }
+ }
+
+ Label {
+ text: qsTr("You can trigger auto laps in the FIT file based on distance. Unit: "+ (settings.miles_unit?"Mi":"KM") +" Default: 0 (disabled).")
+ font.bold: true
+ font.italic: true
+ font.pixelSize: 9
+ textFormat: Text.PlainText
+ wrapMode: Text.WordWrap
+ verticalAlignment: Text.AlignVCenter
+ Layout.alignment: Qt.AlignLeft | Qt.AlignTop
+ Layout.fillWidth: true
+ color: Material.color(Material.Lime)
+ }
}
}
@@ -8492,6 +8889,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!"); }
+ }
+ }
+ }
+ }
}
}
diff --git a/src/shuaa5treadmill.cpp b/src/shuaa5treadmill.cpp
index bd7665ac5..fd2a4d4f8 100644
--- a/src/shuaa5treadmill.cpp
+++ b/src/shuaa5treadmill.cpp
@@ -309,7 +309,7 @@ void shuaa5treadmill::characteristicChanged(const QLowEnergyCharacteristic &char
if (Flags.heartRate) {
if (index < newValue.length()) {
- heart = ((double)((newValue.at(index))));
+ heart = ((double)(((uint8_t)newValue.at(index))));
emit debug(QStringLiteral("Current Heart: ") + QString::number(heart));
} else {
emit debug(QStringLiteral("Error on parsing heart!"));
diff --git a/src/smartrowrower.cpp b/src/smartrowrower.cpp
index 301ae87ca..7a26378f1 100644
--- a/src/smartrowrower.cpp
+++ b/src/smartrowrower.cpp
@@ -184,6 +184,7 @@ double smartrowrower::bikeResistanceToPeloton(double resistance) {
}
void smartrowrower::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -257,10 +258,10 @@ void smartrowrower::characteristicChanged(const QLowEnergyCharacteristic &charac
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
// Distance += ((Speed.value() / 3600000.0) *
- // ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())) );
+ // ((double)lastRefreshCharacteristicChanged.msecsTo(now)) );
Distance = distance;
if (Cadence.value() > 0) {
@@ -268,7 +269,7 @@ void smartrowrower::characteristicChanged(const QLowEnergyCharacteristic &charac
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
diff --git a/src/snodebike.cpp b/src/snodebike.cpp
index 43d88dbca..e9ce28d68 100644
--- a/src/snodebike.cpp
+++ b/src/snodebike.cpp
@@ -113,6 +113,7 @@ void snodebike::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void snodebike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
double heart = 0.0;
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
@@ -164,7 +165,7 @@ void snodebike::characteristicChanged(const QLowEnergyCharacteristic &characteri
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
index += 2;
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
@@ -208,7 +209,7 @@ void snodebike::characteristicChanged(const QLowEnergyCharacteristic &characteri
// else
{
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
}
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
@@ -257,7 +258,7 @@ void snodebike::characteristicChanged(const QLowEnergyCharacteristic &characteri
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
}
@@ -270,7 +271,7 @@ void snodebike::characteristicChanged(const QLowEnergyCharacteristic &characteri
#endif
{
if (Flags.heartRate) {
- heart = ((double)((newValue.at(index))));
+ heart = ((double)(((uint8_t)newValue.at(index))));
// index += 1; // NOTE: clang-analyzer-deadcode.DeadStores
emit debug(QStringLiteral("Current Heart: ") + QString::number(heart));
}
@@ -310,7 +311,7 @@ void snodebike::characteristicChanged(const QLowEnergyCharacteristic &characteri
Resistance = m_pelotonResistance;
emit resistanceRead(Resistance.value());
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
if (heart == 0.0) {
diff --git a/src/solebike.cpp b/src/solebike.cpp
index f42eb06eb..2d8cdb11c 100644
--- a/src/solebike.cpp
+++ b/src/solebike.cpp
@@ -220,6 +220,7 @@ double solebike::bikeResistanceToPeloton(double resistance) {
}
void solebike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -260,7 +261,7 @@ void solebike::characteristicChanged(const QLowEnergyCharacteristic &characteris
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
m_watt = GetWattFromPacket(newValue);
@@ -271,17 +272,17 @@ void solebike::characteristicChanged(const QLowEnergyCharacteristic &characteris
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) {
diff --git a/src/soleelliptical.cpp b/src/soleelliptical.cpp
index 23f6866e7..fa3121330 100644
--- a/src/soleelliptical.cpp
+++ b/src/soleelliptical.cpp
@@ -299,6 +299,7 @@ void soleelliptical::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void soleelliptical::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
@@ -376,11 +377,11 @@ void soleelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
}
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
emit debug(QStringLiteral("Current speed: ") + QString::number(speed));
emit debug(QStringLiteral("Current cadence: ") + QString::number(Cadence.value()));
diff --git a/src/solef80treadmill.cpp b/src/solef80treadmill.cpp
index 5fef62871..07e8517ca 100644
--- a/src/solef80treadmill.cpp
+++ b/src/solef80treadmill.cpp
@@ -674,7 +674,7 @@ void solef80treadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
if (Flags.heartRate) {
if (index < newValue.length()) {
- heart = ((double)((newValue.at(index))));
+ heart = ((double)(((uint8_t)newValue.at(index))));
emit debug(QStringLiteral("Current Heart: ") + QString::number(heart));
} else {
emit debug(QStringLiteral("Error on parsing heart!"));
diff --git a/src/sportsplusbike.cpp b/src/sportsplusbike.cpp
index c8a8c6497..d80d0556b 100644
--- a/src/sportsplusbike.cpp
+++ b/src/sportsplusbike.cpp
@@ -120,6 +120,7 @@ void sportsplusbike::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void sportsplusbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -142,7 +143,7 @@ void sportsplusbike::characteristicChanged(const QLowEnergyCharacteristic &chara
if (newValue.at(1) == 0x20) {
double speed = GetSpeedFromPacket(newValue);
if (!firstCharChanged) {
- Distance += ((speed / 3600.0) / (1000.0 / (lastTimeCharChanged.msecsTo(QDateTime::currentDateTime()))));
+ Distance += ((speed / 3600.0) / (1000.0 / (lastTimeCharChanged.msecsTo(now))));
}
emit debug(QStringLiteral("Current speed: ") + QString::number(speed));
@@ -151,9 +152,9 @@ void sportsplusbike::characteristicChanged(const QLowEnergyCharacteristic &chara
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
- lastTimeCharChanged = QDateTime::currentDateTime();
+ lastTimeCharChanged = now;
} else if (newValue.at(1) == 0x30) {
double watt = GetWattFromPacket(newValue);
emit debug(QStringLiteral("Current watt: ") + QString::number(watt));
@@ -193,22 +194,22 @@ void sportsplusbike::characteristicChanged(const QLowEnergyCharacteristic &chara
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
emit debug(QStringLiteral("Current speed: ") + QString::number(Speed.value()));
if (!firstCharChanged) {
Distance +=
- ((Speed.value() / 3600.0) / (1000.0 / (lastTimeCharChanged.msecsTo(QDateTime::currentDateTime()))));
+ ((Speed.value() / 3600.0) / (1000.0 / (lastTimeCharChanged.msecsTo(now))));
}
- lastTimeCharChanged = QDateTime::currentDateTime();
+ lastTimeCharChanged = now;
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
} else {
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
@@ -224,7 +225,7 @@ void sportsplusbike::characteristicChanged(const QLowEnergyCharacteristic &chara
double speed = GetSpeedFromPacket(newValue);
cadence = speed * 2.685185;
if (!firstCharChanged) {
- Distance += ((speed / 3600.0) / (1000.0 / (lastTimeCharChanged.msecsTo(QDateTime::currentDateTime()))));
+ Distance += ((speed / 3600.0) / (1000.0 / (lastTimeCharChanged.msecsTo(now))));
}
emit debug(QStringLiteral("Current speed: ") + QString::number(speed));
@@ -233,13 +234,13 @@ void sportsplusbike::characteristicChanged(const QLowEnergyCharacteristic &chara
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
- lastTimeCharChanged = QDateTime::currentDateTime();
+ lastTimeCharChanged = now;
kcal = GetKcalFromPacket(newValue);
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
diff --git a/src/sportstechbike.cpp b/src/sportstechbike.cpp
index 330d7beb2..67ab78714 100644
--- a/src/sportstechbike.cpp
+++ b/src/sportstechbike.cpp
@@ -116,6 +116,7 @@ void sportstechbike::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void sportstechbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -171,7 +172,7 @@ void sportstechbike::characteristicChanged(const QLowEnergyCharacteristic &chara
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
Resistance = requestResistance;
emit resistanceRead(Resistance.value());
diff --git a/src/stagesbike.cpp b/src/stagesbike.cpp
index c6a187d94..5a2e2bfe1 100644
--- a/src/stagesbike.cpp
+++ b/src/stagesbike.cpp
@@ -164,6 +164,7 @@ uint16_t stagesbike::wattsFromResistance(double resistance) {
}
void stagesbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -262,8 +263,8 @@ void stagesbike::characteristicChanged(const QLowEnergyCharacteristic &character
if (cadence >= 0) {
Cadence = cadence;
}
- lastGoodCadence = QDateTime::currentDateTime();
- } else if (lastGoodCadence.msecsTo(QDateTime::currentDateTime()) > 2000) {
+ lastGoodCadence = now;
+ } else if (lastGoodCadence.msecsTo(now) > 2000) {
Cadence = 0;
}
}
@@ -282,12 +283,12 @@ void stagesbike::characteristicChanged(const QLowEnergyCharacteristic &character
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
// if we change this, also change the wattsFromResistance function. We can create a standard function in
@@ -339,7 +340,7 @@ void stagesbike::characteristicChanged(const QLowEnergyCharacteristic &character
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight
// in kg * 3.5) / 200 ) / 60
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
}
@@ -357,7 +358,7 @@ void stagesbike::characteristicChanged(const QLowEnergyCharacteristic &character
}
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
if (!noVirtualDevice) {
#ifdef Q_OS_IOS
diff --git a/src/strydrunpowersensor.cpp b/src/strydrunpowersensor.cpp
index 9e03eb660..e5cb87a2e 100644
--- a/src/strydrunpowersensor.cpp
+++ b/src/strydrunpowersensor.cpp
@@ -78,7 +78,7 @@ void strydrunpowersensor::update() {
// gattWriteCharacteristic.isValid() &&
// gattNotify1Characteristic.isValid() &&
/*initDone*/) {
- update_metrics(true, watts());
+ update_metrics(false, watts());
if (requestInclination != -100) {
Inclination = treadmillInclinationOverrideReverse(requestInclination);
@@ -119,7 +119,9 @@ void strydrunpowersensor::characteristicChanged(const QLowEnergyCharacteristic &
if (newValue.length() > 3) {
powerReceived = true;
- m_watt = (((uint16_t)((uint8_t)newValue.at(3)) << 8) | (uint16_t)((uint8_t)newValue.at(2)));
+ double weight = settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
+ double vwatts = ((9.8 * weight) * (currentInclination().value() / 100.0));
+ m_watt = (((uint16_t)((uint8_t)newValue.at(3)) << 8) | (uint16_t)((uint8_t)newValue.at(2))) + vwatts;
}
emit powerChanged(m_watt.value());
@@ -286,7 +288,7 @@ void strydrunpowersensor::characteristicChanged(const QLowEnergyCharacteristic &
if (Flags.heartRate) {
if (index < newValue.length()) {
- double heart = ((double)((newValue.at(index))));
+ double heart = ((double)(((uint8_t)newValue.at(index))));
emit debug(QStringLiteral("Current Heart: ") + QString::number(heart));
} else {
emit debug(QStringLiteral("Error on parsing heart!"));
@@ -422,6 +424,15 @@ void strydrunpowersensor::characteristicChanged(const QLowEnergyCharacteristic &
qDebug() << QStringLiteral("Current GroundContactMS:") << GroundContactMS.value();
qDebug() << QStringLiteral("Current VerticalOscillationMM:") << VerticalOscillationMM.value();
}
+ } else if (characteristic.uuid() == QBluetoothUuid(QStringLiteral("7e78aa19-72cd-d3b8-a81f-5b7e589bea0f"))) {
+ if (newValue.length() == 20 && newValue.at(0) == 0x32) {
+ GroundContactMS = (((uint16_t)((uint8_t)newValue.at(5)) << 8) | (uint16_t)((uint8_t)newValue.at(4)));
+ emit groundContactChanged(GroundContactMS.value());
+ //VerticalOscillationMM = (((uint16_t)((uint8_t)newValue.at(4)) << 8) | (uint16_t)((uint8_t)newValue.at(3)));
+ //emit verticalOscillationChanged(VerticalOscillationMM.value());
+ qDebug() << QStringLiteral("Current GroundContactMS:") << GroundContactMS.value();
+ qDebug() << QStringLiteral("Current VerticalOscillationMM:") << VerticalOscillationMM.value();
+ }
}
if (!noVirtualDevice) {
diff --git a/src/tacxneo2.cpp b/src/tacxneo2.cpp
index 9baa336c6..350823e6f 100644
--- a/src/tacxneo2.cpp
+++ b/src/tacxneo2.cpp
@@ -175,6 +175,7 @@ void tacxneo2::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void tacxneo2::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -236,8 +237,8 @@ void tacxneo2::characteristicChanged(const QLowEnergyCharacteristic &characteris
if (cadence >= 0 && cadence < 255) {
Cadence = cadence;
}
- lastGoodCadence = QDateTime::currentDateTime();
- } else if (lastGoodCadence.msecsTo(QDateTime::currentDateTime()) > 2000) {
+ lastGoodCadence = now;
+ } else if (lastGoodCadence.msecsTo(now) > 2000) {
Cadence = 0;
}
@@ -249,7 +250,7 @@ void tacxneo2::characteristicChanged(const QLowEnergyCharacteristic &characteris
.toDouble();
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
// Resistance = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
// (uint16_t)((uint8_t)newValue.at(index)))); debug("Current Resistance: " +
@@ -284,9 +285,9 @@ void tacxneo2::characteristicChanged(const QLowEnergyCharacteristic &characteris
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
emit debug(QStringLiteral("Current CrankRevsRead: ") + QString::number(CrankRevsRead));
emit debug(QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime));
diff --git a/src/technogymmyruntreadmill.cpp b/src/technogymmyruntreadmill.cpp
index c3523cbb3..6d5caa88b 100644
--- a/src/technogymmyruntreadmill.cpp
+++ b/src/technogymmyruntreadmill.cpp
@@ -395,7 +395,7 @@ void technogymmyruntreadmill::characteristicChanged(const QLowEnergyCharacterist
if (Flags.heartRate) {
if (index < newValue.length()) {
- heart = ((double)((newValue.at(index))));
+ heart = ((double)(((uint8_t)newValue.at(index))));
emit debug(QStringLiteral("Current Heart: ") + QString::number(heart));
} else {
emit debug(QStringLiteral("Error on parsing heart!"));
diff --git a/src/technogymmyruntreadmillrfcomm.cpp b/src/technogymmyruntreadmillrfcomm.cpp
index 0ef1a4f3f..6017e49de 100644
--- a/src/technogymmyruntreadmillrfcomm.cpp
+++ b/src/technogymmyruntreadmillrfcomm.cpp
@@ -38,8 +38,8 @@ void technogymmyruntreadmillrfcomm::deviceDiscovered(const QBluetoothDeviceInfo
// the full discovery works but it requires 2 or more minutes to complete.
// if you have issue finding your device, open the Technogym app, connect to your
// treadmill, and then you should be safe to use QZ for that moment on
- discoveryAgent->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
- //discoveryAgent->start(QBluetoothServiceDiscoveryAgent::MinimalDiscovery);
+ //discoveryAgent->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
+ discoveryAgent->start(QBluetoothServiceDiscoveryAgent::MinimalDiscovery);
return;
}
}
@@ -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"));
}
}
diff --git a/src/templateinfosenderbuilder.cpp b/src/templateinfosenderbuilder.cpp
index 6707472f1..083d098a9 100644
--- a/src/templateinfosenderbuilder.cpp
+++ b/src/templateinfosenderbuilder.cpp
@@ -995,13 +995,13 @@ void TemplateInfoSenderBuilder::buildContext(bool forceReinit) {
obj.setProperty(QStringLiteral("moving_h"), el.hour());
obj.setProperty(QStringLiteral("speed"), (dep = device->currentSpeed()).value());
obj.setProperty(QStringLiteral("speed_avg"), dep.average());
- obj.setProperty(QStringLiteral("speed_color"), dep.color());
+ obj.setProperty(QStringLiteral("speed_color"), homeform::singleton()->speed->valueFontColor());
obj.setProperty(QStringLiteral("speed_lapavg"), dep.lapAverage());
obj.setProperty(QStringLiteral("speed_lapmax"), dep.lapMax());
obj.setProperty(QStringLiteral("calories"), device->calories().value());
obj.setProperty(QStringLiteral("distance"), device->odometer());
obj.setProperty(QStringLiteral("heart"), (dep = device->currentHeart()).value());
- obj.setProperty(QStringLiteral("heart_color"), dep.color());
+ obj.setProperty(QStringLiteral("heart_color"), homeform::singleton()->heart->valueFontColor());
obj.setProperty(QStringLiteral("heart_avg"), dep.average());
obj.setProperty(QStringLiteral("heart_lapavg"), dep.lapAverage());
obj.setProperty(QStringLiteral("heart_max"), dep.max());
@@ -1011,7 +1011,7 @@ void TemplateInfoSenderBuilder::buildContext(bool forceReinit) {
obj.setProperty(QStringLiteral("difficult"), device->difficult());
obj.setProperty(QStringLiteral("watts"), (dep = device->wattsMetric()).value());
obj.setProperty(QStringLiteral("watts_avg"), dep.average());
- obj.setProperty(QStringLiteral("watts_color"), dep.color());
+ obj.setProperty(QStringLiteral("watts_color"), homeform::singleton()->watt->valueFontColor());
obj.setProperty(QStringLiteral("watts_lapavg"), dep.lapAverage());
obj.setProperty(QStringLiteral("watts_max"), dep.max());
obj.setProperty(QStringLiteral("watts_lapmax"), dep.lapMax());
@@ -1037,6 +1037,16 @@ void TemplateInfoSenderBuilder::buildContext(bool forceReinit) {
obj.setProperty(QStringLiteral("row_remaining_time_m"), 0);
obj.setProperty(QStringLiteral("row_remaining_time_h"), 0);
}
+ if (homeform::singleton()->trainingProgram()) {
+ el = homeform::singleton()->trainingProgram()->remainingTime();
+ obj.setProperty(QStringLiteral("remaining_time_s"), el.second());
+ obj.setProperty(QStringLiteral("remaining_time_m"), el.minute());
+ obj.setProperty(QStringLiteral("remaining_time_h"), el.hour());
+ } else {
+ obj.setProperty(QStringLiteral("remaining_time_s"), 0);
+ obj.setProperty(QStringLiteral("remaining_time_m"), 0);
+ obj.setProperty(QStringLiteral("remaining_time_h"), 0);
+ }
obj.setProperty(
QStringLiteral("nickName"),
(nickName = settings.value(QZSettings::user_nickname, QZSettings::default_user_nickname).toString())
@@ -1054,16 +1064,17 @@ void TemplateInfoSenderBuilder::buildContext(bool forceReinit) {
obj.setProperty(QStringLiteral("power_zone_lapavg"), ((bike *)device)->currentPowerZone().lapAverage());
obj.setProperty(QStringLiteral("power_zone_lapmax"), ((bike *)device)->currentPowerZone().lapMax());
obj.setProperty(QStringLiteral("target_power_zone"), ((bike *)device)->targetPowerZone().value());
+ obj.setProperty(QStringLiteral("power_zone_color"), homeform::singleton()->ftp->valueFontColor());
obj.setProperty(QStringLiteral("peloton_resistance"),
(dep = ((bike *)device)->pelotonResistance()).value());
obj.setProperty(QStringLiteral("peloton_resistance_avg"), dep.average());
- obj.setProperty(QStringLiteral("peloton_resistance_color"), dep.color());
+ obj.setProperty(QStringLiteral("peloton_resistance_color"), homeform::singleton()->peloton_resistance->valueFontColor());
obj.setProperty(QStringLiteral("peloton_resistance_lapavg"), dep.lapAverage());
obj.setProperty(QStringLiteral("peloton_resistance_lapmax"), dep.lapMax());
obj.setProperty(QStringLiteral("peloton_req_resistance"),
(dep = ((bike *)device)->lastRequestedPelotonResistance()).value());
obj.setProperty(QStringLiteral("cadence"), (dep = ((bike *)device)->currentCadence()).value());
- obj.setProperty(QStringLiteral("cadence_color"), dep.color());
+ obj.setProperty(QStringLiteral("cadence_color"), homeform::singleton()->cadence->valueFontColor());
obj.setProperty(QStringLiteral("cadence_avg"), dep.average());
obj.setProperty(QStringLiteral("cadence_lapavg"), dep.lapAverage());
obj.setProperty(QStringLiteral("cadence_lapmax"), dep.lapMax());
@@ -1086,7 +1097,7 @@ void TemplateInfoSenderBuilder::buildContext(bool forceReinit) {
(dep = ((rower *)device)->pelotonResistance()).value());
obj.setProperty(QStringLiteral("peloton_resistance_avg"), dep.average());
obj.setProperty(QStringLiteral("cadence"), (dep = ((rower *)device)->currentCadence()).value());
- obj.setProperty(QStringLiteral("cadence_color"), dep.color());
+ obj.setProperty(QStringLiteral("cadence_color"), homeform::singleton()->cadence->valueFontColor());
obj.setProperty(QStringLiteral("cadence_avg"), dep.average());
obj.setProperty(QStringLiteral("cadence_lapavg"), dep.lapAverage());
obj.setProperty(QStringLiteral("cadence_lapmax"), dep.lapMax());
@@ -1106,7 +1117,7 @@ void TemplateInfoSenderBuilder::buildContext(bool forceReinit) {
obj.setProperty(QStringLiteral("target_inclination"),
((treadmill *)device)->lastRequestedInclination().value());
obj.setProperty(QStringLiteral("cadence"), (dep = ((treadmill *)device)->currentCadence()).value());
- obj.setProperty(QStringLiteral("cadence_color"), dep.color());
+ obj.setProperty(QStringLiteral("cadence_color"), homeform::singleton()->cadence->valueFontColor());
obj.setProperty(QStringLiteral("cadence_avg"), dep.average());
obj.setProperty(QStringLiteral("cadence_lapavg"), dep.lapAverage());
obj.setProperty(QStringLiteral("cadence_lapmax"), dep.lapMax());
@@ -1122,7 +1133,7 @@ void TemplateInfoSenderBuilder::buildContext(bool forceReinit) {
(dep = ((treadmill *)device)->currentVerticalOscillation()).value());
} else if (tp == bluetoothdevice::ELLIPTICAL) {
obj.setProperty(QStringLiteral("cadence"), (dep = ((elliptical *)device)->currentCadence()).value());
- obj.setProperty(QStringLiteral("cadence_color"), dep.color());
+ obj.setProperty(QStringLiteral("cadence_color"), homeform::singleton()->cadence->valueFontColor());
obj.setProperty(QStringLiteral("cadence_avg"), dep.average());
obj.setProperty(QStringLiteral("cadence_lapavg"), dep.lapAverage());
obj.setProperty(QStringLiteral("cadence_lapmax"), dep.lapMax());
diff --git a/src/toorxtreadmill.cpp b/src/toorxtreadmill.cpp
index a73b687f7..6f11f8b6d 100644
--- a/src/toorxtreadmill.cpp
+++ b/src/toorxtreadmill.cpp
@@ -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
diff --git a/src/trainprogram.cpp b/src/trainprogram.cpp
index 71d9202b6..788e1f897 100644
--- a/src/trainprogram.cpp
+++ b/src/trainprogram.cpp
@@ -29,6 +29,12 @@ trainprogram::trainprogram(const QList &rows, bluetooth *b, QString *d
this->description = *description;
if (tags)
this->tags = *tags;
+
+ if(settings.value(QZSettings::zwift_username, QZSettings::default_zwift_username).toString().length() > 0) {
+ zwift_auth_token = new AuthToken(settings.value(QZSettings::zwift_username, QZSettings::default_zwift_username).toString(), settings.value(QZSettings::zwift_password, QZSettings::default_zwift_password).toString());
+ zwift_auth_token->getAccessToken();
+ }
+
/*
int c = 0;
for (c = 0; c < rows.length(); c++) {
@@ -436,27 +442,27 @@ double trainprogram::avgInclinationNext100Meters(int step) {
if (sum == 1) {
return rows.at(currentStep).inclination;
}
- return avg / (double)sum;
+ return avg / (double)km;
}
if (c == currentStep)
km += (rows.at(c).distance - currentStepDistance);
else
km += (rows.at(c).distance);
- avg += rows.at(c).inclination;
+ avg += rows.at(c).inclination * rows.at(c).distance;
sum++;
} else {
if (sum == 1) {
return rows.at(currentStep).inclination;
}
- return avg / (double)sum;
+ return avg / (double)km;
}
c++;
}
if (sum == 1) {
return rows.at(currentStep).inclination;
}
- return avg / (double)sum;
+ return avg / (double)km;
}
double trainprogram::avgAzimuthNext300Meters() {
@@ -572,7 +578,6 @@ void trainprogram::scheduler() {
QMutexLocker(&this->schedulerMutex);
QSettings settings;
-
// outside the if case about a valid train program because the information for the floating window url should be
// sent anyway
if (settings.value(QZSettings::peloton_companion_workout_ocr, QZSettings::default_companion_peloton_workout_ocr)
@@ -590,6 +595,91 @@ void trainprogram::scheduler() {
(bluetoothManager->device()->currentSpeed().value() <= 0 &&
!settings.value(QZSettings::continuous_moving, QZSettings::default_continuous_moving).toBool()) ||
bluetoothManager->device()->isPaused()) {
+
+ if(bluetoothManager->device() && bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL &&
+ settings.value(QZSettings::zwift_username, QZSettings::default_zwift_username).toString().length() > 0 &&
+ zwift_auth_token->access_token.length() > 0) {
+ if(!zwift_world) {
+ zwift_world = new World(1, zwift_auth_token->getAccessToken());
+ qDebug() << "creating zwift api world";
+ }
+ else {
+#ifdef Q_OS_IOS
+#ifndef IO_UNDER_QT
+ if(!h)
+ h = new lockscreen();
+#endif
+#endif
+ if(zwift_player_id == -1) {
+ QString id = zwift_world->player_id();
+ QJsonParseError parseError;
+ QJsonDocument document = QJsonDocument::fromJson(id.toLocal8Bit(), &parseError);
+ QJsonObject ride = document.object();
+ qDebug() << "zwift api player" << ride;
+ zwift_player_id = ride[QStringLiteral("id")].toInt();
+ } else {
+ static int zwift_counter = 5;
+ if(zwift_counter++ >= 4) {
+ zwift_counter = 0;
+ QByteArray bb = zwift_world->playerStatus(zwift_player_id);
+#ifdef Q_OS_IOS
+#ifndef IO_UNDER_QT
+ h->zwift_api_decodemessage_player(bb.data(), bb.length());
+ float alt = h->zwift_api_getaltitude();
+ float distance = h->zwift_api_getdistance();
+#else
+ float alt = 0;
+ float distance = 0;
+#endif
+#elif defined(Q_OS_ANDROID)
+ QAndroidJniEnvironment env;
+ jbyteArray d = env->NewByteArray(bb.length());
+ jbyte *b = env->GetByteArrayElements(d, 0);
+ for (int i = 0; i < bb.length(); i++)
+ b[i] = bb[i];
+ env->SetByteArrayRegion(d, 0, bb.length(), b);
+
+ QAndroidJniObject::callStaticMethod(
+ "org/cagnulen/qdomyoszwift/ZwiftAPI", "zwift_api_decodemessage_player", "([B)V", d);
+ env->DeleteLocalRef(d);
+
+ float alt = QAndroidJniObject::callStaticMethod("org/cagnulen/qdomyoszwift/ZwiftAPI", "getAltitude", "()F");
+ float distance = QAndroidJniObject::callStaticMethod("org/cagnulen/qdomyoszwift/ZwiftAPI", "getDistance", "()F");
+#else
+ float alt = 0;
+ float distance = 0;
+#endif
+ static float old_distance = 0;
+ static float old_alt = 0;
+
+ qDebug() << "zwift api incline1" << old_distance << old_alt << distance << alt;
+
+ if(old_distance > 0) {
+ float delta = distance - old_distance;
+ float deltaA = alt - old_alt;
+ float incline = (deltaA / delta) / 2.0;
+ if(delta > 1) {
+ bool zwift_negative_inclination_x2 =
+ settings.value(QZSettings::zwift_negative_inclination_x2, QZSettings::default_zwift_negative_inclination_x2)
+ .toBool();
+ double offset =
+ settings.value(QZSettings::zwift_inclination_offset, QZSettings::default_zwift_inclination_offset).toDouble();
+ double gain =
+ settings.value(QZSettings::zwift_inclination_gain, QZSettings::default_zwift_inclination_gain).toDouble();
+ double grade = (incline * gain) + offset;
+ if (zwift_negative_inclination_x2 && incline < 0) {
+ grade = ((incline * 2.0) * gain) + offset;
+ }
+ qDebug() << "zwift api incline" << incline << grade << delta << deltaA;
+ bluetoothManager->device()->changeInclination(grade, grade);
+ }
+ }
+ old_distance = distance;
+ old_alt = alt;
+ }
+ }
+ }
+ }
// in case no workout has been selected
// Zwift OCR
@@ -764,7 +854,8 @@ void trainprogram::scheduler() {
emit changeRequestedPelotonResistance(rows.at(0).requested_peloton_resistance);
}
- if (rows.at(0).inclination != -200 && bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE) {
+ if (rows.at(0).inclination != -200 && (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE ||
+ (bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL && !((elliptical*)bluetoothManager->device())->inclinationAvailableByHardware()))) {
// this should be converted in a signal as all the other signals...
double bikeResistanceOffset =
settings.value(QZSettings::bike_resistance_offset, QZSettings::default_bike_resistance_offset)
@@ -776,7 +867,7 @@ void trainprogram::scheduler() {
double inc = rows.at(0).inclination;
bluetoothManager->device()->changeResistance((resistance_t)(round(inc * bikeResistanceGain)) +
bikeResistanceOffset + 1); // resistance start from 1)
- if (!((bike *)bluetoothManager->device())->inclinationAvailableByHardware())
+ if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE && !((bike *)bluetoothManager->device())->inclinationAvailableByHardware())
bluetoothManager->device()->setInclination(inc);
qDebug() << QStringLiteral("trainprogram change inclination") + QString::number(inc);
emit changeInclination(inc, inc);
@@ -916,7 +1007,8 @@ void trainprogram::scheduler() {
}
if (rows.at(currentStep).inclination != -200 &&
- bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE) {
+ (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE ||
+ (bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL && !((elliptical*)bluetoothManager->device())->inclinationAvailableByHardware()))) {
// this should be converted in a signal as all the other signals...
double bikeResistanceOffset =
settings
@@ -931,7 +1023,7 @@ void trainprogram::scheduler() {
bluetoothManager->device()->changeResistance((resistance_t)(round(inc * bikeResistanceGain)) +
bikeResistanceOffset +
1); // resistance start from 1)
- if (!((bike *)bluetoothManager->device())->inclinationAvailableByHardware())
+ if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE && !((bike *)bluetoothManager->device())->inclinationAvailableByHardware())
bluetoothManager->device()->setInclination(inc);
qDebug() << QStringLiteral("trainprogram change inclination") + QString::number(inc);
emit changeInclination(inc, inc);
@@ -1201,8 +1293,8 @@ bool trainprogram::saveXML(const QString &filename, const QList &rows)
void trainprogram::save(const QString &filename) { saveXML(filename, rows); }
-trainprogram *trainprogram::load(const QString &filename, bluetooth *b) {
- if (!filename.right(3).toUpper().compare(QStringLiteral("ZWO"))) {
+trainprogram *trainprogram::load(const QString &filename, bluetooth *b, QString Extension) {
+ if (!Extension.toUpper().compare(QStringLiteral("ZWO"))) {
QString description = "";
QString tags = "";
diff --git a/src/trainprogram.h b/src/trainprogram.h
index a6d1a4410..7aa07e9f8 100644
--- a/src/trainprogram.h
+++ b/src/trainprogram.h
@@ -8,6 +8,13 @@
#include
#include
+#ifdef Q_OS_IOS
+#include "ios/lockscreen.h"
+#endif
+
+#include "zwift-api/PlayerStateWrapper.h"
+#include "zwift-api/zwift_client_auth.h"
+
class trainrow {
public:
QTime duration = QTime(0, 0, 0, 0);
@@ -62,7 +69,7 @@ class trainprogram : public QObject {
trainprogram(const QList &, bluetooth *b, QString *description = nullptr, QString *tags = nullptr,
bool videoAvailable = false);
void save(const QString &filename);
- static trainprogram *load(const QString &filename, bluetooth *b);
+ static trainprogram *load(const QString &filename, bluetooth *b, QString Extension);
static QList loadXML(const QString &filename);
static bool saveXML(const QString &filename, const QList &rows);
QTime totalElapsedTime();
@@ -155,6 +162,15 @@ private slots:
QUdpSocket* pelotonOCRsocket = nullptr;
void pelotonOCRcomputeTime(QString t);
+
+ AuthToken* zwift_auth_token = nullptr;
+ World* zwift_world = nullptr;
+ int zwift_player_id = -1;
+
+#ifdef Q_OS_IOS
+ lockscreen *h = 0;
+#endif
+
};
#endif // TRAINPROGRAM_H
diff --git a/src/treadmill.cpp b/src/treadmill.cpp
index ab30f8a36..fac76d1f3 100644
--- a/src/treadmill.cpp
+++ b/src/treadmill.cpp
@@ -17,6 +17,18 @@ void treadmill::changeSpeed(double speed) {
requestSpeed = (speed * m_difficult) + m_difficult_offset;
}
void treadmill::changeInclination(double grade, double inclination) {
+ QSettings settings;
+ double treadmill_incline_min = settings.value(QZSettings::treadmill_incline_min, QZSettings::default_treadmill_incline_min).toDouble();
+ double treadmill_incline_max = settings.value(QZSettings::treadmill_incline_max, QZSettings::default_treadmill_incline_max).toDouble();
+
+ if(grade < treadmill_incline_min) {
+ grade = treadmill_incline_min;
+ qDebug() << "grade override due to treadmill_incline_min" << grade;
+ } else if(grade > treadmill_incline_max) {
+ grade = treadmill_incline_max;
+ qDebug() << "grade override due to treadmill_incline_max" << grade;
+ }
+
m_lastRawInclinationRequested = grade;
Q_UNUSED(inclination);
qDebug() << "changeInclination" << grade << autoResistanceEnable << m_inclination_difficult
@@ -119,6 +131,7 @@ void treadmill::clearStats() {
Speed.clear(false);
KCal.clear(true);
Distance.clear(true);
+ Distance1s.clear(true);
Heart.clear(false);
m_jouls.clear(true);
elevationAcc = 0;
@@ -138,6 +151,7 @@ void treadmill::setPaused(bool p) {
Speed.setPaused(p);
KCal.setPaused(p);
Distance.setPaused(p);
+ Distance1s.setPaused(p);
Heart.setPaused(p);
m_jouls.setPaused(p);
m_watt.setPaused(p);
@@ -154,6 +168,7 @@ void treadmill::setLap() {
Speed.setLap(false);
KCal.setLap(true);
Distance.setLap(true);
+ Distance1s.setLap(true);
Heart.setLap(false);
m_jouls.setLap(true);
m_watt.setLap(false);
diff --git a/src/truetreadmill.cpp b/src/truetreadmill.cpp
index edc6fc829..6ceb6a078 100644
--- a/src/truetreadmill.cpp
+++ b/src/truetreadmill.cpp
@@ -139,6 +139,7 @@ void truetreadmill::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void truetreadmill::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
QSettings settings;
QString heartRateBeltName =
@@ -214,11 +215,11 @@ void truetreadmill::characteristicChanged(const QLowEnergyCharacteristic &charac
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
200.0) /
(60000.0 / ((double)lastTimeCharacteristicChanged.msecsTo(
- QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
Distance += ((speed / (double)3600.0) /
- ((double)1000.0 / (double)(lastTimeCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
- lastTimeCharacteristicChanged = QDateTime::currentDateTime();
+ ((double)1000.0 / (double)(lastTimeCharacteristicChanged.msecsTo(now))));
+ lastTimeCharacteristicChanged = now;
}
emit debug(QStringLiteral("Current speed: ") + QString::number(speed));
diff --git a/src/trxappgateusbbike.cpp b/src/trxappgateusbbike.cpp
index ce0bcec40..61a02399d 100644
--- a/src/trxappgateusbbike.cpp
+++ b/src/trxappgateusbbike.cpp
@@ -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");
}
diff --git a/src/ultrasportbike.cpp b/src/ultrasportbike.cpp
index b2d3f0ee2..9e922a729 100644
--- a/src/ultrasportbike.cpp
+++ b/src/ultrasportbike.cpp
@@ -159,6 +159,7 @@ double ultrasportbike::bikeResistanceToPeloton(double resistance) {
}
void ultrasportbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -189,7 +190,7 @@ void ultrasportbike::characteristicChanged(const QLowEnergyCharacteristic &chara
{
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
if (watts())
@@ -198,17 +199,17 @@ void ultrasportbike::characteristicChanged(const QLowEnergyCharacteristic &chara
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) {
diff --git a/src/virtualbike.cpp b/src/virtualbike.cpp
index 4088e9b63..b4bd57d4b 100644
--- a/src/virtualbike.cpp
+++ b/src/virtualbike.cpp
@@ -28,6 +28,7 @@ virtualbike::virtualbike(bluetoothdevice *t, bool noWriteResistance, bool noHear
bool echelon =
settings.value(QZSettings::virtual_device_echelon, QZSettings::default_virtual_device_echelon).toBool();
bool ifit = settings.value(QZSettings::virtual_device_ifit, QZSettings::default_virtual_device_ifit).toBool();
+ bool garmin_bluetooth_compatibility = settings.value(QZSettings::garmin_bluetooth_compatibility, QZSettings::default_garmin_bluetooth_compatibility).toBool();
if (settings.value(QZSettings::dircon_yes, QZSettings::default_dircon_yes).toBool()) {
dirconManager = new DirconManager(Bike, bikeResistanceOffset, bikeResistanceGain, this);
@@ -53,12 +54,12 @@ virtualbike::virtualbike(bluetoothdevice *t, bool noWriteResistance, bool noHear
#ifndef IO_UNDER_QT
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
- if (ios_peloton_workaround && !cadence && !echelon && !ifit && !heart_only && !power) {
+ if ((ios_peloton_workaround && !cadence && !echelon && !ifit && !heart_only) || garmin_bluetooth_compatibility) {
qDebug() << "ios_zwift_workaround activated!";
h = new lockscreen();
h->virtualbike_zwift_ios(
- settings.value(QZSettings::bike_heartrate_service, QZSettings::default_bike_heartrate_service).toBool());
+ settings.value(QZSettings::bike_heartrate_service, QZSettings::default_bike_heartrate_service).toBool(), garmin_bluetooth_compatibility);
} else
#endif
diff --git a/src/virtualtreadmill.cpp b/src/virtualtreadmill.cpp
index b9c4de675..919c954e4 100644
--- a/src/virtualtreadmill.cpp
+++ b/src/virtualtreadmill.cpp
@@ -353,7 +353,7 @@ void virtualtreadmill::treadmillProvider() {
if (h->virtualtreadmill_updateFTMS(
normalizeSpeed, 0, (uint16_t)((treadmill *)treadMill)->currentCadence().value() * cadence_multiplier,
(uint16_t)((treadmill *)treadMill)->wattsMetric().value(),
- treadMill->currentInclination().value() * 10)) {
+ treadMill->currentInclination().value() * 10, (uint64_t)(((treadmill *)treadMill)->odometer() * 1000.0))) {
h->virtualtreadmill_setHeartRate(((treadmill *)treadMill)->currentHeart().value());
lastSlopeChanged = h->virtualtreadmill_lastChangeCurrentSlope();
if ((uint64_t)QDateTime::currentSecsSinceEpoch() < lastSlopeChanged + slopeTimeoutSecs)
diff --git a/src/windows/zwift-incline-ai-server.py b/src/windows/zwift-incline-ai-server.py
new file mode 100644
index 000000000..8aad322da
--- /dev/null
+++ b/src/windows/zwift-incline-ai-server.py
@@ -0,0 +1,117 @@
+# iFit-Wolf3 - Autoincline control of treadmill via ADB and OCR
+# Author: Al Udell
+
+# Revised: November 25, 2023
+
+# zwift-incline.py - take Zwift screenshot, crop incline, OCR incline
+
+# imports
+import cv2
+import numpy as np
+import re
+from PIL import Image, ImageGrab
+import requests
+import win32gui
+
+# Enable DPI aware on Windows
+from ctypes import windll
+user32 = windll.user32
+user32.SetProcessDPIAware()
+
+# Take Zwift screenshot - windowed mode only
+hwnd = win32gui.FindWindow(None, 'Zwift')
+if not hwnd:
+ print("Zwift is not running")
+ exit()
+x, y, x1, y1 = win32gui.GetClientRect(hwnd)
+x, y = win32gui.ClientToScreen(hwnd, (x, y))
+x1, y1 = win32gui.ClientToScreen(hwnd, (x1, y1))
+screenshot = ImageGrab.grab((x, y, x1, y1))
+
+# Scale image to 3000 x 2000
+screenshot = screenshot.resize((3000, 2000))
+
+# Crop image to incline area
+screenwidth, screenheight = screenshot.size
+
+# Values for Zwift regular incline
+col1 = int(screenwidth/3000 * 2800)
+row1 = int(screenheight/2000 * 90)
+col2 = int(screenwidth/3000 * 2975)
+row2 = int(screenheight/2000 * 195)
+
+cropped = screenshot.crop((col1, row1, col2, row2))
+
+# Convert image to np array
+cropped_np = np.array(cropped)
+
+# Convert np array to PIL
+cropped_pil = Image.fromarray(cropped_np)
+
+# Convert PIL image to cv2 RGB
+cropped_cv2 = cv2.cvtColor(np.array(cropped_pil), cv2.COLOR_RGB2BGR)
+
+# Convert cv2 RGB to HSV
+result = cropped_cv2.copy()
+image = cv2.cvtColor(cropped_cv2, cv2.COLOR_BGR2HSV)
+
+# Isolate white mask
+lower = np.array([0,0,159])
+upper = np.array([0,0,255])
+mask0 = cv2.inRange(image, lower, upper)
+result0 = cv2.bitwise_and(result, result, mask=mask0)
+
+# Isolate yellow mask
+lower = np.array([24,239,241])
+upper = np.array([24,253,255])
+mask1 = cv2.inRange(image, lower, upper)
+result1 = cv2.bitwise_and(result, result, mask=mask1)
+
+# Isolate orange mask
+lower = np.array([8,191,243])
+upper = np.array([8,192,243])
+mask2 = cv2.inRange(image, lower, upper)
+result2 = cv2.bitwise_and(result, result, mask=mask2)
+
+# Isolate red mask
+lower = np.array([0,255,255])
+upper = np.array([10,255,255])
+mask3 = cv2.inRange(image, lower, upper)
+result3 = cv2.bitwise_and(result, result, mask=mask3)
+
+# Join colour masks
+mask = mask0+mask1+mask2+mask3
+
+# Set output image to zero everywhere except mask
+merge = image.copy()
+merge[np.where(mask==0)] = 0
+
+# Convert to grayscale
+gray = cv2.cvtColor(merge, cv2.COLOR_BGR2GRAY)
+
+# Convert to black/white by threshold
+ret,bin = cv2.threshold(gray, 70, 255, cv2.THRESH_BINARY_INV)
+
+# Apply gaussian blur
+gaussianBlur = cv2.GaussianBlur(bin,(3,3),0)
+
+# Write zwift image
+cv2.imwrite('zwift.png', gaussianBlur, [cv2.IMWRITE_PNG_COMPRESSION, 0])
+
+# OCR image
+image_data = open("zwift.png","rb").read()
+ocr = requests.post("http://localhost:32168/v1/image/ocr", files={"image":image_data}).json()
+
+# Extract label values from the 'predictions' list and merge into a single string
+labels = [prediction['label'] for prediction in ocr.get('predictions', [])]
+result = ''.join(labels)
+
+# Remove all characters that are not "-" and integers from OCR text
+pattern = r"[^-\d]+"
+ocr_text = re.sub(pattern, "", result)
+if ocr_text:
+ incline = ocr_text
+else:
+ incline = 'None'
+
+print(incline)
diff --git a/src/windows/zwift-incline-climb-portal-ai-server.py b/src/windows/zwift-incline-climb-portal-ai-server.py
new file mode 100644
index 000000000..1809faba1
--- /dev/null
+++ b/src/windows/zwift-incline-climb-portal-ai-server.py
@@ -0,0 +1,118 @@
+# iFit-Wolf3 - Autoincline control of treadmill via ADB and OCR
+# Author: Al Udell
+# Revised: November 25, 2023
+
+# zwift-incline-climb-portal.py - take Zwift screenshot, crop incline, OCR incline
+
+# imports
+import cv2
+import numpy as np
+import re
+
+from PIL import Image, ImageGrab
+import requests
+import win32gui
+#import time
+
+# Enable DPI aware on Windows
+from ctypes import windll
+user32 = windll.user32
+user32.SetProcessDPIAware()
+
+# Take Zwift screenshot - windowed mode only
+hwnd = win32gui.FindWindow(None, 'Zwift')
+if not hwnd:
+ print("Zwift is not running")
+ exit()
+x, y, x1, y1 = win32gui.GetClientRect(hwnd)
+x, y = win32gui.ClientToScreen(hwnd, (x, y))
+x1, y1 = win32gui.ClientToScreen(hwnd, (x1, y1))
+screenshot = ImageGrab.grab((x, y, x1, y1))
+
+# Scale image to 3000 x 2000
+screenshot = screenshot.resize((3000, 2000))
+
+# Crop image to incline area
+screenwidth, screenheight = screenshot.size
+
+# Values for Zwift climb portal incline
+col1 = int(screenwidth/3000 * 2822)
+row1 = int(screenheight/2000 * 218)
+col2 = int(screenwidth/3000 * 2980)
+row2 = int(screenheight/2000 * 302)
+
+cropped = screenshot.crop((col1, row1, col2, row2))
+
+# Convert image to np array
+cropped_np = np.array(cropped)
+
+# Convert np array to PIL
+cropped_pil = Image.fromarray(cropped_np)
+
+# Convert PIL image to cv2 RGB
+cropped_cv2 = cv2.cvtColor(np.array(cropped_pil), cv2.COLOR_RGB2BGR)
+
+# Convert cv2 RGB to HSV
+result = cropped_cv2.copy()
+image = cv2.cvtColor(cropped_cv2, cv2.COLOR_BGR2HSV)
+
+# Isolate white mask
+lower = np.array([0,0,159])
+upper = np.array([0,0,255])
+mask0 = cv2.inRange(image, lower, upper)
+result0 = cv2.bitwise_and(result, result, mask=mask0)
+
+# Isolate yellow mask
+lower = np.array([24,239,241])
+upper = np.array([24,253,255])
+mask1 = cv2.inRange(image, lower, upper)
+result1 = cv2.bitwise_and(result, result, mask=mask1)
+
+# Isolate orange mask
+lower = np.array([8,191,243])
+upper = np.array([8,192,243])
+mask2 = cv2.inRange(image, lower, upper)
+result2 = cv2.bitwise_and(result, result, mask=mask2)
+
+# Isolate red mask
+lower = np.array([0,255,255])
+upper = np.array([10,255,255])
+mask3 = cv2.inRange(image, lower, upper)
+result3 = cv2.bitwise_and(result, result, mask=mask3)
+
+# Join colour masks
+mask = mask0+mask1+mask2+mask3
+
+# Set output image to zero everywhere except mask
+merge = image.copy()
+merge[np.where(mask==0)] = 0
+
+# Convert to grayscale
+gray = cv2.cvtColor(merge, cv2.COLOR_BGR2GRAY)
+
+# Convert to black/white by threshold
+ret,bin = cv2.threshold(gray, 70, 255, cv2.THRESH_BINARY_INV)
+
+# Apply gaussian blur
+gaussianBlur = cv2.GaussianBlur(bin,(3,3),0)
+
+# Write zwift image
+cv2.imwrite('zwift.png', gaussianBlur, [cv2.IMWRITE_PNG_COMPRESSION, 0])
+
+# OCR image
+image_data = open("zwift.png","rb").read()
+ocr = requests.post("http://localhost:32168/v1/image/ocr", files={"image":image_data}).json()
+
+# Extract label values from the 'predictions' list and merge into a single string
+labels = [prediction['label'] for prediction in ocr.get('predictions', [])]
+result = ''.join(labels)
+
+# Remove all characters that are not "-" and integers from OCR text
+pattern = r"[^-\d]+"
+ocr_text = re.sub(pattern, "", result)
+if ocr_text:
+ incline = ocr_text
+else:
+ incline = 'None'
+
+print(incline)
diff --git a/src/windows/zwift-incline-climb-portal.py b/src/windows/zwift-incline-climb-portal.py
index b605b6a82..267473402 100644
--- a/src/windows/zwift-incline-climb-portal.py
+++ b/src/windows/zwift-incline-climb-portal.py
@@ -1,107 +1,116 @@
-# iFit-Wolf3 - Autoincline control of treadmill via ADB and OCR
-# Author: Al Udell
-# Revised: August 16, 2023
-
-# zwift-incline-climb-portal.py - take Zwift screenshot, crop incline, OCR incline
-
-# imports
-import cv2
-import numpy as np
-import re
-from datetime import datetime
-from paddleocr import PaddleOCR
-from PIL import Image, ImageGrab
-
-# Take Zwift screenshot
-screenshot = ImageGrab.grab()
-
-# Scale image to 3000 x 2000
-screenshot = screenshot.resize((3000, 2000))
-
-# Crop image to incline area
-screenwidth, screenheight = screenshot.size
-
-# Values for Zwift climb portal incline
-col1 = int(screenwidth/3000 * 2822)
-row1 = int(screenheight/2000 * 218)
-col2 = int(screenwidth/3000 * 2980)
-row2 = int(screenheight/2000 * 302)
-
-cropped = screenshot.crop((col1, row1, col2, row2))
-
-# Scale image to correct size for borderless window mode
-width, height = cropped.size
-cropped = cropped.resize((int(width * 1.3), int(height * 1.3)))
-
-# Convert image to np array
-cropped_np = np.array(cropped)
-
-# Convert np array to PIL
-cropped_pil = Image.fromarray(cropped_np)
-
-# Convert PIL image to cv2 RGB
-cropped_cv2 = cv2.cvtColor(np.array(cropped_pil), cv2.COLOR_RGB2BGR)
-
-# Convert cv2 RGB to HSV
-result = cropped_cv2.copy()
-image = cv2.cvtColor(cropped_cv2, cv2.COLOR_BGR2HSV)
-
-# Isolate white mask
-lower = np.array([0,0,159])
-upper = np.array([0,0,255])
-mask0 = cv2.inRange(image, lower, upper)
-result0 = cv2.bitwise_and(result, result, mask=mask0)
-
-# Isolate yellow mask
-lower = np.array([24,239,241])
-upper = np.array([24,253,255])
-mask1 = cv2.inRange(image, lower, upper)
-result1 = cv2.bitwise_and(result, result, mask=mask1)
-
-# Isolate orange mask
-lower = np.array([8,191,243])
-upper = np.array([8,192,243])
-mask2 = cv2.inRange(image, lower, upper)
-result2 = cv2.bitwise_and(result, result, mask=mask2)
-
-# Isolate red mask
-lower = np.array([0,255,255])
-upper = np.array([10,255,255])
-mask3 = cv2.inRange(image, lower, upper)
-result3 = cv2.bitwise_and(result, result, mask=mask3)
-
-# Join colour masks
-mask = mask0+mask1+mask2+mask3
-
-# Set output image to zero everywhere except mask
-merge = image.copy()
-merge[np.where(mask==0)] = 0
-
-# Convert to grayscale
-gray = cv2.cvtColor(merge, cv2.COLOR_BGR2GRAY)
-
-# Convert to black/white by threshold
-ret,bin = cv2.threshold(gray, 70, 255, cv2.THRESH_BINARY_INV)
-
-# Apply gaussian blur
-gaussianBlur = cv2.GaussianBlur(bin,(3,3),0)
-
-# OCR image
-ocr = PaddleOCR(lang='en', use_gpu=False, enable_mkldnn=True, use_angle_cls=False, table=False, layout=False, show_log=False)
-result = ocr.ocr(gaussianBlur, cls=False, det=True, rec=True)
-
-# Extract OCR text
-ocr_text = ''
-for line in result:
- for word in line:
- ocr_text += f"{word[1][0]}"
-
-# Remove all characters that are not "-" and integers from OCR text
-pattern = r"[^-\d]+"
-ocr_text = re.sub(pattern, "", ocr_text)
-if ocr_text:
- incline = ocr_text
-else:
- incline = 'None'
-
-print(incline)
+# iFit-Wolf3 - Autoincline control of treadmill via ADB and OCR
+# Author: Al Udell
+# Revised: November 23, 2023
+
+# zwift-incline-climb-portal.py - take Zwift screenshot, crop incline, OCR incline
+
+# imports
+import cv2
+import numpy as np
+import re
+import win32gui
+from datetime import datetime
+from paddleocr import PaddleOCR
+from PIL import Image, ImageGrab
+
+# Enable DPI aware on Windows
+from ctypes import windll
+user32 = windll.user32
+user32.SetProcessDPIAware()
+
+# Take Zwift screenshot - windowed mode only
+hwnd = win32gui.FindWindow(None, 'Zwift')
+if not hwnd:
+ print("Zwift is not running")
+ exit()
+x, y, x1, y1 = win32gui.GetClientRect(hwnd)
+x, y = win32gui.ClientToScreen(hwnd, (x, y))
+x1, y1 = win32gui.ClientToScreen(hwnd, (x1, y1))
+screenshot = ImageGrab.grab((x, y, x1, y1))
+
+# Scale image to 3000 x 2000
+screenshot = screenshot.resize((3000, 2000))
+
+# Crop image to incline area
+screenwidth, screenheight = screenshot.size
+
+# Values for Zwift climb portal incline
+col1 = int(screenwidth/3000 * 2822)
+row1 = int(screenheight/2000 * 218)
+col2 = int(screenwidth/3000 * 2980)
+row2 = int(screenheight/2000 * 302)
+
+cropped = screenshot.crop((col1, row1, col2, row2))
+
+# Convert image to np array
+cropped_np = np.array(cropped)
+
+# Convert np array to PIL
+cropped_pil = Image.fromarray(cropped_np)
+
+# Convert PIL image to cv2 RGB
+cropped_cv2 = cv2.cvtColor(np.array(cropped_pil), cv2.COLOR_RGB2BGR)
+
+# Convert cv2 RGB to HSV
+result = cropped_cv2.copy()
+image = cv2.cvtColor(cropped_cv2, cv2.COLOR_BGR2HSV)
+
+# Isolate white mask
+lower = np.array([0,0,159])
+upper = np.array([0,0,255])
+mask0 = cv2.inRange(image, lower, upper)
+result0 = cv2.bitwise_and(result, result, mask=mask0)
+
+# Isolate yellow mask
+lower = np.array([24,239,241])
+upper = np.array([24,253,255])
+mask1 = cv2.inRange(image, lower, upper)
+result1 = cv2.bitwise_and(result, result, mask=mask1)
+
+# Isolate orange mask
+lower = np.array([8,191,243])
+upper = np.array([8,192,243])
+mask2 = cv2.inRange(image, lower, upper)
+result2 = cv2.bitwise_and(result, result, mask=mask2)
+
+# Isolate red mask
+lower = np.array([0,255,255])
+upper = np.array([10,255,255])
+mask3 = cv2.inRange(image, lower, upper)
+result3 = cv2.bitwise_and(result, result, mask=mask3)
+
+# Join colour masks
+mask = mask0+mask1+mask2+mask3
+
+# Set output image to zero everywhere except mask
+merge = image.copy()
+merge[np.where(mask==0)] = 0
+
+# Convert to grayscale
+gray = cv2.cvtColor(merge, cv2.COLOR_BGR2GRAY)
+
+# Convert to black/white by threshold
+ret,bin = cv2.threshold(gray, 70, 255, cv2.THRESH_BINARY_INV)
+
+# Apply gaussian blur
+gaussianBlur = cv2.GaussianBlur(bin,(3,3),0)
+
+# OCR image
+ocr = PaddleOCR(lang='en', use_gpu=False, show_log=False, det_db_unclip_ratio=2.0, det_db_box_thresh=0.40, drop_score=0.40, rec_algorithm='CRNN', cls_model_dir='paddleocr/ch_ppocr_mobile_v2.0_cls_infer', det_model_dir='paddleocr/en_PP-OCRv3_det_infer', rec_model_dir='paddleocr/en_PP-OCRv3_rec_infer')
+result = ocr.ocr(gaussianBlur, cls=False, det=True, rec=True)
+
+# Extract OCR text
+ocr_text = ''
+for line in result:
+ for word in line:
+ ocr_text += f"{word[1][0]}"
+
+# Remove all characters that are not "-" and integers from OCR text
+pattern = r"[^-\d]+"
+ocr_text = re.sub(pattern, "", ocr_text)
+if ocr_text:
+ incline = ocr_text
+else:
+ incline = 'None'
+
+print(incline)
diff --git a/src/windows/zwift-incline.py b/src/windows/zwift-incline.py
index 7b883fa29..1f121ca2a 100644
--- a/src/windows/zwift-incline.py
+++ b/src/windows/zwift-incline.py
@@ -1,103 +1,116 @@
-# iFit-Wolf3 - Autoincline control of treadmill via ADB and OCR
-# Author: Al Udell
-# Revised: August 16, 2023
-
-# zwift-incline.py - take Zwift screenshot, crop incline, OCR incline
-
-# imports
-import cv2
-import numpy as np
-import re
-from datetime import datetime
-from paddleocr import PaddleOCR
-from PIL import Image, ImageGrab
-
-# Take Zwift screenshot
-screenshot = ImageGrab.grab()
-
-# Scale image to 3000 x 2000
-screenshot = screenshot.resize((3000, 2000))
-
-# Crop image to incline area
-screenwidth, screenheight = screenshot.size
-
-# Values for Zwift regular incline
-col1 = int(screenwidth/3000 * 2800)
-row1 = int(screenheight/2000 * 90)
-col2 = int(screenwidth/3000 * 2975)
-row2 = int(screenheight/2000 * 195)
-
-cropped = screenshot.crop((col1, row1, col2, row2))
-
-# Convert image to np array
-cropped_np = np.array(cropped)
-
-# Convert np array to PIL
-cropped_pil = Image.fromarray(cropped_np)
-
-# Convert PIL image to cv2 RGB
-cropped_cv2 = cv2.cvtColor(np.array(cropped_pil), cv2.COLOR_RGB2BGR)
-
-# Convert cv2 RGB to HSV
-result = cropped_cv2.copy()
-image = cv2.cvtColor(cropped_cv2, cv2.COLOR_BGR2HSV)
-
-# Isolate white mask
-lower = np.array([0,0,159])
-upper = np.array([0,0,255])
-mask0 = cv2.inRange(image, lower, upper)
-result0 = cv2.bitwise_and(result, result, mask=mask0)
-
-# Isolate yellow mask
-lower = np.array([24,239,241])
-upper = np.array([24,253,255])
-mask1 = cv2.inRange(image, lower, upper)
-result1 = cv2.bitwise_and(result, result, mask=mask1)
-
-# Isolate orange mask
-lower = np.array([8,191,243])
-upper = np.array([8,192,243])
-mask2 = cv2.inRange(image, lower, upper)
-result2 = cv2.bitwise_and(result, result, mask=mask2)
-
-# Isolate red mask
-lower = np.array([0,255,255])
-upper = np.array([10,255,255])
-mask3 = cv2.inRange(image, lower, upper)
-result3 = cv2.bitwise_and(result, result, mask=mask3)
-
-# Join colour masks
-mask = mask0+mask1+mask2+mask3
-
-# Set output image to zero everywhere except mask
-merge = image.copy()
-merge[np.where(mask==0)] = 0
-
-# Convert to grayscale
-gray = cv2.cvtColor(merge, cv2.COLOR_BGR2GRAY)
-
-# Convert to black/white by threshold
-ret,bin = cv2.threshold(gray, 70, 255, cv2.THRESH_BINARY_INV)
-
-# Apply gaussian blur
-gaussianBlur = cv2.GaussianBlur(bin,(3,3),0)
-
-# OCR image
-ocr = PaddleOCR(lang='en', use_gpu=False, enable_mkldnn=True, use_angle_cls=False, table=False, layout=False, show_log=False)
-result = ocr.ocr(gaussianBlur, cls=False, det=True, rec=True)
-
-# Extract OCR text
-ocr_text = ''
-for line in result:
- for word in line:
- ocr_text += f"{word[1][0]}"
-
-# Remove all characters that are not "-" and integers from OCR text
-pattern = r"[^-\d]+"
-ocr_text = re.sub(pattern, "", ocr_text)
-if ocr_text:
- incline = ocr_text
-else:
- incline = 'None'
-
-print(incline)
+# iFit-Wolf3 - Autoincline control of treadmill via ADB and OCR
+# Author: Al Udell
+# Revised: November 23, 2023
+
+# zwift-incline.py - take Zwift screenshot, crop incline, OCR incline
+
+# imports
+import cv2
+import numpy as np
+import re
+import win32gui
+from datetime import datetime
+from paddleocr import PaddleOCR
+from PIL import Image, ImageGrab
+
+# Enable DPI aware on Windows
+from ctypes import windll
+user32 = windll.user32
+user32.SetProcessDPIAware()
+
+# Take Zwift screenshot - windowed mode only
+hwnd = win32gui.FindWindow(None, 'Zwift')
+if not hwnd:
+ print("Zwift is not running")
+ exit()
+x, y, x1, y1 = win32gui.GetClientRect(hwnd)
+x, y = win32gui.ClientToScreen(hwnd, (x, y))
+x1, y1 = win32gui.ClientToScreen(hwnd, (x1, y1))
+screenshot = ImageGrab.grab((x, y, x1, y1))
+
+# Scale image to 3000 x 2000
+screenshot = screenshot.resize((3000, 2000))
+
+# Crop image to incline area
+screenwidth, screenheight = screenshot.size
+
+# Values for Zwift regular incline
+col1 = int(screenwidth/3000 * 2800)
+row1 = int(screenheight/2000 * 90)
+col2 = int(screenwidth/3000 * 2975)
+row2 = int(screenheight/2000 * 195)
+
+cropped = screenshot.crop((col1, row1, col2, row2))
+
+# Convert image to np array
+cropped_np = np.array(cropped)
+
+# Convert np array to PIL
+cropped_pil = Image.fromarray(cropped_np)
+
+# Convert PIL image to cv2 RGB
+cropped_cv2 = cv2.cvtColor(np.array(cropped_pil), cv2.COLOR_RGB2BGR)
+
+# Convert cv2 RGB to HSV
+result = cropped_cv2.copy()
+image = cv2.cvtColor(cropped_cv2, cv2.COLOR_BGR2HSV)
+
+# Isolate white mask
+lower = np.array([0,0,159])
+upper = np.array([0,0,255])
+mask0 = cv2.inRange(image, lower, upper)
+result0 = cv2.bitwise_and(result, result, mask=mask0)
+
+# Isolate yellow mask
+lower = np.array([24,239,241])
+upper = np.array([24,253,255])
+mask1 = cv2.inRange(image, lower, upper)
+result1 = cv2.bitwise_and(result, result, mask=mask1)
+
+# Isolate orange mask
+lower = np.array([8,191,243])
+upper = np.array([8,192,243])
+mask2 = cv2.inRange(image, lower, upper)
+result2 = cv2.bitwise_and(result, result, mask=mask2)
+
+# Isolate red mask
+lower = np.array([0,255,255])
+upper = np.array([10,255,255])
+mask3 = cv2.inRange(image, lower, upper)
+result3 = cv2.bitwise_and(result, result, mask=mask3)
+
+# Join colour masks
+mask = mask0+mask1+mask2+mask3
+
+# Set output image to zero everywhere except mask
+merge = image.copy()
+merge[np.where(mask==0)] = 0
+
+# Convert to grayscale
+gray = cv2.cvtColor(merge, cv2.COLOR_BGR2GRAY)
+
+# Convert to black/white by threshold
+ret,bin = cv2.threshold(gray, 70, 255, cv2.THRESH_BINARY_INV)
+
+# Apply gaussian blur
+gaussianBlur = cv2.GaussianBlur(bin,(3,3),0)
+
+# OCR image
+ocr = PaddleOCR(lang='en', use_gpu=False, show_log=False, det_db_unclip_ratio=2.0, det_db_box_thresh=0.40, drop_score=0.40, rec_algorithm='CRNN', cls_model_dir='paddleocr/ch_ppocr_mobile_v2.0_cls_infer', det_model_dir='paddleocr/en_PP-OCRv3_det_infer', rec_model_dir='paddleocr/en_PP-OCRv3_rec_infer')
+result = ocr.ocr(gaussianBlur, cls=False, det=True, rec=True)
+
+# Extract OCR text
+ocr_text = ''
+for line in result:
+ for word in line:
+ ocr_text += f"{word[1][0]}"
+
+# Remove all characters that are not "-" and integers from OCR text
+pattern = r"[^-\d]+"
+ocr_text = re.sub(pattern, "", ocr_text)
+if ocr_text:
+ incline = ocr_text
+else:
+ incline = 'None'
+
+print(incline)
diff --git a/src/windows/zwift-workout-ai-server.py b/src/windows/zwift-workout-ai-server.py
new file mode 100644
index 000000000..e8c86bcb9
--- /dev/null
+++ b/src/windows/zwift-workout-ai-server.py
@@ -0,0 +1,74 @@
+# iFit-Workout - Auto-incline and auto-speed control of treadmill via ADB and OCR for Zwift workouts
+# Author: Al Udell
+# Revised: November 25, 2023
+
+# zwift-workout.py - take Zwift screenshot, crop speed/incline instruction, OCR speed/incline
+
+# imports
+import cv2
+import numpy as np
+import re
+from PIL import Image, ImageGrab
+import requests
+import win32gui
+
+# Enable DPI aware on Windows
+from ctypes import windll
+user32 = windll.user32
+user32.SetProcessDPIAware()
+
+# Take Zwift screenshot - windowed mode only
+hwnd = win32gui.FindWindow(None, 'Zwift')
+if not hwnd:
+ print("Zwift is not running")
+ exit()
+x, y, x1, y1 = win32gui.GetClientRect(hwnd)
+x, y = win32gui.ClientToScreen(hwnd, (x, y))
+x1, y1 = win32gui.ClientToScreen(hwnd, (x1, y1))
+screenshot = ImageGrab.grab((x, y, x1, y1))
+
+# Scale image to 3000 x 2000
+screenshot = screenshot.resize((3000, 2000))
+
+# Crop image to workout instruction area
+screenwidth, screenheight = screenshot.size
+
+# Values for Zwift workout instructions
+col1 = int(screenwidth/3000 * 1010)
+row1 = int(screenheight/2000 * 260)
+col2 = int(screenwidth/3000 * 1285)
+row2 = int(screenheight/2000 * 480)
+
+cropped = screenshot.crop((col1, row1, col2, row2))
+
+# Convert image to np array
+cropped_np = np.array(cropped)
+
+# Write zwift image
+cv2.imwrite('zwift.png', cropped_np, [cv2.IMWRITE_PNG_COMPRESSION, 0])
+
+# OCR image
+image_data = open("zwift.png","rb").read()
+ocr = requests.post("http://localhost:32168/v1/image/ocr", files={"image":image_data}).json()
+
+# Extract label values from the 'predictions' list and merge into a single string
+labels = [prediction['label'] for prediction in ocr.get('predictions', [])]
+result = ' '.join(labels)
+
+# Find the speed number
+if "kph" in result.lower():
+ pattern = r'-?\d+(?:\.\d+)?'
+ numbers = re.findall(pattern, result)
+ speed = str(float(numbers[1]))
+else:
+ speed = 'None'
+
+# Find the incline number
+if "incline" in result.lower():
+ pattern = r'-?\d+(?:\.\d+)?'
+ numbers = re.findall(pattern, result)
+ incline = str(float(numbers[0]))
+else:
+ incline = 'None'
+
+print(speed + ";" + incline)
diff --git a/src/windows/zwift-workout.py b/src/windows/zwift-workout.py
index b5f2c76b8..5d822daac 100644
--- a/src/windows/zwift-workout.py
+++ b/src/windows/zwift-workout.py
@@ -1,71 +1,75 @@
-# iFit-Workout - Auto-incline and auto-speed control of treadmill via ADB and OCR for Zwift workouts
-# Author: Al Udell
-# Revised: August 16, 2023
-
-# zwift-workout.py - take Zwift screenshot, crop speed/incline instruction, OCR speed/incline
-
-# imports
-import cv2
-import numpy as np
-import re
-from datetime import datetime
-from paddleocr import PaddleOCR
-from PIL import Image, ImageGrab
-
-# Take Zwift screenshot
-screenshot = ImageGrab.grab()
-
-# Scale image to 3000 x 2000
-screenshot = screenshot.resize((3000, 2000))
-
-# Crop image to workout instruction area
-screenwidth, screenheight = screenshot.size
-
-# Values for Zwift workout instructions
-col1 = int(screenwidth/3000 * 1010)
-row1 = int(screenheight/2000 * 260)
-col2 = int(screenwidth/3000 * 1285)
-row2 = int(screenheight/2000 * 480)
-
-cropped = screenshot.crop((col1, row1, col2, row2))
-
-# Scale image to correct size for borderless window mode
-width, height = cropped.size
-cropped = cropped.resize((int(width * 0.99), int(height * 0.99)))
-
-# Convert image to np array
-cropped_np = np.array(cropped)
-
-# OCR image
-ocr = PaddleOCR(lang='en', use_gpu=False, enable_mkldnn=True, use_angle_cls=False, table=False, layout=False, show_log=False)
-result = ocr.ocr(cropped_np, cls=False, det=True, rec=True)
-
-# Extract OCR text
-ocr_text = ''
-for line in result:
- for word in line:
- ocr_text += f"{word[1][0]} "
-
-# Find the speed number
-num_pattern = r'\d+(\.\d+)?' # Regular expression pattern to match numbers with optional decimal places
-unit_pattern = r'\s+(kph|mph)' # Regular expression pattern to match "kph" or "mph" units
-speed_match = re.search(num_pattern + unit_pattern, ocr_text)
-if speed_match:
- speed = speed_match.group(0)
- pattern = r'\d+\.\d+'
- speed = re.findall(pattern, speed)[0]
-else:
- speed = 'None'
-
-# Find the incline number
-incline_pattern = r'-?\d+\s*%' # Regular expression pattern to match numbers with "%"
-incline_match = re.search(incline_pattern, ocr_text)
-if incline_match:
- incline = incline_match.group(0)
- pattern = r'-?\d+'
- incline = re.findall(pattern, incline)[0]
-else:
- incline = 'None'
-
-print(f"{speed};{incline}")
-
+# iFit-Workout - Auto-incline and auto-speed control of treadmill via ADB and OCR for Zwift workouts
+# Author: Al Udell
+# Revised: November 23, 2023
+
+# zwift-workout.py - take Zwift screenshot, crop speed/incline instruction, OCR speed/incline
+
+# imports
+import cv2
+import numpy as np
+import re
+import win32gui
+from datetime import datetime
+from paddleocr import PaddleOCR
+from PIL import Image, ImageGrab
+
+# Enable DPI aware on Windows
+from ctypes import windll
+user32 = windll.user32
+user32.SetProcessDPIAware()
+
+# Take Zwift screenshot - windowed mode only
+hwnd = win32gui.FindWindow(None, 'Zwift')
+if not hwnd:
+ print("Zwift is not running")
+ exit()
+x, y, x1, y1 = win32gui.GetClientRect(hwnd)
+x, y = win32gui.ClientToScreen(hwnd, (x, y))
+x1, y1 = win32gui.ClientToScreen(hwnd, (x1, y1))
+screenshot = ImageGrab.grab((x, y, x1, y1))
+
+# Scale image to 3000 x 2000
+screenshot = screenshot.resize((3000, 2000))
+
+# Crop image to workout instruction area
+screenwidth, screenheight = screenshot.size
+
+# Values for Zwift workout instructions
+col1 = int(screenwidth/3000 * 1010)
+row1 = int(screenheight/2000 * 260)
+col2 = int(screenwidth/3000 * 1285)
+row2 = int(screenheight/2000 * 480)
+
+cropped = screenshot.crop((col1, row1, col2, row2))
+
+# Convert image to np array
+cropped_np = np.array(cropped)
+
+# OCR image
+ocr = PaddleOCR(lang='en', use_gpu=False, show_log=False, det_db_unclip_ratio=2.0, det_db_box_thresh=0.40, drop_score=0.40, rec_algorithm='CRNN', cls_model_dir='paddleocr/ch_ppocr_mobile_v2.0_cls_infer', det_model_dir='paddleocr/en_PP-OCRv3_det_infer', rec_model_dir='paddleocr/en_PP-OCRv3_rec_infer')
+result = ocr.ocr(cropped_np, cls=False, det=True, rec=True)
+
+# Extract OCR text
+ocr_text = ''
+for line in result:
+ for word in line:
+ ocr_text += f"{word[1][0]} "
+
+# Find the speed number
+if "kph" in ocr_text.lower():
+ pattern = r'-?\d+(?:\.\d+)?'
+ numbers = re.findall(pattern, ocr_text)
+ speed = str(float(numbers[1]))
+else:
+ speed = 'None'
+
+# Find the incline number
+if "incline" in ocr_text.lower():
+ pattern = r'-?\d+(?:\.\d+)?'
+ numbers = re.findall(pattern, ocr_text)
+ incline = str(float(numbers[0]))
+else:
+ incline = 'None'
+
+print(speed + ";" + incline)
+
diff --git a/src/windows_zwift_incline_paddleocr_thread.cpp b/src/windows_zwift_incline_paddleocr_thread.cpp
index 33b49338f..41e40287d 100644
--- a/src/windows_zwift_incline_paddleocr_thread.cpp
+++ b/src/windows_zwift_incline_paddleocr_thread.cpp
@@ -1,11 +1,15 @@
#include "windows_zwift_incline_paddleocr_thread.h"
#include "elliptical.h"
+#if __has_include("aiserver.h")
+#include "aiserver.h"
+#endif
#include "treadmill.h"
#include
#include
#include
#include
#include
+#include
#include
#include
#include
@@ -36,9 +40,21 @@ void windows_zwift_incline_paddleocr_thread::run() {
QString windows_zwift_incline_paddleocr_thread::runPython(QString command) {
#ifdef Q_OS_WINDOWS
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+
+ QString currentPath = env.value("PATH");
+ QString updatedPath = currentPath + ";" + QCoreApplication::applicationDirPath() + "\\python\\x64;C:\\Program Files\\CodeProject\\AI\\modules\\OCR\\bin\\windows\\python37\\venv\\Scripts";
+ env.insert("PATH", updatedPath);
+
QProcess process;
+ process.setProcessEnvironment(env);
+ //qDebug() << "env >> " << env.value("PATH");
qDebug() << "run >> " << command;
+#ifndef AISERVER
process.start("python\\x64\\python.exe", QStringList(command.split(' ')));
+#else
+ process.start("C:\\Program Files\\CodeProject\\AI\\modules\\OCR\\bin\\windows\\python37\\venv\\Scripts\\python.exe", QStringList(command.split(' ')));
+#endif
process.waitForFinished(-1); // will wait forever until finished
QString out = process.readAllStandardOutput();
diff --git a/src/windows_zwift_workout_paddleocr_thread.cpp b/src/windows_zwift_workout_paddleocr_thread.cpp
index a7ea7313a..29e116315 100644
--- a/src/windows_zwift_workout_paddleocr_thread.cpp
+++ b/src/windows_zwift_workout_paddleocr_thread.cpp
@@ -1,11 +1,15 @@
#include "windows_zwift_workout_paddleocr_thread.h"
#include "elliptical.h"
#include "treadmill.h"
+#if __has_include("aiserver.h")
+#include "aiserver.h"
+#endif
#include
#include
#include
#include
#include
+#include
#include
#include
#include
@@ -47,9 +51,21 @@ void windows_zwift_workout_paddleocr_thread::run() {
QString windows_zwift_workout_paddleocr_thread::runPython(QString command) {
#ifdef Q_OS_WINDOWS
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+
+ QString currentPath = env.value("PATH");
+ QString updatedPath = currentPath + ";" + QCoreApplication::applicationDirPath() + "\\python\\x64;C:\\Program Files\\CodeProject\\AI\\modules\\OCR\\bin\\windows\\python37\\venv\\Scripts";
+ env.insert("PATH", updatedPath);
+
QProcess process;
+ process.setProcessEnvironment(env);
+ //qDebug() << "env >> " << env.value("PATH");
qDebug() << "run >> " << command;
+#ifndef AISERVER
process.start("python\\x64\\python.exe", QStringList(command.split(' ')));
+#else
+ process.start("C:\\Program Files\\CodeProject\\AI\\modules\\OCR\\bin\\windows\\python37\\venv\\Scripts\\python.exe", QStringList(command.split(' ')));
+#endif
process.waitForFinished(-1); // will wait forever until finished
QString out = process.readAllStandardOutput();
diff --git a/src/yesoulbike.cpp b/src/yesoulbike.cpp
index 30148e974..25ccdb5ea 100644
--- a/src/yesoulbike.cpp
+++ b/src/yesoulbike.cpp
@@ -110,6 +110,7 @@ void yesoulbike::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void yesoulbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
@@ -139,7 +140,7 @@ void yesoulbike::characteristicChanged(const QLowEnergyCharacteristic &character
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
- fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
+ fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
if (watts())
KCal +=
@@ -147,10 +148,10 @@ void yesoulbike::characteristicChanged(const QLowEnergyCharacteristic &character
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
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
Distance += ((Speed.value() / 3600000.0) *
- ((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
+ ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
if (!settings.value(QZSettings::yesoul_peloton_formula, QZSettings::default_yesoul_peloton_formula).toBool()) {
m_pelotonResistance = ((Resistance.value() * 0.88) *
@@ -168,7 +169,7 @@ void yesoulbike::characteristicChanged(const QLowEnergyCharacteristic &character
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
- lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
+ lastRefreshCharacteristicChanged = now;
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
diff --git a/src/ypooelliptical.cpp b/src/ypooelliptical.cpp
index ff0c23dd8..4ac4f0c48 100644
--- a/src/ypooelliptical.cpp
+++ b/src/ypooelliptical.cpp
@@ -57,7 +57,12 @@ void ypooelliptical::writeCharacteristic(uint8_t *data, uint8_t data_len, const
}
writeBuffer = new QByteArray((const char *)data, data_len);
- gattCustomService->writeCharacteristic(gattWriteCharControlPointId, *writeBuffer);
+ if (gattWriteCharControlPointId.properties() & QLowEnergyCharacteristic::WriteNoResponse) {
+ gattCustomService->writeCharacteristic(gattWriteCharControlPointId, *writeBuffer,
+ QLowEnergyService::WriteWithoutResponse);
+ } else {
+ gattCustomService->writeCharacteristic(gattWriteCharControlPointId, *writeBuffer);
+ }
if (!disable_log) {
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') + QStringLiteral(" // ") + info);
@@ -85,6 +90,9 @@ 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};
@@ -102,13 +110,14 @@ void ypooelliptical::update() {
writeCharacteristic(init5, sizeof(init5), QStringLiteral("init"), false, true);
writeCharacteristic(init1, sizeof(init1), QStringLiteral("init"), false, true);
writeCharacteristic(init5, sizeof(init5), QStringLiteral("init"), false, true);
+ initDone = 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 +179,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(' '));
@@ -179,6 +190,9 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
return;
}
+ if(iconsole_elliptical && initDone == false)
+ initRequest = true;
+
union flags {
struct {
uint32_t moreData : 1;
@@ -205,7 +219,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");
@@ -358,7 +372,7 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
#endif
{
if (Flags.heartRate && !disable_hr_frommachinery && lastPacket.length() > index) {
- Heart = ((double)((lastPacket.at(index))));
+ Heart = ((double)(((uint8_t)lastPacket.at(index))));
// index += 1; // NOTE: clang-analyzer-deadcode.DeadStores
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
} else {
@@ -377,6 +391,39 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
if (Flags.remainingTime) {
// todo
}
+ } else if (iconsole_elliptical) {
+ if (newvalue.length() == 15) {
+ Speed = (double)((((uint8_t)newvalue.at(10)) << 8) | ((uint8_t)newvalue.at(9))) / 100.0;
+ Cadence = newvalue.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 +435,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 +457,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();
emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
@@ -424,6 +473,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);
@@ -553,10 +608,13 @@ void ypooelliptical::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &c
}*/
void ypooelliptical::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
+ QSettings settings;
+ bool iconsole_elliptical = settings.value(QZSettings::iconsole_elliptical, QZSettings::default_iconsole_elliptical).toBool();
emit debug(QStringLiteral("descriptorWritten ") + descriptor.name() + QStringLiteral(" ") + newValue.toHex(' '));
if (gattCustomService != nullptr) {
- initRequest = true;
+ if(!iconsole_elliptical)
+ initRequest = true;
emit connectedAndDiscovered();
}
}
diff --git a/src/ziprotreadmill.cpp b/src/ziprotreadmill.cpp
index da2196c35..fa3a7f685 100644
--- a/src/ziprotreadmill.cpp
+++ b/src/ziprotreadmill.cpp
@@ -173,6 +173,7 @@ void ziprotreadmill::serviceDiscovered(const QBluetoothUuid &gatt) {
}
void ziprotreadmill::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
+ QDateTime now = QDateTime::currentDateTime();
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
QSettings settings;
QString heartRateBeltName =
@@ -233,11 +234,11 @@ void ziprotreadmill::characteristicChanged(const QLowEnergyCharacteristic &chara
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
200.0) /
(60000.0 / ((double)lastTimeCharacteristicChanged.msecsTo(
- QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
+ now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
Distance += ((Speed.value() / 3600.0) /
- (1000.0 / (lastTimeCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
+ (1000.0 / (lastTimeCharacteristicChanged.msecsTo(now))));
}
cadenceFromAppleWatch();
@@ -249,7 +250,7 @@ void ziprotreadmill::characteristicChanged(const QLowEnergyCharacteristic &chara
qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString();
}
- lastTimeCharacteristicChanged = QDateTime::currentDateTime();
+ lastTimeCharacteristicChanged = now;
firstCharacteristicChanged = false;
}
diff --git a/src/zwift-api/PlayerStateWrapper.h b/src/zwift-api/PlayerStateWrapper.h
new file mode 100644
index 000000000..06c0c93b9
--- /dev/null
+++ b/src/zwift-api/PlayerStateWrapper.h
@@ -0,0 +1,178 @@
+#ifndef PLAYERSTATEWRAPPER_H
+#define PLAYERSTATEWRAPPER_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class ZwiftRequest: public QObject {
+ Q_OBJECT
+
+public:
+ ZwiftRequest(const QString& getAccessToken) : getAccessToken(getAccessToken) {}
+
+ QString json(const QString& url) {
+ QNetworkRequest request(QUrl(BASE_URL + url));
+ request.setRawHeader("Accept", "application/json");
+ request.setRawHeader("Authorization", "Bearer " + getAccessToken.toUtf8());
+
+ QNetworkReply* reply = manager.get(request);
+ QEventLoop loop;
+ connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
+ loop.exec();
+
+ if (reply->error() != QNetworkReply::NoError) {
+ qDebug() << "Error: " << reply->errorString();
+ return "";
+ }
+
+ return reply->readAll();
+ }
+
+ QByteArray protobuf(const QString& url) {
+ QNetworkRequest request(QUrl(BASE_URL + url));
+ request.setRawHeader("Accept", "application/x-protobuf-lite");
+ request.setRawHeader("Authorization", "Bearer " + getAccessToken.toUtf8());
+
+ QNetworkReply* reply = manager.get(request);
+ QEventLoop loop;
+ connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
+ loop.exec();
+
+ if (reply->error() != QNetworkReply::NoError) {
+ qDebug() << "Error: " << reply->errorString();
+ return QByteArray();
+ }
+
+ return reply->readAll();
+ }
+
+private:
+ QNetworkAccessManager manager;
+ const QString BASE_URL = "https://us-or-rly101.zwift.com";
+ const QString getAccessToken;
+};
+
+class World {
+public:
+ World(int worldId, const QString& getAccessToken) : worldId(worldId), request(getAccessToken) {}
+
+ QString getPlayers() {
+ return request.json("/relay/worlds/" + QString::number(worldId));
+ }
+
+ QByteArray playerStatus(int playerId) {
+ QByteArray buffer = request.protobuf("/relay/worlds/" + QString::number(worldId) + "/players/" + QString::number(playerId));
+ return buffer;
+ }
+
+ QString player_id() {
+ return request.json("/api/profiles/me");
+ }
+
+private:
+ int worldId;
+ ZwiftRequest request;
+};
+
+class PlayerStateWrapper {
+public:
+
+ enum TURN_SIGNALS {
+ RIGHT = 0,
+ LEFT = 1,
+ STRAIGHT = 2
+ };
+
+ //PlayerStateWrapper(const zwift_messages::PlayerState& playerState) : playerState(playerState) {}
+
+ int getRideOns() {
+ return (playerState.at(19) >> 24) & 0xfff;
+ }
+
+ bool isTurning() {
+ return (playerState.at(19) & 8) != 0;
+ }
+
+ bool isForward() {
+ return (playerState.at(19) & 4) != 0;
+ }
+
+ int getCourse() {
+ return (playerState.at(19) & 0xff0000) >> 16;
+ }
+
+ int getWorld() {
+ return COURSE_TO_WORLD[getCourse()];
+ }
+
+ int getRoadId() {
+ return (playerState.at(20) & 0xff00) >> 8;
+ }
+
+ int getRoadDirection() {
+ return (playerState.at(20) & 0xffff000000) >> 24;
+ }
+
+ TURN_SIGNALS getTurnSignal() {
+ int signalCode = playerState.at(20) & 0x70;
+ if (signalCode == 0x10) {
+ return RIGHT;
+ } else if (signalCode == 0x20) {
+ return LEFT;
+ } else if (signalCode == 0x40) {
+ return STRAIGHT;
+ } else {
+ return STRAIGHT;
+ }
+ }
+
+ int getPowerUp() {
+ return playerState.at(20) & 0xf;
+ }
+
+ bool hasFeatherBoost() {
+ return getPowerUp() == 0;
+ }
+
+ bool hasDraftBoost() {
+ return getPowerUp() == 1;
+ }
+
+ bool hasAeroBoost() {
+ return getPowerUp() == 5;
+ }
+
+ int getCadence() {
+ //return static_cast((playerState.cadenceuhz() * 60) / 1000000);
+ }
+
+ std::string operator()(const std::string& item) {
+ try {
+ //return playerState.GetReflection()->GetString(playerState, playerState.GetDescriptor()->FindFieldByName(item));
+ } catch (const std::exception& e) {
+ qDebug() << "Error: " << e.what();
+ return "";
+ }
+ }
+
+private:
+
+ QMap COURSE_TO_WORLD = {{3, 1}, {4, 2}, {5, 3}, {6, 1}};
+
+ enum COURSES {
+ WATOPIA = 3,
+ RICHMOND = 4,
+ LONDON = 5
+ };
+
+ //zwift_messages::PlayerState playerState;
+ QByteArray playerState;
+};
+
+#endif // PLAYERSTATEWRAPPER_H
diff --git a/src/zwift-api/zwift_client_auth.h b/src/zwift-api/zwift_client_auth.h
new file mode 100644
index 000000000..5a367bce8
--- /dev/null
+++ b/src/zwift-api/zwift_client_auth.h
@@ -0,0 +1,116 @@
+#ifndef ZWIFT_CLIENT_AUTH_H
+#define ZWIFT_CLIENT_AUTH_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class AuthToken : public QObject {
+ Q_OBJECT
+public:
+ QString username;
+ QString password;
+ QString access_token;
+ qint64 expires_in;
+ QString id_token;
+ qint64 not_before_policy;
+ QString refresh_token;
+ qint64 refresh_expires_in;
+ QString session_state;
+ QString token_type;
+ qint64 access_token_expiration;
+ qint64 refresh_token_expiration;
+
+ AuthToken(const QString& username, const QString& password, QObject* parent = nullptr)
+ : QObject(parent), username(username), password(password) {}
+
+ bool haveValidAccessToken() const {
+ return !access_token.isEmpty() && QDateTime::currentMSecsSinceEpoch() < access_token_expiration;
+ }
+
+ bool haveValidRefreshToken() const {
+ return !refresh_token.isEmpty() && QDateTime::currentMSecsSinceEpoch() < refresh_token_expiration;
+ }
+
+ Q_INVOKABLE QString getAccessToken() {
+ if (haveValidAccessToken()) {
+ return access_token;
+ } else {
+ updateTokenData();
+ return access_token;
+ }
+ }
+
+public slots:
+ void updateTokenData() {
+ if (haveValidRefreshToken()) {
+ QUrl url("https://secure.zwift.com/auth/realms/zwift/tokens/access/codes");
+ QUrlQuery query;
+ query.addQueryItem("refresh_token", refresh_token);
+ query.addQueryItem("grant_type", "refresh_token");
+ query.addQueryItem("client_id", "Zwift_Mobile_Link");
+ url.setQuery(query);
+
+ QNetworkRequest request(url);
+ QNetworkReply* reply = networkManager.get(request);
+ connect(reply, &QNetworkReply::finished, [=]() {
+ handleTokenResponse(reply);
+ });
+ } else {
+ QUrl url("https://secure.zwift.com/auth/realms/zwift/tokens/access/codes");
+ QUrlQuery postData;
+ postData.addQueryItem("username", username);
+ postData.addQueryItem("password", password);
+ postData.addQueryItem("grant_type", "password");
+ postData.addQueryItem("client_id", "Zwift_Mobile_Link");
+
+ QNetworkRequest request(url);
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
+ QNetworkReply* reply = networkManager.post(request, postData.toString(QUrl::FullyEncoded).toUtf8());
+ connect(reply, &QNetworkReply::finished, [=]() {
+ handleTokenResponse(reply);
+ });
+ }
+ }
+
+private slots:
+ void handleTokenResponse(QNetworkReply* reply) {
+ if (reply->error() == QNetworkReply::NoError) {
+ QByteArray responseData = reply->readAll();
+ QJsonDocument jsonDocument = QJsonDocument::fromJson(responseData);
+ QJsonObject tokenData = jsonDocument.object();
+
+ QDateTime now = QDateTime::currentDateTime();
+ access_token = tokenData["access_token"].toString();
+ expires_in = tokenData["expires_in"].toInt();
+ id_token = tokenData["id_token"].toString();
+ not_before_policy = tokenData["not-before-policy"].toInt();
+ refresh_token = tokenData["refresh_token"].toString();
+ refresh_expires_in = tokenData["refresh_expires_in"].toInt();
+ session_state = tokenData["session_state"].toString();
+ token_type = tokenData["token_type"].toString();
+
+ access_token_expiration = now.toMSecsSinceEpoch() + (expires_in - 5) * 1000;
+ refresh_token_expiration = now.toMSecsSinceEpoch() + (refresh_expires_in - 5) * 1000;
+
+ qDebug() << "Access Token: " << access_token;
+ } else {
+ qDebug() << "Error fetching token: " << reply->errorString();
+ }
+
+ reply->deleteLater();
+ }
+
+private:
+ QNetworkAccessManager networkManager;
+};
+
+
+#endif // ZWIFT_CLIENT_AUTH_H
diff --git a/src/zwift-api/zwift_messages.pb.cc b/src/zwift-api/zwift_messages.pb.cc
new file mode 100644
index 000000000..aec318c26
--- /dev/null
+++ b/src/zwift-api/zwift_messages.pb.cc
@@ -0,0 +1,5489 @@
+// Generated by the protocol buffer compiler. DO NOT EDIT!
+// source: zwift_messages.proto
+
+#include "zwift_messages.pb.h"
+
+#include
+#include "google/protobuf/io/coded_stream.h"
+#include "google/protobuf/extension_set.h"
+#include "google/protobuf/wire_format_lite.h"
+#include "google/protobuf/descriptor.h"
+#include "google/protobuf/generated_message_reflection.h"
+#include "google/protobuf/reflection_ops.h"
+#include "google/protobuf/wire_format.h"
+#include "google/protobuf/generated_message_tctable_impl.h"
+// @@protoc_insertion_point(includes)
+
+// Must be included last.
+#include "google/protobuf/port_def.inc"
+PROTOBUF_PRAGMA_INIT_SEG
+namespace _pb = ::google::protobuf;
+namespace _pbi = ::google::protobuf::internal;
+namespace _fl = ::google::protobuf::internal::field_layout;
+
+inline constexpr WorldAttributes::Impl_::Impl_(
+ ::_pbi::ConstantInitialized) noexcept
+ : name_(
+ &::google::protobuf::internal::fixed_address_empty_string,
+ ::_pbi::ConstantInitialized()),
+ tag3_{::int64_t{0}},
+ tag5_{::int64_t{0}},
+ world_time_{::int64_t{0}},
+ clock_time_{::int64_t{0}},
+ world_id_{0},
+ _cached_size_{0} {}
+
+template
+PROTOBUF_CONSTEXPR WorldAttributes::WorldAttributes(::_pbi::ConstantInitialized)
+ : _impl_(::_pbi::ConstantInitialized()) {}
+struct WorldAttributesDefaultTypeInternal {
+ PROTOBUF_CONSTEXPR WorldAttributesDefaultTypeInternal() : _instance(::_pbi::ConstantInitialized{}) {}
+ ~WorldAttributesDefaultTypeInternal() {}
+ union {
+ WorldAttributes _instance;
+ };
+};
+
+PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT
+ PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 WorldAttributesDefaultTypeInternal _WorldAttributes_default_instance_;
+
+inline constexpr WorldAttribute::Impl_::Impl_(
+ ::_pbi::ConstantInitialized) noexcept
+ : world_time_{::int64_t{0}},
+ _cached_size_{0} {}
+
+template
+PROTOBUF_CONSTEXPR WorldAttribute::WorldAttribute(::_pbi::ConstantInitialized)
+ : _impl_(::_pbi::ConstantInitialized()) {}
+struct WorldAttributeDefaultTypeInternal {
+ PROTOBUF_CONSTEXPR WorldAttributeDefaultTypeInternal() : _instance(::_pbi::ConstantInitialized{}) {}
+ ~WorldAttributeDefaultTypeInternal() {}
+ union {
+ WorldAttribute _instance;
+ };
+};
+
+PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT
+ PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 WorldAttributeDefaultTypeInternal _WorldAttribute_default_instance_;
+ template
+PROTOBUF_CONSTEXPR UnknownMessage1::UnknownMessage1(::_pbi::ConstantInitialized) {}
+struct UnknownMessage1DefaultTypeInternal {
+ PROTOBUF_CONSTEXPR UnknownMessage1DefaultTypeInternal() : _instance(::_pbi::ConstantInitialized{}) {}
+ ~UnknownMessage1DefaultTypeInternal() {}
+ union {
+ UnknownMessage1 _instance;
+ };
+};
+
+PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT
+ PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 UnknownMessage1DefaultTypeInternal _UnknownMessage1_default_instance_;
+ template
+PROTOBUF_CONSTEXPR UnknownMessage::UnknownMessage(::_pbi::ConstantInitialized) {}
+struct UnknownMessageDefaultTypeInternal {
+ PROTOBUF_CONSTEXPR UnknownMessageDefaultTypeInternal() : _instance(::_pbi::ConstantInitialized{}) {}
+ ~UnknownMessageDefaultTypeInternal() {}
+ union {
+ UnknownMessage _instance;
+ };
+};
+
+PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT
+ PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 UnknownMessageDefaultTypeInternal _UnknownMessage_default_instance_;
+
+inline constexpr SegmentResult::Impl_::Impl_(
+ ::_pbi::ConstantInitialized) noexcept
+ : first_name_(
+ &::google::protobuf::internal::fixed_address_empty_string,
+ ::_pbi::ConstantInitialized()),
+ last_name_(
+ &::google::protobuf::internal::fixed_address_empty_string,
+ ::_pbi::ConstantInitialized()),
+ finish_time_str_(
+ &::google::protobuf::internal::fixed_address_empty_string,
+ ::_pbi::ConstantInitialized()),
+ id_{::int64_t{0}},
+ rider_id_{::int64_t{0}},
+ event_subgroup_id_{::int64_t{0}},
+ elapsed_ms_{::int64_t{0}},
+ powermeter_{0},
+ weight_{0},
+ power_{0},
+ heartrate_{0},
+ _cached_size_{0} {}
+
+template
+PROTOBUF_CONSTEXPR SegmentResult::SegmentResult(::_pbi::ConstantInitialized)
+ : _impl_(::_pbi::ConstantInitialized()) {}
+struct SegmentResultDefaultTypeInternal {
+ PROTOBUF_CONSTEXPR SegmentResultDefaultTypeInternal() : _instance(::_pbi::ConstantInitialized{}) {}
+ ~SegmentResultDefaultTypeInternal() {}
+ union {
+ SegmentResult _instance;
+ };
+};
+
+PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT
+ PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 SegmentResultDefaultTypeInternal _SegmentResult_default_instance_;
+
+inline constexpr RiderAttributes_AttributeMessage::Impl_::Impl_(
+ ::_pbi::ConstantInitialized) noexcept
+ : firstname_(
+ &::google::protobuf::internal::fixed_address_empty_string,
+ ::_pbi::ConstantInitialized()),
+ lastname_(
+ &::google::protobuf::internal::fixed_address_empty_string,
+ ::_pbi::ConstantInitialized()),
+ myid_{0},
+ theirid_{0},
+ countrycode_{0},
+ _cached_size_{0} {}
+
+template
+PROTOBUF_CONSTEXPR RiderAttributes_AttributeMessage::RiderAttributes_AttributeMessage(::_pbi::ConstantInitialized)
+ : _impl_(::_pbi::ConstantInitialized()) {}
+struct RiderAttributes_AttributeMessageDefaultTypeInternal {
+ PROTOBUF_CONSTEXPR RiderAttributes_AttributeMessageDefaultTypeInternal() : _instance(::_pbi::ConstantInitialized{}) {}
+ ~RiderAttributes_AttributeMessageDefaultTypeInternal() {}
+ union {
+ RiderAttributes_AttributeMessage _instance;
+ };
+};
+
+PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT
+ PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 RiderAttributes_AttributeMessageDefaultTypeInternal _RiderAttributes_AttributeMessage_default_instance_;
+
+inline constexpr Profile::Impl_::Impl_(
+ ::_pbi::ConstantInitialized) noexcept
+ : firstname_(
+ &::google::protobuf::internal::fixed_address_empty_string,
+ ::_pbi::ConstantInitialized()),
+ lastname_(
+ &::google::protobuf::internal::fixed_address_empty_string,
+ ::_pbi::ConstantInitialized()),
+ launchedgameclient_(
+ &::google::protobuf::internal::fixed_address_empty_string,
+ ::_pbi::ConstantInitialized()),
+ id_{0},
+ male_{0},
+ weight_{0},
+ bodytype_{0},
+ countrycode_{0},
+ totaldistance_{0},
+ totaldistanceclimbed_{0},
+ totaltimeinminutes_{0},
+ totalwatthours_{0},
+ height_{0},
+ totalexperiencepoints_{0},
+ achievementlevel_{0},
+ powersource_{0},
+ age_{0},
+ currentactivityid_{0},
+ _cached_size_{0} {}
+
+template
+PROTOBUF_CONSTEXPR Profile::Profile(::_pbi::ConstantInitialized)
+ : _impl_(::_pbi::ConstantInitialized()) {}
+struct ProfileDefaultTypeInternal {
+ PROTOBUF_CONSTEXPR ProfileDefaultTypeInternal() : _instance(::_pbi::ConstantInitialized{}) {}
+ ~ProfileDefaultTypeInternal() {}
+ union {
+ Profile _instance;
+ };
+};
+
+PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT
+ PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ProfileDefaultTypeInternal _Profile_default_instance_;
+
+inline constexpr PlayerState::Impl_::Impl_(
+ ::_pbi::ConstantInitialized) noexcept
+ : worldtime_{::int64_t{0}},
+ id_{0},
+ distance_{0},
+ roadtime_{0},
+ laps_{0},
+ speed_{0},
+ roadposition_{0},
+ cadenceuhz_{0},
+ heartrate_{0},
+ heading_{::int64_t{0}},
+ power_{0},
+ lean_{0},
+ climbing_{0},
+ time_{0},
+ f19_{0},
+ f20_{0},
+ customisationid_{::int64_t{0}},
+ progress_{0},
+ justwatching_{0},
+ calories_{0},
+ x_{0},
+ altitude_{0},
+ y_{0},
+ watchingriderid_{0},
+ groupid_{0},
+ sport_{::int64_t{0}},
+ _cached_size_{0} {}
+
+template
+PROTOBUF_CONSTEXPR PlayerState::PlayerState(::_pbi::ConstantInitialized)
+ : _impl_(::_pbi::ConstantInitialized()) {}
+struct PlayerStateDefaultTypeInternal {
+ PROTOBUF_CONSTEXPR PlayerStateDefaultTypeInternal() : _instance(::_pbi::ConstantInitialized{}) {}
+ ~PlayerStateDefaultTypeInternal() {}
+ union {
+ PlayerState _instance;
+ };
+};
+
+PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT
+ PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PlayerStateDefaultTypeInternal _PlayerState_default_instance_;
+
+inline constexpr EventSubgroupProtobuf::Impl_::Impl_(
+ ::_pbi::ConstantInitialized) noexcept
+ : name_(
+ &::google::protobuf::internal::fixed_address_empty_string,
+ ::_pbi::ConstantInitialized()),
+ id_{0},
+ rules_{0},
+ route_{0},
+ laps_{0},
+ startlocation_{0},
+ label_{0},
+ pacetype_{0},
+ jerseyhash_{0},
+ _cached_size_{0} {}
+
+template
+PROTOBUF_CONSTEXPR EventSubgroupProtobuf::EventSubgroupProtobuf(::_pbi::ConstantInitialized)
+ : _impl_(::_pbi::ConstantInitialized()) {}
+struct EventSubgroupProtobufDefaultTypeInternal {
+ PROTOBUF_CONSTEXPR EventSubgroupProtobufDefaultTypeInternal() : _instance(::_pbi::ConstantInitialized{}) {}
+ ~EventSubgroupProtobufDefaultTypeInternal() {}
+ union {
+ EventSubgroupProtobuf _instance;
+ };
+};
+
+PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT
+ PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 EventSubgroupProtobufDefaultTypeInternal _EventSubgroupProtobuf_default_instance_;
+
+inline constexpr ServerToClient::Impl_::Impl_(
+ ::_pbi::ConstantInitialized) noexcept
+ : player_states_{},
+ player_updates_{},
+ tag1_{0},
+ rider_id_{0},
+ world_time_{::int64_t{0}},
+ tag11_{::int64_t{0}},
+ seqno_{0},
+ num_msgs_{0},
+ tag17_{::int64_t{0}},
+ msgnum_{0},
+ _cached_size_{0} {}
+
+template
+PROTOBUF_CONSTEXPR ServerToClient::ServerToClient(::_pbi::ConstantInitialized)
+ : _impl_(::_pbi::ConstantInitialized()) {}
+struct ServerToClientDefaultTypeInternal {
+ PROTOBUF_CONSTEXPR ServerToClientDefaultTypeInternal() : _instance(::_pbi::ConstantInitialized{}) {}
+ ~ServerToClientDefaultTypeInternal() {}
+ union {
+ ServerToClient _instance;
+ };
+};
+
+PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT
+ PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ServerToClientDefaultTypeInternal _ServerToClient_default_instance_;
+
+inline constexpr SegmentResults::Impl_::Impl_(
+ ::_pbi::ConstantInitialized) noexcept
+ : segment_results_{},
+ world_id_{::int64_t{0}},
+ segment_id_{::int64_t{0}},
+ event_subgroup_id_{::int64_t{0}},
+ _cached_size_{0} {}
+
+template
+PROTOBUF_CONSTEXPR SegmentResults::SegmentResults(::_pbi::ConstantInitialized)
+ : _impl_(::_pbi::ConstantInitialized()) {}
+struct SegmentResultsDefaultTypeInternal {
+ PROTOBUF_CONSTEXPR SegmentResultsDefaultTypeInternal() : _instance(::_pbi::ConstantInitialized{}) {}
+ ~SegmentResultsDefaultTypeInternal() {}
+ union {
+ SegmentResults _instance;
+ };
+};
+
+PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT
+ PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 SegmentResultsDefaultTypeInternal _SegmentResults_default_instance_;
+
+inline constexpr RiderAttributes::Impl_::Impl_(
+ ::_pbi::ConstantInitialized) noexcept
+ : _cached_size_{0},
+ attributemessage_{nullptr},
+ f2_{0},
+ f3_{0},
+ theirid_{0},
+ f13_{0} {}
+
+template
+PROTOBUF_CONSTEXPR RiderAttributes::RiderAttributes(::_pbi::ConstantInitialized)
+ : _impl_(::_pbi::ConstantInitialized()) {}
+struct RiderAttributesDefaultTypeInternal {
+ PROTOBUF_CONSTEXPR RiderAttributesDefaultTypeInternal() : _instance(::_pbi::ConstantInitialized{}) {}
+ ~RiderAttributesDefaultTypeInternal() {}
+ union {
+ RiderAttributes _instance;
+ };
+};
+
+PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT
+ PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 RiderAttributesDefaultTypeInternal _RiderAttributes_default_instance_;
+
+inline constexpr Profiles::Impl_::Impl_(
+ ::_pbi::ConstantInitialized) noexcept
+ : profiles_{},
+ _cached_size_{0} {}
+
+template
+PROTOBUF_CONSTEXPR Profiles::Profiles(::_pbi::ConstantInitialized)
+ : _impl_(::_pbi::ConstantInitialized()) {}
+struct ProfilesDefaultTypeInternal {
+ PROTOBUF_CONSTEXPR ProfilesDefaultTypeInternal() : _instance(::_pbi::ConstantInitialized{}) {}
+ ~ProfilesDefaultTypeInternal() {}
+ union {
+ Profiles _instance;
+ };
+};
+
+PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT
+ PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ProfilesDefaultTypeInternal _Profiles_default_instance_;
+
+inline constexpr ClientToServer::Impl_::Impl_(
+ ::_pbi::ConstantInitialized) noexcept
+ : _cached_size_{0},
+ state_{nullptr},
+ connected_{0},
+ rider_id_{0},
+ world_time_{::int64_t{0}},
+ tag8_{::int64_t{0}},
+ tag9_{::int64_t{0}},
+ last_update_{::int64_t{0}},
+ tag11_{::int64_t{0}},
+ last_player_update_{::int64_t{0}},
+ seqno_{0} {}
+
+template
+PROTOBUF_CONSTEXPR ClientToServer::ClientToServer(::_pbi::ConstantInitialized)
+ : _impl_(::_pbi::ConstantInitialized()) {}
+struct ClientToServerDefaultTypeInternal {
+ PROTOBUF_CONSTEXPR ClientToServerDefaultTypeInternal() : _instance(::_pbi::ConstantInitialized{}) {}
+ ~ClientToServerDefaultTypeInternal() {}
+ union {
+ ClientToServer _instance;
+ };
+};
+
+PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT
+ PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ClientToServerDefaultTypeInternal _ClientToServer_default_instance_;
+static ::_pb::Metadata file_level_metadata_zwift_5fmessages_2eproto[14];
+static constexpr const ::_pb::EnumDescriptor**
+ file_level_enum_descriptors_zwift_5fmessages_2eproto = nullptr;
+static constexpr const ::_pb::ServiceDescriptor**
+ file_level_service_descriptors_zwift_5fmessages_2eproto = nullptr;
+const ::uint32_t TableStruct_zwift_5fmessages_2eproto::offsets[] PROTOBUF_SECTION_VARIABLE(
+ protodesc_cold) = {
+ ~0u, // no _has_bits_
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _internal_metadata_),
+ ~0u, // no _extensions_
+ ~0u, // no _oneof_case_
+ ~0u, // no _weak_field_map_
+ ~0u, // no _inlined_string_donated_
+ ~0u, // no _split_
+ ~0u, // no sizeof(Split)
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.id_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.worldtime_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.distance_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.roadtime_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.laps_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.speed_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.roadposition_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.cadenceuhz_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.heartrate_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.power_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.heading_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.lean_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.climbing_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.time_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.f19_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.f20_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.progress_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.customisationid_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.justwatching_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.calories_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.x_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.altitude_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.y_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.watchingriderid_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.groupid_),
+ PROTOBUF_FIELD_OFFSET(::PlayerState, _impl_.sport_),
+ PROTOBUF_FIELD_OFFSET(::ClientToServer, _impl_._has_bits_),
+ PROTOBUF_FIELD_OFFSET(::ClientToServer, _internal_metadata_),
+ ~0u, // no _extensions_
+ ~0u, // no _oneof_case_
+ ~0u, // no _weak_field_map_
+ ~0u, // no _inlined_string_donated_
+ ~0u, // no _split_
+ ~0u, // no sizeof(Split)
+ PROTOBUF_FIELD_OFFSET(::ClientToServer, _impl_.connected_),
+ PROTOBUF_FIELD_OFFSET(::ClientToServer, _impl_.rider_id_),
+ PROTOBUF_FIELD_OFFSET(::ClientToServer, _impl_.world_time_),
+ PROTOBUF_FIELD_OFFSET(::ClientToServer, _impl_.state_),
+ PROTOBUF_FIELD_OFFSET(::ClientToServer, _impl_.seqno_),
+ PROTOBUF_FIELD_OFFSET(::ClientToServer, _impl_.tag8_),
+ PROTOBUF_FIELD_OFFSET(::ClientToServer, _impl_.tag9_),
+ PROTOBUF_FIELD_OFFSET(::ClientToServer, _impl_.last_update_),
+ PROTOBUF_FIELD_OFFSET(::ClientToServer, _impl_.tag11_),
+ PROTOBUF_FIELD_OFFSET(::ClientToServer, _impl_.last_player_update_),
+ ~0u,
+ ~0u,
+ ~0u,
+ 0,
+ ~0u,
+ ~0u,
+ ~0u,
+ ~0u,
+ ~0u,
+ ~0u,
+ ~0u, // no _has_bits_
+ PROTOBUF_FIELD_OFFSET(::SegmentResult, _internal_metadata_),
+ ~0u, // no _extensions_
+ ~0u, // no _oneof_case_
+ ~0u, // no _weak_field_map_
+ ~0u, // no _inlined_string_donated_
+ ~0u, // no _split_
+ ~0u, // no sizeof(Split)
+ PROTOBUF_FIELD_OFFSET(::SegmentResult, _impl_.id_),
+ PROTOBUF_FIELD_OFFSET(::SegmentResult, _impl_.rider_id_),
+ PROTOBUF_FIELD_OFFSET(::SegmentResult, _impl_.event_subgroup_id_),
+ PROTOBUF_FIELD_OFFSET(::SegmentResult, _impl_.first_name_),
+ PROTOBUF_FIELD_OFFSET(::SegmentResult, _impl_.last_name_),
+ PROTOBUF_FIELD_OFFSET(::SegmentResult, _impl_.finish_time_str_),
+ PROTOBUF_FIELD_OFFSET(::SegmentResult, _impl_.elapsed_ms_),
+ PROTOBUF_FIELD_OFFSET(::SegmentResult, _impl_.powermeter_),
+ PROTOBUF_FIELD_OFFSET(::SegmentResult, _impl_.weight_),
+ PROTOBUF_FIELD_OFFSET(::SegmentResult, _impl_.power_),
+ PROTOBUF_FIELD_OFFSET(::SegmentResult, _impl_.heartrate_),
+ ~0u, // no _has_bits_
+ PROTOBUF_FIELD_OFFSET(::SegmentResults, _internal_metadata_),
+ ~0u, // no _extensions_
+ ~0u, // no _oneof_case_
+ ~0u, // no _weak_field_map_
+ ~0u, // no _inlined_string_donated_
+ ~0u, // no _split_
+ ~0u, // no sizeof(Split)
+ PROTOBUF_FIELD_OFFSET(::SegmentResults, _impl_.world_id_),
+ PROTOBUF_FIELD_OFFSET(::SegmentResults, _impl_.segment_id_),
+ PROTOBUF_FIELD_OFFSET(::SegmentResults, _impl_.event_subgroup_id_),
+ PROTOBUF_FIELD_OFFSET(::SegmentResults, _impl_.segment_results_),
+ ~0u, // no _has_bits_
+ PROTOBUF_FIELD_OFFSET(::UnknownMessage1, _internal_metadata_),
+ ~0u, // no _extensions_
+ ~0u, // no _oneof_case_
+ ~0u, // no _weak_field_map_
+ ~0u, // no _inlined_string_donated_
+ ~0u, // no _split_
+ ~0u, // no sizeof(Split)
+ ~0u, // no _has_bits_
+ PROTOBUF_FIELD_OFFSET(::UnknownMessage, _internal_metadata_),
+ ~0u, // no _extensions_
+ ~0u, // no _oneof_case_
+ ~0u, // no _weak_field_map_
+ ~0u, // no _inlined_string_donated_
+ ~0u, // no _split_
+ ~0u, // no sizeof(Split)
+ ~0u, // no _has_bits_
+ PROTOBUF_FIELD_OFFSET(::ServerToClient, _internal_metadata_),
+ ~0u, // no _extensions_
+ ~0u, // no _oneof_case_
+ ~0u, // no _weak_field_map_
+ ~0u, // no _inlined_string_donated_
+ ~0u, // no _split_
+ ~0u, // no sizeof(Split)
+ PROTOBUF_FIELD_OFFSET(::ServerToClient, _impl_.tag1_),
+ PROTOBUF_FIELD_OFFSET(::ServerToClient, _impl_.rider_id_),
+ PROTOBUF_FIELD_OFFSET(::ServerToClient, _impl_.world_time_),
+ PROTOBUF_FIELD_OFFSET(::ServerToClient, _impl_.seqno_),
+ PROTOBUF_FIELD_OFFSET(::ServerToClient, _impl_.player_states_),
+ PROTOBUF_FIELD_OFFSET(::ServerToClient, _impl_.player_updates_),
+ PROTOBUF_FIELD_OFFSET(::ServerToClient, _impl_.tag11_),
+ PROTOBUF_FIELD_OFFSET(::ServerToClient, _impl_.tag17_),
+ PROTOBUF_FIELD_OFFSET(::ServerToClient, _impl_.num_msgs_),
+ PROTOBUF_FIELD_OFFSET(::ServerToClient, _impl_.msgnum_),
+ ~0u, // no _has_bits_
+ PROTOBUF_FIELD_OFFSET(::WorldAttributes, _internal_metadata_),
+ ~0u, // no _extensions_
+ ~0u, // no _oneof_case_
+ ~0u, // no _weak_field_map_
+ ~0u, // no _inlined_string_donated_
+ ~0u, // no _split_
+ ~0u, // no sizeof(Split)
+ PROTOBUF_FIELD_OFFSET(::WorldAttributes, _impl_.world_id_),
+ PROTOBUF_FIELD_OFFSET(::WorldAttributes, _impl_.name_),
+ PROTOBUF_FIELD_OFFSET(::WorldAttributes, _impl_.tag3_),
+ PROTOBUF_FIELD_OFFSET(::WorldAttributes, _impl_.tag5_),
+ PROTOBUF_FIELD_OFFSET(::WorldAttributes, _impl_.world_time_),
+ PROTOBUF_FIELD_OFFSET(::WorldAttributes, _impl_.clock_time_),
+ ~0u, // no _has_bits_
+ PROTOBUF_FIELD_OFFSET(::WorldAttribute, _internal_metadata_),
+ ~0u, // no _extensions_
+ ~0u, // no _oneof_case_
+ ~0u, // no _weak_field_map_
+ ~0u, // no _inlined_string_donated_
+ ~0u, // no _split_
+ ~0u, // no sizeof(Split)
+ PROTOBUF_FIELD_OFFSET(::WorldAttribute, _impl_.world_time_),
+ ~0u, // no _has_bits_
+ PROTOBUF_FIELD_OFFSET(::EventSubgroupProtobuf, _internal_metadata_),
+ ~0u, // no _extensions_
+ ~0u, // no _oneof_case_
+ ~0u, // no _weak_field_map_
+ ~0u, // no _inlined_string_donated_
+ ~0u, // no _split_
+ ~0u, // no sizeof(Split)
+ PROTOBUF_FIELD_OFFSET(::EventSubgroupProtobuf, _impl_.id_),
+ PROTOBUF_FIELD_OFFSET(::EventSubgroupProtobuf, _impl_.name_),
+ PROTOBUF_FIELD_OFFSET(::EventSubgroupProtobuf, _impl_.rules_),
+ PROTOBUF_FIELD_OFFSET(::EventSubgroupProtobuf, _impl_.route_),
+ PROTOBUF_FIELD_OFFSET(::EventSubgroupProtobuf, _impl_.laps_),
+ PROTOBUF_FIELD_OFFSET(::EventSubgroupProtobuf, _impl_.startlocation_),
+ PROTOBUF_FIELD_OFFSET(::EventSubgroupProtobuf, _impl_.label_),
+ PROTOBUF_FIELD_OFFSET(::EventSubgroupProtobuf, _impl_.pacetype_),
+ PROTOBUF_FIELD_OFFSET(::EventSubgroupProtobuf, _impl_.jerseyhash_),
+ ~0u, // no _has_bits_
+ PROTOBUF_FIELD_OFFSET(::RiderAttributes_AttributeMessage, _internal_metadata_),
+ ~0u, // no _extensions_
+ ~0u, // no _oneof_case_
+ ~0u, // no _weak_field_map_
+ ~0u, // no _inlined_string_donated_
+ ~0u, // no _split_
+ ~0u, // no sizeof(Split)
+ PROTOBUF_FIELD_OFFSET(::RiderAttributes_AttributeMessage, _impl_.myid_),
+ PROTOBUF_FIELD_OFFSET(::RiderAttributes_AttributeMessage, _impl_.theirid_),
+ PROTOBUF_FIELD_OFFSET(::RiderAttributes_AttributeMessage, _impl_.firstname_),
+ PROTOBUF_FIELD_OFFSET(::RiderAttributes_AttributeMessage, _impl_.lastname_),
+ PROTOBUF_FIELD_OFFSET(::RiderAttributes_AttributeMessage, _impl_.countrycode_),
+ PROTOBUF_FIELD_OFFSET(::RiderAttributes, _impl_._has_bits_),
+ PROTOBUF_FIELD_OFFSET(::RiderAttributes, _internal_metadata_),
+ ~0u, // no _extensions_
+ ~0u, // no _oneof_case_
+ ~0u, // no _weak_field_map_
+ ~0u, // no _inlined_string_donated_
+ ~0u, // no _split_
+ ~0u, // no sizeof(Split)
+ PROTOBUF_FIELD_OFFSET(::RiderAttributes, _impl_.f2_),
+ PROTOBUF_FIELD_OFFSET(::RiderAttributes, _impl_.f3_),
+ PROTOBUF_FIELD_OFFSET(::RiderAttributes, _impl_.attributemessage_),
+ PROTOBUF_FIELD_OFFSET(::RiderAttributes, _impl_.theirid_),
+ PROTOBUF_FIELD_OFFSET(::RiderAttributes, _impl_.f13_),
+ ~0u,
+ ~0u,
+ 0,
+ ~0u,
+ ~0u,
+ ~0u, // no _has_bits_
+ PROTOBUF_FIELD_OFFSET(::Profiles, _internal_metadata_),
+ ~0u, // no _extensions_
+ ~0u, // no _oneof_case_
+ ~0u, // no _weak_field_map_
+ ~0u, // no _inlined_string_donated_
+ ~0u, // no _split_
+ ~0u, // no sizeof(Split)
+ PROTOBUF_FIELD_OFFSET(::Profiles, _impl_.profiles_),
+ ~0u, // no _has_bits_
+ PROTOBUF_FIELD_OFFSET(::Profile, _internal_metadata_),
+ ~0u, // no _extensions_
+ ~0u, // no _oneof_case_
+ ~0u, // no _weak_field_map_
+ ~0u, // no _inlined_string_donated_
+ ~0u, // no _split_
+ ~0u, // no sizeof(Split)
+ PROTOBUF_FIELD_OFFSET(::Profile, _impl_.id_),
+ PROTOBUF_FIELD_OFFSET(::Profile, _impl_.firstname_),
+ PROTOBUF_FIELD_OFFSET(::Profile, _impl_.lastname_),
+ PROTOBUF_FIELD_OFFSET(::Profile, _impl_.male_),
+ PROTOBUF_FIELD_OFFSET(::Profile, _impl_.weight_),
+ PROTOBUF_FIELD_OFFSET(::Profile, _impl_.bodytype_),
+ PROTOBUF_FIELD_OFFSET(::Profile, _impl_.countrycode_),
+ PROTOBUF_FIELD_OFFSET(::Profile, _impl_.totaldistance_),
+ PROTOBUF_FIELD_OFFSET(::Profile, _impl_.totaldistanceclimbed_),
+ PROTOBUF_FIELD_OFFSET(::Profile, _impl_.totaltimeinminutes_),
+ PROTOBUF_FIELD_OFFSET(::Profile, _impl_.totalwatthours_),
+ PROTOBUF_FIELD_OFFSET(::Profile, _impl_.height_),
+ PROTOBUF_FIELD_OFFSET(::Profile, _impl_.totalexperiencepoints_),
+ PROTOBUF_FIELD_OFFSET(::Profile, _impl_.achievementlevel_),
+ PROTOBUF_FIELD_OFFSET(::Profile, _impl_.powersource_),
+ PROTOBUF_FIELD_OFFSET(::Profile, _impl_.age_),
+ PROTOBUF_FIELD_OFFSET(::Profile, _impl_.launchedgameclient_),
+ PROTOBUF_FIELD_OFFSET(::Profile, _impl_.currentactivityid_),
+};
+
+static const ::_pbi::MigrationSchema
+ schemas[] PROTOBUF_SECTION_VARIABLE(protodesc_cold) = {
+ {0, -1, -1, sizeof(::PlayerState)},
+ {34, 52, -1, sizeof(::ClientToServer)},
+ {62, -1, -1, sizeof(::SegmentResult)},
+ {81, -1, -1, sizeof(::SegmentResults)},
+ {93, -1, -1, sizeof(::UnknownMessage1)},
+ {101, -1, -1, sizeof(::UnknownMessage)},
+ {109, -1, -1, sizeof(::ServerToClient)},
+ {127, -1, -1, sizeof(::WorldAttributes)},
+ {141, -1, -1, sizeof(::WorldAttribute)},
+ {150, -1, -1, sizeof(::EventSubgroupProtobuf)},
+ {167, -1, -1, sizeof(::RiderAttributes_AttributeMessage)},
+ {180, 193, -1, sizeof(::RiderAttributes)},
+ {198, -1, -1, sizeof(::Profiles)},
+ {207, -1, -1, sizeof(::Profile)},
+};
+
+static const ::_pb::Message* const file_default_instances[] = {
+ &::_PlayerState_default_instance_._instance,
+ &::_ClientToServer_default_instance_._instance,
+ &::_SegmentResult_default_instance_._instance,
+ &::_SegmentResults_default_instance_._instance,
+ &::_UnknownMessage1_default_instance_._instance,
+ &::_UnknownMessage_default_instance_._instance,
+ &::_ServerToClient_default_instance_._instance,
+ &::_WorldAttributes_default_instance_._instance,
+ &::_WorldAttribute_default_instance_._instance,
+ &::_EventSubgroupProtobuf_default_instance_._instance,
+ &::_RiderAttributes_AttributeMessage_default_instance_._instance,
+ &::_RiderAttributes_default_instance_._instance,
+ &::_Profiles_default_instance_._instance,
+ &::_Profile_default_instance_._instance,
+};
+const char descriptor_table_protodef_zwift_5fmessages_2eproto[] PROTOBUF_SECTION_VARIABLE(protodesc_cold) = {
+ "\n\024zwift_messages.proto\"\306\003\n\013PlayerState\022\n"
+ "\n\002id\030\001 \001(\005\022\021\n\tworldTime\030\002 \001(\003\022\020\n\010distanc"
+ "e\030\003 \001(\005\022\020\n\010roadTime\030\004 \001(\005\022\014\n\004laps\030\005 \001(\005\022"
+ "\r\n\005speed\030\006 \001(\005\022\024\n\014roadPosition\030\010 \001(\005\022\022\n\n"
+ "cadenceUHz\030\t \001(\005\022\021\n\theartrate\030\013 \001(\005\022\r\n\005p"
+ "ower\030\014 \001(\005\022\017\n\007heading\030\r \001(\003\022\014\n\004lean\030\016 \001("
+ "\005\022\020\n\010climbing\030\017 \001(\005\022\014\n\004time\030\020 \001(\005\022\013\n\003f19"
+ "\030\023 \001(\005\022\013\n\003f20\030\024 \001(\005\022\020\n\010progress\030\025 \001(\005\022\027\n"
+ "\017customisationId\030\026 \001(\003\022\024\n\014justWatching\030\027"
+ " \001(\005\022\020\n\010calories\030\030 \001(\005\022\t\n\001x\030\031 \001(\002\022\020\n\010alt"
+ "itude\030\032 \001(\002\022\t\n\001y\030\033 \001(\002\022\027\n\017watchingRiderI"
+ "d\030\034 \001(\005\022\017\n\007groupId\030\035 \001(\005\022\r\n\005sport\030\037 \001(\003\""
+ "\321\001\n\016ClientToServer\022\021\n\tconnected\030\001 \001(\005\022\020\n"
+ "\010rider_id\030\002 \001(\005\022\022\n\nworld_time\030\003 \001(\003\022\033\n\005s"
+ "tate\030\007 \001(\0132\014.PlayerState\022\r\n\005seqno\030\004 \001(\005\022"
+ "\014\n\004tag8\030\010 \001(\003\022\014\n\004tag9\030\t \001(\003\022\023\n\013last_upda"
+ "te\030\n \001(\003\022\r\n\005tag11\030\013 \001(\003\022\032\n\022last_player_u"
+ "pdate\030\014 \001(\003\"\342\001\n\rSegmentResult\022\n\n\002id\030\001 \001("
+ "\003\022\020\n\010rider_id\030\002 \001(\003\022\031\n\021event_subgroup_id"
+ "\030\006 \001(\003\022\022\n\nfirst_name\030\007 \001(\t\022\021\n\tlast_name\030"
+ "\010 \001(\t\022\027\n\017finish_time_str\030\n \001(\t\022\022\n\nelapse"
+ "d_ms\030\013 \001(\003\022\022\n\npowermeter\030\014 \001(\005\022\016\n\006weight"
+ "\030\r \001(\005\022\r\n\005power\030\017 \001(\005\022\021\n\theartrate\030\023 \001(\005"
+ "\"z\n\016SegmentResults\022\020\n\010world_id\030\001 \001(\003\022\022\n\n"
+ "segment_id\030\002 \001(\003\022\031\n\021event_subgroup_id\030\003 "
+ "\001(\003\022\'\n\017segment_results\030\004 \003(\0132\016.SegmentRe"
+ "sult\"\021\n\017UnknownMessage1\"\020\n\016UnknownMessag"
+ "e\"\341\001\n\016ServerToClient\022\014\n\004tag1\030\001 \001(\005\022\020\n\010ri"
+ "der_id\030\002 \001(\005\022\022\n\nworld_time\030\003 \001(\003\022\r\n\005seqn"
+ "o\030\004 \001(\005\022#\n\rplayer_states\030\010 \003(\0132\014.PlayerS"
+ "tate\022\'\n\016player_updates\030\t \003(\0132\017.UnknownMe"
+ "ssage\022\r\n\005tag11\030\013 \001(\003\022\r\n\005tag17\030\021 \001(\003\022\020\n\010n"
+ "um_msgs\030\022 \001(\005\022\016\n\006msgnum\030\023 \001(\005\"u\n\017WorldAt"
+ "tributes\022\020\n\010world_id\030\001 \001(\005\022\014\n\004name\030\002 \001(\t"
+ "\022\014\n\004tag3\030\003 \001(\003\022\014\n\004tag5\030\004 \001(\003\022\022\n\nworld_ti"
+ "me\030\006 \001(\003\022\022\n\nclock_time\030\007 \001(\003\"$\n\016WorldAtt"
+ "ribute\022\022\n\nworld_time\030\002 \001(\003\"\251\001\n\025EventSubg"
+ "roupProtobuf\022\n\n\002id\030\001 \001(\005\022\014\n\004name\030\002 \001(\t\022\r"
+ "\n\005rules\030\010 \001(\005\022\r\n\005route\030\026 \001(\005\022\014\n\004laps\030\031 \001"
+ "(\005\022\025\n\rstartLocation\030\035 \001(\005\022\r\n\005label\030\036 \001(\005"
+ "\022\020\n\010paceType\030\037 \001(\005\022\022\n\njerseyHash\030$ \001(\005\"\361"
+ "\001\n\017RiderAttributes\022\n\n\002f2\030\002 \001(\005\022\n\n\002f3\030\003 \001"
+ "(\005\022;\n\020attributeMessage\030\004 \001(\0132!.RiderAttr"
+ "ibutes.AttributeMessage\022\017\n\007theirId\030\n \001(\005"
+ "\022\013\n\003f13\030\r \001(\005\032k\n\020AttributeMessage\022\014\n\004myI"
+ "d\030\001 \001(\005\022\017\n\007theirId\030\002 \001(\005\022\021\n\tfirstName\030\003 "
+ "\001(\t\022\020\n\010lastName\030\004 \001(\t\022\023\n\013countryCode\030\005 \001"
+ "(\005\"&\n\010Profiles\022\032\n\010profiles\030\001 \003(\0132\010.Profi"
+ "le\"\212\003\n\007Profile\022\n\n\002id\030\001 \001(\005\022\021\n\tfirstName\030"
+ "\004 \001(\t\022\020\n\010lastName\030\005 \001(\t\022\014\n\004male\030\006 \001(\005\022\016\n"
+ "\006weight\030\t \001(\005\022\020\n\010bodyType\030\014 \001(\005\022\023\n\013count"
+ "ryCode\030\" \001(\005\022\025\n\rtotalDistance\030# \001(\005\022\034\n\024t"
+ "otalDistanceClimbed\030$ \001(\005\022\032\n\022totalTimeIn"
+ "Minutes\030% \001(\005\022\026\n\016totalWattHours\030) \001(\005\022\016\n"
+ "\006height\030* \001(\005\022\035\n\025totalExperiencePoints\030."
+ " \001(\005\022\030\n\020achievementLevel\0301 \001(\005\022\023\n\013powerS"
+ "ource\0304 \001(\005\022\013\n\003age\0307 \001(\005\022\032\n\022launchedGame"
+ "Client\030l \001(\t\022\031\n\021currentActivityId\030m \001(\005b"
+ "\006proto3"
+};
+static ::absl::once_flag descriptor_table_zwift_5fmessages_2eproto_once;
+const ::_pbi::DescriptorTable descriptor_table_zwift_5fmessages_2eproto = {
+ false,
+ false,
+ 2327,
+ descriptor_table_protodef_zwift_5fmessages_2eproto,
+ "zwift_messages.proto",
+ &descriptor_table_zwift_5fmessages_2eproto_once,
+ nullptr,
+ 0,
+ 14,
+ schemas,
+ file_default_instances,
+ TableStruct_zwift_5fmessages_2eproto::offsets,
+ file_level_metadata_zwift_5fmessages_2eproto,
+ file_level_enum_descriptors_zwift_5fmessages_2eproto,
+ file_level_service_descriptors_zwift_5fmessages_2eproto,
+};
+
+// This function exists to be marked as weak.
+// It can significantly speed up compilation by breaking up LLVM's SCC
+// in the .pb.cc translation units. Large translation units see a
+// reduction of more than 35% of walltime for optimized builds. Without
+// the weak attribute all the messages in the file, including all the
+// vtables and everything they use become part of the same SCC through
+// a cycle like:
+// GetMetadata -> descriptor table -> default instances ->
+// vtables -> GetMetadata
+// By adding a weak function here we break the connection from the
+// individual vtables back into the descriptor table.
+PROTOBUF_ATTRIBUTE_WEAK const ::_pbi::DescriptorTable* descriptor_table_zwift_5fmessages_2eproto_getter() {
+ return &descriptor_table_zwift_5fmessages_2eproto;
+}
+// Force running AddDescriptors() at dynamic initialization time.
+PROTOBUF_ATTRIBUTE_INIT_PRIORITY2
+static ::_pbi::AddDescriptorsRunner dynamic_init_dummy_zwift_5fmessages_2eproto(&descriptor_table_zwift_5fmessages_2eproto);
+// ===================================================================
+
+class PlayerState::_Internal {
+ public:
+};
+
+PlayerState::PlayerState(::google::protobuf::Arena* arena)
+ : ::google::protobuf::Message(arena) {
+ SharedCtor(arena);
+ // @@protoc_insertion_point(arena_constructor:PlayerState)
+}
+PlayerState::PlayerState(
+ ::google::protobuf::Arena* arena, const PlayerState& from)
+ : PlayerState(arena) {
+ MergeFrom(from);
+}
+inline PROTOBUF_NDEBUG_INLINE PlayerState::Impl_::Impl_(
+ ::google::protobuf::internal::InternalVisibility visibility,
+ ::google::protobuf::Arena* arena)
+ : _cached_size_{0} {}
+
+inline void PlayerState::SharedCtor(::_pb::Arena* arena) {
+ new (&_impl_) Impl_(internal_visibility(), arena);
+ ::memset(reinterpret_cast(&_impl_) +
+ offsetof(Impl_, worldtime_),
+ 0,
+ offsetof(Impl_, sport_) -
+ offsetof(Impl_, worldtime_) +
+ sizeof(Impl_::sport_));
+}
+PlayerState::~PlayerState() {
+ // @@protoc_insertion_point(destructor:PlayerState)
+ _internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>();
+ SharedDtor();
+}
+inline void PlayerState::SharedDtor() {
+ ABSL_DCHECK(GetArena() == nullptr);
+ _impl_.~Impl_();
+}
+
+PROTOBUF_NOINLINE void PlayerState::Clear() {
+// @@protoc_insertion_point(message_clear_start:PlayerState)
+ PROTOBUF_TSAN_WRITE(&_impl_._tsan_detect_race);
+ ::uint32_t cached_has_bits = 0;
+ // Prevent compiler warnings about cached_has_bits being unused
+ (void) cached_has_bits;
+
+ ::memset(&_impl_.worldtime_, 0, static_cast<::size_t>(
+ reinterpret_cast(&_impl_.sport_) -
+ reinterpret_cast(&_impl_.worldtime_)) + sizeof(_impl_.sport_));
+ _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>();
+}
+
+const char* PlayerState::_InternalParse(
+ const char* ptr, ::_pbi::ParseContext* ctx) {
+ ptr = ::_pbi::TcParser::ParseLoop(this, ptr, ctx, &_table_.header);
+ return ptr;
+}
+
+
+PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1
+const ::_pbi::TcParseTable<5, 26, 0, 0, 2> PlayerState::_table_ = {
+ {
+ 0, // no _has_bits_
+ 0, // no _extensions_
+ 31, 248, // max_field_number, fast_idx_mask
+ offsetof(decltype(_table_), field_lookup_table),
+ 2684551744, // skipmap
+ offsetof(decltype(_table_), field_entries),
+ 26, // num_field_entries
+ 0, // num_aux_entries
+ offsetof(decltype(_table_), field_names), // no aux_entries
+ &_PlayerState_default_instance_._instance,
+ ::_pbi::TcParser::GenericFallback, // fallback
+ }, {{
+ {::_pbi::TcParser::MiniParse, {}},
+ // int32 id = 1;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(PlayerState, _impl_.id_), 63>(),
+ {8, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.id_)}},
+ // int64 worldTime = 2;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(PlayerState, _impl_.worldtime_), 63>(),
+ {16, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.worldtime_)}},
+ // int32 distance = 3;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(PlayerState, _impl_.distance_), 63>(),
+ {24, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.distance_)}},
+ // int32 roadTime = 4;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(PlayerState, _impl_.roadtime_), 63>(),
+ {32, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.roadtime_)}},
+ // int32 laps = 5;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(PlayerState, _impl_.laps_), 63>(),
+ {40, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.laps_)}},
+ // int32 speed = 6;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(PlayerState, _impl_.speed_), 63>(),
+ {48, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.speed_)}},
+ {::_pbi::TcParser::MiniParse, {}},
+ // int32 roadPosition = 8;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(PlayerState, _impl_.roadposition_), 63>(),
+ {64, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.roadposition_)}},
+ // int32 cadenceUHz = 9;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(PlayerState, _impl_.cadenceuhz_), 63>(),
+ {72, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.cadenceuhz_)}},
+ {::_pbi::TcParser::MiniParse, {}},
+ // int32 heartrate = 11;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(PlayerState, _impl_.heartrate_), 63>(),
+ {88, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.heartrate_)}},
+ // int32 power = 12;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(PlayerState, _impl_.power_), 63>(),
+ {96, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.power_)}},
+ // int64 heading = 13;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(PlayerState, _impl_.heading_), 63>(),
+ {104, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.heading_)}},
+ // int32 lean = 14;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(PlayerState, _impl_.lean_), 63>(),
+ {112, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.lean_)}},
+ // int32 climbing = 15;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(PlayerState, _impl_.climbing_), 63>(),
+ {120, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.climbing_)}},
+ // int32 time = 16;
+ {::_pbi::TcParser::FastV32S2,
+ {384, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.time_)}},
+ {::_pbi::TcParser::MiniParse, {}},
+ {::_pbi::TcParser::MiniParse, {}},
+ // int32 f19 = 19;
+ {::_pbi::TcParser::FastV32S2,
+ {408, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.f19_)}},
+ // int32 f20 = 20;
+ {::_pbi::TcParser::FastV32S2,
+ {416, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.f20_)}},
+ // int32 progress = 21;
+ {::_pbi::TcParser::FastV32S2,
+ {424, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.progress_)}},
+ // int64 customisationId = 22;
+ {::_pbi::TcParser::FastV64S2,
+ {432, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.customisationid_)}},
+ // int32 justWatching = 23;
+ {::_pbi::TcParser::FastV32S2,
+ {440, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.justwatching_)}},
+ // int32 calories = 24;
+ {::_pbi::TcParser::FastV32S2,
+ {448, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.calories_)}},
+ // float x = 25;
+ {::_pbi::TcParser::FastF32S2,
+ {461, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.x_)}},
+ // float altitude = 26;
+ {::_pbi::TcParser::FastF32S2,
+ {469, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.altitude_)}},
+ // float y = 27;
+ {::_pbi::TcParser::FastF32S2,
+ {477, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.y_)}},
+ // int32 watchingRiderId = 28;
+ {::_pbi::TcParser::FastV32S2,
+ {480, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.watchingriderid_)}},
+ // int32 groupId = 29;
+ {::_pbi::TcParser::FastV32S2,
+ {488, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.groupid_)}},
+ {::_pbi::TcParser::MiniParse, {}},
+ // int64 sport = 31;
+ {::_pbi::TcParser::FastV64S2,
+ {504, 63, 0, PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.sport_)}},
+ }}, {{
+ 65535, 65535
+ }}, {{
+ // int32 id = 1;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.id_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int64 worldTime = 2;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.worldtime_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt64)},
+ // int32 distance = 3;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.distance_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int32 roadTime = 4;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.roadtime_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int32 laps = 5;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.laps_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int32 speed = 6;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.speed_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int32 roadPosition = 8;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.roadposition_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int32 cadenceUHz = 9;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.cadenceuhz_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int32 heartrate = 11;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.heartrate_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int32 power = 12;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.power_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int64 heading = 13;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.heading_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt64)},
+ // int32 lean = 14;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.lean_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int32 climbing = 15;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.climbing_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int32 time = 16;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.time_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int32 f19 = 19;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.f19_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int32 f20 = 20;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.f20_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int32 progress = 21;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.progress_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int64 customisationId = 22;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.customisationid_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt64)},
+ // int32 justWatching = 23;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.justwatching_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int32 calories = 24;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.calories_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // float x = 25;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.x_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kFloat)},
+ // float altitude = 26;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.altitude_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kFloat)},
+ // float y = 27;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.y_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kFloat)},
+ // int32 watchingRiderId = 28;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.watchingriderid_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int32 groupId = 29;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.groupid_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int64 sport = 31;
+ {PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.sport_), 0, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt64)},
+ }},
+ // no aux_entries
+ {{
+ }},
+};
+
+::uint8_t* PlayerState::_InternalSerialize(
+ ::uint8_t* target,
+ ::google::protobuf::io::EpsCopyOutputStream* stream) const {
+ // @@protoc_insertion_point(serialize_to_array_start:PlayerState)
+ ::uint32_t cached_has_bits = 0;
+ (void)cached_has_bits;
+
+ // int32 id = 1;
+ if (this->_internal_id() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt32ToArrayWithField<1>(
+ stream, this->_internal_id(), target);
+ }
+
+ // int64 worldTime = 2;
+ if (this->_internal_worldtime() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt64ToArrayWithField<2>(
+ stream, this->_internal_worldtime(), target);
+ }
+
+ // int32 distance = 3;
+ if (this->_internal_distance() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt32ToArrayWithField<3>(
+ stream, this->_internal_distance(), target);
+ }
+
+ // int32 roadTime = 4;
+ if (this->_internal_roadtime() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt32ToArrayWithField<4>(
+ stream, this->_internal_roadtime(), target);
+ }
+
+ // int32 laps = 5;
+ if (this->_internal_laps() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt32ToArrayWithField<5>(
+ stream, this->_internal_laps(), target);
+ }
+
+ // int32 speed = 6;
+ if (this->_internal_speed() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt32ToArrayWithField<6>(
+ stream, this->_internal_speed(), target);
+ }
+
+ // int32 roadPosition = 8;
+ if (this->_internal_roadposition() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt32ToArrayWithField<8>(
+ stream, this->_internal_roadposition(), target);
+ }
+
+ // int32 cadenceUHz = 9;
+ if (this->_internal_cadenceuhz() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt32ToArrayWithField<9>(
+ stream, this->_internal_cadenceuhz(), target);
+ }
+
+ // int32 heartrate = 11;
+ if (this->_internal_heartrate() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt32ToArrayWithField<11>(
+ stream, this->_internal_heartrate(), target);
+ }
+
+ // int32 power = 12;
+ if (this->_internal_power() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt32ToArrayWithField<12>(
+ stream, this->_internal_power(), target);
+ }
+
+ // int64 heading = 13;
+ if (this->_internal_heading() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt64ToArrayWithField<13>(
+ stream, this->_internal_heading(), target);
+ }
+
+ // int32 lean = 14;
+ if (this->_internal_lean() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt32ToArrayWithField<14>(
+ stream, this->_internal_lean(), target);
+ }
+
+ // int32 climbing = 15;
+ if (this->_internal_climbing() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt32ToArrayWithField<15>(
+ stream, this->_internal_climbing(), target);
+ }
+
+ // int32 time = 16;
+ if (this->_internal_time() != 0) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteInt32ToArray(
+ 16, this->_internal_time(), target);
+ }
+
+ // int32 f19 = 19;
+ if (this->_internal_f19() != 0) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteInt32ToArray(
+ 19, this->_internal_f19(), target);
+ }
+
+ // int32 f20 = 20;
+ if (this->_internal_f20() != 0) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteInt32ToArray(
+ 20, this->_internal_f20(), target);
+ }
+
+ // int32 progress = 21;
+ if (this->_internal_progress() != 0) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteInt32ToArray(
+ 21, this->_internal_progress(), target);
+ }
+
+ // int64 customisationId = 22;
+ if (this->_internal_customisationid() != 0) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteInt64ToArray(
+ 22, this->_internal_customisationid(), target);
+ }
+
+ // int32 justWatching = 23;
+ if (this->_internal_justwatching() != 0) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteInt32ToArray(
+ 23, this->_internal_justwatching(), target);
+ }
+
+ // int32 calories = 24;
+ if (this->_internal_calories() != 0) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteInt32ToArray(
+ 24, this->_internal_calories(), target);
+ }
+
+ // float x = 25;
+ static_assert(sizeof(::uint32_t) == sizeof(float),
+ "Code assumes ::uint32_t and float are the same size.");
+ float tmp_x = this->_internal_x();
+ ::uint32_t raw_x;
+ memcpy(&raw_x, &tmp_x, sizeof(tmp_x));
+ if (raw_x != 0) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteFloatToArray(
+ 25, this->_internal_x(), target);
+ }
+
+ // float altitude = 26;
+ static_assert(sizeof(::uint32_t) == sizeof(float),
+ "Code assumes ::uint32_t and float are the same size.");
+ float tmp_altitude = this->_internal_altitude();
+ ::uint32_t raw_altitude;
+ memcpy(&raw_altitude, &tmp_altitude, sizeof(tmp_altitude));
+ if (raw_altitude != 0) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteFloatToArray(
+ 26, this->_internal_altitude(), target);
+ }
+
+ // float y = 27;
+ static_assert(sizeof(::uint32_t) == sizeof(float),
+ "Code assumes ::uint32_t and float are the same size.");
+ float tmp_y = this->_internal_y();
+ ::uint32_t raw_y;
+ memcpy(&raw_y, &tmp_y, sizeof(tmp_y));
+ if (raw_y != 0) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteFloatToArray(
+ 27, this->_internal_y(), target);
+ }
+
+ // int32 watchingRiderId = 28;
+ if (this->_internal_watchingriderid() != 0) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteInt32ToArray(
+ 28, this->_internal_watchingriderid(), target);
+ }
+
+ // int32 groupId = 29;
+ if (this->_internal_groupid() != 0) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteInt32ToArray(
+ 29, this->_internal_groupid(), target);
+ }
+
+ // int64 sport = 31;
+ if (this->_internal_sport() != 0) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteInt64ToArray(
+ 31, this->_internal_sport(), target);
+ }
+
+ if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) {
+ target =
+ ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray(
+ _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream);
+ }
+ // @@protoc_insertion_point(serialize_to_array_end:PlayerState)
+ return target;
+}
+
+::size_t PlayerState::ByteSizeLong() const {
+// @@protoc_insertion_point(message_byte_size_start:PlayerState)
+ ::size_t total_size = 0;
+
+ ::uint32_t cached_has_bits = 0;
+ // Prevent compiler warnings about cached_has_bits being unused
+ (void) cached_has_bits;
+
+ // int64 worldTime = 2;
+ if (this->_internal_worldtime() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(
+ this->_internal_worldtime());
+ }
+
+ // int32 id = 1;
+ if (this->_internal_id() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(
+ this->_internal_id());
+ }
+
+ // int32 distance = 3;
+ if (this->_internal_distance() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(
+ this->_internal_distance());
+ }
+
+ // int32 roadTime = 4;
+ if (this->_internal_roadtime() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(
+ this->_internal_roadtime());
+ }
+
+ // int32 laps = 5;
+ if (this->_internal_laps() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(
+ this->_internal_laps());
+ }
+
+ // int32 speed = 6;
+ if (this->_internal_speed() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(
+ this->_internal_speed());
+ }
+
+ // int32 roadPosition = 8;
+ if (this->_internal_roadposition() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(
+ this->_internal_roadposition());
+ }
+
+ // int32 cadenceUHz = 9;
+ if (this->_internal_cadenceuhz() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(
+ this->_internal_cadenceuhz());
+ }
+
+ // int32 heartrate = 11;
+ if (this->_internal_heartrate() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(
+ this->_internal_heartrate());
+ }
+
+ // int64 heading = 13;
+ if (this->_internal_heading() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(
+ this->_internal_heading());
+ }
+
+ // int32 power = 12;
+ if (this->_internal_power() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(
+ this->_internal_power());
+ }
+
+ // int32 lean = 14;
+ if (this->_internal_lean() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(
+ this->_internal_lean());
+ }
+
+ // int32 climbing = 15;
+ if (this->_internal_climbing() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(
+ this->_internal_climbing());
+ }
+
+ // int32 time = 16;
+ if (this->_internal_time() != 0) {
+ total_size += 2 + ::_pbi::WireFormatLite::Int32Size(
+ this->_internal_time());
+ }
+
+ // int32 f19 = 19;
+ if (this->_internal_f19() != 0) {
+ total_size += 2 + ::_pbi::WireFormatLite::Int32Size(
+ this->_internal_f19());
+ }
+
+ // int32 f20 = 20;
+ if (this->_internal_f20() != 0) {
+ total_size += 2 + ::_pbi::WireFormatLite::Int32Size(
+ this->_internal_f20());
+ }
+
+ // int64 customisationId = 22;
+ if (this->_internal_customisationid() != 0) {
+ total_size += 2 + ::_pbi::WireFormatLite::Int64Size(
+ this->_internal_customisationid());
+ }
+
+ // int32 progress = 21;
+ if (this->_internal_progress() != 0) {
+ total_size += 2 + ::_pbi::WireFormatLite::Int32Size(
+ this->_internal_progress());
+ }
+
+ // int32 justWatching = 23;
+ if (this->_internal_justwatching() != 0) {
+ total_size += 2 + ::_pbi::WireFormatLite::Int32Size(
+ this->_internal_justwatching());
+ }
+
+ // int32 calories = 24;
+ if (this->_internal_calories() != 0) {
+ total_size += 2 + ::_pbi::WireFormatLite::Int32Size(
+ this->_internal_calories());
+ }
+
+ // float x = 25;
+ static_assert(sizeof(::uint32_t) == sizeof(float),
+ "Code assumes ::uint32_t and float are the same size.");
+ float tmp_x = this->_internal_x();
+ ::uint32_t raw_x;
+ memcpy(&raw_x, &tmp_x, sizeof(tmp_x));
+ if (raw_x != 0) {
+ total_size += 6;
+ }
+
+ // float altitude = 26;
+ static_assert(sizeof(::uint32_t) == sizeof(float),
+ "Code assumes ::uint32_t and float are the same size.");
+ float tmp_altitude = this->_internal_altitude();
+ ::uint32_t raw_altitude;
+ memcpy(&raw_altitude, &tmp_altitude, sizeof(tmp_altitude));
+ if (raw_altitude != 0) {
+ total_size += 6;
+ }
+
+ // float y = 27;
+ static_assert(sizeof(::uint32_t) == sizeof(float),
+ "Code assumes ::uint32_t and float are the same size.");
+ float tmp_y = this->_internal_y();
+ ::uint32_t raw_y;
+ memcpy(&raw_y, &tmp_y, sizeof(tmp_y));
+ if (raw_y != 0) {
+ total_size += 6;
+ }
+
+ // int32 watchingRiderId = 28;
+ if (this->_internal_watchingriderid() != 0) {
+ total_size += 2 + ::_pbi::WireFormatLite::Int32Size(
+ this->_internal_watchingriderid());
+ }
+
+ // int32 groupId = 29;
+ if (this->_internal_groupid() != 0) {
+ total_size += 2 + ::_pbi::WireFormatLite::Int32Size(
+ this->_internal_groupid());
+ }
+
+ // int64 sport = 31;
+ if (this->_internal_sport() != 0) {
+ total_size += 2 + ::_pbi::WireFormatLite::Int64Size(
+ this->_internal_sport());
+ }
+
+ return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_);
+}
+
+const ::google::protobuf::Message::ClassData PlayerState::_class_data_ = {
+ PlayerState::MergeImpl,
+ nullptr, // OnDemandRegisterArenaDtor
+};
+const ::google::protobuf::Message::ClassData* PlayerState::GetClassData() const {
+ return &_class_data_;
+}
+
+void PlayerState::MergeImpl(::google::protobuf::Message& to_msg, const ::google::protobuf::Message& from_msg) {
+ auto* const _this = static_cast(&to_msg);
+ auto& from = static_cast(from_msg);
+ // @@protoc_insertion_point(class_specific_merge_from_start:PlayerState)
+ ABSL_DCHECK_NE(&from, _this);
+ ::uint32_t cached_has_bits = 0;
+ (void) cached_has_bits;
+
+ if (from._internal_worldtime() != 0) {
+ _this->_internal_set_worldtime(from._internal_worldtime());
+ }
+ if (from._internal_id() != 0) {
+ _this->_internal_set_id(from._internal_id());
+ }
+ if (from._internal_distance() != 0) {
+ _this->_internal_set_distance(from._internal_distance());
+ }
+ if (from._internal_roadtime() != 0) {
+ _this->_internal_set_roadtime(from._internal_roadtime());
+ }
+ if (from._internal_laps() != 0) {
+ _this->_internal_set_laps(from._internal_laps());
+ }
+ if (from._internal_speed() != 0) {
+ _this->_internal_set_speed(from._internal_speed());
+ }
+ if (from._internal_roadposition() != 0) {
+ _this->_internal_set_roadposition(from._internal_roadposition());
+ }
+ if (from._internal_cadenceuhz() != 0) {
+ _this->_internal_set_cadenceuhz(from._internal_cadenceuhz());
+ }
+ if (from._internal_heartrate() != 0) {
+ _this->_internal_set_heartrate(from._internal_heartrate());
+ }
+ if (from._internal_heading() != 0) {
+ _this->_internal_set_heading(from._internal_heading());
+ }
+ if (from._internal_power() != 0) {
+ _this->_internal_set_power(from._internal_power());
+ }
+ if (from._internal_lean() != 0) {
+ _this->_internal_set_lean(from._internal_lean());
+ }
+ if (from._internal_climbing() != 0) {
+ _this->_internal_set_climbing(from._internal_climbing());
+ }
+ if (from._internal_time() != 0) {
+ _this->_internal_set_time(from._internal_time());
+ }
+ if (from._internal_f19() != 0) {
+ _this->_internal_set_f19(from._internal_f19());
+ }
+ if (from._internal_f20() != 0) {
+ _this->_internal_set_f20(from._internal_f20());
+ }
+ if (from._internal_customisationid() != 0) {
+ _this->_internal_set_customisationid(from._internal_customisationid());
+ }
+ if (from._internal_progress() != 0) {
+ _this->_internal_set_progress(from._internal_progress());
+ }
+ if (from._internal_justwatching() != 0) {
+ _this->_internal_set_justwatching(from._internal_justwatching());
+ }
+ if (from._internal_calories() != 0) {
+ _this->_internal_set_calories(from._internal_calories());
+ }
+ static_assert(sizeof(::uint32_t) == sizeof(float),
+ "Code assumes ::uint32_t and float are the same size.");
+ float tmp_x = from._internal_x();
+ ::uint32_t raw_x;
+ memcpy(&raw_x, &tmp_x, sizeof(tmp_x));
+ if (raw_x != 0) {
+ _this->_internal_set_x(from._internal_x());
+ }
+ static_assert(sizeof(::uint32_t) == sizeof(float),
+ "Code assumes ::uint32_t and float are the same size.");
+ float tmp_altitude = from._internal_altitude();
+ ::uint32_t raw_altitude;
+ memcpy(&raw_altitude, &tmp_altitude, sizeof(tmp_altitude));
+ if (raw_altitude != 0) {
+ _this->_internal_set_altitude(from._internal_altitude());
+ }
+ static_assert(sizeof(::uint32_t) == sizeof(float),
+ "Code assumes ::uint32_t and float are the same size.");
+ float tmp_y = from._internal_y();
+ ::uint32_t raw_y;
+ memcpy(&raw_y, &tmp_y, sizeof(tmp_y));
+ if (raw_y != 0) {
+ _this->_internal_set_y(from._internal_y());
+ }
+ if (from._internal_watchingriderid() != 0) {
+ _this->_internal_set_watchingriderid(from._internal_watchingriderid());
+ }
+ if (from._internal_groupid() != 0) {
+ _this->_internal_set_groupid(from._internal_groupid());
+ }
+ if (from._internal_sport() != 0) {
+ _this->_internal_set_sport(from._internal_sport());
+ }
+ _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>(from._internal_metadata_);
+}
+
+void PlayerState::CopyFrom(const PlayerState& from) {
+// @@protoc_insertion_point(class_specific_copy_from_start:PlayerState)
+ if (&from == this) return;
+ Clear();
+ MergeFrom(from);
+}
+
+PROTOBUF_NOINLINE bool PlayerState::IsInitialized() const {
+ return true;
+}
+
+::_pbi::CachedSize* PlayerState::AccessCachedSize() const {
+ return &_impl_._cached_size_;
+}
+void PlayerState::InternalSwap(PlayerState* PROTOBUF_RESTRICT other) {
+ using std::swap;
+ _internal_metadata_.InternalSwap(&other->_internal_metadata_);
+ ::google::protobuf::internal::memswap<
+ PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.sport_)
+ + sizeof(PlayerState::_impl_.sport_)
+ - PROTOBUF_FIELD_OFFSET(PlayerState, _impl_.worldtime_)>(
+ reinterpret_cast(&_impl_.worldtime_),
+ reinterpret_cast(&other->_impl_.worldtime_));
+}
+
+::google::protobuf::Metadata PlayerState::GetMetadata() const {
+ return ::_pbi::AssignDescriptors(
+ &descriptor_table_zwift_5fmessages_2eproto_getter, &descriptor_table_zwift_5fmessages_2eproto_once,
+ file_level_metadata_zwift_5fmessages_2eproto[0]);
+}
+// ===================================================================
+
+class ClientToServer::_Internal {
+ public:
+ using HasBits = decltype(std::declval()._impl_._has_bits_);
+ static constexpr ::int32_t kHasBitsOffset =
+ 8 * PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_._has_bits_);
+ static const ::PlayerState& state(const ClientToServer* msg);
+ static void set_has_state(HasBits* has_bits) {
+ (*has_bits)[0] |= 1u;
+ }
+};
+
+const ::PlayerState& ClientToServer::_Internal::state(const ClientToServer* msg) {
+ return *msg->_impl_.state_;
+}
+ClientToServer::ClientToServer(::google::protobuf::Arena* arena)
+ : ::google::protobuf::Message(arena) {
+ SharedCtor(arena);
+ // @@protoc_insertion_point(arena_constructor:ClientToServer)
+}
+inline PROTOBUF_NDEBUG_INLINE ClientToServer::Impl_::Impl_(
+ ::google::protobuf::internal::InternalVisibility visibility, ::google::protobuf::Arena* arena,
+ const Impl_& from)
+ : _has_bits_{from._has_bits_},
+ _cached_size_{0} {}
+
+ClientToServer::ClientToServer(
+ ::google::protobuf::Arena* arena,
+ const ClientToServer& from)
+ : ::google::protobuf::Message(arena) {
+ ClientToServer* const _this = this;
+ (void)_this;
+ _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>(
+ from._internal_metadata_);
+ new (&_impl_) Impl_(internal_visibility(), arena, from._impl_);
+ ::uint32_t cached_has_bits = _impl_._has_bits_[0];
+ _impl_.state_ = (cached_has_bits & 0x00000001u)
+ ? CreateMaybeMessage<::PlayerState>(arena, *from._impl_.state_)
+ : nullptr;
+ ::memcpy(reinterpret_cast(&_impl_) +
+ offsetof(Impl_, connected_),
+ reinterpret_cast(&from._impl_) +
+ offsetof(Impl_, connected_),
+ offsetof(Impl_, seqno_) -
+ offsetof(Impl_, connected_) +
+ sizeof(Impl_::seqno_));
+
+ // @@protoc_insertion_point(copy_constructor:ClientToServer)
+}
+inline PROTOBUF_NDEBUG_INLINE ClientToServer::Impl_::Impl_(
+ ::google::protobuf::internal::InternalVisibility visibility,
+ ::google::protobuf::Arena* arena)
+ : _cached_size_{0} {}
+
+inline void ClientToServer::SharedCtor(::_pb::Arena* arena) {
+ new (&_impl_) Impl_(internal_visibility(), arena);
+ ::memset(reinterpret_cast(&_impl_) +
+ offsetof(Impl_, state_),
+ 0,
+ offsetof(Impl_, seqno_) -
+ offsetof(Impl_, state_) +
+ sizeof(Impl_::seqno_));
+}
+ClientToServer::~ClientToServer() {
+ // @@protoc_insertion_point(destructor:ClientToServer)
+ _internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>();
+ SharedDtor();
+}
+inline void ClientToServer::SharedDtor() {
+ ABSL_DCHECK(GetArena() == nullptr);
+ delete _impl_.state_;
+ _impl_.~Impl_();
+}
+
+PROTOBUF_NOINLINE void ClientToServer::Clear() {
+// @@protoc_insertion_point(message_clear_start:ClientToServer)
+ PROTOBUF_TSAN_WRITE(&_impl_._tsan_detect_race);
+ ::uint32_t cached_has_bits = 0;
+ // Prevent compiler warnings about cached_has_bits being unused
+ (void) cached_has_bits;
+
+ cached_has_bits = _impl_._has_bits_[0];
+ if (cached_has_bits & 0x00000001u) {
+ ABSL_DCHECK(_impl_.state_ != nullptr);
+ _impl_.state_->Clear();
+ }
+ ::memset(&_impl_.connected_, 0, static_cast<::size_t>(
+ reinterpret_cast(&_impl_.seqno_) -
+ reinterpret_cast(&_impl_.connected_)) + sizeof(_impl_.seqno_));
+ _impl_._has_bits_.Clear();
+ _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>();
+}
+
+const char* ClientToServer::_InternalParse(
+ const char* ptr, ::_pbi::ParseContext* ctx) {
+ ptr = ::_pbi::TcParser::ParseLoop(this, ptr, ctx, &_table_.header);
+ return ptr;
+}
+
+
+PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1
+const ::_pbi::TcParseTable<4, 10, 1, 0, 2> ClientToServer::_table_ = {
+ {
+ PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_._has_bits_),
+ 0, // no _extensions_
+ 12, 120, // max_field_number, fast_idx_mask
+ offsetof(decltype(_table_), field_lookup_table),
+ 4294963248, // skipmap
+ offsetof(decltype(_table_), field_entries),
+ 10, // num_field_entries
+ 1, // num_aux_entries
+ offsetof(decltype(_table_), aux_entries),
+ &_ClientToServer_default_instance_._instance,
+ ::_pbi::TcParser::GenericFallback, // fallback
+ }, {{
+ {::_pbi::TcParser::MiniParse, {}},
+ // int32 connected = 1;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(ClientToServer, _impl_.connected_), 63>(),
+ {8, 63, 0, PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.connected_)}},
+ // int32 rider_id = 2;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(ClientToServer, _impl_.rider_id_), 63>(),
+ {16, 63, 0, PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.rider_id_)}},
+ // int64 world_time = 3;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(ClientToServer, _impl_.world_time_), 63>(),
+ {24, 63, 0, PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.world_time_)}},
+ // int32 seqno = 4;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(ClientToServer, _impl_.seqno_), 63>(),
+ {32, 63, 0, PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.seqno_)}},
+ {::_pbi::TcParser::MiniParse, {}},
+ {::_pbi::TcParser::MiniParse, {}},
+ // .PlayerState state = 7;
+ {::_pbi::TcParser::FastMtS1,
+ {58, 0, 0, PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.state_)}},
+ // int64 tag8 = 8;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(ClientToServer, _impl_.tag8_), 63>(),
+ {64, 63, 0, PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.tag8_)}},
+ // int64 tag9 = 9;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(ClientToServer, _impl_.tag9_), 63>(),
+ {72, 63, 0, PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.tag9_)}},
+ // int64 last_update = 10;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(ClientToServer, _impl_.last_update_), 63>(),
+ {80, 63, 0, PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.last_update_)}},
+ // int64 tag11 = 11;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(ClientToServer, _impl_.tag11_), 63>(),
+ {88, 63, 0, PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.tag11_)}},
+ // int64 last_player_update = 12;
+ {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(ClientToServer, _impl_.last_player_update_), 63>(),
+ {96, 63, 0, PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.last_player_update_)}},
+ {::_pbi::TcParser::MiniParse, {}},
+ {::_pbi::TcParser::MiniParse, {}},
+ {::_pbi::TcParser::MiniParse, {}},
+ }}, {{
+ 65535, 65535
+ }}, {{
+ // int32 connected = 1;
+ {PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.connected_), -1, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int32 rider_id = 2;
+ {PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.rider_id_), -1, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // int64 world_time = 3;
+ {PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.world_time_), -1, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt64)},
+ // int32 seqno = 4;
+ {PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.seqno_), -1, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt32)},
+ // .PlayerState state = 7;
+ {PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.state_), _Internal::kHasBitsOffset + 0, 0,
+ (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)},
+ // int64 tag8 = 8;
+ {PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.tag8_), -1, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt64)},
+ // int64 tag9 = 9;
+ {PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.tag9_), -1, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt64)},
+ // int64 last_update = 10;
+ {PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.last_update_), -1, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt64)},
+ // int64 tag11 = 11;
+ {PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.tag11_), -1, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt64)},
+ // int64 last_player_update = 12;
+ {PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.last_player_update_), -1, 0,
+ (0 | ::_fl::kFcSingular | ::_fl::kInt64)},
+ }}, {{
+ {::_pbi::TcParser::GetTable<::PlayerState>()},
+ }}, {{
+ }},
+};
+
+::uint8_t* ClientToServer::_InternalSerialize(
+ ::uint8_t* target,
+ ::google::protobuf::io::EpsCopyOutputStream* stream) const {
+ // @@protoc_insertion_point(serialize_to_array_start:ClientToServer)
+ ::uint32_t cached_has_bits = 0;
+ (void)cached_has_bits;
+
+ // int32 connected = 1;
+ if (this->_internal_connected() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt32ToArrayWithField<1>(
+ stream, this->_internal_connected(), target);
+ }
+
+ // int32 rider_id = 2;
+ if (this->_internal_rider_id() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt32ToArrayWithField<2>(
+ stream, this->_internal_rider_id(), target);
+ }
+
+ // int64 world_time = 3;
+ if (this->_internal_world_time() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt64ToArrayWithField<3>(
+ stream, this->_internal_world_time(), target);
+ }
+
+ // int32 seqno = 4;
+ if (this->_internal_seqno() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt32ToArrayWithField<4>(
+ stream, this->_internal_seqno(), target);
+ }
+
+ cached_has_bits = _impl_._has_bits_[0];
+ // .PlayerState state = 7;
+ if (cached_has_bits & 0x00000001u) {
+ target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage(
+ 7, _Internal::state(this),
+ _Internal::state(this).GetCachedSize(), target, stream);
+ }
+
+ // int64 tag8 = 8;
+ if (this->_internal_tag8() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt64ToArrayWithField<8>(
+ stream, this->_internal_tag8(), target);
+ }
+
+ // int64 tag9 = 9;
+ if (this->_internal_tag9() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt64ToArrayWithField<9>(
+ stream, this->_internal_tag9(), target);
+ }
+
+ // int64 last_update = 10;
+ if (this->_internal_last_update() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt64ToArrayWithField<10>(
+ stream, this->_internal_last_update(), target);
+ }
+
+ // int64 tag11 = 11;
+ if (this->_internal_tag11() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt64ToArrayWithField<11>(
+ stream, this->_internal_tag11(), target);
+ }
+
+ // int64 last_player_update = 12;
+ if (this->_internal_last_player_update() != 0) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteInt64ToArrayWithField<12>(
+ stream, this->_internal_last_player_update(), target);
+ }
+
+ if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) {
+ target =
+ ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray(
+ _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream);
+ }
+ // @@protoc_insertion_point(serialize_to_array_end:ClientToServer)
+ return target;
+}
+
+::size_t ClientToServer::ByteSizeLong() const {
+// @@protoc_insertion_point(message_byte_size_start:ClientToServer)
+ ::size_t total_size = 0;
+
+ ::uint32_t cached_has_bits = 0;
+ // Prevent compiler warnings about cached_has_bits being unused
+ (void) cached_has_bits;
+
+ // .PlayerState state = 7;
+ cached_has_bits = _impl_._has_bits_[0];
+ if (cached_has_bits & 0x00000001u) {
+ total_size +=
+ 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*_impl_.state_);
+ }
+
+ // int32 connected = 1;
+ if (this->_internal_connected() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(
+ this->_internal_connected());
+ }
+
+ // int32 rider_id = 2;
+ if (this->_internal_rider_id() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(
+ this->_internal_rider_id());
+ }
+
+ // int64 world_time = 3;
+ if (this->_internal_world_time() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(
+ this->_internal_world_time());
+ }
+
+ // int64 tag8 = 8;
+ if (this->_internal_tag8() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(
+ this->_internal_tag8());
+ }
+
+ // int64 tag9 = 9;
+ if (this->_internal_tag9() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(
+ this->_internal_tag9());
+ }
+
+ // int64 last_update = 10;
+ if (this->_internal_last_update() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(
+ this->_internal_last_update());
+ }
+
+ // int64 tag11 = 11;
+ if (this->_internal_tag11() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(
+ this->_internal_tag11());
+ }
+
+ // int64 last_player_update = 12;
+ if (this->_internal_last_player_update() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(
+ this->_internal_last_player_update());
+ }
+
+ // int32 seqno = 4;
+ if (this->_internal_seqno() != 0) {
+ total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(
+ this->_internal_seqno());
+ }
+
+ return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_);
+}
+
+const ::google::protobuf::Message::ClassData ClientToServer::_class_data_ = {
+ ClientToServer::MergeImpl,
+ nullptr, // OnDemandRegisterArenaDtor
+};
+const ::google::protobuf::Message::ClassData* ClientToServer::GetClassData() const {
+ return &_class_data_;
+}
+
+void ClientToServer::MergeImpl(::google::protobuf::Message& to_msg, const ::google::protobuf::Message& from_msg) {
+ auto* const _this = static_cast(&to_msg);
+ auto& from = static_cast(from_msg);
+ // @@protoc_insertion_point(class_specific_merge_from_start:ClientToServer)
+ ABSL_DCHECK_NE(&from, _this);
+ ::uint32_t cached_has_bits = 0;
+ (void) cached_has_bits;
+
+ if ((from._impl_._has_bits_[0] & 0x00000001u) != 0) {
+ _this->_internal_mutable_state()->::PlayerState::MergeFrom(
+ from._internal_state());
+ }
+ if (from._internal_connected() != 0) {
+ _this->_internal_set_connected(from._internal_connected());
+ }
+ if (from._internal_rider_id() != 0) {
+ _this->_internal_set_rider_id(from._internal_rider_id());
+ }
+ if (from._internal_world_time() != 0) {
+ _this->_internal_set_world_time(from._internal_world_time());
+ }
+ if (from._internal_tag8() != 0) {
+ _this->_internal_set_tag8(from._internal_tag8());
+ }
+ if (from._internal_tag9() != 0) {
+ _this->_internal_set_tag9(from._internal_tag9());
+ }
+ if (from._internal_last_update() != 0) {
+ _this->_internal_set_last_update(from._internal_last_update());
+ }
+ if (from._internal_tag11() != 0) {
+ _this->_internal_set_tag11(from._internal_tag11());
+ }
+ if (from._internal_last_player_update() != 0) {
+ _this->_internal_set_last_player_update(from._internal_last_player_update());
+ }
+ if (from._internal_seqno() != 0) {
+ _this->_internal_set_seqno(from._internal_seqno());
+ }
+ _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>(from._internal_metadata_);
+}
+
+void ClientToServer::CopyFrom(const ClientToServer& from) {
+// @@protoc_insertion_point(class_specific_copy_from_start:ClientToServer)
+ if (&from == this) return;
+ Clear();
+ MergeFrom(from);
+}
+
+PROTOBUF_NOINLINE bool ClientToServer::IsInitialized() const {
+ return true;
+}
+
+::_pbi::CachedSize* ClientToServer::AccessCachedSize() const {
+ return &_impl_._cached_size_;
+}
+void ClientToServer::InternalSwap(ClientToServer* PROTOBUF_RESTRICT other) {
+ using std::swap;
+ _internal_metadata_.InternalSwap(&other->_internal_metadata_);
+ swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]);
+ ::google::protobuf::internal::memswap<
+ PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.seqno_)
+ + sizeof(ClientToServer::_impl_.seqno_)
+ - PROTOBUF_FIELD_OFFSET(ClientToServer, _impl_.state_)>(
+ reinterpret_cast(&_impl_.state_),
+ reinterpret_cast