mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
175 Commits
build-970
...
build-1007
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adc47fd19c | ||
|
|
e876ef97cd | ||
|
|
903409d962 | ||
|
|
9295554195 | ||
|
|
8d6cfe03ac | ||
|
|
70d5051a6f | ||
|
|
124e4ec561 | ||
|
|
036db83321 | ||
|
|
23dfa67fe5 | ||
|
|
79d7a09203 | ||
|
|
96d9fb485b | ||
|
|
68c4d954ef | ||
|
|
ef7bedacb8 | ||
|
|
e7a1373305 | ||
|
|
cfd06df25e | ||
|
|
89bc6d0529 | ||
|
|
0446000270 | ||
|
|
9908e8ca98 | ||
|
|
326f09c903 | ||
|
|
2b52206795 | ||
|
|
4cadcddac1 | ||
|
|
8910b8bf28 | ||
|
|
fc3287758e | ||
|
|
c94a03bb23 | ||
|
|
2c5ba21b99 | ||
|
|
4f00550400 | ||
|
|
dfd622c948 | ||
|
|
a7d66727f3 | ||
|
|
06a5c412bd | ||
|
|
0a3616ec0e | ||
|
|
b4226306b0 | ||
|
|
4f3353303a | ||
|
|
81b832071a | ||
|
|
d1966df73c | ||
|
|
e194291efb | ||
|
|
af88f6cd0d | ||
|
|
62838da761 | ||
|
|
7236608f59 | ||
|
|
2570f2843c | ||
|
|
a9fe9bebaf | ||
|
|
15a7c3abd0 | ||
|
|
7872950f65 | ||
|
|
4a711368e3 | ||
|
|
fbcc7e4478 | ||
|
|
e23af2e5f5 | ||
|
|
9c6fed4d48 | ||
|
|
26ac25d3ba | ||
|
|
19beae66bb | ||
|
|
037f660825 | ||
|
|
47e719bff0 | ||
|
|
020f30d8df | ||
|
|
7078508ba9 | ||
|
|
eb002332ed | ||
|
|
ea1da07e71 | ||
|
|
e1d32cd747 | ||
|
|
1bb3450512 | ||
|
|
ce1a78156e | ||
|
|
9d95e52d12 | ||
|
|
73c072583a | ||
|
|
253e2b7eab | ||
|
|
650c6de692 | ||
|
|
1ac4e20efb | ||
|
|
f08ea4346e | ||
|
|
c206886639 | ||
|
|
61bf953b1a | ||
|
|
1dcd35e825 | ||
|
|
5a7bb8b103 | ||
|
|
c4be4f068f | ||
|
|
4534c334bc | ||
|
|
9fa6d6d8b1 | ||
|
|
a5b34161c1 | ||
|
|
bf2c6929e1 | ||
|
|
2a451c3120 | ||
|
|
1169714908 | ||
|
|
14de4e4760 | ||
|
|
712f527ce0 | ||
|
|
0631c64ba5 | ||
|
|
85c43db53e | ||
|
|
8394bf3f19 | ||
|
|
bd1f25f016 | ||
|
|
95f340063a | ||
|
|
2be1d82e8d | ||
|
|
501af18298 | ||
|
|
724292bd34 | ||
|
|
cbbdebdf84 | ||
|
|
02c17dcf55 | ||
|
|
23d1f9d8c0 | ||
|
|
f4e0d3596d | ||
|
|
3b012bc946 | ||
|
|
33a5a2c80f | ||
|
|
e8b481d517 | ||
|
|
dcfa58b3a9 | ||
|
|
fd4106cf00 | ||
|
|
87dddac5f4 | ||
|
|
5488af7e35 | ||
|
|
0a3bd56f15 | ||
|
|
a5ae8f994b | ||
|
|
6a0b3e7fc4 | ||
|
|
a7620c38d0 | ||
|
|
0060e316dc | ||
|
|
b71321f301 | ||
|
|
c99ef80d78 | ||
|
|
2adf3fe27b | ||
|
|
ade033eb59 | ||
|
|
42666cf1e9 | ||
|
|
530f11f67c | ||
|
|
0391db60aa | ||
|
|
486c90a112 | ||
|
|
d1767797d7 | ||
|
|
fbe03d23f3 | ||
|
|
361280c131 | ||
|
|
04e0fc6e7c | ||
|
|
ab52eee127 | ||
|
|
94825252f7 | ||
|
|
93f13817be | ||
|
|
739ea4e841 | ||
|
|
fe3ad9ffb4 | ||
|
|
8fce809ee9 | ||
|
|
c156cbff99 | ||
|
|
268be8e0f5 | ||
|
|
5581e1c0e1 | ||
|
|
7fea2d442f | ||
|
|
74276764a6 | ||
|
|
a3e54782bb | ||
|
|
b7bc80b2a3 | ||
|
|
b869a41f3d | ||
|
|
9c7954945f | ||
|
|
13cd666718 | ||
|
|
c3e627e85b | ||
|
|
f23c24ae9b | ||
|
|
d27da35beb | ||
|
|
6457b205e4 | ||
|
|
bea7b61dcc | ||
|
|
2cc8d51a6c | ||
|
|
5410b806bb | ||
|
|
b937d8bd71 | ||
|
|
cd25cfab8e | ||
|
|
229e6ad461 | ||
|
|
977cae1cbd | ||
|
|
c8a9be2ca6 | ||
|
|
c3acf82a9b | ||
|
|
ddfc60bbf5 | ||
|
|
445646fe02 | ||
|
|
3dd3c8fb40 | ||
|
|
fb390b3618 | ||
|
|
ca5fb75f3a | ||
|
|
e881ce5f0f | ||
|
|
8002e47551 | ||
|
|
5b66b5705d | ||
|
|
d1c5521d2a | ||
|
|
74151edfb3 | ||
|
|
00f6747d7d | ||
|
|
0101955ad3 | ||
|
|
f6f9a95f06 | ||
|
|
3d82b89db0 | ||
|
|
8c7b549a45 | ||
|
|
3ad4dc1cfe | ||
|
|
7524314f74 | ||
|
|
94545e8958 | ||
|
|
2c74b2d2e2 | ||
|
|
108c190254 | ||
|
|
466209307e | ||
|
|
acccba59dc | ||
|
|
9325e2f9d1 | ||
|
|
36ebff2667 | ||
|
|
6d0d08b5fb | ||
|
|
e695a1e291 | ||
|
|
133488221b | ||
|
|
b186b672ea | ||
|
|
2badef3daf | ||
|
|
f8700296fb | ||
|
|
0f79fb56c7 | ||
|
|
d8412c95d4 | ||
|
|
469c239eed | ||
|
|
7fad542553 |
32
.github/workflows/main.yml
vendored
32
.github/workflows/main.yml
vendored
@@ -141,7 +141,7 @@ jobs:
|
||||
cd src/debug
|
||||
mkdir output
|
||||
mkdir appx
|
||||
cp qdomyos-zwift.exe output/
|
||||
cp qdomyos-zwift.* output/
|
||||
cd output
|
||||
windeployqt --qmldir ../../ qdomyos-zwift.exe
|
||||
cp "C:/mingw64/bin/libwinpthread-1.dll" .
|
||||
@@ -168,7 +168,7 @@ jobs:
|
||||
cd src/debug
|
||||
mkdir output
|
||||
mkdir appx
|
||||
cp qdomyos-zwift.exe output/
|
||||
cp qdomyos-zwift.* output/
|
||||
cd output
|
||||
windeployqt --qmldir ../../ qdomyos-zwift.exe
|
||||
cp "C:/mingw64/bin/libwinpthread-1.dll" .
|
||||
@@ -338,7 +338,7 @@ jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
linux-x86-build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
@@ -862,7 +862,7 @@ jobs:
|
||||
cd src/debug
|
||||
mkdir output
|
||||
mkdir appx
|
||||
cp qdomyos-zwift.exe output/
|
||||
cp qdomyos-zwift.* output/
|
||||
cd output
|
||||
windeployqt --qmldir ../../ qdomyos-zwift.exe
|
||||
cp ../../../icons/iOS/iTunesArtwork@2x.png .
|
||||
@@ -890,7 +890,7 @@ jobs:
|
||||
cd src/debug
|
||||
mkdir output
|
||||
mkdir appx
|
||||
cp qdomyos-zwift.exe output/
|
||||
cp qdomyos-zwift.* output/
|
||||
cd output
|
||||
windeployqt --qmldir ../../ qdomyos-zwift.exe
|
||||
cp "C:/mingw64/bin/libwinpthread-1.dll" .
|
||||
@@ -1052,7 +1052,7 @@ jobs:
|
||||
cd src/debug
|
||||
mkdir output
|
||||
mkdir appx
|
||||
cp qdomyos-zwift.exe output/
|
||||
cp qdomyos-zwift.* output/
|
||||
cd output
|
||||
windeployqt --qmldir ../../ qdomyos-zwift.exe
|
||||
cp ../../../icons/iOS/iTunesArtwork@2x.png .
|
||||
@@ -1126,14 +1126,17 @@ jobs:
|
||||
make -j$(nproc)
|
||||
"
|
||||
|
||||
- name: Rename binary
|
||||
run: mv src/qdomyos-zwift src/qdomyos-zwift-32bit
|
||||
|
||||
- name: Archive Raspberry Pi binary
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: raspberry-pi-binary
|
||||
path: src/qdomyos-zwift
|
||||
path: src/qdomyos-zwift-32bit
|
||||
|
||||
raspberry-pi-build-and-image-64bit:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -1177,11 +1180,14 @@ jobs:
|
||||
make -j$(nproc)
|
||||
"
|
||||
|
||||
- name: Rename binary
|
||||
run: mv src/qdomyos-zwift src/qdomyos-zwift-64bit
|
||||
|
||||
- name: Archive Raspberry Pi binary
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: raspberry-pi-64bit-binary
|
||||
path: src/qdomyos-zwift
|
||||
path: src/qdomyos-zwift-64bit
|
||||
|
||||
- name: Download and expand Raspberry Pi OS image
|
||||
run: |
|
||||
@@ -1217,7 +1223,7 @@ jobs:
|
||||
|
||||
- name: Copy binary to Raspberry Pi image
|
||||
run: |
|
||||
sudo cp src/qdomyos-zwift /mnt/raspbian/home/pi/
|
||||
sudo cp src/qdomyos-zwift-64bit /mnt/raspbian/home/pi/qdomyos-zwift
|
||||
sudo chown 1000:1000 /mnt/raspbian/home/pi/qdomyos-zwift
|
||||
|
||||
- name: Setup auto-start for qdomyos-zwift
|
||||
@@ -1263,7 +1269,7 @@ jobs:
|
||||
permissions: write-all
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.event_name == 'schedule'
|
||||
needs: [linux-x86-build, window-msvc2019-build, ios-build, window-build, android-build] # Specify the job dependencies
|
||||
needs: [linux-x86-build, window-msvc2019-build, ios-build, window-build, android-build, raspberry-pi-build-and-image-64bit, raspberry-pi-build] # Specify the job dependencies
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -1292,6 +1298,6 @@ jobs:
|
||||
windows-binary-no-python/*
|
||||
windows-binary/*
|
||||
fdroid-android-trial/*
|
||||
raspberry-pi-binary/*
|
||||
raspberry-pi-64bit-binary/*
|
||||
raspberry-pi-binary/qdomyos-zwift-32bit
|
||||
raspberry-pi-64bit-binary/qdomyos-zwift-64bit
|
||||
2024-10-22-raspios-bookworm-arm64-lite.img.xz
|
||||
|
||||
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "(Windows) Launch",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "C://Users//violarob//Downloads//windows-msvc2019-binary-no-python (1)//output/qdomyos-zwift.exe",
|
||||
"symbolSearchPath": "C://Users//violarob//Downloads//windows-msvc2019-binary-no-python (1)//output/qdomyos-zwift.pdb",
|
||||
"sourceFileMap": {
|
||||
"d:/a/qdomyos-zwift/qdomyos-zwift": "c:/work/qdomyos-zwift/"
|
||||
"compiled_source_path": "C://Users//violarob//Downloads//windows-msvc2019-binary-no-python (1)//output/"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -96,6 +96,9 @@ Zwift bridge for Treadmills and Bike!
|
||||
|:---|:---:|:---:|:---:|:---:|---:|
|
||||
|Resistance shifting with bluetooth remote|X||X|||
|
||||
|TTS support|X|X|X|X||
|
||||
|Zwift Play & Click support|X|||||
|
||||
|MQTT integration|X|X|X|X||
|
||||
|OpenSoundControl integration|X|X|X|X||
|
||||
|
||||
|
||||
### Installation
|
||||
|
||||
@@ -290,6 +290,8 @@
|
||||
87586A4325B8341B00A243C4 /* moc_proformbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87586A4225B8341B00A243C4 /* moc_proformbike.cpp */; };
|
||||
875CA9462D0C740000667EE6 /* moc_kineticinroadbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875CA9452D0C740000667EE6 /* moc_kineticinroadbike.cpp */; };
|
||||
875CA9492D0C742500667EE6 /* kineticinroadbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875CA9482D0C742500667EE6 /* kineticinroadbike.cpp */; };
|
||||
875CA94C2D130F8100667EE6 /* moc_osc.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875CA94B2D130F8100667EE6 /* moc_osc.cpp */; };
|
||||
875CA9552D130FBC00667EE6 /* osc.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875CA9502D130FBC00667EE6 /* osc.cpp */; };
|
||||
875F69B926342E8D0009FD78 /* spirittreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875F69B826342E8D0009FD78 /* spirittreadmill.cpp */; };
|
||||
875F69BB26342E9A0009FD78 /* moc_spirittreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875F69BA26342E9A0009FD78 /* moc_spirittreadmill.cpp */; };
|
||||
8762D50F2601F7EA00F6F049 /* M3iNS.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8762D50B2601F7EA00F6F049 /* M3iNS.mm */; };
|
||||
@@ -365,6 +367,7 @@
|
||||
8772A0E825E43AE70080718C /* moc_trxappgateusbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772A0E725E43AE70080718C /* moc_trxappgateusbbike.cpp */; };
|
||||
8772B7F42CB55E80004AB8E9 /* moc_deerruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772B7F32CB55E80004AB8E9 /* moc_deerruntreadmill.cpp */; };
|
||||
8772B7F72CB55E98004AB8E9 /* deerruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772B7F62CB55E98004AB8E9 /* deerruntreadmill.cpp */; };
|
||||
877350F72D1C08E60070CBD8 /* SmartControl.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 877350F62D1C08E50070CBD8 /* SmartControl.cpp */; };
|
||||
8775008329E876F8008E48B7 /* iconceptelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8775008129E876F7008E48B7 /* iconceptelliptical.cpp */; };
|
||||
8775008529E87713008E48B7 /* moc_iconceptelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8775008429E87712008E48B7 /* moc_iconceptelliptical.cpp */; };
|
||||
877758B32C98627300BB1697 /* moc_sportstechelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 877758B22C98627300BB1697 /* moc_sportstechelliptical.cpp */; };
|
||||
@@ -436,6 +439,8 @@
|
||||
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 */; };
|
||||
87A3BC27265642A300D302E3 /* moc_echelonrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC25265642A200D302E3 /* moc_echelonrower.cpp */; };
|
||||
87A3DD9B2D3413790060BAEB /* moc_lifespantreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3DD9A2D3413790060BAEB /* moc_lifespantreadmill.cpp */; };
|
||||
87A3DD9C2D3413790060BAEB /* lifespantreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3DD992D3413790060BAEB /* lifespantreadmill.cpp */; };
|
||||
87A4B76125AF27CB0027EF3C /* metric.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A4B75F25AF27CB0027EF3C /* metric.cpp */; };
|
||||
87A6825A2CE3AB3100586A2A /* moc_sramAXSController.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A682592CE3AB3100586A2A /* moc_sramAXSController.cpp */; };
|
||||
87A6825D2CE3AB4000586A2A /* sramAXSController.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A6825C2CE3AB4000586A2A /* sramAXSController.cpp */; };
|
||||
@@ -541,6 +546,8 @@
|
||||
87E5D2C825E69F4700BDBE6C /* moc_horizontreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87E5D2C725E69F4700BDBE6C /* moc_horizontreadmill.cpp */; };
|
||||
87E6A85825B5C88E00371D28 /* moc_flywheelbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87E6A85725B5C88E00371D28 /* moc_flywheelbike.cpp */; };
|
||||
87E6A85B25B5C8B900371D28 /* flywheelbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87E6A85925B5C8B900371D28 /* flywheelbike.cpp */; };
|
||||
87EAC3D32D1D8D1D004FE975 /* moc_pitpatbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EAC3D22D1D8D1D004FE975 /* moc_pitpatbike.cpp */; };
|
||||
87EAC3D62D1D8D34004FE975 /* pitpatbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EAC3D52D1D8D34004FE975 /* pitpatbike.cpp */; };
|
||||
87EB917627EE5FB3002535E1 /* nautilusbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EB917427EE5FB3002535E1 /* nautilusbike.cpp */; };
|
||||
87EB918227EE5FE7002535E1 /* moc_inapptransaction.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EB917727EE5FE7002535E1 /* moc_inapptransaction.cpp */; };
|
||||
87EB918327EE5FE7002535E1 /* moc_inappstore.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EB917827EE5FE7002535E1 /* moc_inappstore.cpp */; };
|
||||
@@ -565,6 +572,10 @@
|
||||
87FA11AB27C5ECD1008AC5D1 /* ultrasportbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FA11AA27C5ECD1008AC5D1 /* ultrasportbike.cpp */; };
|
||||
87FA11AD27C5ECE4008AC5D1 /* moc_ultrasportbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FA11AC27C5ECE4008AC5D1 /* moc_ultrasportbike.cpp */; };
|
||||
87FA94672B6B89FD00B6AB9A /* SwiftUI.framework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 87FA94662B6B89FD00B6AB9A /* SwiftUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||
87FC40BC2D2E74F9008BA736 /* cycleopsphantombike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FC40BA2D2E74F9008BA736 /* cycleopsphantombike.cpp */; };
|
||||
87FC40BD2D2E74F9008BA736 /* moc_cycleopsphantombike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FC40BB2D2E74F9008BA736 /* moc_cycleopsphantombike.cpp */; };
|
||||
87FE06812D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FE06802D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp */; };
|
||||
87FE06842D170D5600CDAAF6 /* trxappgateusbrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FE06832D170D5600CDAAF6 /* trxappgateusbrower.cpp */; };
|
||||
87FE5BAF2692F3130056EFC8 /* tacxneo2.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FE5BAD2692F3130056EFC8 /* tacxneo2.cpp */; };
|
||||
87FE5BB12692F31E0056EFC8 /* moc_tacxneo2.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FE5BB02692F31E0056EFC8 /* moc_tacxneo2.cpp */; };
|
||||
87FFA13727BBE3FF00924E4E /* solebike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FFA13527BBE3FE00924E4E /* solebike.cpp */; };
|
||||
@@ -1164,6 +1175,15 @@
|
||||
875CA9452D0C740000667EE6 /* moc_kineticinroadbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_kineticinroadbike.cpp; sourceTree = "<group>"; };
|
||||
875CA9472D0C742500667EE6 /* kineticinroadbike.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = kineticinroadbike.h; path = ../src/devices/kineticinroadbike/kineticinroadbike.h; sourceTree = SOURCE_ROOT; };
|
||||
875CA9482D0C742500667EE6 /* kineticinroadbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = kineticinroadbike.cpp; path = ../src/devices/kineticinroadbike/kineticinroadbike.cpp; sourceTree = SOURCE_ROOT; };
|
||||
875CA94B2D130F8100667EE6 /* moc_osc.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_osc.cpp; sourceTree = "<group>"; };
|
||||
875CA94D2D130FBC00667EE6 /* client.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = client.hpp; path = ../src/oscpp/client.hpp; sourceTree = SOURCE_ROOT; };
|
||||
875CA94E2D130FBC00667EE6 /* error.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = error.hpp; path = ../src/oscpp/error.hpp; sourceTree = SOURCE_ROOT; };
|
||||
875CA94F2D130FBC00667EE6 /* osc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = osc.h; path = ../src/osc.h; sourceTree = SOURCE_ROOT; };
|
||||
875CA9502D130FBC00667EE6 /* osc.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = osc.cpp; path = ../src/osc.cpp; sourceTree = SOURCE_ROOT; };
|
||||
875CA9512D130FBC00667EE6 /* print.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = print.hpp; path = ../src/oscpp/print.hpp; sourceTree = SOURCE_ROOT; };
|
||||
875CA9522D130FBC00667EE6 /* server.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = server.hpp; path = ../src/oscpp/server.hpp; sourceTree = SOURCE_ROOT; };
|
||||
875CA9532D130FBC00667EE6 /* types.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = types.hpp; path = ../src/oscpp/types.hpp; sourceTree = SOURCE_ROOT; };
|
||||
875CA9542D130FBC00667EE6 /* util.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = util.hpp; path = ../src/oscpp/util.hpp; sourceTree = SOURCE_ROOT; };
|
||||
875F69B726342E8D0009FD78 /* spirittreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = spirittreadmill.h; path = ../src/devices/spirittreadmill/spirittreadmill.h; sourceTree = "<group>"; };
|
||||
875F69B826342E8D0009FD78 /* spirittreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = spirittreadmill.cpp; path = ../src/devices/spirittreadmill/spirittreadmill.cpp; sourceTree = "<group>"; };
|
||||
875F69BA26342E9A0009FD78 /* moc_spirittreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_spirittreadmill.cpp; sourceTree = "<group>"; };
|
||||
@@ -1286,6 +1306,8 @@
|
||||
8772B7F32CB55E80004AB8E9 /* moc_deerruntreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_deerruntreadmill.cpp; sourceTree = "<group>"; };
|
||||
8772B7F62CB55E98004AB8E9 /* deerruntreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = deerruntreadmill.cpp; sourceTree = "<group>"; };
|
||||
8772B7F92CB5603A004AB8E9 /* deerruntreadmill.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = deerruntreadmill.h; path = ../src/devices/deeruntreadmill/deerruntreadmill.h; sourceTree = SOURCE_ROOT; };
|
||||
877350F52D1C08E50070CBD8 /* SmartControl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SmartControl.h; path = ../src/devices/kineticinroadbike/SmartControl.h; sourceTree = SOURCE_ROOT; };
|
||||
877350F62D1C08E50070CBD8 /* SmartControl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SmartControl.cpp; path = ../src/devices/kineticinroadbike/SmartControl.cpp; sourceTree = SOURCE_ROOT; };
|
||||
8775008129E876F7008E48B7 /* iconceptelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = iconceptelliptical.cpp; path = ../src/devices/iconceptelliptical/iconceptelliptical.cpp; sourceTree = "<group>"; };
|
||||
8775008229E876F7008E48B7 /* iconceptelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = iconceptelliptical.h; path = ../src/devices/iconceptelliptical/iconceptelliptical.h; sourceTree = "<group>"; };
|
||||
8775008429E87712008E48B7 /* moc_iconceptelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_iconceptelliptical.cpp; sourceTree = "<group>"; };
|
||||
@@ -1391,6 +1413,9 @@
|
||||
87A3BC212656429400D302E3 /* rower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = rower.h; path = ../src/devices/rower.h; sourceTree = "<group>"; };
|
||||
87A3BC24265642A200D302E3 /* moc_rower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_rower.cpp; sourceTree = "<group>"; };
|
||||
87A3BC25265642A200D302E3 /* moc_echelonrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_echelonrower.cpp; sourceTree = "<group>"; };
|
||||
87A3DD982D3413790060BAEB /* lifespantreadmill.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = lifespantreadmill.h; path = ../src/devices/lifespantreadmill/lifespantreadmill.h; sourceTree = SOURCE_ROOT; };
|
||||
87A3DD992D3413790060BAEB /* lifespantreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = lifespantreadmill.cpp; path = ../src/devices/lifespantreadmill/lifespantreadmill.cpp; sourceTree = SOURCE_ROOT; };
|
||||
87A3DD9A2D3413790060BAEB /* moc_lifespantreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_lifespantreadmill.cpp; sourceTree = "<group>"; };
|
||||
87A3EBB925D2CFED0040EB4C /* sportstechbike.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = sportstechbike.h; path = ../src/devices/sportstechbike/sportstechbike.h; sourceTree = "<group>"; };
|
||||
87A3EBBA25D2CFED0040EB4C /* sportstechbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = sportstechbike.cpp; path = ../src/devices/sportstechbike/sportstechbike.cpp; sourceTree = "<group>"; };
|
||||
87A4B75F25AF27CB0027EF3C /* metric.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = metric.cpp; path = ../src/metric.cpp; sourceTree = "<group>"; };
|
||||
@@ -1548,6 +1573,9 @@
|
||||
87E6A85725B5C88E00371D28 /* moc_flywheelbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_flywheelbike.cpp; sourceTree = "<group>"; };
|
||||
87E6A85925B5C8B900371D28 /* flywheelbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = flywheelbike.cpp; path = ../src/devices/flywheelbike/flywheelbike.cpp; sourceTree = "<group>"; };
|
||||
87E6A85A25B5C8B900371D28 /* flywheelbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = flywheelbike.h; path = ../src/devices/flywheelbike/flywheelbike.h; sourceTree = "<group>"; };
|
||||
87EAC3D22D1D8D1D004FE975 /* moc_pitpatbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_pitpatbike.cpp; sourceTree = "<group>"; };
|
||||
87EAC3D42D1D8D34004FE975 /* pitpatbike.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = pitpatbike.h; path = ../src/devices/pitpatbike/pitpatbike.h; sourceTree = SOURCE_ROOT; };
|
||||
87EAC3D52D1D8D34004FE975 /* pitpatbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = pitpatbike.cpp; path = ../src/devices/pitpatbike/pitpatbike.cpp; sourceTree = SOURCE_ROOT; };
|
||||
87EB917427EE5FB3002535E1 /* nautilusbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = nautilusbike.cpp; path = ../src/devices/nautilusbike/nautilusbike.cpp; sourceTree = "<group>"; };
|
||||
87EB917527EE5FB3002535E1 /* nautilusbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nautilusbike.h; path = ../src/devices/nautilusbike/nautilusbike.h; sourceTree = "<group>"; };
|
||||
87EB917727EE5FE7002535E1 /* moc_inapptransaction.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_inapptransaction.cpp; sourceTree = "<group>"; };
|
||||
@@ -1583,7 +1611,13 @@
|
||||
87FA11AC27C5ECE4008AC5D1 /* moc_ultrasportbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_ultrasportbike.cpp; sourceTree = "<group>"; };
|
||||
87FA94662B6B89FD00B6AB9A /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
|
||||
87FA94682B6B8A5A00B6AB9A /* libSystem.B.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libSystem.B.tbd; path = usr/lib/libSystem.B.tbd; sourceTree = SDKROOT; };
|
||||
87FC40B92D2E74F9008BA736 /* cycleopsphantombike.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = cycleopsphantombike.h; path = ../src/devices/cycleopsphantombike/cycleopsphantombike.h; sourceTree = SOURCE_ROOT; };
|
||||
87FC40BA2D2E74F9008BA736 /* cycleopsphantombike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = cycleopsphantombike.cpp; path = ../src/devices/cycleopsphantombike/cycleopsphantombike.cpp; sourceTree = SOURCE_ROOT; };
|
||||
87FC40BB2D2E74F9008BA736 /* moc_cycleopsphantombike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_cycleopsphantombike.cpp; sourceTree = "<group>"; };
|
||||
87FD05DDC378E9BAD82A818F /* fit_ohr_settings_mesg_listener.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = fit_ohr_settings_mesg_listener.hpp; path = "/Users/cagnulein/qdomyos-zwift/src/fit-sdk/fit_ohr_settings_mesg_listener.hpp"; sourceTree = "<absolute>"; };
|
||||
87FE06802D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_trxappgateusbrower.cpp; sourceTree = "<group>"; };
|
||||
87FE06822D170D5600CDAAF6 /* trxappgateusbrower.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = trxappgateusbrower.h; path = ../src/devices/trxappgateusbrower/trxappgateusbrower.h; sourceTree = SOURCE_ROOT; };
|
||||
87FE06832D170D5600CDAAF6 /* trxappgateusbrower.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = trxappgateusbrower.cpp; path = ../src/devices/trxappgateusbrower/trxappgateusbrower.cpp; sourceTree = SOURCE_ROOT; };
|
||||
87FE5BAD2692F3130056EFC8 /* tacxneo2.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = tacxneo2.cpp; path = ../src/devices/tacxneo2/tacxneo2.cpp; sourceTree = "<group>"; };
|
||||
87FE5BAE2692F3130056EFC8 /* tacxneo2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = tacxneo2.h; path = ../src/devices/tacxneo2/tacxneo2.h; sourceTree = "<group>"; };
|
||||
87FE5BB02692F31E0056EFC8 /* moc_tacxneo2.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_tacxneo2.cpp; sourceTree = "<group>"; };
|
||||
@@ -2134,6 +2168,26 @@
|
||||
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
87A3DD982D3413790060BAEB /* lifespantreadmill.h */,
|
||||
87A3DD992D3413790060BAEB /* lifespantreadmill.cpp */,
|
||||
87A3DD9A2D3413790060BAEB /* moc_lifespantreadmill.cpp */,
|
||||
87FC40B92D2E74F9008BA736 /* cycleopsphantombike.h */,
|
||||
87FC40BA2D2E74F9008BA736 /* cycleopsphantombike.cpp */,
|
||||
87FC40BB2D2E74F9008BA736 /* moc_cycleopsphantombike.cpp */,
|
||||
87EAC3D42D1D8D34004FE975 /* pitpatbike.h */,
|
||||
87EAC3D52D1D8D34004FE975 /* pitpatbike.cpp */,
|
||||
87EAC3D22D1D8D1D004FE975 /* moc_pitpatbike.cpp */,
|
||||
877350F52D1C08E50070CBD8 /* SmartControl.h */,
|
||||
877350F62D1C08E50070CBD8 /* SmartControl.cpp */,
|
||||
875CA94D2D130FBC00667EE6 /* client.hpp */,
|
||||
875CA94E2D130FBC00667EE6 /* error.hpp */,
|
||||
875CA94F2D130FBC00667EE6 /* osc.h */,
|
||||
875CA9502D130FBC00667EE6 /* osc.cpp */,
|
||||
875CA9512D130FBC00667EE6 /* print.hpp */,
|
||||
875CA9522D130FBC00667EE6 /* server.hpp */,
|
||||
875CA9532D130FBC00667EE6 /* types.hpp */,
|
||||
875CA9542D130FBC00667EE6 /* util.hpp */,
|
||||
875CA94B2D130F8100667EE6 /* moc_osc.cpp */,
|
||||
875CA9472D0C742500667EE6 /* kineticinroadbike.h */,
|
||||
875CA9482D0C742500667EE6 /* kineticinroadbike.cpp */,
|
||||
875CA9452D0C740000667EE6 /* moc_kineticinroadbike.cpp */,
|
||||
@@ -2598,6 +2652,9 @@
|
||||
87A2E0202B2B024200E6168F /* swiftDebug.h */,
|
||||
8730A3912B404159007E336D /* zwift_protobuf_layer.swift */,
|
||||
87B871922CE1E94D009B06CA /* zwifthubbike.swift */,
|
||||
87FE06822D170D5600CDAAF6 /* trxappgateusbrower.h */,
|
||||
87FE06832D170D5600CDAAF6 /* trxappgateusbrower.cpp */,
|
||||
87FE06802D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp */,
|
||||
);
|
||||
name = Sources;
|
||||
sourceTree = "<group>";
|
||||
@@ -3541,7 +3598,9 @@
|
||||
87E6A85B25B5C8B900371D28 /* flywheelbike.cpp in Compile Sources */,
|
||||
87182A09276BBAF600141463 /* virtualrower.cpp in Compile Sources */,
|
||||
87C424262BC1294000503687 /* moc_treadmillErgTable.cpp in Compile Sources */,
|
||||
877350F72D1C08E60070CBD8 /* SmartControl.cpp in Compile Sources */,
|
||||
873824ED27E647A9004F1B46 /* resolver.cpp in Compile Sources */,
|
||||
875CA9552D130FBC00667EE6 /* osc.cpp in Compile Sources */,
|
||||
878531652711A3E1004B153D /* activiotreadmill.cpp in Compile Sources */,
|
||||
87DAE16926E9FF5000B0527E /* moc_shuaa5treadmill.cpp in Compile Sources */,
|
||||
48BA9CE9D6F256A15E8FB25D /* fit_mesg.cpp in Compile Sources */,
|
||||
@@ -3562,6 +3621,7 @@
|
||||
872089172CE65451008C2C17 /* qmqtttopicname.cpp in Compile Sources */,
|
||||
872089182CE65451008C2C17 /* qmqttpublishproperties.cpp in Compile Sources */,
|
||||
872089192CE65451008C2C17 /* qmqttauthenticationproperties.cpp in Compile Sources */,
|
||||
87FE06842D170D5600CDAAF6 /* trxappgateusbrower.cpp in Compile Sources */,
|
||||
87BCE6BD29F28F72001F70EB /* ypooelliptical.cpp in Compile Sources */,
|
||||
87C7074327E4CF5900E79C46 /* keepbike.cpp in Compile Sources */,
|
||||
87B871932CE1E94D009B06CA /* zwifthubbike.swift in Compile Sources */,
|
||||
@@ -3657,6 +3717,7 @@
|
||||
873CD20727EF8D8A000131BC /* inappproduct.cpp in Compile Sources */,
|
||||
872088EB2CE6543C008C2C17 /* moc_mqttpublisher.cpp in Compile Sources */,
|
||||
872088EC2CE6543C008C2C17 /* moc_qmqttclient.cpp in Compile Sources */,
|
||||
875CA94C2D130F8100667EE6 /* moc_osc.cpp in Compile Sources */,
|
||||
872088ED2CE6543C008C2C17 /* moc_qmqttmessage.cpp in Compile Sources */,
|
||||
872088EE2CE6543C008C2C17 /* moc_qmqttsubscription.cpp in Compile Sources */,
|
||||
872088EF2CE6543C008C2C17 /* moc_qmqttconnection_p.cpp in Compile Sources */,
|
||||
@@ -3684,6 +3745,7 @@
|
||||
873824AE27E64706004F1B46 /* moc_browser.cpp in Compile Sources */,
|
||||
8738249727E646E3004F1B46 /* characteristicnotifier2a53.cpp in Compile Sources */,
|
||||
DF373364C5474D877506CB26 /* FitMesg.mm in Compile Sources */,
|
||||
87FE06812D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp in Compile Sources */,
|
||||
872261F0289EA887006A6F75 /* moc_nordictrackelliptical.cpp in Compile Sources */,
|
||||
873824E327E647A8004F1B46 /* bitmap.cpp in Compile Sources */,
|
||||
87FE5BB12692F31E0056EFC8 /* moc_tacxneo2.cpp in Compile Sources */,
|
||||
@@ -3747,6 +3809,7 @@
|
||||
8762D5102601F7EA00F6F049 /* M3iNSQT.cpp in Compile Sources */,
|
||||
872261EE289EA873006A6F75 /* nordictrackelliptical.cpp in Compile Sources */,
|
||||
8718CBA3263063BD004BF4EE /* templateinfosender.cpp in Compile Sources */,
|
||||
87EAC3D62D1D8D34004FE975 /* pitpatbike.cpp in Compile Sources */,
|
||||
E8B499F921FB0AB55C7A8A8B /* moc_gpx.cpp in Compile Sources */,
|
||||
87E6A85825B5C88E00371D28 /* moc_flywheelbike.cpp in Compile Sources */,
|
||||
8754D24C27F786F0003D7054 /* virtualrower.swift in Compile Sources */,
|
||||
@@ -3778,6 +3841,7 @@
|
||||
8738248127E646C1004F1B46 /* characteristicnotifier2ad2.cpp in Compile Sources */,
|
||||
87A6825A2CE3AB3100586A2A /* moc_sramAXSController.cpp in Compile Sources */,
|
||||
87A0771029B641D600A368BF /* wahookickrheadwind.cpp in Compile Sources */,
|
||||
87EAC3D32D1D8D1D004FE975 /* moc_pitpatbike.cpp in Compile Sources */,
|
||||
8791A8AB25C861BD003B50B2 /* inspirebike.cpp in Compile Sources */,
|
||||
876BFC9D27BE35C5001D7645 /* bowflext216treadmill.cpp in Compile Sources */,
|
||||
871235C126B297720012D0F2 /* moc_kingsmithr1protreadmill.cpp in Compile Sources */,
|
||||
@@ -3816,6 +3880,8 @@
|
||||
87EB918827EE5FE7002535E1 /* moc_inappstoreqmltype.cpp in Compile Sources */,
|
||||
87083D9626678EFA0072410D /* zwiftworkout.cpp in Compile Sources */,
|
||||
87C5F0B826285E5F0067A1B5 /* stagesbike.cpp in Compile Sources */,
|
||||
87FC40BC2D2E74F9008BA736 /* cycleopsphantombike.cpp in Compile Sources */,
|
||||
87FC40BD2D2E74F9008BA736 /* moc_cycleopsphantombike.cpp in Compile Sources */,
|
||||
87C5F0D526285E7E0067A1B5 /* moc_mimehtml.cpp in Compile Sources */,
|
||||
871B9FD4265E6A9A00DB41F4 /* moc_powerzonepack.cpp in Compile Sources */,
|
||||
87A0C4BF262329B500121A76 /* moc_cscbike.cpp in Compile Sources */,
|
||||
@@ -3823,6 +3889,8 @@
|
||||
8790FDE1277B0AC600247550 /* moc_nautilustreadmill.cpp in Compile Sources */,
|
||||
8727A47927849EB200019B5D /* moc_paferstreadmill.cpp in Compile Sources */,
|
||||
8783153B25E8D81E0007817C /* moc_sportstechbike.cpp in Compile Sources */,
|
||||
87A3DD9B2D3413790060BAEB /* moc_lifespantreadmill.cpp in Compile Sources */,
|
||||
87A3DD9C2D3413790060BAEB /* lifespantreadmill.cpp in Compile Sources */,
|
||||
875F69BB26342E9A0009FD78 /* moc_spirittreadmill.cpp in Compile Sources */,
|
||||
8768C8BF2BBC11C80099DBE1 /* transport.c in Compile Sources */,
|
||||
);
|
||||
@@ -4165,7 +4233,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 970;
|
||||
CURRENT_PROJECT_VERSION = 1007;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1";
|
||||
@@ -4359,7 +4427,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 970;
|
||||
CURRENT_PROJECT_VERSION = 1007;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -4589,7 +4657,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 970;
|
||||
CURRENT_PROJECT_VERSION = 1007;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -4685,7 +4753,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 970;
|
||||
CURRENT_PROJECT_VERSION = 1007;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
@@ -4777,7 +4845,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 970;
|
||||
CURRENT_PROJECT_VERSION = 1007;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -4891,7 +4959,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 970;
|
||||
CURRENT_PROJECT_VERSION = 1007;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
|
||||
@@ -177,6 +177,124 @@ If everything is working as expected, **enable your service at boot time** :
|
||||
|
||||
Then reboot to check operations (`sudo reboot`)
|
||||
|
||||
### (optional) Treadmill Auto-Detection and Service Management
|
||||
This section provides a reliable way to manage the QZ service based on the treadmill's power state. Using a `bluetoothctl`-based Bash script, this solution ensures the QZ service starts when the treadmill is detected and stops when it is not.
|
||||
|
||||
- **Bluetooth Discovery**: Monitors treadmill availability via `bluetoothctl`.
|
||||
- **Service Control**: Automatically starts and stops the QZ service.
|
||||
- **Logging**: Tracks treadmill status and actions in a log file.
|
||||
|
||||
**Notes:**
|
||||
- Ensure `bluetoothctl` is installed and working on your system.
|
||||
- Replace `I_TL` in the script with your treadmill's Bluetooth name. You can find your device name via `bluetoothctl scan on`
|
||||
- Adjust the sleep interval (`sleep 30`) in the script as needed for your use case.
|
||||
|
||||
Step 1: Save the following script as `/root/qz-treadmill-monitor.sh`:
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
LOG_FILE="/var/log/qz-treadmill-monitor.log"
|
||||
TARGET_DEVICE="I_TL"
|
||||
SCAN_INTERVAL=30 # Time in seconds between checks
|
||||
SERVICE_NAME="qz"
|
||||
|
||||
log() {
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
is_service_running() {
|
||||
systemctl is-active --quiet "$SERVICE_NAME"
|
||||
return $?
|
||||
}
|
||||
|
||||
scan_for_device() {
|
||||
log "Starting Bluetooth scan for $TARGET_DEVICE..."
|
||||
|
||||
# Run bluetoothctl scan in the background and capture output
|
||||
bluetoothctl scan on &>/dev/null &
|
||||
SCAN_PID=$!
|
||||
|
||||
# Allow some time for devices to appear
|
||||
sleep 5
|
||||
|
||||
# Check if the target device appears in the list
|
||||
bluetoothctl devices | grep -q "$TARGET_DEVICE"
|
||||
DEVICE_FOUND=$?
|
||||
|
||||
# Stop scanning
|
||||
kill "$SCAN_PID"
|
||||
bluetoothctl scan off &>/dev/null
|
||||
|
||||
if [ $DEVICE_FOUND -eq 0 ]; then
|
||||
log "Device '$TARGET_DEVICE' found."
|
||||
return 0
|
||||
else
|
||||
log "Device '$TARGET_DEVICE' not found."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
manage_service() {
|
||||
local device_found=$1
|
||||
if $device_found; then
|
||||
if ! is_service_running; then
|
||||
log "Starting QZ service..."
|
||||
systemctl start "$SERVICE_NAME"
|
||||
else
|
||||
log "QZ service is already running."
|
||||
fi
|
||||
else
|
||||
if is_service_running; then
|
||||
log "Stopping QZ service..."
|
||||
systemctl stop "$SERVICE_NAME"
|
||||
else
|
||||
log "QZ service is already stopped."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
while true; do
|
||||
log "Checking for treadmill status..."
|
||||
if scan_for_device; then
|
||||
manage_service true
|
||||
else
|
||||
manage_service false
|
||||
fi
|
||||
log "Waiting for $SCAN_INTERVAL seconds before next check..."
|
||||
sleep "$SCAN_INTERVAL"
|
||||
done
|
||||
```
|
||||
|
||||
Step2: To ensure the script runs continuously, create a systemd service file at `/etc/systemd/system/qz-treadmill-monitor.service`
|
||||
```bash
|
||||
[Unit]
|
||||
Description=QZ Treadmill Monitor Service
|
||||
After=bluetooth.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/root/qz-treadmill-monitor.sh
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
User=root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Step 3: Enable and Start the Service
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable qz-treadmill-monitor
|
||||
sudo systemctl start qz-treadmill-monitor
|
||||
```
|
||||
|
||||
Monitor logs are written to `/var/log/qz-treadmill-monitor.log`. Use the following command to check logs in real-time:
|
||||
```bash
|
||||
sudo tail -f /var/log/qz-treadmill-monitor.log
|
||||
```
|
||||
|
||||
|
||||
|
||||
### (optional) Enable overlay FS
|
||||
|
||||
|
||||
37
qdomyos-zwift.code-workspace
Normal file
37
qdomyos-zwift.code-workspace
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"files.associations": {
|
||||
"list": "cpp",
|
||||
"chrono": "cpp",
|
||||
"complex": "cpp",
|
||||
"functional": "cpp",
|
||||
"optional": "cpp",
|
||||
"system_error": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"xlocnum": "cpp",
|
||||
"xtr1common": "cpp",
|
||||
"qhttpserver": "cpp",
|
||||
"array": "cpp",
|
||||
"deque": "cpp",
|
||||
"map": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"vector": "cpp",
|
||||
"xstring": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"xutility": "cpp",
|
||||
"xlocale": "cpp",
|
||||
"filesystem": "cpp",
|
||||
"bitset": "cpp",
|
||||
"iterator": "cpp",
|
||||
"xhash": "cpp",
|
||||
"xtree": "cpp",
|
||||
"ostream": "cpp",
|
||||
"locale": "cpp"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,59 +10,87 @@ ColumnLayout {
|
||||
property alias textFont: accordionText.font.family
|
||||
property alias textFontSize: accordionText.font.pixelSize
|
||||
property alias indicatRectColor: indicatRect.color
|
||||
default property alias accordionContent: contentPlaceholder.data
|
||||
spacing: 0
|
||||
default property alias accordionContent: contentLoader.sourceComponent
|
||||
|
||||
Layout.fillWidth: true;
|
||||
// Signal emitted when content becomes visible
|
||||
signal contentBecameVisible()
|
||||
|
||||
spacing: 0
|
||||
Layout.fillWidth: true
|
||||
|
||||
Rectangle {
|
||||
id: accordionHeader
|
||||
color: "red"
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true;
|
||||
Layout.fillWidth: true
|
||||
height: 48
|
||||
|
||||
Rectangle{
|
||||
id:indicatRect
|
||||
x: 16; y: 20
|
||||
width: 8; height: 8
|
||||
radius: 8
|
||||
color: "white"
|
||||
Rectangle {
|
||||
id: indicatRect
|
||||
x: 16; y: 20
|
||||
width: 8; height: 8
|
||||
radius: 8
|
||||
color: "white"
|
||||
}
|
||||
|
||||
Text {
|
||||
id: accordionText
|
||||
x:34;y:13
|
||||
x: 34; y: 13
|
||||
color: "#FFFFFF"
|
||||
text: rootElement.title
|
||||
}
|
||||
|
||||
Image {
|
||||
y:13
|
||||
anchors.right: parent.right
|
||||
y: 13
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 20
|
||||
width: 30; height: 30
|
||||
id: indicatImg
|
||||
source: "qrc:/icons/arrow-collapse-vertical.png"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
rootElement.isOpen = !rootElement.isOpen
|
||||
if(rootElement.isOpen)
|
||||
{
|
||||
if(rootElement.isOpen) {
|
||||
indicatImg.source = "qrc:/icons/arrow-expand-vertical.png"
|
||||
}else{
|
||||
} else {
|
||||
indicatImg.source = "qrc:/icons/arrow-collapse-vertical.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This will get filled with the content
|
||||
ColumnLayout {
|
||||
id: contentPlaceholder
|
||||
visible: rootElement.isOpen
|
||||
Layout.fillWidth: true;
|
||||
// Loader with enhanced visibility handling
|
||||
Loader {
|
||||
id: contentLoader
|
||||
active: rootElement.isOpen
|
||||
visible: false // Start invisible
|
||||
Layout.fillWidth: true
|
||||
asynchronous: false
|
||||
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
item.Layout.fillWidth = true
|
||||
visible = true
|
||||
rootElement.contentBecameVisible()
|
||||
}
|
||||
}
|
||||
|
||||
// Handle visibility changes
|
||||
onVisibleChanged: {
|
||||
if (visible && status === Loader.Ready) {
|
||||
rootElement.contentBecameVisible()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle accordion closing
|
||||
onIsOpenChanged: {
|
||||
if (!isOpen) {
|
||||
contentLoader.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20
src/IndicatorOnlySwitch.qml
Normal file
20
src/IndicatorOnlySwitch.qml
Normal file
@@ -0,0 +1,20 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.0
|
||||
import Qt.labs.settings 1.0
|
||||
import QtQuick.Dialogs 1.0
|
||||
|
||||
SwitchDelegate {
|
||||
id: root
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (mouse.x > parent.width - parent.indicator.width) {
|
||||
root.checked = !root.checked
|
||||
root.clicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
AccordionElement {
|
||||
StaticAccordionElement {
|
||||
title: qsTr("Settings folder")
|
||||
indicatRectColor: Material.color(Material.Grey)
|
||||
textColor: Material.color(Material.Grey)
|
||||
|
||||
68
src/StaticAccordionElement.qml
Normal file
68
src/StaticAccordionElement.qml
Normal file
@@ -0,0 +1,68 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
ColumnLayout {
|
||||
id: rootElement
|
||||
property bool isOpen: false
|
||||
property string title: ""
|
||||
property alias color: accordionHeader.color
|
||||
property alias textColor: accordionText.color
|
||||
property alias textFont: accordionText.font.family
|
||||
property alias textFontSize: accordionText.font.pixelSize
|
||||
property alias indicatRectColor: indicatRect.color
|
||||
default property alias accordionContent: contentPlaceholder.data
|
||||
spacing: 0
|
||||
|
||||
Layout.fillWidth: true;
|
||||
|
||||
Rectangle {
|
||||
id: accordionHeader
|
||||
color: "red"
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true;
|
||||
height: 48
|
||||
|
||||
Rectangle{
|
||||
id:indicatRect
|
||||
x: 16; y: 20
|
||||
width: 8; height: 8
|
||||
radius: 8
|
||||
color: "white"
|
||||
}
|
||||
|
||||
Text {
|
||||
id: accordionText
|
||||
x:34;y:13
|
||||
color: "#FFFFFF"
|
||||
text: rootElement.title
|
||||
}
|
||||
Image {
|
||||
y:13
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 20
|
||||
width: 30; height: 30
|
||||
id: indicatImg
|
||||
source: "qrc:/icons/arrow-collapse-vertical.png"
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
rootElement.isOpen = !rootElement.isOpen
|
||||
if(rootElement.isOpen)
|
||||
{
|
||||
indicatImg.source = "qrc:/icons/arrow-expand-vertical.png"
|
||||
}else{
|
||||
indicatImg.source = "qrc:/icons/arrow-collapse-vertical.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This will get filled with the content
|
||||
ColumnLayout {
|
||||
id: contentPlaceholder
|
||||
visible: rootElement.isOpen
|
||||
Layout.fillWidth: true;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.18.9" android:versionCode="953" android:installLocation="auto">
|
||||
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.18.17" android:versionCode="999" android:installLocation="auto">
|
||||
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
|
||||
Remove the comment if you do not require these default permissions. -->
|
||||
<!-- %%INSERT_PERMISSIONS -->
|
||||
|
||||
@@ -50,7 +50,7 @@ dependencies {
|
||||
implementation "androidx.appcompat:appcompat:$appcompat_version"
|
||||
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
||||
implementation 'com.github.mik3y:usb-serial-for-android:v3.4.6'
|
||||
implementation files('libs/usb-serial-for-android-3.8.1.aar')
|
||||
androidTestImplementation "com.android.support:support-annotations:28.0.0"
|
||||
implementation 'com.google.android.gms:play-services-wearable:+'
|
||||
|
||||
|
||||
BIN
src/android/libs/usb-serial-for-android-3.8.1.aar
Normal file
BIN
src/android/libs/usb-serial-for-android-3.8.1.aar
Normal file
Binary file not shown.
1
src/build-qrc-qml.sh
Executable file
1
src/build-qrc-qml.sh
Executable file
@@ -0,0 +1 @@
|
||||
/Users/cagnulein/Qt/5.15.2/ios/bin/rcc qml.qrc -o ../build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qrc_qml.cpp
|
||||
@@ -96,30 +96,51 @@ double bike::gears() {
|
||||
}
|
||||
return m_gears + gears_offset;
|
||||
}
|
||||
|
||||
void bike::setGears(double gears) {
|
||||
QSettings settings;
|
||||
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
|
||||
double gears_offset = settings.value(QZSettings::gears_offset, QZSettings::default_gears_offset).toDouble();
|
||||
gears -= gears_offset;
|
||||
qDebug() << "setGears" << gears;
|
||||
|
||||
// Check for boundaries and emit failure signals
|
||||
if(gears_zwift_ratio && (gears > 24 || gears < 1)) {
|
||||
qDebug() << "new gear value ignored because of gears_zwift_ratio setting!";
|
||||
if(gears > 24) {
|
||||
emit gearFailedUp();
|
||||
} else {
|
||||
emit gearFailedDown();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(gears > maxGears()) {
|
||||
qDebug() << "new gear value ignored because of maxGears" << maxGears();
|
||||
emit gearFailedUp();
|
||||
return;
|
||||
}
|
||||
|
||||
if(gears < minGears()) {
|
||||
qDebug() << "new gear value ignored because of minGears" << minGears();
|
||||
emit gearFailedDown();
|
||||
return;
|
||||
}
|
||||
|
||||
if(m_gears > gears) {
|
||||
emit gearOkDown();
|
||||
} else {
|
||||
emit gearOkUp();
|
||||
}
|
||||
|
||||
m_gears = gears;
|
||||
if(homeform::singleton()) {
|
||||
homeform::singleton()->updateGearsValue();
|
||||
}
|
||||
|
||||
if (settings.value(QZSettings::gears_restore_value, QZSettings::default_gears_restore_value).toBool())
|
||||
settings.setValue(QZSettings::gears_current_value, m_gears);
|
||||
|
||||
if (lastRawRequestedResistanceValue != -1) {
|
||||
changeResistance(lastRawRequestedResistanceValue);
|
||||
}
|
||||
|
||||
@@ -81,6 +81,10 @@ class bike : public bluetoothdevice {
|
||||
void resistanceChanged(resistance_t resistance);
|
||||
void resistanceRead(resistance_t resistance);
|
||||
void steeringAngleChanged(double angle);
|
||||
void gearOkUp(); // Signal when gear up succeeds
|
||||
void gearOkDown(); // Signal when gear down succeeds
|
||||
void gearFailedUp(); // Signal when gear up hits max
|
||||
void gearFailedDown(); // Signal when gear down hits min
|
||||
|
||||
protected:
|
||||
metric RequestedResistance;
|
||||
|
||||
@@ -491,6 +491,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
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();
|
||||
bool iconsole_rower = settings.value(QZSettings::iconsole_rower, QZSettings::default_iconsole_rower).toBool();
|
||||
|
||||
if (!heartRateBeltFound) {
|
||||
|
||||
@@ -954,6 +955,23 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit searchingStop();
|
||||
}
|
||||
this->signalBluetoothDeviceConnected(domyosBike);
|
||||
} else if (b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")) && iconsole_rower &&
|
||||
!trxappgateusbRower && ftms_bike.contains(QZSettings::default_ftms_bike) && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
trxappgateusbRower = new trxappgateusbrower(noWriteResistance, noHeartService,
|
||||
bikeResistanceOffset, bikeResistanceGain);
|
||||
emit deviceConnected(b);
|
||||
connect(trxappgateusbRower, &bluetoothdevice::connectedAndDiscovered, this,
|
||||
&bluetooth::connectedAndDiscovered);
|
||||
// connect(domyosElliptical, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
connect(trxappgateusbRower, &trxappgateusbrower::debug, this, &bluetooth::debug);
|
||||
trxappgateusbRower->deviceDiscovered(b);
|
||||
connect(this, &bluetooth::searchingStop, trxappgateusbRower, &trxappgateusbrower::searchingStop);
|
||||
if (this->discoveryAgent && !this->discoveryAgent->isActive()) {
|
||||
emit searchingStop();
|
||||
}
|
||||
this->signalBluetoothDeviceConnected(trxappgateusbRower);
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("FAL-SPORTS")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")) && iconsole_elliptical)) &&
|
||||
!trxappgateusbElliptical && ftms_bike.contains(QZSettings::default_ftms_bike) && filter) {
|
||||
@@ -1288,7 +1306,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.name().toUpper().startsWith(QStringLiteral("F63")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("ST90")) && !deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("TRX7.5")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("S77")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("S77")) && sole_inclination) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("F85")) && sole_inclination)) &&
|
||||
!soleF80 && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
@@ -1366,9 +1384,11 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
(b.name().toUpper().startsWith(QStringLiteral("DOMYOS-TC")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && !settings.value(QZSettings::domyostreadmill_notfmts, QZSettings::default_domyostreadmill_notfmts).toBool()) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("XT685")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("XT285")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("FITNESS")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("WELLFIT TM")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("XTERRA TR")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("T118_")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("TM4500")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("RUNN ")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("YPOO-MINI PRO-")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("BFX_T9_")) ||
|
||||
@@ -1387,11 +1407,14 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.name().toUpper().startsWith(QStringLiteral("MOBVOI TM")) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("LB600")) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("TUNTURI T60-")) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("TUNTURI T90-")) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("KETTLER TREADMILL")) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("ASSAULTRUNNER")) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("CITYSPORTS-LINKER")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("TP1")) && b.name().length() == 3) || // 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("S77")) && !sole_inclination) || // FMTS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("F89")) && !sole_inclination) || // FMTS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("F80")) && !sole_inclination) || // FMTS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("ANPLUS-"))) // FTMS
|
||||
@@ -1488,6 +1511,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
}
|
||||
#endif
|
||||
} else if ((b.name().toUpper().startsWith("TACX ") ||
|
||||
b.name().toUpper().startsWith("NEO 3M ") ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("THINK X")) ||
|
||||
b.address() == QBluetoothAddress("C1:14:D9:9C:FB:01") || // specific TACX NEO 2 #1707
|
||||
(b.name().toUpper().startsWith("TACX SMART BIKE"))) &&
|
||||
@@ -1505,6 +1529,20 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
// connect(tacxneo2Bike, SIGNAL(inclinationChanged(double)), this, SLOT(inclinationChanged(double)));
|
||||
tacxneo2Bike->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(tacxneo2Bike);
|
||||
} else if ((b.name().toUpper().startsWith("INDOORCYCLE")) &&
|
||||
!cycleopsphantomBike && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
cycleopsphantomBike = new cycleopsphantombike(noWriteResistance, noHeartService);
|
||||
// stateFileRead();
|
||||
emit(deviceConnected(b));
|
||||
connect(cycleopsphantomBike, SIGNAL(connectedAndDiscovered()), this, SLOT(connectedAndDiscovered()));
|
||||
// connect(cycleopsphantomBike, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
connect(cycleopsphantomBike, SIGNAL(debug(QString)), this, SLOT(debug(QString)));
|
||||
// connect(cycleopsphantomBike, SIGNAL(speedChanged(double)), this, SLOT(speedChanged(double)));
|
||||
// connect(cycleopsphantomBike, SIGNAL(inclinationChanged(double)), this, SLOT(inclinationChanged(double)));
|
||||
cycleopsphantomBike->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(cycleopsphantomBike);
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral(">CABLE")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("MD")) && b.name().length() == 7) ||
|
||||
// BIKE 1, BIKE 2, BIKE 3...
|
||||
@@ -1588,8 +1626,15 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
(b.name().toUpper().startsWith("TPS-SPBIKE-2.0")) ||
|
||||
(b.name().toUpper().startsWith("NEO BIKE SMART")) ||
|
||||
(b.name().toUpper().startsWith("ZDRIVE")) ||
|
||||
(b.name().toUpper().startsWith("TUNTURI E60-")) ||
|
||||
(b.name().toUpper().startsWith("JFBK5.0")) ||
|
||||
(b.name().toUpper().startsWith("JFBK7.0")) ||
|
||||
(b.name().toUpper().startsWith("SPEEDRACEX")) ||
|
||||
(b.name().toUpper().startsWith("POOBOO")) ||
|
||||
(b.name().toUpper().startsWith("ZYCLE ZPRO")) ||
|
||||
(b.name().toUpper().startsWith("SM720I")) ||
|
||||
(b.name().toUpper().startsWith("T300P_")) ||
|
||||
(b.name().toUpper().startsWith("T200_")) ||
|
||||
(b.name().toUpper().startsWith("VFSPINBIKE")) ||
|
||||
(b.name().toUpper().startsWith("GLT") && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
|
||||
(b.name().toUpper().startsWith("SPORT01-") && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // Labgrey Magnetic Exercise Bike https://www.amazon.co.uk/dp/B0CXMF1NPY?_encoding=UTF8&psc=1&ref=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&ref_=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&social_share=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&skipTwisterOG=1
|
||||
@@ -1810,6 +1855,20 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
connect(ziproTreadmill, &ziprotreadmill::inclinationChanged, this, &bluetooth::inclinationChanged);
|
||||
ziproTreadmill->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(ziproTreadmill);
|
||||
} else if ((b.name().toUpper().startsWith(QLatin1String("LIFESPAN-TM"))) && !lifespanTreadmill && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
lifespanTreadmill = new lifespantreadmill(this->pollDeviceTime, noConsole, noHeartService);
|
||||
// stateFileRead();
|
||||
emit deviceConnected(b);
|
||||
connect(lifespanTreadmill, &bluetoothdevice::connectedAndDiscovered, this,
|
||||
&bluetooth::connectedAndDiscovered);
|
||||
// connect(ziproTreadmill, SIGNAL(disconnected()), this, SLOT(restart())); connect(echelonStride,
|
||||
connect(lifespanTreadmill, &lifespantreadmill::debug, this, &bluetooth::debug);
|
||||
connect(lifespanTreadmill, &lifespantreadmill::speedChanged, this, &bluetooth::speedChanged);
|
||||
connect(lifespanTreadmill, &lifespantreadmill::inclinationChanged, this, &bluetooth::inclinationChanged);
|
||||
lifespanTreadmill->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(lifespanTreadmill);
|
||||
} else if ((b.name().startsWith(QStringLiteral("ECH-ROW")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("ROWSPORT")) ||
|
||||
b.name().startsWith(QStringLiteral("ROW-S"))) &&
|
||||
@@ -1828,7 +1887,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
// connect(echelonRower, SIGNAL(inclinationChanged(double)), this, SLOT(inclinationChanged(double)));
|
||||
echelonRower->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(echelonRower);
|
||||
} else if (b.name().startsWith(QStringLiteral("ECH")) && !echelonRower && !echelonStride &&
|
||||
} else if (b.name().startsWith(QStringLiteral("ECH")) && !echelonRower && !echelonStride && !ftmsBike &&
|
||||
!echelonConnectSport && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
@@ -2042,7 +2101,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
// SLOT(inclinationChanged(double)));
|
||||
eslinkerTreadmill->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(eslinkerTreadmill);
|
||||
} else if (b.name().toUpper().startsWith(QStringLiteral("PITPAT")) && !deerrunTreadmill && filter) {
|
||||
} else if (b.name().toUpper().startsWith(QStringLiteral("PITPAT-T")) && !deerrunTreadmill && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
deerrunTreadmill = new deerruntreadmill(this->pollDeviceTime, noConsole, noHeartService);
|
||||
@@ -2194,7 +2253,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
(b.name().toUpper().startsWith(QStringLiteral("XT485")) && !deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("XT800")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("XT900"))) &&
|
||||
!spiritTreadmill && filter) {
|
||||
!spiritTreadmill && !horizonTreadmill && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
spiritTreadmill = new spirittreadmill();
|
||||
@@ -2226,7 +2285,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
(b.name().toUpper().startsWith(QStringLiteral("DKN RUN"))) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("ADIDAS "))) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("REEBOK")))) &&
|
||||
!trxappgateusb && !trxappgateusbBike && !toorx_bike && !toorx_ftms && !toorx_ftms_treadmill && !iconsole_elliptical &&
|
||||
!trxappgateusb && !trxappgateusbBike && !toorx_bike && !toorx_ftms && !toorx_ftms_treadmill && !iconsole_elliptical && !iconsole_rower &&
|
||||
filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
@@ -2254,7 +2313,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.name().toUpper().contains(QStringLiteral("CR011R")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("DKN MOTION"))) &&
|
||||
(toorx_bike))) &&
|
||||
!trxappgateusb && !toorx_ftms && !toorx_ftms_treadmill && !trxappgateusbBike && filter && !iconsole_elliptical) {
|
||||
!trxappgateusb && !toorx_ftms && !toorx_ftms_treadmill && !trxappgateusbBike && filter && !iconsole_elliptical && !iconsole_rower) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
trxappgateusbBike =
|
||||
@@ -2448,6 +2507,28 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit searchingStop();
|
||||
}
|
||||
this->signalBluetoothDeviceConnected(chronoBike);
|
||||
} else if (b.name().toUpper().startsWith(QStringLiteral("PITPAT-S")) && !pitpatBike && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
pitpatBike = new pitpatbike(noWriteResistance, noHeartService, bikeResistanceOffset,
|
||||
bikeResistanceGain);
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
stateFileRead();
|
||||
#endif
|
||||
emit deviceConnected(b);
|
||||
connect(pitpatBike, &bluetoothdevice::connectedAndDiscovered, this, &bluetooth::connectedAndDiscovered);
|
||||
//connect(pitpatBike, &pitpatbike::debug, this, &bluetooth::debug);
|
||||
// NOTE: Commented due to #358
|
||||
// connect(chronoBike, SIGNAL(speedChanged(double)), this, SLOT(speedChanged(double)));
|
||||
// NOTE: Commented due to #358
|
||||
// connect(chronoBike, SIGNAL(inclinationChanged(double)), this, SLOT(inclinationChanged(double)));
|
||||
pitpatBike->deviceDiscovered(b);
|
||||
// NOTE: Commented due to #358
|
||||
// connect(this, SIGNAL(searchingStop()), chronoBike, SLOT(searchingStop()));
|
||||
if (this->discoveryAgent && !this->discoveryAgent->isActive()) {
|
||||
emit searchingStop();
|
||||
}
|
||||
this->signalBluetoothDeviceConnected(pitpatBike);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2779,6 +2860,7 @@ void bluetooth::connectedAndDiscovered() {
|
||||
}
|
||||
|
||||
if(settings.value(QZSettings::zwift_play, QZSettings::default_zwift_play).toBool()) {
|
||||
bool zwiftplay_swap = settings.value(QZSettings::zwiftplay_swap, QZSettings::default_zwiftplay_swap).toBool();
|
||||
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
|
||||
if ((((b.name().toUpper().startsWith("ZWIFT PLAY"))) || b.name().toUpper().startsWith("ZWIFT RIDE") || b.name().toUpper().startsWith("ZWIFT SF2")) && zwiftPlayDevice.size() < 2 && this->device() &&
|
||||
this->device()->deviceType() == bluetoothdevice::BIKE) {
|
||||
@@ -2786,7 +2868,7 @@ void bluetooth::connectedAndDiscovered() {
|
||||
if(b.manufacturerData(2378).size() > 0) {
|
||||
qDebug() << "this should be 3 or 2. is it? " << int(b.manufacturerData(2378).at(0));
|
||||
zwiftPlayDevice.append(new zwiftclickremote(this->device(),
|
||||
int(b.manufacturerData(2378).at(0)) == 3 ? AbstractZapDevice::ZWIFT_PLAY_TYPE::LEFT : AbstractZapDevice::ZWIFT_PLAY_TYPE::RIGHT));
|
||||
int(b.manufacturerData(2378).at(0)) == 3 || int(b.manufacturerData(2378).at(0)) == 7 ? AbstractZapDevice::ZWIFT_PLAY_TYPE::LEFT : AbstractZapDevice::ZWIFT_PLAY_TYPE::RIGHT));
|
||||
} else {
|
||||
qDebug() << "manufacturer not found for ZWIFT CLICK";
|
||||
zwiftPlayDevice.append(new zwiftclickremote(this->device(),
|
||||
@@ -2798,6 +2880,14 @@ void bluetooth::connectedAndDiscovered() {
|
||||
connect(zwiftPlayDevice.last(), &zwiftclickremote::debug, this, &bluetooth::debug);
|
||||
connect(zwiftPlayDevice.last()->playDevice, &ZwiftPlayDevice::plus, (bike*)this->device(), &bike::gearUp);
|
||||
connect(zwiftPlayDevice.last()->playDevice, &ZwiftPlayDevice::minus, (bike*)this->device(), &bike::gearDown);
|
||||
if((zwiftPlayDevice.last()->typeZap == AbstractZapDevice::LEFT && !zwiftplay_swap) ||
|
||||
(zwiftPlayDevice.last()->typeZap == AbstractZapDevice::RIGHT && zwiftplay_swap)) {
|
||||
connect((bike*)this->device(), &bike::gearOkUp, this, &bluetooth::gearUp);
|
||||
connect((bike*)this->device(), &bike::gearFailedUp, this, &bluetooth::gearFailedUp);
|
||||
} else {
|
||||
connect((bike*)this->device(), &bike::gearOkDown, this, &bluetooth::gearDown);
|
||||
connect((bike*)this->device(), &bike::gearFailedDown, this, &bluetooth::gearFailedDown);
|
||||
}
|
||||
zwiftPlayDevice.last()->deviceDiscovered(b);
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested("Zwift Play/Ride Connected!");
|
||||
@@ -2873,6 +2963,50 @@ void bluetooth::connectedAndDiscovered() {
|
||||
firstConnected = false;
|
||||
}
|
||||
|
||||
void bluetooth::gearUp() {
|
||||
QSettings settings;
|
||||
bool zwiftplay_swap = settings.value(QZSettings::zwiftplay_swap, QZSettings::default_zwiftplay_swap).toBool();
|
||||
foreach(zwiftclickremote* p, zwiftPlayDevice) {
|
||||
if((p->typeZap == AbstractZapDevice::LEFT && !zwiftplay_swap) || (p->typeZap == AbstractZapDevice::RIGHT && zwiftplay_swap)) {
|
||||
p->vibrate(0x20);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bluetooth::gearDown() {
|
||||
QSettings settings;
|
||||
bool zwiftplay_swap = settings.value(QZSettings::zwiftplay_swap, QZSettings::default_zwiftplay_swap).toBool();
|
||||
foreach(zwiftclickremote* p, zwiftPlayDevice) {
|
||||
if((p->typeZap == AbstractZapDevice::RIGHT && !zwiftplay_swap) || (p->typeZap == AbstractZapDevice::LEFT && zwiftplay_swap)) {
|
||||
p->vibrate(0x20);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bluetooth::gearFailedUp() {
|
||||
QSettings settings;
|
||||
bool zwiftplay_swap = settings.value(QZSettings::zwiftplay_swap, QZSettings::default_zwiftplay_swap).toBool();
|
||||
foreach(zwiftclickremote* p, zwiftPlayDevice) {
|
||||
if((p->typeZap == AbstractZapDevice::LEFT && !zwiftplay_swap) || (p->typeZap == AbstractZapDevice::RIGHT && zwiftplay_swap)) {
|
||||
p->vibrate(0x60);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bluetooth::gearFailedDown() {
|
||||
QSettings settings;
|
||||
bool zwiftplay_swap = settings.value(QZSettings::zwiftplay_swap, QZSettings::default_zwiftplay_swap).toBool();
|
||||
foreach(zwiftclickremote* p, zwiftPlayDevice) {
|
||||
if((p->typeZap == AbstractZapDevice::RIGHT && !zwiftplay_swap) || (p->typeZap == AbstractZapDevice::LEFT && zwiftplay_swap)) {
|
||||
p->vibrate(0x60);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bluetooth::heartRate(uint8_t heart) { Q_UNUSED(heart) }
|
||||
|
||||
void bluetooth::restart() {
|
||||
@@ -3089,6 +3223,11 @@ void bluetooth::restart() {
|
||||
delete tacxneo2Bike;
|
||||
tacxneo2Bike = nullptr;
|
||||
}
|
||||
if (cycleopsphantomBike) {
|
||||
|
||||
delete cycleopsphantomBike;
|
||||
cycleopsphantomBike = nullptr;
|
||||
}
|
||||
if (stagesBike) {
|
||||
|
||||
delete stagesBike;
|
||||
@@ -3135,6 +3274,11 @@ void bluetooth::restart() {
|
||||
delete trxappgateusbElliptical;
|
||||
trxappgateusbElliptical = nullptr;
|
||||
}
|
||||
if (trxappgateusbRower) {
|
||||
|
||||
delete trxappgateusbRower;
|
||||
trxappgateusbRower = nullptr;
|
||||
}
|
||||
if (soleBike) {
|
||||
|
||||
delete soleBike;
|
||||
@@ -3180,6 +3324,10 @@ void bluetooth::restart() {
|
||||
delete ziproTreadmill;
|
||||
ziproTreadmill = nullptr;
|
||||
}
|
||||
if (lifespanTreadmill) {
|
||||
delete lifespanTreadmill;
|
||||
lifespanTreadmill = nullptr;
|
||||
}
|
||||
if (octaneElliptical) {
|
||||
|
||||
delete octaneElliptical;
|
||||
@@ -3342,6 +3490,11 @@ void bluetooth::restart() {
|
||||
delete chronoBike;
|
||||
chronoBike = nullptr;
|
||||
}
|
||||
if (pitpatBike) {
|
||||
|
||||
delete pitpatBike;
|
||||
pitpatBike = nullptr;
|
||||
}
|
||||
if (snodeBike) {
|
||||
|
||||
delete snodeBike;
|
||||
@@ -3517,6 +3670,8 @@ bluetoothdevice *bluetooth::device() {
|
||||
return npeCableBike;
|
||||
} else if (tacxneo2Bike) {
|
||||
return tacxneo2Bike;
|
||||
} else if (cycleopsphantomBike) {
|
||||
return cycleopsphantomBike;
|
||||
} else if (stagesBike) {
|
||||
return stagesBike;
|
||||
} else if (toorx) {
|
||||
@@ -3535,6 +3690,8 @@ bluetoothdevice *bluetooth::device() {
|
||||
return trxappgateusbBike;
|
||||
} else if (trxappgateusbElliptical) {
|
||||
return trxappgateusbElliptical;
|
||||
} else if (trxappgateusbRower) {
|
||||
return trxappgateusbRower;
|
||||
} else if (soleBike) {
|
||||
return soleBike;
|
||||
} else if (keepBike) {
|
||||
@@ -3577,6 +3734,8 @@ bluetoothdevice *bluetooth::device() {
|
||||
return octaneTreadmill;
|
||||
} else if (ziproTreadmill) {
|
||||
return ziproTreadmill;
|
||||
} else if (lifespanTreadmill) {
|
||||
return lifespanTreadmill;
|
||||
} else if (octaneElliptical) {
|
||||
return octaneElliptical;
|
||||
} else if (ftmsRower) {
|
||||
@@ -3633,6 +3792,8 @@ bluetoothdevice *bluetooth::device() {
|
||||
return inspireBike;
|
||||
} else if (chronoBike) {
|
||||
return chronoBike;
|
||||
} else if (pitpatBike) {
|
||||
return pitpatBike;
|
||||
} else if (m3iBike) {
|
||||
return m3iBike;
|
||||
} else if (snodeBike) {
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include "devices/concept2skierg/concept2skierg.h"
|
||||
#include "devices/crossrope/crossrope.h"
|
||||
#include "devices/cscbike/cscbike.h"
|
||||
#include "devices/cycleopsphantombike/cycleopsphantombike.h"
|
||||
#include "devices/deeruntreadmill/deerruntreadmill.h"
|
||||
#include "devices/domyosbike/domyosbike.h"
|
||||
#include "devices/domyoselliptical/domyoselliptical.h"
|
||||
@@ -72,6 +73,7 @@
|
||||
#include "devices/kingsmithr1protreadmill/kingsmithr1protreadmill.h"
|
||||
#include "devices/kingsmithr2treadmill/kingsmithr2treadmill.h"
|
||||
#include "devices/lifefitnesstreadmill/lifefitnesstreadmill.h"
|
||||
#include "devices/lifespantreadmill/lifespantreadmill.h"
|
||||
#include "devices/m3ibike/m3ibike.h"
|
||||
#include "devices/mcfbike/mcfbike.h"
|
||||
#include "devices/mepanelbike/mepanelbike.h"
|
||||
@@ -88,6 +90,7 @@
|
||||
#include "devices/pafersbike/pafersbike.h"
|
||||
#include "devices/paferstreadmill/paferstreadmill.h"
|
||||
#include "devices/pelotonbike/pelotonbike.h"
|
||||
#include "devices/pitpatbike/pitpatbike.h"
|
||||
#include "devices/proformbike/proformbike.h"
|
||||
#include "devices/proformelliptical/proformelliptical.h"
|
||||
#include "devices/proformellipticaltrainer/proformellipticaltrainer.h"
|
||||
@@ -132,6 +135,7 @@
|
||||
#include "devices/truetreadmill/truetreadmill.h"
|
||||
#include "devices/trxappgateusbbike/trxappgateusbbike.h"
|
||||
#include "devices/trxappgateusbelliptical/trxappgateusbelliptical.h"
|
||||
#include "devices/trxappgateusbrower/trxappgateusbrower.h"
|
||||
#include "devices/trxappgateusbtreadmill/trxappgateusbtreadmill.h"
|
||||
#include "devices/ultrasportbike/ultrasportbike.h"
|
||||
#include "devices/wahookickrheadwind/wahookickrheadwind.h"
|
||||
@@ -183,6 +187,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
csafeelliptical *csafeElliptical = nullptr;
|
||||
#endif
|
||||
concept2skierg *concept2Skierg = nullptr;
|
||||
cycleopsphantombike *cycleopsphantomBike = nullptr;
|
||||
deerruntreadmill *deerrunTreadmill = nullptr;
|
||||
domyostreadmill *domyos = nullptr;
|
||||
domyosbike *domyosBike = nullptr;
|
||||
@@ -198,6 +203,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
nautiluselliptical *nautilusElliptical = nullptr;
|
||||
nautilustreadmill *nautilusTreadmill = nullptr;
|
||||
trxappgateusbbike *trxappgateusbBike = nullptr;
|
||||
trxappgateusbrower *trxappgateusbRower = nullptr;
|
||||
trxappgateusbelliptical *trxappgateusbElliptical = nullptr;
|
||||
echelonconnectsport *echelonConnectSport = nullptr;
|
||||
yesoulbike *yesoulBike = nullptr;
|
||||
@@ -251,6 +257,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
smartrowrower *smartrowRower = nullptr;
|
||||
echelonstride *echelonStride = nullptr;
|
||||
lifefitnesstreadmill *lifefitnessTreadmill = nullptr;
|
||||
lifespantreadmill *lifespanTreadmill = nullptr;
|
||||
keepbike *keepBike = nullptr;
|
||||
kingsmithr1protreadmill *kingsmithR1ProTreadmill = nullptr;
|
||||
kingsmithr2treadmill *kingsmithR2Treadmill = nullptr;
|
||||
@@ -258,6 +265,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
pafersbike *pafersBike = nullptr;
|
||||
paferstreadmill *pafersTreadmill = nullptr;
|
||||
tacxneo2 *tacxneo2Bike = nullptr;
|
||||
pitpatbike *pitpatBike = nullptr;
|
||||
renphobike *renphoBike = nullptr;
|
||||
shuaa5treadmill *shuaA5Treadmill = nullptr;
|
||||
heartratebelt *heartRateBelt = nullptr;
|
||||
@@ -357,6 +365,10 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
void speedChanged(double);
|
||||
void inclinationChanged(double, double);
|
||||
void connectedAndDiscovered();
|
||||
void gearDown();
|
||||
void gearUp();
|
||||
void gearFailedDown();
|
||||
void gearFailedUp();
|
||||
|
||||
signals:
|
||||
};
|
||||
|
||||
@@ -90,7 +90,7 @@ void cscbike::update() {
|
||||
/*initDone*/) {
|
||||
bool cadence_sensor_as_bike =
|
||||
settings.value(QZSettings::cadence_sensor_as_bike, QZSettings::default_cadence_sensor_as_bike).toBool();
|
||||
update_metrics(true, watts(), !cadence_sensor_as_bike);
|
||||
update_metrics(false, watts(), !cadence_sensor_as_bike);
|
||||
|
||||
if(lastGoodCadence.secsTo(QDateTime::currentDateTime()) > 5 && !charNotified) {
|
||||
readMethod = true;
|
||||
|
||||
1002
src/devices/cycleopsphantombike/cycleopsphantombike.cpp
Normal file
1002
src/devices/cycleopsphantombike/cycleopsphantombike.cpp
Normal file
File diff suppressed because it is too large
Load Diff
134
src/devices/cycleopsphantombike/cycleopsphantombike.h
Normal file
134
src/devices/cycleopsphantombike/cycleopsphantombike.h
Normal file
@@ -0,0 +1,134 @@
|
||||
#ifndef CYCLEOPSPHANTOMBIKE_H
|
||||
#define CYCLEOPSPHANTOMBIKE_H
|
||||
|
||||
#include <QBluetoothDeviceDiscoveryAgent>
|
||||
#include <QtBluetooth/qlowenergyadvertisingdata.h>
|
||||
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristic.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristicdata.h>
|
||||
#include <QtBluetooth/qlowenergycontroller.h>
|
||||
#include <QtBluetooth/qlowenergydescriptordata.h>
|
||||
#include <QtBluetooth/qlowenergyservice.h>
|
||||
#include <QtBluetooth/qlowenergyservicedata.h>
|
||||
#include <QtCore/qbytearray.h>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#else
|
||||
#include <QtGui/qguiapplication.h>
|
||||
#endif
|
||||
#include <QtCore/qlist.h>
|
||||
#include <QtCore/qmutex.h>
|
||||
#include <QtCore/qscopedpointer.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include "devices/bike.h"
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "ios/lockscreen.h"
|
||||
#endif
|
||||
|
||||
class cycleopsphantombike : public bike {
|
||||
Q_OBJECT
|
||||
public:
|
||||
cycleopsphantombike(bool noWriteResistance, bool noHeartService);
|
||||
void changePower(int32_t power) override;
|
||||
bool connected() override;
|
||||
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
|
||||
|
||||
private:
|
||||
enum class ControlMode : uint8_t {
|
||||
Headless = 0x00,
|
||||
ManualPower = 0x01,
|
||||
ManualSlope = 0x02,
|
||||
PowerRange = 0x03,
|
||||
WarmUp = 0x04,
|
||||
RollDown = 0x05
|
||||
};
|
||||
|
||||
enum class ControlStatus : uint8_t {
|
||||
SpeedOkay = 0x00,
|
||||
SpeedUp = 0x01,
|
||||
SpeedDown = 0x02,
|
||||
RollDownInitializing = 0x03,
|
||||
RollDownInProcess = 0x04,
|
||||
RollDownPassed = 0x05,
|
||||
RollDownFailed = 0x06
|
||||
};
|
||||
void setControlMode(ControlMode mode, int16_t parameter1 = 0, int16_t parameter2 = 0);
|
||||
|
||||
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
|
||||
bool wait_for_response = false);
|
||||
void startDiscover();
|
||||
void forceInclination(double inclination);
|
||||
uint16_t watts() override;
|
||||
double bikeResistanceToPeloton(double resistance);
|
||||
void setUserConfiguration(double wheelDiameter, double gearRatio);
|
||||
|
||||
QTimer *refresh;
|
||||
|
||||
const int max_resistance = 100;
|
||||
|
||||
QList<QLowEnergyService *> gattCommunicationChannelService;
|
||||
QLowEnergyCharacteristic gattWriteCharControlPointId;
|
||||
QLowEnergyCharacteristic gattWriteCharCustomId;
|
||||
QLowEnergyService *gattPowerService = nullptr;
|
||||
QLowEnergyService *gattCustomService;
|
||||
// QLowEnergyCharacteristic gattNotify1Characteristic;
|
||||
|
||||
uint8_t sec1Update = 0;
|
||||
QByteArray lastPacket;
|
||||
QDateTime lastRefreshCharacteristicChanged2A5B = QDateTime::currentDateTime();
|
||||
QDateTime lastRefreshCharacteristicChanged2AD2 = QDateTime::currentDateTime();
|
||||
QDateTime lastRefreshCharacteristicChangedPower = QDateTime::currentDateTime();
|
||||
QDateTime lastGoodCadence = QDateTime::currentDateTime();
|
||||
uint8_t firstStateChanged = 0;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = false;
|
||||
|
||||
uint16_t oldLastCrankEventTime = 0;
|
||||
uint16_t oldCrankRevs = 0;
|
||||
uint16_t CrankRevsRead = 0;
|
||||
|
||||
double lastGearValue = -1;
|
||||
bool resistance_received = false;
|
||||
|
||||
#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 QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
|
||||
void characteristicRead(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
void descriptorRead(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);
|
||||
|
||||
void powerPacketReceived(const QByteArray &b);
|
||||
};
|
||||
|
||||
#endif // CYCLEOPSPHANTOMBIKE_H
|
||||
@@ -367,7 +367,7 @@ void domyoselliptical::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
|
||||
double domyoselliptical::GetSpeedFromPacket(const QByteArray &packet) {
|
||||
|
||||
uint16_t convertedData = (packet.at(6) << 8) | packet.at(7);
|
||||
uint16_t convertedData = (packet.at(6) << 8) | ((uint8_t)packet.at(7));
|
||||
double data = (double)convertedData / 10.0f;
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -195,3 +195,11 @@ resistance_t fakebike::resistanceFromPowerRequest(uint16_t power) {
|
||||
|
||||
uint16_t fakebike::watts() { return m_watt.value(); }
|
||||
bool fakebike::connected() { return true; }
|
||||
|
||||
double fakebike::maxGears() {
|
||||
return 24;
|
||||
}
|
||||
|
||||
double fakebike::minGears() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ class fakebike : public bike {
|
||||
uint16_t watts() override;
|
||||
resistance_t maxResistance() override { return 100; }
|
||||
resistance_t resistanceFromPowerRequest(uint16_t power) override;
|
||||
double maxGears() override;
|
||||
double minGears() override;
|
||||
|
||||
private:
|
||||
QTimer *refresh;
|
||||
|
||||
@@ -111,7 +111,7 @@ class fitshowtreadmill : public treadmill {
|
||||
bool firstCharacteristicChanged = true;
|
||||
int MAX_INCLINE = 30;
|
||||
int COUNTDOWN_VALUE = 0;
|
||||
int MAX_SPEED = 30;
|
||||
int MAX_SPEED = 300;
|
||||
int MIN_INCLINE = 0;
|
||||
int MIN_SPEED = 0;
|
||||
int UNIT = -100;
|
||||
|
||||
@@ -21,7 +21,7 @@ focustreadmill::focustreadmill(uint32_t pollDeviceTime, bool noConsole, bool noH
|
||||
double forceInitInclination) {
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = false;
|
||||
#endif
|
||||
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
|
||||
@@ -109,7 +109,7 @@ bool ftmsbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QStrin
|
||||
}
|
||||
writeBuffer = new QByteArray((const char *)data, data_len);
|
||||
|
||||
if (gattWriteCharControlPointId.properties() & QLowEnergyCharacteristic::WriteNoResponse) {
|
||||
if (gattWriteCharControlPointId.properties() & QLowEnergyCharacteristic::WriteNoResponse && !DOMYOS) {
|
||||
gattFTMSService->writeCharacteristic(gattWriteCharControlPointId, *writeBuffer,
|
||||
QLowEnergyService::WriteWithoutResponse);
|
||||
} else {
|
||||
@@ -228,7 +228,7 @@ void ftmsbike::forceResistance(resistance_t requestResistance) {
|
||||
|
||||
QSettings settings;
|
||||
if (!settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool() &&
|
||||
resistance_lvl_mode == false && _3G_Cardio_RB == false) {
|
||||
resistance_lvl_mode == false && _3G_Cardio_RB == false && JFBK5_0 == false) {
|
||||
uint8_t write[] = {FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS, 0x00, 0x00, 0x00, 0x00, 0x28, 0x19};
|
||||
|
||||
double fr = (((double)requestResistance) * bikeResistanceGain) + ((double)bikeResistanceOffset);
|
||||
@@ -240,12 +240,20 @@ void ftmsbike::forceResistance(resistance_t requestResistance) {
|
||||
writeCharacteristic(write, sizeof(write),
|
||||
QStringLiteral("forceResistance ") + QString::number(requestResistance));
|
||||
} else {
|
||||
uint8_t write[] = {FTMS_SET_TARGET_RESISTANCE_LEVEL, 0x00};
|
||||
if(_3G_Cardio_RB)
|
||||
requestResistance = requestResistance * 10;
|
||||
write[1] = ((uint8_t)(requestResistance));
|
||||
writeCharacteristic(write, sizeof(write),
|
||||
QStringLiteral("forceResistance ") + QString::number(requestResistance));
|
||||
if(JFBK5_0) {
|
||||
uint8_t write[] = {FTMS_SET_TARGET_RESISTANCE_LEVEL, 0x00, 0x00};
|
||||
write[1] = ((uint16_t)requestResistance * 10) & 0xFF;
|
||||
write[2] = ((uint16_t)requestResistance * 10) >> 8;
|
||||
writeCharacteristic(write, sizeof(write),
|
||||
QStringLiteral("forceResistance ") + QString::number(requestResistance));
|
||||
} else {
|
||||
uint8_t write[] = {FTMS_SET_TARGET_RESISTANCE_LEVEL, 0x00};
|
||||
if(_3G_Cardio_RB)
|
||||
requestResistance = requestResistance * 10;
|
||||
write[1] = ((uint8_t)(requestResistance));
|
||||
writeCharacteristic(write, sizeof(write),
|
||||
QStringLiteral("forceResistance ") + QString::number(requestResistance));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,6 +289,8 @@ void ftmsbike::update() {
|
||||
}
|
||||
|
||||
auto virtualBike = this->VirtualBike();
|
||||
QSettings settings;
|
||||
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
|
||||
|
||||
if (requestResistance != -1 || lastGearValue != gears()) {
|
||||
if (requestResistance > 100) {
|
||||
@@ -297,18 +307,24 @@ void ftmsbike::update() {
|
||||
if (((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike || resistance_lvl_mode) &&
|
||||
(requestPower == 0 || requestPower == -1)) {
|
||||
init();
|
||||
forceResistance(requestResistance + (gears() * 5));
|
||||
|
||||
if(DIRETO_XR && gears_zwift_ratio)
|
||||
setWheelDiameter(wheelCircumference::gearsToWheelDiameter(gears()));
|
||||
else
|
||||
forceResistance(requestResistance + (gears() * 5));
|
||||
}
|
||||
}
|
||||
requestResistance = -1;
|
||||
}
|
||||
if((virtualBike && virtualBike->ftmsDeviceConnected()) && lastGearValue != gears() && lastRawRequestedInclinationValue != -100 && lastPacketFromFTMS.length() >= 7) {
|
||||
qDebug() << "injecting fake ftms frame in order to send the new gear value ASAP" << lastPacketFromFTMS.toHex(' ');
|
||||
ftmsCharacteristicChanged(QLowEnergyCharacteristic(), lastPacketFromFTMS);
|
||||
if(DIRETO_XR && gears_zwift_ratio) {
|
||||
setWheelDiameter(wheelCircumference::gearsToWheelDiameter(gears()));
|
||||
} else {
|
||||
qDebug() << "injecting fake ftms frame in order to send the new gear value ASAP" << lastPacketFromFTMS.toHex(' ');
|
||||
ftmsCharacteristicChanged(QLowEnergyCharacteristic(), lastPacketFromFTMS);
|
||||
}
|
||||
}
|
||||
|
||||
QSettings settings;
|
||||
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
|
||||
if(zwiftPlayService && gears_zwift_ratio && lastGearValue != gears()) {
|
||||
QSettings settings;
|
||||
wheelCircumference::GearTable table;
|
||||
@@ -429,7 +445,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
}
|
||||
|
||||
// Wattbike Atom First Generation - Display Gears
|
||||
if(characteristic.uuid() == QBluetoothUuid(QStringLiteral("b4cc1224-bc02-4cae-adb9-1217ad2860d1")) &&
|
||||
if(WATTBIKE && characteristic.uuid() == QBluetoothUuid(QStringLiteral("b4cc1224-bc02-4cae-adb9-1217ad2860d1")) &&
|
||||
newValue.length() > 3 && newValue.at(1) == 0x03 && (uint8_t)newValue.at(2) == 0xb6) {
|
||||
uint8_t gear = newValue.at(3);
|
||||
qDebug() << "watt bike gears" << gear;
|
||||
@@ -526,12 +542,15 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
|
||||
|
||||
if (Flags.resistanceLvl) {
|
||||
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()));
|
||||
resistance_received = true;
|
||||
double d = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
|
||||
(uint16_t)((uint8_t)newValue.at(index))));
|
||||
index += 2;
|
||||
if(Resistance.value() > 0) {
|
||||
Resistance = d;
|
||||
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
|
||||
emit resistanceRead(Resistance.value());
|
||||
resistance_received = true;
|
||||
}
|
||||
}
|
||||
double ac = 0.01243107769;
|
||||
double bc = 1.145964912;
|
||||
@@ -895,7 +914,7 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
|
||||
qDebug() << s->serviceUuid() << QStringLiteral("connected!");
|
||||
|
||||
if (settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool() || ICSE || SCH_190U) {
|
||||
if (settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool() || ICSE || SCH_190U || DOMYOS) {
|
||||
QBluetoothUuid ftmsService((quint16)0x1826);
|
||||
if (s->serviceUuid() != ftmsService) {
|
||||
qDebug() << QStringLiteral("hammer racer bike wants to be subscribed only to FTMS service in order "
|
||||
@@ -955,7 +974,7 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
}
|
||||
|
||||
QBluetoothUuid _zwiftPlayWriteCharControlPointId(QStringLiteral("00000003-19ca-4651-86e5-fa29dcdd09d1"));
|
||||
if (c.uuid() == _zwiftPlayWriteCharControlPointId) {
|
||||
if (c.uuid() == _zwiftPlayWriteCharControlPointId && !DIRETO_XR) {
|
||||
qDebug() << QStringLiteral("Zwift Play service and Control Point found");
|
||||
zwiftPlayWriteChar = c;
|
||||
zwiftPlayService = s;
|
||||
@@ -1041,22 +1060,25 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact
|
||||
lastPacketFromFTMS.append(b.at(i));
|
||||
qDebug() << "lastPacketFromFTMS" << lastPacketFromFTMS.toHex(' ');
|
||||
int16_t slope = (((uint8_t)b.at(3)) + (b.at(4) << 8));
|
||||
if (gears() != 0) {
|
||||
slope += (gears() * 50);
|
||||
}
|
||||
if(!DIRETO_XR) {
|
||||
if (gears() != 0) {
|
||||
slope += (gears() * 50);
|
||||
}
|
||||
|
||||
if(min_inclination > (((double)slope) / 100.0)) {
|
||||
slope = min_inclination * 100;
|
||||
qDebug() << "grade override due to min_inclination " << min_inclination;
|
||||
}
|
||||
if(min_inclination > (((double)slope) / 100.0)) {
|
||||
slope = min_inclination * 100;
|
||||
qDebug() << "grade override due to min_inclination " << min_inclination;
|
||||
}
|
||||
|
||||
slope *= gain;
|
||||
slope += (offset * 100);
|
||||
slope *= gain;
|
||||
slope += (offset * 100);
|
||||
}
|
||||
|
||||
b[3] = slope & 0xFF;
|
||||
b[4] = slope >> 8;
|
||||
b[4] = slope >> 8;
|
||||
|
||||
qDebug() << "applying gears mod" << gears() << slope;
|
||||
|
||||
} else if(b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS && zwiftPlayService != nullptr && gears_zwift_ratio) {
|
||||
int16_t slope = (((uint8_t)b.at(3)) + (b.at(4) << 8));
|
||||
|
||||
@@ -1157,7 +1179,7 @@ void ftmsbike::serviceScanDone(void) {
|
||||
gattCommunicationChannelService.constLast()->discoverDetails();
|
||||
|
||||
// watt bikes has the 6 as default gear value
|
||||
if(s == QBluetoothUuid(QStringLiteral("b4cc1223-bc02-4cae-adb9-1217ad2860d1"))) {
|
||||
if(s == QBluetoothUuid(QStringLiteral("b4cc1223-bc02-4cae-adb9-1217ad2860d1")) && SS2K == false) {
|
||||
WATTBIKE = true;
|
||||
qDebug() << QStringLiteral("restoring gear 6 to watt bikes");
|
||||
setGears(6);
|
||||
@@ -1203,6 +1225,7 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
ICSE = true;
|
||||
} else if ((bluetoothDevice.name().toUpper().startsWith("DOMYOS"))) {
|
||||
qDebug() << QStringLiteral("DOMYOS found");
|
||||
resistance_lvl_mode = true;
|
||||
DOMYOS = true;
|
||||
} else if ((bluetoothDevice.name().toUpper().startsWith("3G Cardio RB"))) {
|
||||
qDebug() << QStringLiteral("_3G_Cardio_RB found");
|
||||
@@ -1213,6 +1236,19 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if(bluetoothDevice.name().toUpper().startsWith("D2RIDE")) {
|
||||
qDebug() << QStringLiteral("D2RIDE found");
|
||||
D2RIDE = true;
|
||||
} else if(bluetoothDevice.name().toUpper().startsWith("VFSPINBIKE")) {
|
||||
qDebug() << QStringLiteral("VFSPINBIKE found");
|
||||
VFSPINBIKE = true;
|
||||
} else if(bluetoothDevice.name().toUpper().startsWith("SMARTSPIN2K")) {
|
||||
qDebug() << QStringLiteral("SS2K found");
|
||||
SS2K = true;
|
||||
} else if(bluetoothDevice.name().toUpper().startsWith("DIRETO XR")) {
|
||||
qDebug() << QStringLiteral("DIRETO XR found");
|
||||
DIRETO_XR = true;
|
||||
} else if(bluetoothDevice.name().toUpper().startsWith("JFBK5.0") || bluetoothDevice.name().toUpper().startsWith("JFBK7.0")) {
|
||||
qDebug() << QStringLiteral("JFBK5.0 found");
|
||||
resistance_lvl_mode = true;
|
||||
JFBK5_0 = true;
|
||||
}
|
||||
|
||||
if(settings.value(QZSettings::force_resistance_instead_inclination, QZSettings::default_force_resistance_instead_inclination).toBool()) {
|
||||
@@ -1259,6 +1295,17 @@ bool ftmsbike::connected() {
|
||||
return m_control->state() == QLowEnergyController::DiscoveredState;
|
||||
}
|
||||
|
||||
void ftmsbike::setWheelDiameter(double diameter) {
|
||||
uint8_t write[] = {FTMS_SET_WHEEL_CIRCUMFERENCE, 0x00, 0x00};
|
||||
|
||||
diameter = diameter * 10.0;
|
||||
|
||||
write[1] = ((uint16_t)diameter) & 0xFF;
|
||||
write[2] = ((uint16_t)diameter) >> 8;
|
||||
|
||||
writeCharacteristic(write, sizeof(write), QStringLiteral("setWheelCircumference ") + QString::number(diameter));
|
||||
}
|
||||
|
||||
uint16_t ftmsbike::watts() {
|
||||
if (currentCadence().value() == 0) {
|
||||
return 0;
|
||||
@@ -1280,7 +1327,7 @@ double ftmsbike::maxGears() {
|
||||
QSettings settings;
|
||||
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
|
||||
|
||||
if(zwiftPlayService != nullptr && gears_zwift_ratio) {
|
||||
if((zwiftPlayService != nullptr || DIRETO_XR) && gears_zwift_ratio) {
|
||||
wheelCircumference::GearTable g;
|
||||
return g.maxGears;
|
||||
} else if(WATTBIKE) {
|
||||
@@ -1294,7 +1341,7 @@ double ftmsbike::minGears() {
|
||||
QSettings settings;
|
||||
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
|
||||
|
||||
if(zwiftPlayService != nullptr && gears_zwift_ratio) {
|
||||
if((zwiftPlayService != nullptr || DIRETO_XR) && gears_zwift_ratio ) {
|
||||
return 1;
|
||||
} else if(WATTBIKE) {
|
||||
return 1;
|
||||
|
||||
@@ -84,6 +84,7 @@ class ftmsbike : public bike {
|
||||
bool wait_for_response = false);
|
||||
void zwiftPlayInit();
|
||||
void startDiscover();
|
||||
void setWheelDiameter(double diameter);
|
||||
uint16_t watts() override;
|
||||
void init();
|
||||
void forceResistance(resistance_t requestResistance);
|
||||
@@ -128,6 +129,10 @@ class ftmsbike : public bike {
|
||||
bool SCH_190U = false;
|
||||
bool D2RIDE = false;
|
||||
bool WATTBIKE = false;
|
||||
bool VFSPINBIKE = false;
|
||||
bool SS2K = false;
|
||||
bool DIRETO_XR = false;
|
||||
bool JFBK5_0 = false;
|
||||
|
||||
uint8_t battery_level = 0;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "heartratebelt.h"
|
||||
#include "homeform.h"
|
||||
#include <QBluetoothLocalDevice>
|
||||
#include <QDateTime>
|
||||
#include <QEventLoop>
|
||||
@@ -118,6 +119,10 @@ void heartratebelt::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
// QStringLiteral("Disabled")).toString();//NOTE: clazy-unused-non-trivial-variable
|
||||
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
|
||||
device.address().toString() + ')');
|
||||
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested(device.name() + QStringLiteral(" connected!"));
|
||||
|
||||
// if(device.name().startsWith(heartRateBeltName))
|
||||
{
|
||||
bluetoothDevice = device;
|
||||
|
||||
@@ -816,7 +816,13 @@ void horizontreadmill::btinit() {
|
||||
messageID = 0x10;
|
||||
}
|
||||
|
||||
if(wellfit_treadmill || SW_TREADMILL) {
|
||||
if(YPOO_MINI_PRO) {
|
||||
uint8_t write[] = {0x01, 0x00, 0x00, 0x03, 0x08, 0x00, 0x02, 0x09};
|
||||
writeCharacteristic(gattFTMSService, gattWriteCharControlPointIdYpooMiniPro, write, sizeof(write), "requestControl", false, false);
|
||||
QThread::msleep(500);
|
||||
}
|
||||
|
||||
if(wellfit_treadmill || SW_TREADMILL || YPOO_MINI_PRO) {
|
||||
uint8_t write[] = {FTMS_REQUEST_CONTROL};
|
||||
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
|
||||
false);
|
||||
@@ -1174,7 +1180,7 @@ void horizontreadmill::forceSpeed(double requestSpeed) {
|
||||
}
|
||||
} else if (gattFTMSService) {
|
||||
// for the Tecnogym Myrun
|
||||
if(!anplus_treadmill && !trx3500_treadmill && !wellfit_treadmill && !mobvoi_tmp_treadmill && !SW_TREADMILL && !ICONCEPT_FTMS_treadmill) {
|
||||
if(!anplus_treadmill && !trx3500_treadmill && !wellfit_treadmill && !mobvoi_tmp_treadmill && !SW_TREADMILL && !ICONCEPT_FTMS_treadmill && !YPOO_MINI_PRO) {
|
||||
uint8_t write[] = {FTMS_REQUEST_CONTROL};
|
||||
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
|
||||
false);
|
||||
@@ -1244,7 +1250,7 @@ void horizontreadmill::forceIncline(double requestIncline) {
|
||||
}
|
||||
} else if (gattFTMSService) {
|
||||
// for the Tecnogym Myrun
|
||||
if(!anplus_treadmill && !trx3500_treadmill && !mobvoi_tmp_treadmill && !SW_TREADMILL && !ICONCEPT_FTMS_treadmill) {
|
||||
if(!anplus_treadmill && !trx3500_treadmill && !mobvoi_tmp_treadmill && !SW_TREADMILL && !ICONCEPT_FTMS_treadmill && !YPOO_MINI_PRO) {
|
||||
uint8_t write[] = {FTMS_REQUEST_CONTROL};
|
||||
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
|
||||
false);
|
||||
@@ -2132,6 +2138,7 @@ void horizontreadmill::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
QBluetoothUuid _gattCrossTrainerDataId((quint16)0x2ACE);
|
||||
QBluetoothUuid _gattInclinationSupported((quint16)0x2AD5);
|
||||
QBluetoothUuid _DomyosServiceId(QStringLiteral("49535343-fe7d-4ae5-8fa9-9fafd205e455"));
|
||||
QBluetoothUuid _YpooMiniProCharId(QStringLiteral("d18d2c10-c44c-11e8-a355-529269fb1459"));
|
||||
emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
|
||||
|
||||
for (QLowEnergyService *s : qAsConst(gattCommunicationChannelService)) {
|
||||
@@ -2184,12 +2191,16 @@ void horizontreadmill::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
// some treadmills doesn't have the control point and also are Cross Trainer devices so i need
|
||||
// anyway to get the FTMS Service at least
|
||||
gattFTMSService = s;
|
||||
}/* else if (c.uuid() == _gattInclinationSupported) {
|
||||
} else if(c.uuid() == _YpooMiniProCharId && YPOO_MINI_PRO) {
|
||||
qDebug() << QStringLiteral("YPOO MINI PRO Control Point found");
|
||||
gattWriteCharControlPointIdYpooMiniPro = c;
|
||||
}
|
||||
/* else if (c.uuid() == _gattInclinationSupported) {
|
||||
s->readCharacteristic(c);
|
||||
qDebug() << s->serviceUuid() << c.uuid() << "reading!";
|
||||
}*/
|
||||
|
||||
if (c.properties() & QLowEnergyCharacteristic::Write && c.uuid() == _gattWriteCharCustomService && !BOWFLEX_T9 &&
|
||||
if (c.properties() & QLowEnergyCharacteristic::Write && c.uuid() == _gattWriteCharCustomService && !BOWFLEX_T9 && !MX_TM &&
|
||||
!settings
|
||||
.value(QZSettings::horizon_treadmill_force_ftms,
|
||||
QZSettings::default_horizon_treadmill_force_ftms)
|
||||
@@ -2390,7 +2401,8 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} 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-"))) {
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("TUNTURI T60-")) ||
|
||||
device.name().toUpper().startsWith(QStringLiteral("TUNTURI T90-"))) {
|
||||
tunturi_t60_treadmill = true;
|
||||
qDebug() << QStringLiteral("TUNTURI T60 TREADMILL workaround ON!");
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("F85"))) {
|
||||
@@ -2405,6 +2417,9 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
sole_tt8_treadmill = true;
|
||||
minInclination = -6.0;
|
||||
qDebug() << QStringLiteral("SOLE TT8 TREADMILL workaround ON!");
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("S77"))) {
|
||||
sole_s77_treadmill = true;
|
||||
qDebug() << QStringLiteral("SOLE S77 TREADMILL workaround ON!");
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("SCHWINN 810"))) {
|
||||
schwinn_810_treadmill = true;
|
||||
qDebug() << QStringLiteral("Schwinn 810 TREADMILL workaround ON!");
|
||||
@@ -2427,6 +2442,12 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if ((device.name().toUpper().startsWith(QStringLiteral("BFX_T9_")))) {
|
||||
qDebug() << QStringLiteral("BOWFLEX T9 found");
|
||||
BOWFLEX_T9 = true;
|
||||
} else if ((device.name().toUpper().startsWith(QStringLiteral("YPOO-MINI PRO-")))) {
|
||||
qDebug() << QStringLiteral("YPOO-MINI PRO found");
|
||||
YPOO_MINI_PRO = true;
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("MX-TM "))) {
|
||||
qDebug() << QStringLiteral("MX-TM found");
|
||||
MX_TM = true;
|
||||
}
|
||||
|
||||
if (device.name().toUpper().startsWith(QStringLiteral("TRX3500"))) {
|
||||
@@ -3151,7 +3172,7 @@ void horizontreadmill::testProfileCRC() {
|
||||
double horizontreadmill::minStepInclination() {
|
||||
QSettings settings;
|
||||
bool toorx_ftms_treadmill = settings.value(QZSettings::toorx_ftms_treadmill, QZSettings::default_toorx_ftms_treadmill).toBool();
|
||||
if (kettler_treadmill || trx3500_treadmill || toorx_ftms_treadmill || sole_tt8_treadmill || ICONCEPT_FTMS_treadmill || SW_TREADMILL)
|
||||
if (kettler_treadmill || trx3500_treadmill || toorx_ftms_treadmill || sole_tt8_treadmill || ICONCEPT_FTMS_treadmill || SW_TREADMILL || sole_s77_treadmill)
|
||||
return 1.0;
|
||||
else
|
||||
return 0.5;
|
||||
|
||||
@@ -58,6 +58,7 @@ class horizontreadmill : public treadmill {
|
||||
|
||||
QList<QLowEnergyService *> gattCommunicationChannelService;
|
||||
QLowEnergyCharacteristic gattWriteCharControlPointId;
|
||||
QLowEnergyCharacteristic gattWriteCharControlPointIdYpooMiniPro;
|
||||
QLowEnergyService *gattFTMSService = nullptr;
|
||||
QLowEnergyCharacteristic gattWriteCharCustomService;
|
||||
QLowEnergyService *gattCustomService = nullptr;
|
||||
@@ -90,6 +91,7 @@ class horizontreadmill : public treadmill {
|
||||
bool kettler_treadmill = false;
|
||||
bool wellfit_treadmill = false;
|
||||
bool sole_tt8_treadmill = false;
|
||||
bool sole_s77_treadmill = false;
|
||||
bool anplus_treadmill = false;
|
||||
bool tunturi_t60_treadmill = false;
|
||||
bool trx3500_treadmill = false;
|
||||
@@ -103,6 +105,8 @@ class horizontreadmill : public treadmill {
|
||||
bool DOMYOS = false;
|
||||
bool SW_TREADMILL = false;
|
||||
bool BOWFLEX_T9 = false;
|
||||
bool YPOO_MINI_PRO = false;
|
||||
bool MX_TM = false;
|
||||
|
||||
void testProfileCRC();
|
||||
void updateProfileCRC();
|
||||
|
||||
363
src/devices/kineticinroadbike/SmartControl.cpp
Normal file
363
src/devices/kineticinroadbike/SmartControl.cpp
Normal file
@@ -0,0 +1,363 @@
|
||||
//
|
||||
// SmartControl.c
|
||||
//
|
||||
// Copyright © 2017 Kinetic. All rights reserved.
|
||||
//
|
||||
|
||||
#include "SmartControl.h"
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#define SensorHz 10000
|
||||
|
||||
|
||||
typedef enum smart_control_command
|
||||
{
|
||||
SMART_CONTROL_COMMAND_SET_PERFORMANCE = 0x00,
|
||||
SMART_CONTROL_COMMAND_SPINDOWN_CALIBRATION = 0x03
|
||||
} smart_control_command;
|
||||
|
||||
|
||||
uint32_t getrandom(uint32_t upper_bound) {
|
||||
static std::random_device rd; // Used to initialize the random engine
|
||||
static std::mt19937 gen(rd()); // Mersenne Twister engine
|
||||
std::uniform_int_distribution<uint32_t> dis(0, upper_bound - 1);
|
||||
return dis(gen);
|
||||
}
|
||||
|
||||
uint8_t hash8WithSeed(uint8_t hash, const uint8_t *buffer, uint8_t length)
|
||||
{
|
||||
const uint8_t crc8_table[256] = {
|
||||
0x00, 0x91, 0xe3, 0x72, 0x07, 0x96, 0xe4, 0x75,
|
||||
0x0e, 0x9f, 0xed, 0x7c, 0x09, 0x98, 0xea, 0x7b,
|
||||
0x1c, 0x8d, 0xff, 0x6e, 0x1b, 0x8a, 0xf8, 0x69,
|
||||
0x12, 0x83, 0xf1, 0x60, 0x15, 0x84, 0xf6, 0x67,
|
||||
0x38, 0xa9, 0xdb, 0x4a, 0x3f, 0xae, 0xdc, 0x4d,
|
||||
0x36, 0xa7, 0xd5, 0x44, 0x31, 0xa0, 0xd2, 0x43,
|
||||
0x24, 0xb5, 0xc7, 0x56, 0x23, 0xb2, 0xc0, 0x51,
|
||||
0x2a, 0xbb, 0xc9, 0x58, 0x2d, 0xbc, 0xce, 0x5f,
|
||||
0x70, 0xe1, 0x93, 0x02, 0x77, 0xe6, 0x94, 0x05,
|
||||
0x7e, 0xef, 0x9d, 0x0c, 0x79, 0xe8, 0x9a, 0x0b,
|
||||
0x6c, 0xfd, 0x8f, 0x1e, 0x6b, 0xfa, 0x88, 0x19,
|
||||
0x62, 0xf3, 0x81, 0x10, 0x65, 0xf4, 0x86, 0x17,
|
||||
0x48, 0xd9, 0xab, 0x3a, 0x4f, 0xde, 0xac, 0x3d,
|
||||
0x46, 0xd7, 0xa5, 0x34, 0x41, 0xd0, 0xa2, 0x33,
|
||||
0x54, 0xc5, 0xb7, 0x26, 0x53, 0xc2, 0xb0, 0x21,
|
||||
0x5a, 0xcb, 0xb9, 0x28, 0x5d, 0xcc, 0xbe, 0x2f,
|
||||
0xe0, 0x71, 0x03, 0x92, 0xe7, 0x76, 0x04, 0x95,
|
||||
0xee, 0x7f, 0x0d, 0x9c, 0xe9, 0x78, 0x0a, 0x9b,
|
||||
0xfc, 0x6d, 0x1f, 0x8e, 0xfb, 0x6a, 0x18, 0x89,
|
||||
0xf2, 0x63, 0x11, 0x80, 0xf5, 0x64, 0x16, 0x87,
|
||||
0xd8, 0x49, 0x3b, 0xaa, 0xdf, 0x4e, 0x3c, 0xad,
|
||||
0xd6, 0x47, 0x35, 0xa4, 0xd1, 0x40, 0x32, 0xa3,
|
||||
0xc4, 0x55, 0x27, 0xb6, 0xc3, 0x52, 0x20, 0xb1,
|
||||
0xca, 0x5b, 0x29, 0xb8, 0xcd, 0x5c, 0x2e, 0xbf,
|
||||
0x90, 0x01, 0x73, 0xe2, 0x97, 0x06, 0x74, 0xe5,
|
||||
0x9e, 0x0f, 0x7d, 0xec, 0x99, 0x08, 0x7a, 0xeb,
|
||||
0x8c, 0x1d, 0x6f, 0xfe, 0x8b, 0x1a, 0x68, 0xf9,
|
||||
0x82, 0x13, 0x61, 0xf0, 0x85, 0x14, 0x66, 0xf7,
|
||||
0xa8, 0x39, 0x4b, 0xda, 0xaf, 0x3e, 0x4c, 0xdd,
|
||||
0xa6, 0x37, 0x45, 0xd4, 0xa1, 0x30, 0x42, 0xd3,
|
||||
0xb4, 0x25, 0x57, 0xc6, 0xb3, 0x22, 0x50, 0xc1,
|
||||
0xba, 0x2b, 0x59, 0xc8, 0xbd, 0x2c, 0x5e, 0xcf
|
||||
};
|
||||
|
||||
for (uint8_t byte_index = 0; byte_index < length; byte_index++) {
|
||||
hash = crc8_table[hash ^ buffer[byte_index]];
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
double smart_control_speed_for_ticks(uint16_t ticks)
|
||||
{
|
||||
if (ticks == 0 || ticks == 65535) {
|
||||
return 0;
|
||||
}
|
||||
return (6107.2561186) / ((double)ticks);
|
||||
}
|
||||
|
||||
double smart_control_ticks_to_seconds(uint32_t ticks)
|
||||
{
|
||||
return (double)ticks / (double)SensorHz;
|
||||
}
|
||||
|
||||
smart_control_power_data smart_control_process_power_data(uint8_t *data, size_t size)
|
||||
{
|
||||
uint8_t hashSeed = 0x42;
|
||||
std::vector<uint8_t> inData(size); // Use vector instead of VLA
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
inData[i] = data[i];
|
||||
}
|
||||
uint8_t hash = hash8WithSeed(hashSeed, &inData[size - 1], 1);
|
||||
for (size_t index = 0; index < size - 1; index++) {
|
||||
inData[index] ^= hash;
|
||||
hash = hash8WithSeed(hash, &inData[index], 1);
|
||||
}
|
||||
|
||||
smart_control_power_data powerData;
|
||||
|
||||
if (size >= 14) {
|
||||
powerData.mode = (smart_control_mode)inData[0];
|
||||
powerData.targetResistance = ((uint16_t)inData[1] << 8) | (uint16_t)inData[2];
|
||||
powerData.power = ((uint16_t)inData[3] << 8) | (uint16_t)inData[4];
|
||||
powerData.cadenceRPM = inData[12];
|
||||
|
||||
if (size >= 18) {
|
||||
uint32_t metersPerHour = ((uint32_t)inData[13] << 24) | ((uint32_t)inData[14] << 16) | ((uint32_t)inData[15] << 8) | (uint32_t)inData[16];
|
||||
powerData.speedKPH = metersPerHour / 1000.0;
|
||||
} else {
|
||||
uint16_t rollerTicks = ((uint16_t)inData[5] << 8) | (uint16_t)inData[6];
|
||||
powerData.speedKPH = smart_control_speed_for_ticks(rollerTicks);
|
||||
}
|
||||
} else {
|
||||
powerData.mode = SMART_CONTROL_MODE_ERG;
|
||||
powerData.targetResistance = 0;
|
||||
powerData.cadenceRPM = 0;
|
||||
powerData.power = 0;
|
||||
powerData.speedKPH = 0;
|
||||
}
|
||||
|
||||
return powerData;
|
||||
}
|
||||
|
||||
smart_control_config_data smart_control_process_config_data(uint8_t *data, size_t size)
|
||||
{
|
||||
uint8_t hashSeed = 0x42;
|
||||
std::vector<uint8_t> inData(size); // Use vector instead of VLA
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
inData[i] = data[i];
|
||||
}
|
||||
uint8_t hash = hash8WithSeed(hashSeed, &inData[size - 1], 1);
|
||||
for (size_t index = 0; index < size - 1; index++) {
|
||||
inData[index] ^= hash;
|
||||
hash = hash8WithSeed(hash, &inData[index], 1);
|
||||
}
|
||||
|
||||
smart_control_config_data configData;
|
||||
|
||||
if (size >= 5) {
|
||||
configData.updateRate = inData[0];
|
||||
configData.tickRate = ((uint32_t)inData[1] << 16) | ((uint32_t)inData[2] << 8) | (uint32_t)inData[3];
|
||||
configData.firmwareUpdateState = inData[4];
|
||||
|
||||
if (size >= 13) {
|
||||
configData.systemStatus = ((uint16_t)inData[5] << 8) | (uint16_t)inData[6];
|
||||
configData.calibrationState = (smart_control_calibration_state)inData[7];
|
||||
uint32_t spindownTicks = ((uint32_t)inData[8] << 24) | ((uint32_t)inData[9] << 16) | ((uint32_t)inData[10] << 8) | (uint32_t)inData[11];
|
||||
configData.spindownTime = smart_control_ticks_to_seconds(spindownTicks);
|
||||
}
|
||||
if (size >= 15) {
|
||||
uint16_t metersPerHour = ((uint16_t)inData[12] << 8) | (uint16_t)inData[13];
|
||||
configData.calibrationThresholdKPH = metersPerHour / 1000.0;
|
||||
} else {
|
||||
configData.calibrationThresholdKPH = 33.8;
|
||||
}
|
||||
if (size >= 18) {
|
||||
uint16_t metersPerHour = ((uint16_t)inData[14] << 8) | (uint16_t)inData[15];
|
||||
configData.brakeCalibrationThresholdKPH = metersPerHour / 1000.0;
|
||||
configData.brakeStrength = inData[16];
|
||||
} else {
|
||||
configData.brakeCalibrationThresholdKPH = 45;
|
||||
configData.brakeStrength = 55;
|
||||
}
|
||||
if (size >= 19) {
|
||||
configData.brakeOffset = inData[17];
|
||||
} else {
|
||||
configData.brakeOffset = 128;
|
||||
}
|
||||
if (size >= 20) {
|
||||
configData.noiseFilter = inData[18];
|
||||
} else {
|
||||
configData.noiseFilter = 1;
|
||||
}
|
||||
} else {
|
||||
configData.updateRate = 1;
|
||||
configData.tickRate = 10000;
|
||||
configData.firmwareUpdateState = 0;
|
||||
configData.systemStatus = 0;
|
||||
configData.calibrationState = SMART_CONTROL_CALIBRATION_STATE_NOT_PERFORMED;
|
||||
configData.spindownTime = 0;
|
||||
configData.calibrationThresholdKPH = 33.8;
|
||||
configData.brakeCalibrationThresholdKPH = 45;
|
||||
configData.brakeStrength = 55;
|
||||
configData.brakeOffset = 128;
|
||||
configData.noiseFilter = 1;
|
||||
}
|
||||
|
||||
return configData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#ifndef MIN
|
||||
#define MIN(a,b) (((a)<(b))?(a):(b))
|
||||
#define MAX(a,b) (((a)>(b))?(a):(b))
|
||||
#endif
|
||||
|
||||
|
||||
smart_control_set_mode_erg_data smart_control_set_mode_erg_command(uint16_t targetWatts)
|
||||
{
|
||||
smart_control_set_mode_erg_data data;
|
||||
|
||||
uint16_t clamped = MAX(0, MIN(0xFFFF, targetWatts));
|
||||
|
||||
data.bytes[0] = SMART_CONTROL_COMMAND_SET_PERFORMANCE;
|
||||
data.bytes[1] = SMART_CONTROL_MODE_ERG;
|
||||
data.bytes[2] = (uint16_t)clamped >> 8;
|
||||
data.bytes[3] = (uint16_t)clamped;
|
||||
data.bytes[4] = getrandom(0x100); // nonce
|
||||
uint8_t dataLength = 5;
|
||||
|
||||
// Encode Packet
|
||||
uint8_t hashSeed = 0x42;
|
||||
uint8_t hash = hash8WithSeed(hashSeed, &data.bytes[dataLength - 1], 1);
|
||||
for (unsigned index = 0; index < dataLength - 1; index++) {
|
||||
uint8_t temp = data.bytes[index];
|
||||
data.bytes[index] ^= hash;
|
||||
hash = hash8WithSeed(hash, &temp, 1);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
smart_control_set_mode_fluid_data smart_control_set_mode_fluid_command(uint8_t level)
|
||||
{
|
||||
smart_control_set_mode_fluid_data data;
|
||||
|
||||
uint8_t clamped = MAX(0, MIN(9, level));
|
||||
|
||||
data.bytes[0] = SMART_CONTROL_COMMAND_SET_PERFORMANCE;
|
||||
data.bytes[1] = SMART_CONTROL_MODE_FLUID;
|
||||
data.bytes[2] = clamped;
|
||||
data.bytes[3] = getrandom(0x100); // nonce
|
||||
uint8_t dataLength = 4;
|
||||
|
||||
// Encode Packet
|
||||
uint8_t hashSeed = 0x42;
|
||||
uint8_t hash = hash8WithSeed(hashSeed, &data.bytes[dataLength - 1], 1);
|
||||
for (unsigned index = 0; index < dataLength - 1; index++) {
|
||||
uint8_t temp = data.bytes[index];
|
||||
data.bytes[index] ^= hash;
|
||||
hash = hash8WithSeed(hash, &temp, 1);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
smart_control_set_mode_brake_data smart_control_set_mode_brake_command(float percent)
|
||||
{
|
||||
smart_control_set_mode_brake_data data;
|
||||
|
||||
// normalize to 0-65535
|
||||
float clamped = MAX(0, MIN(1, percent));
|
||||
uint16_t normalized = (uint16_t) round(65535 * clamped);
|
||||
|
||||
data.bytes[0] = SMART_CONTROL_COMMAND_SET_PERFORMANCE;
|
||||
data.bytes[1] = SMART_CONTROL_MODE_BRAKE;
|
||||
data.bytes[2] = normalized >> 8;
|
||||
data.bytes[3] = normalized;
|
||||
data.bytes[4] = getrandom(0x100); // nonce
|
||||
uint8_t dataLength = 5;
|
||||
|
||||
// Encode Packet
|
||||
uint8_t hashSeed = 0x42;
|
||||
uint8_t hash = hash8WithSeed(hashSeed, &data.bytes[dataLength - 1], 1);
|
||||
for (unsigned index = 0; index < dataLength - 1; index++) {
|
||||
uint8_t temp = data.bytes[index];
|
||||
data.bytes[index] ^= hash;
|
||||
hash = hash8WithSeed(hash, &temp, 1);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
smart_control_set_mode_simulation_data smart_control_set_mode_simulation_command(float weightKG, float rollingCoeff, float windCoeff, float grade, float windSpeedMPS)
|
||||
{
|
||||
smart_control_set_mode_simulation_data data;
|
||||
|
||||
data.bytes[0] = SMART_CONTROL_COMMAND_SET_PERFORMANCE;
|
||||
data.bytes[1] = SMART_CONTROL_MODE_SIMULATION;
|
||||
|
||||
// weight is in KGs ... multiply by 100 to get 2 points of precision
|
||||
uint16_t weight100 = (uint16_t) roundf(MIN(655.36, weightKG) * 100);
|
||||
data.bytes[2] = weight100 >> 8;
|
||||
data.bytes[3] = weight100;
|
||||
|
||||
// Rolling coeff is < 1. multiply by 10,000 to get 5 points of precision
|
||||
// coeff cannot be larger than 6.5536 otherwise it rolls over ...
|
||||
uint16_t rr10000 = (uint16_t) roundf(MIN(6.5536, rollingCoeff) * 10000);
|
||||
data.bytes[4] = rr10000 >> 8;
|
||||
data.bytes[5] = rr10000;
|
||||
|
||||
// Wind coeff is typically < 1. multiply by 10,000 to get 5 points of precision
|
||||
// coeff cannot be larger than 6.5536 otherwise it rolls over ...
|
||||
uint16_t wr10000 = (uint16_t) roundf(MIN(6.5536, windCoeff) * 10000);
|
||||
data.bytes[6] = wr10000 >> 8;
|
||||
data.bytes[7] = wr10000;
|
||||
|
||||
// Grade is between -45.0 and 45.0
|
||||
// Mulitply by 100 to get 2 points of precision
|
||||
int16_t grade100 = (int16_t) roundf(MAX(-45, MIN(45, grade)) * 100);
|
||||
data.bytes[8] = grade100 >> 8;
|
||||
data.bytes[9] = grade100;
|
||||
|
||||
// windspeed is in meters / second. convert to CM / second
|
||||
int16_t windSpeedCM = (int16_t) roundf(windSpeedMPS * 100);
|
||||
data.bytes[10] = windSpeedCM >> 8;
|
||||
data.bytes[11] = windSpeedCM;
|
||||
|
||||
data.bytes[12] = getrandom(0x100); // nonce
|
||||
uint8_t dataLength = 13;
|
||||
|
||||
// Encode Packet
|
||||
uint8_t hashSeed = 0x42;
|
||||
uint8_t hash = hash8WithSeed(hashSeed, &data.bytes[dataLength - 1], 1);
|
||||
for (unsigned index = 0; index < dataLength - 1; index++) {
|
||||
uint8_t temp = data.bytes[index];
|
||||
data.bytes[index] ^= hash;
|
||||
hash = hash8WithSeed(hash, &temp, 1);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
smart_control_calibration_command_data smart_control_start_calibration_command(bool brakeCalibration)
|
||||
{
|
||||
smart_control_calibration_command_data data;
|
||||
|
||||
data.bytes[0] = SMART_CONTROL_COMMAND_SPINDOWN_CALIBRATION;
|
||||
data.bytes[1] = 0x01;
|
||||
data.bytes[2] = brakeCalibration ? 0x01 : 0x00;
|
||||
data.bytes[3] = getrandom(0x100); // nonce
|
||||
uint8_t dataLength = 4;
|
||||
|
||||
// Encode Packet
|
||||
uint8_t hashSeed = 0x42;
|
||||
uint8_t hash = hash8WithSeed(hashSeed, &data.bytes[dataLength - 1], 1);
|
||||
for (unsigned index = 0; index < dataLength - 1; index++) {
|
||||
uint8_t temp = data.bytes[index];
|
||||
data.bytes[index] ^= hash;
|
||||
hash = hash8WithSeed(hash, &temp, 1);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
smart_control_calibration_command_data smart_control_stop_calibration_command()
|
||||
{
|
||||
smart_control_calibration_command_data data;
|
||||
|
||||
data.bytes[0] = SMART_CONTROL_COMMAND_SPINDOWN_CALIBRATION;
|
||||
data.bytes[1] = 0x00;
|
||||
data.bytes[2] = 0x00;
|
||||
data.bytes[3] = getrandom(0x100); // nonce
|
||||
uint8_t dataLength = 4;
|
||||
|
||||
// Encode Packet
|
||||
uint8_t hashSeed = 0x42;
|
||||
uint8_t hash = hash8WithSeed(hashSeed, &data.bytes[dataLength - 1], 1);
|
||||
for (unsigned index = 0; index < dataLength - 1; index++) {
|
||||
uint8_t temp = data.bytes[index];
|
||||
data.bytes[index] ^= hash;
|
||||
hash = hash8WithSeed(hash, &temp, 1);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
237
src/devices/kineticinroadbike/SmartControl.h
Normal file
237
src/devices/kineticinroadbike/SmartControl.h
Normal file
@@ -0,0 +1,237 @@
|
||||
//
|
||||
// SmartControl.h
|
||||
//
|
||||
// Copyright © 2017 Kinetic. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef SmartControl_h
|
||||
#define SmartControl_h
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static const char SMART_CONTROL_SERVICE_UUID[] = "E9410200-B434-446B-B5CC-36592FC4C724";
|
||||
static const char SMART_CONTROL_SERVICE_POWER_UUID[] = "E9410201-B434-446B-B5CC-36592FC4C724";
|
||||
static const char SMART_CONTROL_SERVICE_CONFIG_UUID[] = "E9410202-B434-446B-B5CC-36592FC4C724";
|
||||
static const char SMART_CONTROL_SERVICE_CONTROL_UUID[] = "E9410203-B434-446B-B5CC-36592FC4C724";
|
||||
|
||||
/*! Smart Control Resistance Mode */
|
||||
typedef enum smart_control_mode
|
||||
{
|
||||
SMART_CONTROL_MODE_ERG = 0x00,
|
||||
SMART_CONTROL_MODE_FLUID = 0x01,
|
||||
SMART_CONTROL_MODE_BRAKE = 0x02,
|
||||
SMART_CONTROL_MODE_SIMULATION = 0x03
|
||||
} smart_control_mode;
|
||||
|
||||
/*! Smart Control Power Data */
|
||||
typedef struct smart_control_power_data
|
||||
{
|
||||
/*! Current Resistance Mode */
|
||||
smart_control_mode mode;
|
||||
|
||||
/*! Current Power (Watts) */
|
||||
uint16_t power;
|
||||
|
||||
/*! Current Speed (KPH) */
|
||||
double speedKPH;
|
||||
|
||||
/*! Current Cadence (Virtual RPM) */
|
||||
uint8_t cadenceRPM;
|
||||
|
||||
/*! Current wattage the RU is Targetting */
|
||||
uint16_t targetResistance;
|
||||
|
||||
} smart_control_power_data;
|
||||
|
||||
/*!
|
||||
Deserialize the raw power data (bytes) broadcast by Smart Control.
|
||||
|
||||
@param data The raw data broadcast from the [Power Service -> Power] Characteristic
|
||||
@param size The size of the data array
|
||||
|
||||
@return Smart Control Power Data Struct
|
||||
*/
|
||||
smart_control_power_data smart_control_process_power_data(uint8_t *data, size_t size);
|
||||
|
||||
/*! Smart Control Calibration State */
|
||||
typedef enum smart_control_calibration_state
|
||||
{
|
||||
SMART_CONTROL_CALIBRATION_STATE_NOT_PERFORMED = 0,
|
||||
SMART_CONTROL_CALIBRATION_STATE_INITIALIZING = 1,
|
||||
SMART_CONTROL_CALIBRATION_STATE_SPEED_UP = 2,
|
||||
SMART_CONTROL_CALIBRATION_STATE_START_COASTING = 3,
|
||||
SMART_CONTROL_CALIBRATION_STATE_COASTING = 4,
|
||||
SMART_CONTROL_CALIBRATION_STATE_SPEED_UP_DETECTED = 5,
|
||||
SMART_CONTROL_CALIBRATION_STATE_COMPLETE = 10
|
||||
} smart_control_calibration_state;
|
||||
|
||||
/*! Smart Control Configuration Data */
|
||||
typedef struct smart_control_config_data
|
||||
{
|
||||
/*! Power Data Update Rate (Hz) */
|
||||
uint8_t updateRate;
|
||||
|
||||
/*! Current Calibration State of the RU */
|
||||
smart_control_calibration_state calibrationState;
|
||||
|
||||
/*! Current Spindown Time being applied to the Power Data */
|
||||
double spindownTime;
|
||||
|
||||
/*! Calibration Speed Threshold (KPH) */
|
||||
double calibrationThresholdKPH;
|
||||
|
||||
/*! Brake Calibration Speed Threshold (KPH) */
|
||||
double brakeCalibrationThresholdKPH;
|
||||
|
||||
/*! Clock Speed of Data Update (Hz) */
|
||||
uint32_t tickRate;
|
||||
|
||||
/*! System Health Status (non-zero indicates problem) */
|
||||
uint16_t systemStatus;
|
||||
|
||||
/*! Firmware Update State (Internal Use Only) */
|
||||
uint8_t firmwareUpdateState;
|
||||
|
||||
/*! Normalized Brake Strength calculated by a Brake Calibration */
|
||||
uint8_t brakeStrength;
|
||||
|
||||
/*! Normalized Brake Offset calculated by a Brake Calibration */
|
||||
uint8_t brakeOffset;
|
||||
|
||||
/*! Noise Filter Strength */
|
||||
uint8_t noiseFilter;
|
||||
|
||||
} smart_control_config_data;
|
||||
|
||||
/*!
|
||||
Deserialize the raw config data (bytes) broadcast by Smart Control.
|
||||
|
||||
@param data The raw data broadcast from the [Power Service -> Config] Characteristic
|
||||
@param size The size of the data array
|
||||
|
||||
@return Smart Control Config Data Struct
|
||||
*/
|
||||
smart_control_config_data smart_control_process_config_data(uint8_t *data, size_t size);
|
||||
|
||||
/*! Command Structs to write to the Control Point Characteristic */
|
||||
#ifdef _MSC_VER
|
||||
#pragma pack(push, 1)
|
||||
#endif
|
||||
|
||||
typedef struct smart_control_set_mode_erg_data
|
||||
{
|
||||
uint8_t bytes[5];
|
||||
}
|
||||
#ifndef _MSC_VER
|
||||
__attribute__((packed))
|
||||
#endif
|
||||
smart_control_set_mode_erg_data;
|
||||
|
||||
typedef struct smart_control_set_mode_fluid_data
|
||||
{
|
||||
uint8_t bytes[4];
|
||||
}
|
||||
#ifndef _MSC_VER
|
||||
__attribute__((packed))
|
||||
#endif
|
||||
smart_control_set_mode_fluid_data;
|
||||
|
||||
typedef struct smart_control_set_mode_brake_data
|
||||
{
|
||||
uint8_t bytes[5];
|
||||
}
|
||||
#ifndef _MSC_VER
|
||||
__attribute__((packed))
|
||||
#endif
|
||||
smart_control_set_mode_brake_data;
|
||||
|
||||
typedef struct smart_control_set_mode_simulation_data
|
||||
{
|
||||
uint8_t bytes[13];
|
||||
}
|
||||
#ifndef _MSC_VER
|
||||
__attribute__((packed))
|
||||
#endif
|
||||
smart_control_set_mode_simulation_data;
|
||||
|
||||
typedef struct smart_control_calibration_command_data
|
||||
{
|
||||
uint8_t bytes[4];
|
||||
}
|
||||
#ifndef _MSC_VER
|
||||
__attribute__((packed))
|
||||
#endif
|
||||
smart_control_calibration_command_data;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma pack(pop)
|
||||
#endif
|
||||
|
||||
/*!
|
||||
Creates the Command to put the Resistance Unit into ERG mode with a target wattage.
|
||||
|
||||
@param targetWatts The target wattage the RU should try to maintain by adjusting the brake position
|
||||
|
||||
@return Write the bytes of the struct to the Control Point Characteristic (w/ response)
|
||||
*/
|
||||
smart_control_set_mode_erg_data smart_control_set_mode_erg_command(uint16_t targetWatts);
|
||||
|
||||
/*!
|
||||
Creates the Command to put the Resistance Unit into a "Fluid" mode, mimicking a fluid trainer.
|
||||
This mode is a simplified interface for the Simulation Mode, where:
|
||||
Rider + Bike weight is 85kg
|
||||
Rolling Coeff is 0.004
|
||||
Wind Resistance is 0.60
|
||||
Grade is equal to the "level" parameter
|
||||
Wind Speed is 0.0
|
||||
|
||||
@param level Difficulty level (0-9) the RU should apply (simulated grade %)
|
||||
|
||||
@return Write the bytes of the struct to the Control Point Characteristic (w/ response)
|
||||
*/
|
||||
smart_control_set_mode_fluid_data smart_control_set_mode_fluid_command(uint8_t level);
|
||||
|
||||
/*!
|
||||
Creates the Command to put the Resistance Unit Brake at a specific position (as a percent).
|
||||
|
||||
@param percent Percent (0-1) of brake resistance to apply.
|
||||
|
||||
@return Write the bytes of the struct to the Control Point Characteristic (w/ response)
|
||||
*/
|
||||
smart_control_set_mode_brake_data smart_control_set_mode_brake_command(float percent);
|
||||
|
||||
/*!
|
||||
Creates the Command to put the Resistance Unit into Simulation mode.
|
||||
|
||||
@param weightKG Weight of Rider and Bike in Kilograms (kg)
|
||||
@param rollingCoeff Rolling Resistance Coefficient (0.004 for asphault)
|
||||
@param windCoeff Wind Resistance Coeffienct (0.6 default)
|
||||
@param grade Grade (-45 to 45) of simulated hill
|
||||
@param windSpeedMPS Head or Tail wind speed (meters / second)
|
||||
|
||||
@return Write the bytes of the struct to the Control Point Characteristic (w/ response)
|
||||
*/
|
||||
smart_control_set_mode_simulation_data smart_control_set_mode_simulation_command(float weightKG, float rollingCoeff, float windCoeff, float grade, float windSpeedMPS);
|
||||
|
||||
/*!
|
||||
Creates the Command to start the Calibration Process.
|
||||
|
||||
@param brakeCalibration Calibrates the brake (only needs to be done once, result is stored on unit)
|
||||
|
||||
@return Write the bytes of the struct to the Control Point Characteristic (w/ response)
|
||||
*/
|
||||
smart_control_calibration_command_data smart_control_start_calibration_command(bool brakeCalibration);
|
||||
|
||||
/*!
|
||||
Creates the Command to stop the Calibration Process.
|
||||
This is not necessary if the calibration process is allowed to complete.
|
||||
|
||||
@return Write the bytes of the struct to the Control Point Characteristic (w/ response)
|
||||
*/
|
||||
smart_control_calibration_command_data smart_control_stop_calibration_command(void);
|
||||
|
||||
#endif /* SmartControl_h */
|
||||
@@ -184,8 +184,7 @@ double kineticinroadbike::bikeResistanceToPeloton(double resistance) {
|
||||
}
|
||||
|
||||
void kineticinroadbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic,
|
||||
const QByteArray &newValue) {
|
||||
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
const QByteArray &newValue) {
|
||||
Q_UNUSED(characteristic);
|
||||
QSettings settings;
|
||||
QString heartRateBeltName =
|
||||
@@ -195,25 +194,40 @@ void kineticinroadbike::characteristicChanged(const QLowEnergyCharacteristic &ch
|
||||
|
||||
lastPacket = newValue;
|
||||
|
||||
return;
|
||||
QByteArray encryptedData = newValue;
|
||||
int dataSize = encryptedData.size();
|
||||
|
||||
/*if ((uint8_t)(newValue.at(0)) != 0xf0 && (uint8_t)(newValue.at(1)) != 0xd1)
|
||||
return;*/
|
||||
|
||||
double distance = GetDistanceFromPacket(newValue);
|
||||
|
||||
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
|
||||
.toString()
|
||||
.startsWith(QStringLiteral("Disabled"))) {
|
||||
Cadence = ((uint8_t)newValue.at(10));
|
||||
if (dataSize < 14 || characteristic.uuid() != QBluetoothUuid(QStringLiteral("e9410201-b434-446b-b5cc-36592fc4c724"))) {
|
||||
qDebug() << "Invalid data size";
|
||||
return;
|
||||
}
|
||||
|
||||
smart_control_power_data pD = smart_control_process_power_data((uint8_t *)newValue.data(), dataSize);
|
||||
|
||||
// Set the parsed values to the bike metrics
|
||||
Resistance = pD.targetResistance;
|
||||
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
|
||||
Speed = 0.37497622 * ((double)Cadence.value());
|
||||
Speed = pD.speedKPH;
|
||||
} else {
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
watts(), Inclination.value(), Speed.value(),
|
||||
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
}
|
||||
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
|
||||
.toString()
|
||||
.startsWith(QStringLiteral("Disabled"))) {
|
||||
Cadence = pD.cadenceRPM;
|
||||
}
|
||||
m_watt = pD.power;
|
||||
|
||||
// Debug output
|
||||
qDebug() << "Decrypted values:";
|
||||
qDebug() << " Mode:" << pD.mode;
|
||||
qDebug() << " Resistance:" << pD.targetResistance;
|
||||
qDebug() << " Power:" << pD.power << "watts";
|
||||
qDebug() << " Speed:" << pD.speedKPH << "km/h";
|
||||
qDebug() << " Cadence:" << pD.cadenceRPM << "rpm";
|
||||
|
||||
if (watts())
|
||||
KCal +=
|
||||
((((0.048 * ((double)watts()) + 1.19) *
|
||||
@@ -222,6 +236,7 @@ void kineticinroadbike::characteristicChanged(const QLowEnergyCharacteristic &ch
|
||||
(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() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
|
||||
@@ -255,16 +270,13 @@ void kineticinroadbike::characteristicChanged(const QLowEnergyCharacteristic &ch
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// these useless lines are needed to calculate the AVG resistance and AVG peloton resistance since
|
||||
// echelon just send the resistance values when it changes
|
||||
Resistance = Resistance.value();
|
||||
m_pelotonResistance = m_pelotonResistance.value();
|
||||
|
||||
qDebug() << QStringLiteral("Current Local elapsed: ") + GetElapsedFromPacket(newValue).toString();
|
||||
qDebug() << QStringLiteral("Current Speed: ") + QString::number(Speed.value());
|
||||
qDebug() << QStringLiteral("Current Calculate Distance: ") + QString::number(Distance.value());
|
||||
qDebug() << QStringLiteral("Current Cadence: ") + QString::number(Cadence.value());
|
||||
qDebug() << QStringLiteral("Current Distance: ") + QString::number(distance);
|
||||
qDebug() << QStringLiteral("Current Distance: ") + QString::number(Distance.value());
|
||||
qDebug() << QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs);
|
||||
qDebug() << QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime);
|
||||
qDebug() << QStringLiteral("Current Watt: ") + QString::number(watts());
|
||||
@@ -317,8 +329,10 @@ void kineticinroadbike::btinit() {
|
||||
}
|
||||
|
||||
void kineticinroadbike::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
QBluetoothUuid _gattWriteCharacteristicId(QStringLiteral("E9410102-B434-446B-B5CC-36592FC4C724"));
|
||||
QBluetoothUuid _gattNotify1CharacteristicId(QStringLiteral("E9410101-B434-446B-B5CC-36592FC4C724"));
|
||||
QBluetoothUuid _gattWriteCharacteristicId(QStringLiteral("e9410203-b434-446b-b5cc-36592fc4c724"));
|
||||
QBluetoothUuid _gattNotify1CharacteristicId(QStringLiteral("e9410201-b434-446b-b5cc-36592fc4c724"));
|
||||
QBluetoothUuid _gattNotify2CharacteristicId(QStringLiteral("e9410202-b434-446b-b5cc-36592fc4c724"));
|
||||
QBluetoothUuid _gattNotify3CharacteristicId(QStringLiteral("e9410204-b434-446b-b5cc-36592fc4c724"));
|
||||
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
|
||||
qDebug() << QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state));
|
||||
@@ -326,8 +340,18 @@ void kineticinroadbike::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
if (state == QLowEnergyService::ServiceDiscovered) {
|
||||
// qDebug() << gattCommunicationChannelService->characteristics();
|
||||
|
||||
if (gattCommunicationChannelService->state() == QLowEnergyService::ServiceDiscovered) {
|
||||
// establish hook into notifications
|
||||
auto characteristics_list = gattCommunicationChannelService->characteristics();
|
||||
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
|
||||
qDebug() << QStringLiteral("char uuid") << c.uuid() << QStringLiteral("handle") << c.handle() << c.properties();
|
||||
}
|
||||
}
|
||||
|
||||
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
|
||||
gattNotify1Characteristic = gattCommunicationChannelService->characteristic(_gattNotify1CharacteristicId);
|
||||
gattNotify2Characteristic = gattCommunicationChannelService->characteristic(_gattNotify2CharacteristicId);
|
||||
gattNotify3Characteristic = gattCommunicationChannelService->characteristic(_gattNotify3CharacteristicId);
|
||||
Q_ASSERT(gattWriteCharacteristic.isValid());
|
||||
Q_ASSERT(gattNotify1Characteristic.isValid());
|
||||
|
||||
@@ -392,6 +416,10 @@ void kineticinroadbike::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
descriptor.append((char)0x00);
|
||||
gattCommunicationChannelService->writeDescriptor(
|
||||
gattNotify1Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
|
||||
gattCommunicationChannelService->writeDescriptor(
|
||||
gattNotify2Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
|
||||
gattCommunicationChannelService->writeDescriptor(
|
||||
gattNotify3Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,7 +439,7 @@ void kineticinroadbike::characteristicWritten(const QLowEnergyCharacteristic &ch
|
||||
void kineticinroadbike::serviceScanDone(void) {
|
||||
qDebug() << QStringLiteral("serviceScanDone");
|
||||
|
||||
QBluetoothUuid _gattCommunicationChannelServiceId(QStringLiteral("E9410100-B434-446B-B5CC-36592FC4C724"));
|
||||
QBluetoothUuid _gattCommunicationChannelServiceId(QStringLiteral("e9410200-b434-446b-b5cc-36592fc4c724"));
|
||||
|
||||
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
#include "ios/lockscreen.h"
|
||||
#endif
|
||||
|
||||
#include "SmartControl.h"
|
||||
|
||||
class kineticinroadbike : public bike {
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -61,6 +63,8 @@ class kineticinroadbike : public bike {
|
||||
QLowEnergyService *gattCommunicationChannelService = nullptr;
|
||||
QLowEnergyCharacteristic gattWriteCharacteristic;
|
||||
QLowEnergyCharacteristic gattNotify1Characteristic;
|
||||
QLowEnergyCharacteristic gattNotify2Characteristic;
|
||||
QLowEnergyCharacteristic gattNotify3Characteristic;
|
||||
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
|
||||
400
src/devices/lifespantreadmill/lifespantreadmill.cpp
Normal file
400
src/devices/lifespantreadmill/lifespantreadmill.cpp
Normal file
@@ -0,0 +1,400 @@
|
||||
#include "lifespantreadmill.h"
|
||||
#include "keepawakehelper.h"
|
||||
#include "virtualdevices/virtualtreadmill.h"
|
||||
#include <QBluetoothLocalDevice>
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
#include <QMetaEnum>
|
||||
#include <QSettings>
|
||||
#include <chrono>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
lifespantreadmill::lifespantreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService,
|
||||
double forceInitSpeed, double forceInitInclination) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
this->noConsole = noConsole;
|
||||
this->noHeartService = noHeartService;
|
||||
this->pollDeviceTime = pollDeviceTime;
|
||||
|
||||
if (forceInitSpeed > 0)
|
||||
lastSpeed = forceInitSpeed;
|
||||
|
||||
if (forceInitInclination > 0)
|
||||
lastInclination = forceInitInclination;
|
||||
|
||||
refresh = new QTimer(this);
|
||||
connect(refresh, &QTimer::timeout, this, &lifespantreadmill::update);
|
||||
refresh->start(500ms);
|
||||
}
|
||||
|
||||
void lifespantreadmill::writeCharacteristic(uint8_t* data, uint8_t data_len, const QString& info, bool disable_log,
|
||||
bool wait_for_response) {
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
|
||||
QByteArray command((const char*)data, data_len);
|
||||
lastPacket = command;
|
||||
|
||||
// Determine command type from packet
|
||||
if (command.startsWith(QByteArray::fromHex("A182"))) {
|
||||
currentCommand = CommandState::QuerySpeed;
|
||||
} else if (command.startsWith(QByteArray::fromHex("A185"))) {
|
||||
currentCommand = CommandState::QueryDistance;
|
||||
} else if (command.startsWith(QByteArray::fromHex("A187"))) {
|
||||
currentCommand = CommandState::QueryCalories;
|
||||
} else if (command.startsWith(QByteArray::fromHex("A189"))) {
|
||||
currentCommand = CommandState::QueryTime;
|
||||
} else if (command.startsWith(QByteArray::fromHex("D0"))) {
|
||||
currentCommand = CommandState::SetSpeed;
|
||||
} else if (command.startsWith(QByteArray::fromHex("E1"))) {
|
||||
currentCommand = CommandState::Start;
|
||||
} else if (command.startsWith(QByteArray::fromHex("E0"))) {
|
||||
currentCommand = CommandState::Stop;
|
||||
} else if (command.startsWith(QByteArray::fromHex("A188"))) {
|
||||
currentCommand = CommandState::QuerySteps;
|
||||
}
|
||||
|
||||
if (wait_for_response) {
|
||||
connect(this, &lifespantreadmill::packetReceived, &loop, &QEventLoop::quit);
|
||||
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
|
||||
} else {
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, &loop, &QEventLoop::quit);
|
||||
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
|
||||
}
|
||||
|
||||
if (writeBuffer) {
|
||||
delete writeBuffer;
|
||||
}
|
||||
writeBuffer = new QByteArray((const char *)data, data_len);
|
||||
|
||||
if (gattWriteCharacteristic.properties() & QLowEnergyCharacteristic::WriteNoResponse) {
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer,
|
||||
QLowEnergyService::WriteWithoutResponse);
|
||||
} else {
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer);
|
||||
}
|
||||
|
||||
if (!disable_log)
|
||||
qDebug() << " >> " << writeBuffer->toHex(' ') << " // " << info;
|
||||
|
||||
loop.exec();
|
||||
}
|
||||
|
||||
void lifespantreadmill::btinit(bool startTape) {
|
||||
const QByteArray initSequence[] = {
|
||||
QByteArray::fromHex("0200000000"), // Unknown
|
||||
QByteArray::fromHex("C200000000"), // Firmware
|
||||
QByteArray::fromHex("E9FF000000"), // Zeroes
|
||||
QByteArray::fromHex("E400F40000") // Zeroes
|
||||
};
|
||||
|
||||
for (const auto& cmd : initSequence) {
|
||||
writeCharacteristic((uint8_t*)cmd.data(), cmd.size(), QStringLiteral("init"), false, true);
|
||||
}
|
||||
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
double lifespantreadmill::GetSpeedFromPacket(const QByteArray& packet) {
|
||||
if (packet.length() < 4) return 0.0;
|
||||
return ((double)((uint16_t)((uint8_t)packet.at(2)) + ((uint16_t)((uint8_t)packet.at(3))) / 100.0));
|
||||
}
|
||||
|
||||
double lifespantreadmill::GetInclinationFromPacket(const QByteArray& packet) {
|
||||
if (packet.length() < 3) return 0.0;
|
||||
return packet[2];
|
||||
}
|
||||
|
||||
double lifespantreadmill::GetKcalFromPacket(const QByteArray& packet) {
|
||||
if (packet.length() < 4) return 0.0;
|
||||
return (packet[2] << 8) | packet[3];
|
||||
}
|
||||
|
||||
double lifespantreadmill::GetDistanceFromPacket(const QByteArray& packet) {
|
||||
if (packet.length() < 4) return 0.0;
|
||||
double data = ((packet[2] << 8) | packet[3]) / 10.0;
|
||||
return data;
|
||||
}
|
||||
|
||||
void lifespantreadmill::forceSpeed(double requestSpeed) {
|
||||
uint16_t speed_int = (uint16_t)(requestSpeed * 100);
|
||||
uint8_t units = speed_int / 100;
|
||||
uint8_t hundredths = speed_int % 100;
|
||||
uint8_t cmd[] = {0xd0, units, hundredths, 0, 0};
|
||||
writeCharacteristic(cmd, sizeof(cmd), QStringLiteral("set speed"), false, true);
|
||||
}
|
||||
|
||||
void lifespantreadmill::forceIncline(double requestIncline) {
|
||||
// Not implemented for this model
|
||||
}
|
||||
|
||||
void lifespantreadmill::updateDisplay(uint16_t elapsed) {
|
||||
// Not implemented for this model
|
||||
}
|
||||
|
||||
void lifespantreadmill::changeInclinationRequested(double grade, double percentage) {
|
||||
if (percentage < 0)
|
||||
percentage = 0;
|
||||
changeInclination(grade, percentage);
|
||||
}
|
||||
|
||||
uint32_t lifespantreadmill::GetStepsFromPacket(const QByteArray& packet) {
|
||||
if (packet.length() < 4) return 0;
|
||||
return (packet[2] << 8) | packet[3];
|
||||
}
|
||||
|
||||
void lifespantreadmill::update() {
|
||||
if (m_control->state() == QLowEnergyController::UnconnectedState) {
|
||||
emit disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
static uint8_t queue = 0;
|
||||
|
||||
qDebug() << m_control->state() << bluetoothDevice.isValid() << gattCommunicationChannelService
|
||||
<< gattWriteCharacteristic.isValid() << initDone << requestSpeed << requestInclination;
|
||||
|
||||
if (initRequest) {
|
||||
initRequest = false;
|
||||
btinit((lastSpeed > 0 ? true : false));
|
||||
} else if (bluetoothDevice.isValid() && m_control->state() == QLowEnergyController::DiscoveredState &&
|
||||
gattCommunicationChannelService && gattWriteCharacteristic.isValid() && initDone) {
|
||||
QSettings settings;
|
||||
// ******************************************* virtual treadmill init *************************************
|
||||
if (!firstInit && !this->hasVirtualDevice()) {
|
||||
bool virtual_device_enabled =
|
||||
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
|
||||
if (virtual_device_enabled) {
|
||||
emit debug(QStringLiteral("creating virtual treadmill interface..."));
|
||||
auto virtualTreadMill = new virtualtreadmill(this, noHeartService);
|
||||
connect(virtualTreadMill, &virtualtreadmill::debug, this, &lifespantreadmill::debug);
|
||||
connect(virtualTreadMill, &virtualtreadmill::changeInclination, this,
|
||||
&lifespantreadmill::changeInclinationRequested);
|
||||
this->setVirtualDevice(virtualTreadMill, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
firstInit = 1;
|
||||
}
|
||||
}
|
||||
// ********************************************************************************************************
|
||||
|
||||
if(queue == 0) {
|
||||
// Query metrics periodically
|
||||
uint8_t speedQuery[] = {0xA1, 0x82, 0x00, 0x00, 0x00};
|
||||
writeCharacteristic(speedQuery, sizeof(speedQuery), QStringLiteral("query speed"), false, true);
|
||||
queue = 1;
|
||||
} else {
|
||||
uint8_t stepQuery[] = {0xA1, 0x88, 0x00, 0x00, 0x00};
|
||||
writeCharacteristic(stepQuery, sizeof(stepQuery), QStringLiteral("query steps"), false, true);
|
||||
queue = 0;
|
||||
}
|
||||
|
||||
if (requestStart != -1) {
|
||||
uint8_t start[] = {0xE1, 0x00, 0x00, 0x00, 0x00};
|
||||
writeCharacteristic(start, sizeof(start), QStringLiteral("start"), false, true);
|
||||
requestStart = -1;
|
||||
emit tapeStarted();
|
||||
}
|
||||
|
||||
if (requestStop != -1 || requestPause != -1) {
|
||||
uint8_t stop[] = {0xE0, 0x00, 0x00, 0x00, 0x00};
|
||||
writeCharacteristic(stop, sizeof(stop), QStringLiteral("stop"), false, true);
|
||||
requestStop = -1;
|
||||
requestPause = -1;
|
||||
}
|
||||
|
||||
update_metrics(true, watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()));
|
||||
}
|
||||
}
|
||||
|
||||
void lifespantreadmill::characteristicChanged(const QLowEnergyCharacteristic& characteristic,
|
||||
const QByteArray& newValue) {
|
||||
QSettings settings;
|
||||
QByteArray value = newValue;
|
||||
qDebug() << " << " << value.length() << value.toHex(' ') << (int)currentCommand;
|
||||
|
||||
double speed = 0.0;
|
||||
switch(currentCommand) {
|
||||
case CommandState::QuerySpeed:
|
||||
speed = GetSpeedFromPacket(value);
|
||||
if (Speed.value() != speed) {
|
||||
emit speedChanged(speed);
|
||||
}
|
||||
emit debug(QStringLiteral("Current speed: ") + QString::number(speed));
|
||||
Speed = speed;
|
||||
if (speed > 0) {
|
||||
lastSpeed = speed;
|
||||
}
|
||||
break;
|
||||
case CommandState::QueryDistance:
|
||||
Distance = GetDistanceFromPacket(value);
|
||||
break;
|
||||
case CommandState::QueryCalories:
|
||||
KCal = GetKcalFromPacket(value);
|
||||
break;
|
||||
case CommandState::QuerySteps:
|
||||
{
|
||||
uint32_t newSteps = GetStepsFromPacket(value);
|
||||
if (uint32_t(StepCount.value()) != newSteps) {
|
||||
StepCount = newSteps;
|
||||
emit debug(QStringLiteral("Current steps: ") + QString::number(StepCount.value()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!firstCharacteristicChanged) {
|
||||
if (watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) {
|
||||
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)lastTimeCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
|
||||
}
|
||||
|
||||
Distance += ((Speed.value() / 3600.0) /
|
||||
(1000.0 / (lastTimeCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
|
||||
}
|
||||
|
||||
update_hr_from_external();
|
||||
|
||||
cadenceFromAppleWatch();
|
||||
|
||||
lastTimeCharacteristicChanged = QDateTime::currentDateTime();
|
||||
firstCharacteristicChanged = false;
|
||||
currentCommand = CommandState::None;
|
||||
emit packetReceived();
|
||||
}
|
||||
|
||||
bool lifespantreadmill::connected() {
|
||||
return initDone;
|
||||
}
|
||||
|
||||
double lifespantreadmill::minStepInclination() {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
bool lifespantreadmill::autoPauseWhenSpeedIsZero() {
|
||||
return lastStart == 0 || QDateTime::currentMSecsSinceEpoch() > (lastStart + 10000);
|
||||
}
|
||||
|
||||
bool lifespantreadmill::autoStartWhenSpeedIsGreaterThenZero() {
|
||||
return (lastStop == 0 || QDateTime::currentMSecsSinceEpoch() > (lastStop + 25000)) && requestStop == -1;
|
||||
}
|
||||
|
||||
// Direct copy of Bowflex Bluetooth compatibility functions
|
||||
void lifespantreadmill::serviceDiscovered(const QBluetoothUuid& gatt) {
|
||||
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString());
|
||||
}
|
||||
|
||||
void lifespantreadmill::serviceScanDone(void) {
|
||||
emit debug(QStringLiteral("serviceScanDone"));
|
||||
|
||||
QBluetoothUuid _gattCommunicationChannelServiceId((uint16_t)0xfff0);
|
||||
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
|
||||
if (gattCommunicationChannelService == nullptr) {
|
||||
qDebug() << "WRONG SERVICE";
|
||||
return;
|
||||
}
|
||||
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,
|
||||
&lifespantreadmill::stateChanged);
|
||||
gattCommunicationChannelService->discoverDetails();
|
||||
}
|
||||
|
||||
void lifespantreadmill::characteristicWritten(const QLowEnergyCharacteristic& characteristic,
|
||||
const QByteArray& newValue) {
|
||||
Q_UNUSED(characteristic);
|
||||
emit debug(QStringLiteral("characteristicWritten ") + newValue.toHex(' '));
|
||||
}
|
||||
|
||||
void lifespantreadmill::descriptorWritten(const QLowEnergyDescriptor& descriptor,
|
||||
const QByteArray& newValue) {
|
||||
emit debug(QStringLiteral("descriptorWritten ") + descriptor.name() + " " + newValue.toHex(' '));
|
||||
initRequest = true;
|
||||
|
||||
emit connectedAndDiscovered();
|
||||
}
|
||||
|
||||
void lifespantreadmill::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
|
||||
emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
|
||||
if (state == QLowEnergyService::ServiceDiscovered) {
|
||||
QBluetoothUuid _gattWriteCharacteristicId((uint16_t)0xfff2);
|
||||
QBluetoothUuid _gattNotifyCharacteristicId((uint16_t)0xfff1);
|
||||
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
|
||||
gattNotify1Characteristic = gattCommunicationChannelService->characteristic(_gattNotifyCharacteristicId);
|
||||
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this,
|
||||
&lifespantreadmill::characteristicChanged);
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this,
|
||||
&lifespantreadmill::characteristicWritten);
|
||||
connect(gattCommunicationChannelService,
|
||||
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
|
||||
this, &lifespantreadmill::errorService);
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this,
|
||||
&lifespantreadmill::descriptorWritten);
|
||||
|
||||
QByteArray descriptor;
|
||||
descriptor.append((char)0x01);
|
||||
descriptor.append((char)0x00);
|
||||
gattCommunicationChannelService->writeDescriptor(
|
||||
gattNotify1Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
void lifespantreadmill::controllerStateChanged(QLowEnergyController::ControllerState state) {
|
||||
qDebug() << QStringLiteral("controllerStateChanged") << state;
|
||||
if (state == QLowEnergyController::UnconnectedState) {
|
||||
Speed = 0;
|
||||
emit debug(QStringLiteral("Current speed: ") + QString::number(Speed.value()));
|
||||
initDone = false;
|
||||
}
|
||||
}
|
||||
|
||||
void lifespantreadmill::errorService(QLowEnergyService::ServiceError err) {
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
|
||||
emit debug(QStringLiteral("errorService ") + QString::fromLocal8Bit(metaEnum.valueToKey(err)));
|
||||
}
|
||||
|
||||
void lifespantreadmill::error(QLowEnergyController::Error err) {
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
|
||||
emit debug(QStringLiteral("error ") + QString::fromLocal8Bit(metaEnum.valueToKey(err)));
|
||||
}
|
||||
|
||||
void lifespantreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
|
||||
device.address().toString() + ')');
|
||||
{
|
||||
bluetoothDevice = device;
|
||||
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
|
||||
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &lifespantreadmill::serviceDiscovered);
|
||||
connect(m_control, &QLowEnergyController::discoveryFinished, this, &lifespantreadmill::serviceScanDone);
|
||||
connect(m_control,
|
||||
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
|
||||
this, &lifespantreadmill::error);
|
||||
connect(m_control, &QLowEnergyController::stateChanged, this, &lifespantreadmill::controllerStateChanged);
|
||||
|
||||
connect(m_control,
|
||||
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
|
||||
this, [this](QLowEnergyController::Error error) {
|
||||
Q_UNUSED(error);
|
||||
Q_UNUSED(this);
|
||||
emit debug(QStringLiteral("Cannot connect to remote device."));
|
||||
emit disconnected();
|
||||
});
|
||||
connect(m_control, &QLowEnergyController::connected, this, [this]() {
|
||||
Q_UNUSED(this);
|
||||
emit debug(QStringLiteral("Controller connected. Search services..."));
|
||||
m_control->discoverServices();
|
||||
});
|
||||
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
|
||||
Q_UNUSED(this);
|
||||
emit debug(QStringLiteral("LowEnergy controller disconnected"));
|
||||
emit disconnected();
|
||||
});
|
||||
|
||||
m_control->connectToDevice();
|
||||
return;
|
||||
}
|
||||
}
|
||||
110
src/devices/lifespantreadmill/lifespantreadmill.h
Normal file
110
src/devices/lifespantreadmill/lifespantreadmill.h
Normal file
@@ -0,0 +1,110 @@
|
||||
#ifndef LIFESPANTREADMILL_H
|
||||
#define LIFESPANTREADMILL_H
|
||||
|
||||
#include <QBluetoothDeviceDiscoveryAgent>
|
||||
#include <QtBluetooth/qlowenergyadvertisingdata.h>
|
||||
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristic.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristicdata.h>
|
||||
#include <QtBluetooth/qlowenergycontroller.h>
|
||||
#include <QtBluetooth/qlowenergydescriptordata.h>
|
||||
#include <QtBluetooth/qlowenergyservice.h>
|
||||
#include <QtBluetooth/qlowenergyservicedata.h>
|
||||
#include <QtCore/qbytearray.h>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#else
|
||||
#include <QtGui/qguiapplication.h>
|
||||
#endif
|
||||
#include <QtCore/qlist.h>
|
||||
#include <QtCore/qmutex.h>
|
||||
#include <QtCore/qscopedpointer.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
|
||||
#include "treadmill.h"
|
||||
|
||||
class lifespantreadmill : public treadmill {
|
||||
Q_OBJECT
|
||||
public:
|
||||
lifespantreadmill(uint32_t poolDeviceTime = 200, bool noConsole = false, bool noHeartService = false,
|
||||
double forceInitSpeed = 0.0, double forceInitInclination = 0.0);
|
||||
bool connected() override;
|
||||
double minStepInclination() override;
|
||||
bool autoPauseWhenSpeedIsZero() override;
|
||||
bool autoStartWhenSpeedIsGreaterThenZero() override;
|
||||
bool canHandleSpeedChange() override { return false; }
|
||||
bool canHandleInclineChange() override { return false; }
|
||||
|
||||
private:
|
||||
double GetSpeedFromPacket(const QByteArray &packet);
|
||||
double GetInclinationFromPacket(const QByteArray &packet);
|
||||
double GetKcalFromPacket(const QByteArray &packet);
|
||||
double GetDistanceFromPacket(const QByteArray &packet);
|
||||
void forceSpeed(double requestSpeed);
|
||||
void forceIncline(double requestIncline);
|
||||
void updateDisplay(uint16_t elapsed);
|
||||
void btinit(bool startTape);
|
||||
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
|
||||
bool wait_for_response = false);
|
||||
void startDiscover();
|
||||
bool noConsole = false;
|
||||
bool noHeartService = false;
|
||||
uint32_t GetStepsFromPacket(const QByteArray& packet);
|
||||
uint32_t pollDeviceTime = 200;
|
||||
uint8_t sec1Update = 0;
|
||||
uint8_t firstInit = 0;
|
||||
QByteArray lastPacket;
|
||||
QDateTime lastTimeCharacteristicChanged;
|
||||
bool firstCharacteristicChanged = true;
|
||||
|
||||
QTimer *refresh;
|
||||
|
||||
QLowEnergyService *gattCommunicationChannelService = nullptr;
|
||||
QLowEnergyCharacteristic gattWriteCharacteristic;
|
||||
QLowEnergyCharacteristic gattNotify1Characteristic;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
enum class CommandState {
|
||||
None,
|
||||
QuerySpeed,
|
||||
QueryDistance,
|
||||
QueryCalories,
|
||||
QueryTime,
|
||||
QuerySteps,
|
||||
SetSpeed,
|
||||
Start,
|
||||
Stop
|
||||
};
|
||||
CommandState currentCommand = CommandState::None;
|
||||
|
||||
Q_SIGNALS:
|
||||
void disconnected();
|
||||
void debug(QString string);
|
||||
void speedChanged(double speed);
|
||||
void packetReceived();
|
||||
|
||||
public slots:
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
|
||||
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 changeInclinationRequested(double grade, double percentage);
|
||||
|
||||
void serviceDiscovered(const QBluetoothUuid &gatt);
|
||||
void serviceScanDone(void);
|
||||
void update();
|
||||
void error(QLowEnergyController::Error err);
|
||||
void errorService(QLowEnergyService::ServiceError);
|
||||
};
|
||||
|
||||
#endif // LIFESPANTREADMILL_H
|
||||
@@ -264,7 +264,7 @@ void nordictrackifitadbbike::processPendingDatagrams() {
|
||||
if(freemotion_coachbike_b22_7)
|
||||
m_pelotonResistance = (100 / 24) * resistance;
|
||||
else
|
||||
m_pelotonResistance = (100 / 32) * resistance;
|
||||
m_pelotonResistance = bikeResistanceToPeloton(resistance);
|
||||
qDebug() << QStringLiteral("Current Peloton Resistance: ") << m_pelotonResistance.value()
|
||||
<< resistance;
|
||||
if(!gearsAvailable && !nordictrackadbbike_resistance) {
|
||||
@@ -497,56 +497,88 @@ void nordictrackifitadbbike::onHRM(int hrm) {
|
||||
}
|
||||
}
|
||||
|
||||
resistance_t nordictrackifitadbbike::pelotonToBikeResistance(int pelotonResistance) {
|
||||
if (pelotonResistance <= 10) {
|
||||
double nordictrackifitadbbike::bikeResistanceToPeloton(resistance_t bikeResistance) {
|
||||
for (resistance_t i = 1; i < max_resistance; i++) {
|
||||
if (pelotonToBikeResistance(i) <= bikeResistance && pelotonToBikeResistance(i + 1) > bikeResistance) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
if (bikeResistance < pelotonToBikeResistance(1))
|
||||
return 1;
|
||||
else
|
||||
return 100;
|
||||
}
|
||||
|
||||
resistance_t nordictrackifitadbbike::pelotonToBikeResistance(int pelotonResistance) {
|
||||
QSettings settings;
|
||||
int resistanceLevel;
|
||||
|
||||
if (pelotonResistance <= 5) {
|
||||
resistanceLevel = 1;
|
||||
}
|
||||
if (pelotonResistance <= 20) {
|
||||
return 2;
|
||||
else if (pelotonResistance <= 7) {
|
||||
resistanceLevel = 2;
|
||||
}
|
||||
if (pelotonResistance <= 25) {
|
||||
return 3;
|
||||
else if (pelotonResistance <= 9) {
|
||||
resistanceLevel = 3;
|
||||
}
|
||||
if (pelotonResistance <= 30) {
|
||||
return 4;
|
||||
else if (pelotonResistance <= 10) {
|
||||
resistanceLevel = 4;
|
||||
}
|
||||
if (pelotonResistance <= 35) {
|
||||
return 5;
|
||||
else if (pelotonResistance <= 15) {
|
||||
resistanceLevel = 5;
|
||||
}
|
||||
if (pelotonResistance <= 40) {
|
||||
return 6;
|
||||
else if (pelotonResistance <= 25) {
|
||||
resistanceLevel = 6;
|
||||
}
|
||||
if (pelotonResistance <= 45) {
|
||||
return 7;
|
||||
else if (pelotonResistance <= 30) {
|
||||
resistanceLevel = 7;
|
||||
}
|
||||
if (pelotonResistance <= 50) {
|
||||
return 8;
|
||||
else if (pelotonResistance <= 35) {
|
||||
resistanceLevel = 8;
|
||||
}
|
||||
if (pelotonResistance <= 55) {
|
||||
return 9;
|
||||
else if (pelotonResistance <= 40) {
|
||||
resistanceLevel = 9;
|
||||
}
|
||||
if (pelotonResistance <= 60) {
|
||||
return 10;
|
||||
else if (pelotonResistance <= 45) {
|
||||
resistanceLevel = 10;
|
||||
}
|
||||
if (pelotonResistance <= 65) {
|
||||
return 11;
|
||||
else if (pelotonResistance <= 50) {
|
||||
resistanceLevel = 11;
|
||||
}
|
||||
if (pelotonResistance <= 70) {
|
||||
return 12;
|
||||
else if (pelotonResistance <= 55) {
|
||||
resistanceLevel = 12;
|
||||
}
|
||||
if (pelotonResistance <= 75) {
|
||||
return 13;
|
||||
else if (pelotonResistance <= 60) {
|
||||
resistanceLevel = 13;
|
||||
}
|
||||
if (pelotonResistance <= 80) {
|
||||
return 14;
|
||||
else if (pelotonResistance <= 65) {
|
||||
resistanceLevel = 14;
|
||||
}
|
||||
if (pelotonResistance <= 85) {
|
||||
return 15;
|
||||
else if (pelotonResistance <= 70) {
|
||||
resistanceLevel = 15;
|
||||
}
|
||||
if (pelotonResistance <= 100) {
|
||||
return 16;
|
||||
else if (pelotonResistance <= 75) {
|
||||
resistanceLevel = 16;
|
||||
}
|
||||
return Resistance.value();
|
||||
else if (pelotonResistance <= 80) {
|
||||
resistanceLevel = 17;
|
||||
}
|
||||
else if (pelotonResistance <= 85) {
|
||||
resistanceLevel = 18;
|
||||
}
|
||||
else if (pelotonResistance <= 95) {
|
||||
resistanceLevel = 19;
|
||||
}
|
||||
else if (pelotonResistance <= 100) {
|
||||
resistanceLevel = 20;
|
||||
}
|
||||
else {
|
||||
return Resistance.value();
|
||||
}
|
||||
|
||||
return (resistanceLevel * settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
|
||||
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
|
||||
}
|
||||
|
||||
void nordictrackifitadbbike::forceResistance(double resistance) {}
|
||||
|
||||
@@ -69,11 +69,12 @@ class nordictrackifitadbbike : public bike {
|
||||
bool ifitCompatible() override;
|
||||
|
||||
private:
|
||||
const resistance_t max_resistance = 17; // max inclination for s22i
|
||||
const resistance_t max_resistance = 20; // max inclination for s22i
|
||||
void forceResistance(double resistance);
|
||||
uint16_t watts() override;
|
||||
double getDouble(QString v);
|
||||
uint16_t wattsFromResistance(double inclination, double cadence);
|
||||
double bikeResistanceToPeloton(resistance_t resistance);
|
||||
|
||||
QTimer *refresh;
|
||||
|
||||
|
||||
464
src/devices/pitpatbike/pitpatbike.cpp
Normal file
464
src/devices/pitpatbike/pitpatbike.cpp
Normal file
@@ -0,0 +1,464 @@
|
||||
#include "pitpatbike.h"
|
||||
#include "homeform.h"
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "keepawakehelper.h"
|
||||
#endif
|
||||
#include "virtualdevices/virtualbike.h"
|
||||
#include <QBluetoothLocalDevice>
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
#include <QMetaEnum>
|
||||
#include <QSettings>
|
||||
#include <chrono>
|
||||
#include <math.h>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
|
||||
#endif
|
||||
|
||||
pitpatbike::pitpatbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
#endif
|
||||
m_watt.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, &pitpatbike::update);
|
||||
refresh->start(200ms);
|
||||
}
|
||||
|
||||
void pitpatbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
|
||||
bool wait_for_response) {
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
|
||||
// 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(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);
|
||||
}
|
||||
|
||||
if (gattCommunicationChannelService->state() != QLowEnergyService::ServiceState::ServiceDiscovered ||
|
||||
m_control->state() == QLowEnergyController::UnconnectedState) {
|
||||
qDebug() << QStringLiteral("writeCharacteristic error because the connection is closed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gattWriteCharacteristic.isValid()) {
|
||||
qDebug() << QStringLiteral("gattWriteCharacteristic is invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
if (writeBuffer) {
|
||||
delete writeBuffer;
|
||||
}
|
||||
writeBuffer = new QByteArray((const char *)data, data_len);
|
||||
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer);
|
||||
|
||||
if (!disable_log) {
|
||||
qDebug() << QStringLiteral(" >> ") + writeBuffer->toHex(' ') +
|
||||
QStringLiteral(" // ") + info;
|
||||
}
|
||||
|
||||
loop.exec();
|
||||
}
|
||||
|
||||
void pitpatbike::forceResistance(resistance_t requestResistance) {
|
||||
uint8_t noOpData[] = {0x6a, 0x06, 0x51, 0x82, 0x01, 0x01, 0xd5, 0x43};
|
||||
noOpData[5] = requestResistance;
|
||||
|
||||
uint8_t crc = 0;
|
||||
for(int i = 0; i < sizeof(noOpData) - 2; i++) {
|
||||
crc ^= noOpData[i];
|
||||
}
|
||||
noOpData[6] = crc ^ 0x6A;
|
||||
|
||||
writeCharacteristic(noOpData, sizeof(noOpData), QStringLiteral("force resistance"), false, true);
|
||||
}
|
||||
|
||||
void pitpatbike::sendPoll() {
|
||||
uint8_t noOpData[] = {0x6a, 0x05, 0xfd, 0xf8, 0x43};
|
||||
writeCharacteristic(noOpData, sizeof(noOpData), QStringLiteral("noOp"), false, true);
|
||||
}
|
||||
|
||||
void pitpatbike::update() {
|
||||
if (m_control->state() == QLowEnergyController::UnconnectedState) {
|
||||
emit disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
if (initRequest) {
|
||||
initRequest = false;
|
||||
btinit();
|
||||
} else if (bluetoothDevice.isValid() && m_control->state() == QLowEnergyController::DiscoveredState &&
|
||||
gattCommunicationChannelService && gattWriteCharacteristic.isValid() &&
|
||||
gattNotify1Characteristic.isValid() && initDone) {
|
||||
update_metrics(true, watts());
|
||||
|
||||
// sending poll every 2 seconds
|
||||
if (sec1Update++ >= (1000 / refresh->interval())) {
|
||||
sec1Update = 0;
|
||||
sendPoll();
|
||||
// updateDisplay(elapsed);
|
||||
}
|
||||
|
||||
if (requestResistance != -1) {
|
||||
if (requestResistance > max_resistance)
|
||||
requestResistance = max_resistance;
|
||||
else if (requestResistance <= 0)
|
||||
requestResistance = 1;
|
||||
|
||||
if (requestResistance != currentResistance().value()) {
|
||||
qDebug() << QStringLiteral("writing resistance ") + QString::number(requestResistance);
|
||||
forceResistance(requestResistance);
|
||||
}
|
||||
requestResistance = -1;
|
||||
}
|
||||
if (requestStart != -1) {
|
||||
qDebug() << QStringLiteral("starting...");
|
||||
|
||||
// btinit();
|
||||
|
||||
requestStart = -1;
|
||||
emit bikeStarted();
|
||||
}
|
||||
if (requestStop != -1) {
|
||||
qDebug() << QStringLiteral("stopping...");
|
||||
// writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
|
||||
requestStop = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pitpatbike::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
qDebug() << QStringLiteral("serviceDiscovered ") + gatt.toString();
|
||||
}
|
||||
|
||||
resistance_t pitpatbike::pelotonToBikeResistance(int pelotonResistance) {
|
||||
for (resistance_t i = 1; i < max_resistance; i++) {
|
||||
if (bikeResistanceToPeloton(i) <= pelotonResistance && bikeResistanceToPeloton(i + 1) > pelotonResistance) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
if (pelotonResistance < bikeResistanceToPeloton(1))
|
||||
return 1;
|
||||
else
|
||||
return max_resistance;
|
||||
}
|
||||
|
||||
double pitpatbike::bikeResistanceToPeloton(double resistance) {
|
||||
QSettings settings;
|
||||
// 0,0097x3 - 0,4972x2 + 10,126x - 37,08
|
||||
double p = ((pow(resistance, 3) * 0.0097) - (0.4972 * pow(resistance, 2)) + (10.126 * resistance) - 37.08);
|
||||
if (p < 0) {
|
||||
p = 0;
|
||||
}
|
||||
return (p * settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
|
||||
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
|
||||
}
|
||||
|
||||
void pitpatbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic,
|
||||
const QByteArray &newValue) {
|
||||
// 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();
|
||||
|
||||
qDebug() << " << " + newValue.toHex(' ');
|
||||
|
||||
lastPacket = newValue;
|
||||
|
||||
if (newValue.length() != 30) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*if ((uint8_t)(newValue.at(0)) != 0xf0 && (uint8_t)(newValue.at(1)) != 0xd1)
|
||||
return;*/
|
||||
|
||||
double distance = GetDistanceFromPacket(newValue);
|
||||
|
||||
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
|
||||
.toString()
|
||||
.startsWith(QStringLiteral("Disabled"))) {
|
||||
Cadence = ((uint8_t)newValue.at(25));
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
m_watt = (uint16_t)((uint8_t)newValue.at(24)) + ((uint16_t)((uint8_t)newValue.at(23)) << 8);
|
||||
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() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
|
||||
if (Cadence.value() > 0) {
|
||||
CrankRevs++;
|
||||
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
|
||||
}
|
||||
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
|
||||
#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(QLatin1String("Disabled"))) {
|
||||
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->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
|
||||
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// these useless lines are needed to calculate the AVG resistance and AVG peloton resistance since
|
||||
// echelon just send the resistance values when it changes
|
||||
Resistance = newValue.at(5);
|
||||
m_pelotonResistance = m_pelotonResistance.value();
|
||||
|
||||
qDebug() << QStringLiteral("Current Local elapsed: ") + GetElapsedFromPacket(newValue).toString();
|
||||
qDebug() << QStringLiteral("Current Speed: ") + QString::number(Speed.value());
|
||||
qDebug() << QStringLiteral("Current Calculate Distance: ") + QString::number(Distance.value());
|
||||
qDebug() << QStringLiteral("Current Cadence: ") + QString::number(Cadence.value());
|
||||
qDebug() << QStringLiteral("Current Distance: ") + QString::number(distance);
|
||||
qDebug() << QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs);
|
||||
qDebug() << QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime);
|
||||
qDebug() << QStringLiteral("Current Watt: ") + QString::number(watts());
|
||||
|
||||
if (m_control->error() != QLowEnergyController::NoError) {
|
||||
qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString();
|
||||
}
|
||||
}
|
||||
|
||||
QTime pitpatbike::GetElapsedFromPacket(const QByteArray &packet) {
|
||||
uint16_t convertedData = (packet.at(3) << 8) | packet.at(4);
|
||||
QTime t(0, convertedData / 60, convertedData % 60);
|
||||
return t;
|
||||
}
|
||||
|
||||
double pitpatbike::GetDistanceFromPacket(const QByteArray &packet) {
|
||||
uint16_t convertedData = (packet.at(7) << 8) | packet.at(8);
|
||||
double data = ((double)convertedData) / 100.0f;
|
||||
return data;
|
||||
}
|
||||
|
||||
void pitpatbike::btinit() {
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
void pitpatbike::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
QBluetoothUuid _gattWriteCharacteristicId((uint16_t)0xfbb1);
|
||||
QBluetoothUuid _gattNotify1CharacteristicId((uint16_t)0xfbb2);
|
||||
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
|
||||
qDebug() << QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state));
|
||||
|
||||
if (state == QLowEnergyService::ServiceDiscovered) {
|
||||
// qDebug() << gattCommunicationChannelService->characteristics();
|
||||
|
||||
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
|
||||
gattNotify1Characteristic = gattCommunicationChannelService->characteristic(_gattNotify1CharacteristicId);
|
||||
Q_ASSERT(gattWriteCharacteristic.isValid());
|
||||
Q_ASSERT(gattNotify1Characteristic.isValid());
|
||||
|
||||
// establish hook into notifications
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this,
|
||||
&pitpatbike::characteristicChanged);
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this,
|
||||
&pitpatbike::characteristicWritten);
|
||||
connect(gattCommunicationChannelService,
|
||||
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
|
||||
this, &pitpatbike::errorService);
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this,
|
||||
&pitpatbike::descriptorWritten);
|
||||
|
||||
// ******************************************* 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();
|
||||
bool virtual_device_rower =
|
||||
settings.value(QZSettings::virtual_device_rower, QZSettings::default_virtual_device_rower).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...");
|
||||
auto virtualRower = new virtualrower(this, noWriteResistance, noHeartService);
|
||||
// connect(virtualRower,&virtualrower::debug ,this,&echelonrower::debug);
|
||||
this->setVirtualDevice(virtualRower, VIRTUAL_DEVICE_MODE::ALTERNATIVE);
|
||||
} else {
|
||||
qDebug() << QStringLiteral("creating virtual bike interface...");
|
||||
auto virtualBike =
|
||||
new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
|
||||
// connect(virtualBike,&virtualbike::debug ,this,&pitpatbike::debug);
|
||||
connect(virtualBike, &virtualbike::changeInclination, this, &pitpatbike::changeInclination);
|
||||
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
}
|
||||
}
|
||||
}
|
||||
firstStateChanged = 1;
|
||||
// ********************************************************************************************************
|
||||
|
||||
QByteArray descriptor;
|
||||
descriptor.append((char)0x01);
|
||||
descriptor.append((char)0x00);
|
||||
gattCommunicationChannelService->writeDescriptor(
|
||||
gattNotify1Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
void pitpatbike::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
|
||||
qDebug() << QStringLiteral("descriptorWritten ") + descriptor.name() + QStringLiteral(" ") + newValue.toHex(' ');
|
||||
|
||||
initRequest = true;
|
||||
emit connectedAndDiscovered();
|
||||
}
|
||||
|
||||
void pitpatbike::characteristicWritten(const QLowEnergyCharacteristic &characteristic,
|
||||
const QByteArray &newValue) {
|
||||
Q_UNUSED(characteristic);
|
||||
qDebug() << QStringLiteral("characteristicWritten ") + newValue.toHex(' ');
|
||||
}
|
||||
|
||||
void pitpatbike::serviceScanDone(void) {
|
||||
qDebug() << QStringLiteral("serviceScanDone");
|
||||
|
||||
QBluetoothUuid _gattCommunicationChannelServiceId((uint16_t)0xfbb0);
|
||||
|
||||
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,
|
||||
&pitpatbike::stateChanged);
|
||||
if(gattCommunicationChannelService != nullptr) {
|
||||
gattCommunicationChannelService->discoverDetails();
|
||||
} else {
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested("Bluetooth Service Error! Restart the bike!");
|
||||
m_control->disconnectFromDevice();
|
||||
}
|
||||
}
|
||||
|
||||
void pitpatbike::errorService(QLowEnergyService::ServiceError err) {
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
|
||||
qDebug() << QStringLiteral("pitpatbike::errorService") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
|
||||
m_control->errorString();
|
||||
}
|
||||
|
||||
void pitpatbike::error(QLowEnergyController::Error err) {
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
|
||||
qDebug() << QStringLiteral("pitpatbike::error") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
|
||||
m_control->errorString();
|
||||
}
|
||||
|
||||
void pitpatbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
qDebug() << QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
|
||||
device.address().toString() + ')';
|
||||
bluetoothDevice = device;
|
||||
|
||||
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
|
||||
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &pitpatbike::serviceDiscovered);
|
||||
connect(m_control, &QLowEnergyController::discoveryFinished, this, &pitpatbike::serviceScanDone);
|
||||
connect(m_control,
|
||||
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
|
||||
this, &pitpatbike::error);
|
||||
connect(m_control, &QLowEnergyController::stateChanged, this, &pitpatbike::controllerStateChanged);
|
||||
|
||||
connect(m_control,
|
||||
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
|
||||
this, [this](QLowEnergyController::Error error) {
|
||||
Q_UNUSED(error);
|
||||
Q_UNUSED(this);
|
||||
qDebug() << QStringLiteral("Cannot connect to remote device.");
|
||||
emit disconnected();
|
||||
});
|
||||
connect(m_control, &QLowEnergyController::connected, this, [this]() {
|
||||
Q_UNUSED(this);
|
||||
qDebug() << QStringLiteral("Controller connected. Search services...");
|
||||
m_control->discoverServices();
|
||||
});
|
||||
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
|
||||
Q_UNUSED(this);
|
||||
qDebug() << QStringLiteral("LowEnergy controller disconnected");
|
||||
emit disconnected();
|
||||
});
|
||||
|
||||
// Connect
|
||||
m_control->connectToDevice();
|
||||
return;
|
||||
}
|
||||
|
||||
bool pitpatbike::connected() {
|
||||
if (!m_control) {
|
||||
return false;
|
||||
}
|
||||
return m_control->state() == QLowEnergyController::DiscoveredState;
|
||||
}
|
||||
|
||||
uint16_t pitpatbike::watts() {
|
||||
if (currentCadence().value() == 0) {
|
||||
return 0;
|
||||
}
|
||||
return m_watt.value();
|
||||
}
|
||||
|
||||
void pitpatbike::controllerStateChanged(QLowEnergyController::ControllerState state) {
|
||||
qDebug() << QStringLiteral("controllerStateChanged") << state;
|
||||
if (state == QLowEnergyController::UnconnectedState && m_control) {
|
||||
lastResistanceBeforeDisconnection = Resistance.value();
|
||||
qDebug() << QStringLiteral("trying to connect back again...");
|
||||
initDone = false;
|
||||
m_control->connectToDevice();
|
||||
}
|
||||
}
|
||||
104
src/devices/pitpatbike/pitpatbike.h
Normal file
104
src/devices/pitpatbike/pitpatbike.h
Normal file
@@ -0,0 +1,104 @@
|
||||
#ifndef PITPATBIKE_H
|
||||
#define PITPATBIKE_H
|
||||
|
||||
#include <QBluetoothDeviceDiscoveryAgent>
|
||||
#include <QtBluetooth/qlowenergyadvertisingdata.h>
|
||||
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristic.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristicdata.h>
|
||||
#include <QtBluetooth/qlowenergycontroller.h>
|
||||
#include <QtBluetooth/qlowenergydescriptordata.h>
|
||||
#include <QtBluetooth/qlowenergyservice.h>
|
||||
#include <QtBluetooth/qlowenergyservicedata.h>
|
||||
#include <QtCore/qbytearray.h>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#else
|
||||
#include <QtGui/qguiapplication.h>
|
||||
#endif
|
||||
#include <QtCore/qlist.h>
|
||||
#include <QtCore/qmutex.h>
|
||||
#include <QtCore/qscopedpointer.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include "devices/bike.h"
|
||||
#include "virtualdevices/virtualbike.h"
|
||||
#include "virtualdevices/virtualrower.h"
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "ios/lockscreen.h"
|
||||
#endif
|
||||
|
||||
class pitpatbike : public bike {
|
||||
Q_OBJECT
|
||||
public:
|
||||
pitpatbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain);
|
||||
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
|
||||
resistance_t maxResistance() override { return max_resistance; }
|
||||
bool connected() override;
|
||||
|
||||
private:
|
||||
const resistance_t max_resistance = 32;
|
||||
double bikeResistanceToPeloton(double resistance);
|
||||
double GetDistanceFromPacket(const QByteArray &packet);
|
||||
QTime GetElapsedFromPacket(const 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 forceResistance(resistance_t requestResistance);
|
||||
void sendPoll();
|
||||
uint16_t watts() override;
|
||||
|
||||
QTimer *refresh;
|
||||
|
||||
QLowEnergyService *gattCommunicationChannelService = nullptr;
|
||||
QLowEnergyCharacteristic gattWriteCharacteristic;
|
||||
QLowEnergyCharacteristic gattNotify1Characteristic;
|
||||
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
uint8_t sec1Update = 0;
|
||||
QByteArray lastPacket;
|
||||
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
uint8_t firstStateChanged = 0;
|
||||
resistance_t lastResistanceBeforeDisconnection = -1;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = false;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
lockscreen *h = 0;
|
||||
#endif
|
||||
|
||||
Q_SIGNALS:
|
||||
void disconnected();
|
||||
|
||||
public slots:
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
|
||||
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 // PITPATBIKE_H
|
||||
@@ -75,9 +75,9 @@ void proformtreadmill::forceIncline(double incline) {
|
||||
write[14] = write[11] + write[12] + 0x11;
|
||||
} else if (proform_treadmill_8_0 || proform_treadmill_705_cst || proform_treadmill_705_cst_V78_239 || proform_treadmill_9_0 || proform_treadmill_se ||
|
||||
proform_treadmill_z1300i || proform_treadmill_l6_0s || norditrack_s25_treadmill || proform_8_5_treadmill || nordictrack_treadmill_exp_5i || proform_2000_treadmill ||
|
||||
proform_treadmill_sport_8_5 || proform_treadmill_505_cst || proform_carbon_tl || proform_proshox2 || nordictrack_s20i_treadmill || proform_595i_proshox2 ||
|
||||
proform_treadmill_sport_8_5 || proform_treadmill_505_cst || proform_505_cst_80_44 || proform_carbon_tl || proform_proshox2 || nordictrack_s20i_treadmill || proform_595i_proshox2 ||
|
||||
proform_treadmill_8_7 || proform_carbon_tl_PFTL59720 || proform_treadmill_sport_70 || proform_treadmill_575i || proform_performance_400i || proform_treadmill_c700 ||
|
||||
proform_treadmill_c960i || nordictrack_tseries5_treadmill || proform_carbon_tl_PFTL59722c
|
||||
proform_treadmill_c960i || nordictrack_tseries5_treadmill || proform_carbon_tl_PFTL59722c || proform_treadmill_1500_pro || proform_trainer_8_0 || proform_treadmill_705_cst_V80_44
|
||||
) {
|
||||
write[14] = write[11] + write[12] + 0x12;
|
||||
} else if (!nordictrack_t65s_treadmill && !nordictrack_s30_treadmill && !nordictrack_s20_treadmill && !nordictrack_t65s_83_treadmill) {
|
||||
@@ -104,9 +104,9 @@ void proformtreadmill::forceSpeed(double speed) {
|
||||
write[14] = write[11] + write[12] + 0x11;
|
||||
} else if (proform_treadmill_8_0 || proform_treadmill_9_0 || proform_treadmill_se || proform_cadence_lt ||
|
||||
proform_treadmill_z1300i || proform_treadmill_l6_0s || norditrack_s25_treadmill || proform_8_5_treadmill || nordictrack_treadmill_exp_5i || proform_2000_treadmill ||
|
||||
proform_treadmill_sport_8_5 || proform_treadmill_505_cst || proform_treadmill_705_cst || proform_treadmill_705_cst_V78_239 || proform_carbon_tl || proform_proshox2 || nordictrack_s20i_treadmill || proform_595i_proshox2 ||
|
||||
proform_treadmill_sport_8_5 || proform_treadmill_505_cst || proform_505_cst_80_44 || proform_treadmill_705_cst || proform_treadmill_705_cst_V78_239 || proform_carbon_tl || proform_proshox2 || nordictrack_s20i_treadmill || proform_595i_proshox2 ||
|
||||
proform_treadmill_8_7 || proform_carbon_tl_PFTL59720 || proform_treadmill_sport_70 || proform_treadmill_575i || proform_performance_400i || proform_treadmill_c700 ||
|
||||
proform_treadmill_c960i || nordictrack_tseries5_treadmill || proform_carbon_tl_PFTL59722c) {
|
||||
proform_treadmill_c960i || nordictrack_tseries5_treadmill || proform_carbon_tl_PFTL59722c || proform_treadmill_1500_pro || proform_trainer_8_0 || proform_treadmill_705_cst_V80_44) {
|
||||
write[14] = write[11] + write[12] + 0x11;
|
||||
} else if (!nordictrack_t65s_treadmill && !nordictrack_s30_treadmill && !nordictrack_s20_treadmill && !nordictrack_t65s_83_treadmill) {
|
||||
for (uint8_t i = 0; i < 7; i++) {
|
||||
@@ -259,6 +259,130 @@ void proformtreadmill::update() {
|
||||
if (counterPoll > 5) {
|
||||
counterPoll = 0;
|
||||
}
|
||||
} else if (proform_treadmill_705_cst_V80_44) {
|
||||
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"));
|
||||
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..."));
|
||||
requestStart = -1;
|
||||
emit tapeStarted();
|
||||
}
|
||||
if (requestStop != -1) {
|
||||
emit debug(QStringLiteral("stopping..."));
|
||||
requestStop = -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
counterPoll++;
|
||||
if (counterPoll > 5) {
|
||||
counterPoll = 0;
|
||||
}
|
||||
} else if (proform_treadmill_1500_pro) {
|
||||
uint8_t noOpData1[] = {0xfe, 0x02, 0x14, 0x03};
|
||||
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x10, 0x04, 0x10, 0x02, 0x00,
|
||||
0x0a, 0x1b, 0x94, 0x30, 0x00, 0x00, 0x40, 0x50, 0x00, 0x80};
|
||||
uint8_t noOpData3[] = {0xff, 0x02, 0x18, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t noOpData4[] = {0xfe, 0x02, 0x19, 0x03};
|
||||
uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00,
|
||||
0x0f, 0x80, 0x0a, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t noOpData6[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x85, 0x00, 0x10, 0x8a, 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;
|
||||
}
|
||||
if (requestStart != -1) {
|
||||
emit debug(QStringLiteral("starting..."));
|
||||
requestStart = -1;
|
||||
emit tapeStarted();
|
||||
}
|
||||
if (requestStop != -1) {
|
||||
emit debug(QStringLiteral("stopping..."));
|
||||
requestStop = -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"));
|
||||
break;
|
||||
}
|
||||
counterPoll++;
|
||||
if (counterPoll > 5) {
|
||||
counterPoll = 0;
|
||||
}
|
||||
} else if (nordictrack_tseries5_treadmill) {
|
||||
// Frame 1
|
||||
uint8_t noOpData1[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
@@ -2236,6 +2360,68 @@ void proformtreadmill::update() {
|
||||
if (counterPoll > 5) {
|
||||
counterPoll = 0;
|
||||
}
|
||||
} else if (proform_trainer_8_0) {
|
||||
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);
|
||||
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..."));
|
||||
requestStart = -1;
|
||||
emit tapeStarted();
|
||||
}
|
||||
if (requestStop != -1) {
|
||||
emit debug(QStringLiteral("stopping..."));
|
||||
requestStop = -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
counterPoll++;
|
||||
if (counterPoll > 5) {
|
||||
counterPoll = 0;
|
||||
}
|
||||
} else if (proform_treadmill_c700) {
|
||||
uint8_t noOpData1[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00,
|
||||
@@ -2400,6 +2586,88 @@ void proformtreadmill::update() {
|
||||
if (counterPoll > 5) {
|
||||
counterPoll = 0;
|
||||
}
|
||||
} else if (proform_505_cst_80_44) {
|
||||
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, 0x80, 0x70, 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 (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};
|
||||
@@ -2565,8 +2833,8 @@ void proformtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
newValue.at(3) != 0x04 ||
|
||||
|
||||
((nordictrack10 || nordictrackt70 || proform_treadmill_1800i || proform_treadmill_z1300i || proform_treadmill_705_cst || proform_treadmill_705_cst_V78_239 ||
|
||||
proform_treadmill_8_0 || proform_treadmill_9_0 || nordictrack_incline_trainer_x7i || proform_treadmill_sport_8_5 || proform_treadmill_505_cst ||
|
||||
proform_proshox2 || proform_595i_proshox2 || proform_performance_400i) &&
|
||||
proform_treadmill_8_0 || proform_treadmill_9_0 || nordictrack_incline_trainer_x7i || proform_treadmill_sport_8_5 || proform_treadmill_505_cst || proform_505_cst_80_44 ||
|
||||
proform_proshox2 || proform_595i_proshox2 || proform_performance_400i || proform_treadmill_705_cst_V80_44) &&
|
||||
(newValue.at(4) != 0x02 || (newValue.at(5) != 0x31 && newValue.at(5) != 0x34))) ||
|
||||
|
||||
((norditrack_s25i_treadmill) && (newValue.at(4) != 0x02 || (newValue.at(5) != 0x2f))) ||
|
||||
@@ -2574,7 +2842,7 @@ void proformtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
((nordictrack_t65s_treadmill || proform_pro_1000_treadmill || nordictrack_t65s_83_treadmill || nordictrack_s30_treadmill ||
|
||||
nordictrack_s20_treadmill || proform_treadmill_se || proform_cadence_lt || proform_8_5_treadmill || nordictrack_treadmill_exp_5i || proform_carbon_tl ||
|
||||
nordictrack_s20i_treadmill || proform_treadmill_8_7 || proform_carbon_tl_PFTL59720 || proform_treadmill_575i || nordictrack_tseries5_treadmill ||
|
||||
proform_carbon_tl_PFTL59722c) &&
|
||||
proform_carbon_tl_PFTL59722c || proform_treadmill_1500_pro) &&
|
||||
(newValue.at(4) != 0x02 || newValue.at(5) != 0x2e)) ||
|
||||
|
||||
(((uint8_t)newValue.at(12)) == 0xFF && ((uint8_t)newValue.at(13)) == 0xFF &&
|
||||
@@ -2591,7 +2859,7 @@ void proformtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
m_watts = (((uint16_t)((uint8_t)newValue.at(15)) << 8) + (uint16_t)((uint8_t)newValue.at(14)));
|
||||
|
||||
// for the proform_treadmill_se this field is the distance in meters ;)
|
||||
if (m_watts > 3000 && !proform_treadmill_se) {
|
||||
if (m_watts > 3000 && !proform_treadmill_se && !nordictrack_s20i_treadmill && !nordictrack_tseries5_treadmill) {
|
||||
m_watts = 0;
|
||||
} else {
|
||||
if (!proform_cadence_lt) {
|
||||
@@ -2701,6 +2969,10 @@ void proformtreadmill::btinit() {
|
||||
proform_treadmill_c960i = settings.value(QZSettings::proform_treadmill_c960i, QZSettings::default_proform_treadmill_c960i).toBool();
|
||||
nordictrack_tseries5_treadmill = settings.value(QZSettings::nordictrack_tseries5_treadmill, QZSettings::default_nordictrack_tseries5_treadmill).toBool();
|
||||
proform_carbon_tl_PFTL59722c = settings.value(QZSettings::proform_carbon_tl_PFTL59722c, QZSettings::default_proform_carbon_tl_PFTL59722c).toBool();
|
||||
proform_treadmill_1500_pro = settings.value(QZSettings::proform_treadmill_1500_pro, QZSettings::default_proform_treadmill_1500_pro).toBool();
|
||||
proform_505_cst_80_44 = settings.value(QZSettings::proform_505_cst_80_44, QZSettings::default_proform_505_cst_80_44).toBool();
|
||||
proform_trainer_8_0 = settings.value(QZSettings::proform_trainer_8_0, QZSettings::default_proform_trainer_8_0).toBool();
|
||||
proform_treadmill_705_cst_V80_44 = settings.value(QZSettings::proform_treadmill_705_cst_V80_44, QZSettings::default_proform_treadmill_705_cst_V80_44).toBool();
|
||||
|
||||
// bool proform_treadmill_995i = settings.value(QZSettings::proform_treadmill_995i,
|
||||
// QZSettings::default_proform_treadmill_995i).toBool();
|
||||
@@ -2859,6 +3131,109 @@ void proformtreadmill::btinit() {
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
} else if (proform_505_cst_80_44) {
|
||||
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, 0x04,
|
||||
0x00, 0x36, 0xa4, 0x08, 0x7a, 0xea, 0x68, 0xdc, 0x46, 0xce};
|
||||
uint8_t initData15[] = {0x01, 0x12, 0x4c, 0xb0, 0x32, 0xb2, 0x50, 0xd4, 0x5e, 0xc6,
|
||||
0x74, 0xf8, 0x6a, 0x1a, 0xb8, 0x2c, 0xd6, 0x7e, 0x1c, 0x80};
|
||||
uint8_t initData16[] = {0xff, 0x08, 0x22, 0xc2, 0xa0, 0x80, 0x02, 0x00, 0x00, 0x50,
|
||||
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, 0x0c,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t noOpData3[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t noOpData4[] = {0xfe, 0x02, 0x19, 0x03};
|
||||
uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x0e,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t noOpData6[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3a, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t noOpData7[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
uint8_t noOpData8[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00,
|
||||
0x0d, 0x80, 0x0a, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t noOpData9[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x80, 0x70, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t noOpData10[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
uint8_t noOpData11[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00,
|
||||
0x0d, 0x1b, 0x94, 0x31, 0x00, 0x00, 0x40, 0x50, 0x00, 0x80};
|
||||
uint8_t noOpData12[] = {0xff, 0x05, 0x18, 0x00, 0x00, 0x01, 0x2f, 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(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(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);
|
||||
writeCharacteristic(noOpData7, sizeof(noOpData7), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(noOpData8, sizeof(noOpData8), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(noOpData9, sizeof(noOpData9), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(noOpData10, sizeof(noOpData10), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(noOpData11, sizeof(noOpData11), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(noOpData12, sizeof(noOpData12), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
} else if (proform_carbon_tl) {
|
||||
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
|
||||
uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
|
||||
@@ -2931,6 +3306,98 @@ void proformtreadmill::btinit() {
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
} else if (proform_treadmill_1500_pro) {
|
||||
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, 0x57, 0x70, 0x93, 0xbc, 0xe7, 0x00, 0x3b, 0x54, 0x87};
|
||||
uint8_t initData15[] = {0x01, 0x12, 0xb0, 0xe3, 0x2c, 0x57, 0x80, 0xdb, 0x14, 0x57,
|
||||
0x90, 0xd3, 0x1c, 0x47, 0x80, 0xfb, 0x34, 0x67, 0xd0, 0x03};
|
||||
uint8_t initData16[] = {0xff, 0x08, 0x6c, 0xd7, 0x00, 0x90, 0x02, 0x00, 0x00, 0x37,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData17[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
uint8_t initData18[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x0c,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData19[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData20[] = {0xfe, 0x02, 0x19, 0x03};
|
||||
uint8_t initData21[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x0e,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData22[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3a, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData23[] = {0xfe, 0x02, 0x19, 0x03};
|
||||
uint8_t initData24[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00,
|
||||
0x0f, 0x00, 0x10, 0x00, 0xd8, 0x1c, 0x48, 0x00, 0x00, 0xe0};
|
||||
uint8_t initData25[] = {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);
|
||||
writeCharacteristic(initData20, sizeof(initData20), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(initData21, sizeof(initData21), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(initData22, sizeof(initData22), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(initData23, sizeof(initData23), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(initData24, sizeof(initData24), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(initData25, sizeof(initData25), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
} else if (nordictrack_tseries5_treadmill) {
|
||||
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
|
||||
uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
|
||||
@@ -4345,6 +4812,80 @@ void proformtreadmill::btinit() {
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
} else if (proform_treadmill_705_cst_V80_44) {
|
||||
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, 0x41, 0x58, 0x7d, 0x90, 0xb1, 0xd0, 0xf5, 0x28, 0x41};
|
||||
uint8_t initData11[] = {0x01, 0x12, 0x78, 0xad, 0xc0, 0xf1, 0x20, 0x75, 0x88, 0xc1, 0x18, 0x5d, 0x90, 0xd1, 0x10, 0x55, 0x88, 0xc1, 0x38, 0x6d};
|
||||
uint8_t initData12[] = {0xff, 0x08, 0xa0, 0x11, 0x40, 0x80, 0x02, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData13[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
uint8_t initData14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00, 0x0d, 0x00, 0x10, 0x00, 0xd8, 0x1c, 0x48, 0x00, 0x00, 0xe0};
|
||||
uint8_t initData15[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x10, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData16[] = {0xfe, 0x02, 0x19, 0x03};
|
||||
uint8_t initData17[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData18[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData19[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
uint8_t initData20[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData21[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData22[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
uint8_t initData23[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00, 0x0d, 0x1b, 0x94, 0x31, 0x00, 0x00, 0x40, 0x50, 0x00, 0x80};
|
||||
uint8_t initData24[] = {0xff, 0x05, 0x18, 0x00, 0x00, 0x01, 0x2f, 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(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);
|
||||
writeCharacteristic(initData20, sizeof(initData20), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(initData21, sizeof(initData21), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(initData22, sizeof(initData22), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(initData23, sizeof(initData23), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(initData24, sizeof(initData24), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
} else if (nordictrack_treadmill_exp_5i) {
|
||||
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
|
||||
uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
|
||||
@@ -4500,6 +5041,113 @@ void proformtreadmill::btinit() {
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
} else if (proform_trainer_8_0) {
|
||||
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, 0x04,
|
||||
0x00, 0x28, 0xec, 0xa2, 0x6e, 0x2c, 0xf8, 0xbe, 0x8a, 0x40};
|
||||
uint8_t initData15[] = {0x01, 0x12, 0x14, 0xea, 0xb6, 0x84, 0x70, 0x26, 0x02, 0xf8,
|
||||
0xdc, 0xb2, 0x9e, 0x7c, 0x48, 0x2e, 0x3a, 0x10, 0xe4, 0xfa};
|
||||
uint8_t initData16[] = {0xff, 0x08, 0xc6, 0xd4, 0xe0, 0x80, 0x02, 0x00, 0x00, 0x9a,
|
||||
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);
|
||||
|
||||
uint8_t noOpData1[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x0c,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t noOpData3[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t noOpData4[] = {0xfe, 0x02, 0x19, 0x03};
|
||||
uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x0e,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t noOpData6[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3a, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
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);
|
||||
|
||||
uint8_t noOpData7[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00,
|
||||
0x0d, 0x00, 0x10, 0x00, 0xd8, 0x1c, 0x48, 0x00, 0x00, 0xe0};
|
||||
uint8_t noOpData8[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x10, 0x62, 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, 0x04, 0x13, 0x02, 0x00,
|
||||
0x0d, 0x1b, 0x94, 0x31, 0x00, 0x00, 0x40, 0x50, 0x00, 0x80};
|
||||
uint8_t noOpData11[] = {0xff, 0x05, 0x18, 0x00, 0x00, 0x01, 0x2f, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t noOpData12[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
|
||||
writeCharacteristic(noOpData7, sizeof(noOpData7), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(noOpData8, sizeof(noOpData8), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(noOpData9, sizeof(noOpData9), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(noOpData10, sizeof(noOpData10), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(noOpData11, sizeof(noOpData11), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
writeCharacteristic(noOpData12, sizeof(noOpData12), 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};
|
||||
@@ -5527,105 +6175,97 @@ void proformtreadmill::btinit() {
|
||||
writeCharacteristic(noOpData15, sizeof(noOpData15), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
} else if (nordictrack_s20i_treadmill) {
|
||||
unsigned char array1[] = {0xfe, 0x02, 0x08, 0x02};
|
||||
unsigned char array2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
unsigned char array3[] = {0xfe, 0x02, 0x08, 0x02};
|
||||
unsigned char array4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x80, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
unsigned char array5[] = {0xfe, 0x02, 0x08, 0x02};
|
||||
unsigned char array6[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x88, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
unsigned char array7[] = {0xfe, 0x02, 0x0a, 0x02};
|
||||
unsigned char array8[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x82, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
unsigned char array9[] = {0xfe, 0x02, 0x0a, 0x02};
|
||||
unsigned char array10[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
unsigned char array11[] = {0xfe, 0x02, 0x08, 0x02};
|
||||
unsigned char array12[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
unsigned char array13[] = {0xfe, 0x02, 0x2c, 0x04};
|
||||
unsigned char array14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x04, 0x28, 0x90, 0x07, 0x01, 0x85, 0x08, 0x91, 0x18, 0xad, 0x30, 0xc1, 0x50, 0xe5};
|
||||
unsigned char array15[] = {0x01, 0x12, 0x88, 0x11, 0xb8, 0x5d, 0xe0, 0x81, 0x20, 0xc5, 0x68, 0x31, 0xd8, 0x6d, 0x30, 0xc1, 0x90, 0x25, 0xe8, 0xb1};
|
||||
unsigned char array16[] = {0xff, 0x08, 0x78, 0x3d, 0xc0, 0x98, 0x02, 0x00, 0x00, 0xed, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
unsigned char array17[] = {0xfe, 0x02, 0x19, 0x03};
|
||||
unsigned char array18[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
unsigned char array19[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
unsigned char array20[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
unsigned char array21[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
unsigned char array22[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
unsigned char array23[] = {0xfe, 0x02, 0x19, 0x03};
|
||||
unsigned char array24[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00, 0x0f, 0x00, 0x10, 0x00, 0xd8, 0x1c, 0x48, 0x00, 0x00, 0xe0};
|
||||
unsigned char array25[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02}; // from pkt1268
|
||||
uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1286
|
||||
uint8_t initData3[] = {0xfe, 0x02, 0x08, 0x02}; // from pkt1319
|
||||
uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x80, 0x88,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1329
|
||||
uint8_t initData5[] = {0xfe, 0x02, 0x08, 0x02}; // from pkt1362
|
||||
uint8_t initData6[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x88, 0x90,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1370
|
||||
uint8_t initData7[] = {0xfe, 0x02, 0x0a, 0x02}; // from pkt1431
|
||||
uint8_t initData8[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x82, 0x00,
|
||||
0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1436
|
||||
uint8_t initData9[] = {0xfe, 0x02, 0x0a, 0x02}; // from pkt1445
|
||||
uint8_t initData10[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00,
|
||||
0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1447
|
||||
uint8_t initData11[] = {0xfe, 0x02, 0x08, 0x02}; // from pkt1454
|
||||
uint8_t initData12[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1456
|
||||
uint8_t initData13[] = {0xfe, 0x02, 0x2c, 0x04}; // from pkt1466
|
||||
uint8_t initData14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x04, 0x28, 0x90, 0x07,
|
||||
0x01, 0x85, 0x08, 0x91, 0x18, 0xad, 0x30, 0xc1, 0x50, 0xe5}; // from pkt1468
|
||||
uint8_t initData15[] = {0x01, 0x12, 0x88, 0x11, 0xb8, 0x5d, 0xe0, 0x81, 0x20, 0xc5,
|
||||
0x68, 0x31, 0xd8, 0x6d, 0x30, 0xc1, 0x90, 0x25, 0xe8, 0xb1};
|
||||
|
||||
writeCharacteristic(array1, sizeof(array1), QStringLiteral("init"), false, false);
|
||||
uint8_t initData16[] = {0xff, 0x08, 0x78, 0x3d, 0xc0, 0x98, 0x02, 0x00, 0x00, 0xed,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1475
|
||||
uint8_t initData17[] = {0xfe, 0x02, 0x17, 0x03}; // from pkt1482
|
||||
uint8_t initData18[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x0c,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1484
|
||||
uint8_t initData19[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1486
|
||||
uint8_t initData20[] = {0xfe, 0x02, 0x19, 0x03}; // from pkt1493
|
||||
uint8_t initData21[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x0e,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1495
|
||||
uint8_t initData22[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3a, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1498
|
||||
uint8_t initData23[] = {0xfe, 0x02, 0x19, 0x03}; // from pkt1506
|
||||
uint8_t initData24[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00,
|
||||
0x0f, 0x00, 0x10, 0x00, 0xd8, 0x1c, 0x48, 0x00, 0x00, 0xe0}; // from pkt1509
|
||||
uint8_t initData25[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x6e, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1511
|
||||
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array2, sizeof(array2), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array3, sizeof(array3), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array4, sizeof(array4), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array5, sizeof(array5), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array6, sizeof(array6), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array7, sizeof(array7), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array8, sizeof(array8), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array9, sizeof(array9), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array10, sizeof(array10), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array11, sizeof(array11), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array12, sizeof(array12), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array13, sizeof(array13), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData13, sizeof(initData13), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array14, sizeof(array14), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData14, sizeof(initData14), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array15, sizeof(array15), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData15, sizeof(initData15), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array16, sizeof(array16), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData16, sizeof(initData16), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array17, sizeof(array17), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array18, sizeof(array18), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData18, sizeof(initData18), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array19, sizeof(array19), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData19, sizeof(initData19), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array20, sizeof(array20), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData20, sizeof(initData20), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array21, sizeof(array21), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData21, sizeof(initData21), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array22, sizeof(array22), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData22, sizeof(initData22), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array23, sizeof(array23), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData23, sizeof(initData23), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array24, sizeof(array24), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData24, sizeof(initData24), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
writeCharacteristic(array25, sizeof(array25), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData25, sizeof(initData25), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(sleepms);
|
||||
|
||||
} else if (nordictrack_s30_treadmill) {
|
||||
|
||||
@@ -105,6 +105,10 @@ class proformtreadmill : public treadmill {
|
||||
bool proform_treadmill_c960i = false;
|
||||
bool nordictrack_tseries5_treadmill = false;
|
||||
bool proform_carbon_tl_PFTL59722c = false;
|
||||
bool proform_treadmill_1500_pro = false;
|
||||
bool proform_505_cst_80_44 = false;
|
||||
bool proform_trainer_8_0 = false;
|
||||
bool proform_treadmill_705_cst_V80_44 = false;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
lockscreen *h = 0;
|
||||
|
||||
@@ -494,6 +494,10 @@ void proformwifibike::characteristicChanged(const QString &newValue) {
|
||||
m_watt = watt;
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
|
||||
}
|
||||
} else {
|
||||
qDebug() << "watt to 0 due to cadence = 0";
|
||||
m_watt = 0;
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
|
||||
}
|
||||
|
||||
if (!values[QStringLiteral("Actual Incline")].isUndefined()) {
|
||||
|
||||
@@ -138,9 +138,11 @@ void sportsplusrower::characteristicChanged(const QLowEnergyCharacteristic &char
|
||||
m_watt = ((newValue.at(10) >> 4) * 10) + (newValue.at(10) & 0x0F);
|
||||
emit debug(QStringLiteral("Current watt: ") + QString::number(m_watt.value()));
|
||||
|
||||
Cadence = ((newValue.at(3) >> 4) * 10) + (newValue.at(3) & 0x0F);
|
||||
Speed = 0.37497622 * ((double)Cadence.value());
|
||||
emit debug(QStringLiteral("Current speed: ") + QString::number(Speed.value()));
|
||||
if(newValue.at(1) == 0x10) {
|
||||
Cadence = ((newValue.at(3) >> 4) * 10) + (newValue.at(3) & 0x0F);
|
||||
Speed = 0.37497622 * ((double)Cadence.value());
|
||||
emit debug(QStringLiteral("Current speed: ") + QString::number(Speed.value()));
|
||||
}
|
||||
|
||||
if (!firstCharChanged) {
|
||||
Distance +=
|
||||
@@ -170,7 +172,7 @@ void sportsplusrower::characteristicChanged(const QLowEnergyCharacteristic &char
|
||||
}
|
||||
FanSpeed = 0;
|
||||
|
||||
emit debug(QStringLiteral("Current cadence: ") + QString::number(cadence));
|
||||
emit debug(QStringLiteral("Current cadence: ") + QString::number(Cadence.value()));
|
||||
// emit debug(QStringLiteral("Current resistance: ") + QString::number(resistance));
|
||||
emit debug(QStringLiteral("Current heart: ") + QString::number(Heart.value()));
|
||||
emit debug(QStringLiteral("Current KCal: ") + QString::number(kcal));
|
||||
|
||||
@@ -78,7 +78,10 @@ void strydrunpowersensor::update() {
|
||||
// gattWriteCharacteristic.isValid() &&
|
||||
// gattNotify1Characteristic.isValid() &&
|
||||
/*initDone*/) {
|
||||
update_metrics(false, watts());
|
||||
QSettings settings;
|
||||
bool power_as_treadmill =
|
||||
settings.value(QZSettings::power_sensor_as_treadmill, QZSettings::default_power_sensor_as_treadmill).toBool();
|
||||
update_metrics(false, watts(), !power_as_treadmill);
|
||||
|
||||
if (requestInclination != -100) {
|
||||
Inclination = treadmillInclinationOverrideReverse(requestInclination);
|
||||
@@ -520,6 +523,11 @@ void strydrunpowersensor::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
connect(s, &QLowEnergyService::descriptorWritten, this, &strydrunpowersensor::descriptorWritten);
|
||||
connect(s, &QLowEnergyService::descriptorRead, this, &strydrunpowersensor::descriptorRead);
|
||||
|
||||
if(FORERUNNER && s->serviceUuid() != QBluetoothUuid::HeartRate && s->serviceUuid() != QBluetoothUuid::RunningSpeedAndCadence) {
|
||||
qDebug() << "skipping garmin services!" << s->serviceUuid();
|
||||
continue;
|
||||
}
|
||||
|
||||
qDebug() << s->serviceUuid() << QStringLiteral("connected!");
|
||||
|
||||
auto characteristics_list = s->characteristics();
|
||||
@@ -664,6 +672,10 @@ void strydrunpowersensor::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
device.address().toString() + ')');
|
||||
{
|
||||
bluetoothDevice = device;
|
||||
if(bluetoothDevice.name().toUpper().startsWith("FORERUNNER")) {
|
||||
FORERUNNER = true;
|
||||
qDebug() << "FORERUNNER WORKAROUND!";
|
||||
}
|
||||
|
||||
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
|
||||
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &strydrunpowersensor::serviceDiscovered);
|
||||
|
||||
@@ -71,6 +71,8 @@ class strydrunpowersensor : public treadmill {
|
||||
|
||||
bool powerReceived = false;
|
||||
|
||||
bool FORERUNNER = false;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
lockscreen *h = 0;
|
||||
#endif
|
||||
|
||||
@@ -740,6 +740,12 @@ void tacxneo2::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
|
||||
qDebug() << s->serviceUuid() << QStringLiteral("connected!");
|
||||
|
||||
if(s->serviceUuid() == QBluetoothUuid(QStringLiteral("fe03a000-17d0-470a-8798-4ad3e1c1f35b")) ||
|
||||
s->serviceUuid() == QBluetoothUuid(QStringLiteral("fe031000-17d0-470a-8798-4ad3e1c1f35b"))) {
|
||||
qDebug() << "skipping service" << s->serviceUuid();
|
||||
continue;
|
||||
}
|
||||
|
||||
auto characteristics = s->characteristics();
|
||||
for (const QLowEnergyCharacteristic &c : characteristics) {
|
||||
qDebug() << QStringLiteral("char uuid") << c.uuid() << QStringLiteral("handle") << c.handle();
|
||||
|
||||
@@ -65,7 +65,7 @@ bluetoothdevice::BLUETOOTH_TYPE treadmill::deviceType() { return bluetoothdevice
|
||||
double treadmill::minStepInclination() { return 0.5; }
|
||||
double treadmill::minStepSpeed() { return 0.5; }
|
||||
|
||||
void treadmill::update_metrics(bool watt_calc, const double watts) {
|
||||
void treadmill::update_metrics(bool watt_calc, const double watts, const bool from_accessory) {
|
||||
|
||||
QDateTime current = QDateTime::currentDateTime();
|
||||
double deltaTime = (((double)_lastTimeUpdate.msecsTo(current)) / ((double)1000.0));
|
||||
@@ -74,6 +74,8 @@ void treadmill::update_metrics(bool watt_calc, const double watts) {
|
||||
settings.value(QZSettings::power_sensor_as_treadmill, QZSettings::default_power_sensor_as_treadmill).toBool();
|
||||
|
||||
simulateInclinationWithSpeed();
|
||||
if(!from_accessory)
|
||||
followPowerBySpeed();
|
||||
|
||||
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
|
||||
.toString()
|
||||
@@ -476,6 +478,47 @@ bool treadmill::simulateInclinationWithSpeed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool treadmill::followPowerBySpeed() {
|
||||
QSettings settings;
|
||||
bool r = false;
|
||||
bool treadmill_follow_wattage =
|
||||
settings
|
||||
.value(QZSettings::treadmill_follow_wattage,
|
||||
QZSettings::default_treadmill_follow_wattage)
|
||||
.toBool();
|
||||
double w = settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
|
||||
static double lastInclination = 0;
|
||||
static double lastWattage = 0;
|
||||
|
||||
if (treadmill_follow_wattage) {
|
||||
|
||||
if (currentInclination().value() != lastInclination && lastWattage != 0) {
|
||||
double newspeed = 0;
|
||||
double bestSpeed = 0.1;
|
||||
double bestDifference = fabs(wattsCalc(w, bestSpeed, currentInclination().value()) - lastWattage);
|
||||
for (int speed = 1; speed <= 300; speed++) {
|
||||
double s = ((double)speed) / 10.0;
|
||||
double thisDifference = fabs(wattsCalc(w, s, currentInclination().value()) - lastWattage);
|
||||
if (thisDifference < bestDifference) {
|
||||
bestDifference = thisDifference;
|
||||
bestSpeed = s;
|
||||
}
|
||||
}
|
||||
// Now bestSpeed is the speed closest to the desired wattage
|
||||
newspeed = bestSpeed;
|
||||
qDebug() << QStringLiteral("changing speed to") << newspeed << "due to inclination changed" << currentInclination().value() << lastInclination;
|
||||
changeSpeedAndInclination(newspeed, currentInclination().value());
|
||||
r = true;
|
||||
}
|
||||
}
|
||||
|
||||
lastInclination = currentInclination().value();
|
||||
lastWattage = wattsMetric().value();
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
QTime treadmill::lastRequestedPace() {
|
||||
QSettings settings;
|
||||
bool miles = settings.value(QZSettings::miles_unit, QZSettings::default_miles_unit).toBool();
|
||||
@@ -594,3 +637,19 @@ void treadmill::changePower(int32_t power) {
|
||||
|
||||
metric treadmill::lastRequestedPower() { return RequestedPower; }
|
||||
|
||||
QTime treadmill::speedToPace(double Speed) {
|
||||
QSettings settings;
|
||||
bool miles = settings.value(QZSettings::miles_unit, QZSettings::default_miles_unit).toBool();
|
||||
double unit_conversion = 1.0;
|
||||
if (miles) {
|
||||
unit_conversion = 0.621371;
|
||||
}
|
||||
if (Speed == 0) {
|
||||
return QTime(0, 0, 0, 0);
|
||||
} else {
|
||||
double speed = Speed * unit_conversion;
|
||||
return QTime(0, (int)(1.0 / (speed / 60.0)),
|
||||
(((double)(1.0 / (speed / 60.0)) - ((double)((int)(1.0 / (speed / 60.0))))) * 60.0), 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ class treadmill : public bluetoothdevice {
|
||||
|
||||
public:
|
||||
treadmill();
|
||||
void update_metrics(bool watt_calc, const double watts);
|
||||
void update_metrics(bool watt_calc, const double watts, const bool from_accessory = false);
|
||||
metric lastRequestedSpeed() { return RequestedSpeed; }
|
||||
QTime lastRequestedPace();
|
||||
metric lastRequestedInclination() { return RequestedInclination; }
|
||||
@@ -48,6 +48,7 @@ class treadmill : public bluetoothdevice {
|
||||
virtual bool canHandleSpeedChange() { return true; }
|
||||
virtual bool canHandleInclineChange() { return true; }
|
||||
double runningStressScore();
|
||||
QTime speedToPace(double Speed);
|
||||
|
||||
public slots:
|
||||
virtual void changeSpeed(double speed);
|
||||
@@ -86,6 +87,7 @@ class treadmill : public bluetoothdevice {
|
||||
|
||||
private:
|
||||
bool simulateInclinationWithSpeed();
|
||||
bool followPowerBySpeed();
|
||||
void evaluateStepCount();
|
||||
};
|
||||
|
||||
|
||||
416
src/devices/trxappgateusbrower/trxappgateusbrower.cpp
Normal file
416
src/devices/trxappgateusbrower/trxappgateusbrower.cpp
Normal file
@@ -0,0 +1,416 @@
|
||||
#include "trxappgateusbrower.h"
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "keepawakehelper.h"
|
||||
#endif
|
||||
#include "virtualdevices/virtualbike.h"
|
||||
#include "virtualdevices/virtualtreadmill.h"
|
||||
#include <QBluetoothLocalDevice>
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
#include <QMetaEnum>
|
||||
#include <QSettings>
|
||||
#include <QThread>
|
||||
#include <chrono>
|
||||
#include <math.h>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
trxappgateusbrower::trxappgateusbrower(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
m_watt.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, &trxappgateusbrower::update);
|
||||
refresh->start(200ms);
|
||||
}
|
||||
|
||||
void trxappgateusbrower::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);
|
||||
}
|
||||
|
||||
if (writeBuffer) {
|
||||
delete writeBuffer;
|
||||
}
|
||||
writeBuffer = new QByteArray((const char *)data, data_len);
|
||||
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer);
|
||||
|
||||
if (!disable_log) {
|
||||
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') + QStringLiteral(" // ") + info);
|
||||
}
|
||||
|
||||
loop.exec();
|
||||
}
|
||||
|
||||
void trxappgateusbrower::forceResistance(resistance_t requestResistance) {
|
||||
uint8_t noOpData1[] = {0xf0, 0xa6, 0x35, 0x01, 0x02, 0xce};
|
||||
noOpData1[4] = requestResistance + 1;
|
||||
noOpData1[5] = noOpData1[4] + 0xcc;
|
||||
writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("writingResistance"));
|
||||
}
|
||||
|
||||
void trxappgateusbrower::update() {
|
||||
if (m_control->state() == QLowEnergyController::UnconnectedState) {
|
||||
emit disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
if (initRequest) {
|
||||
initRequest = false;
|
||||
btinit();
|
||||
} else if (bluetoothDevice.isValid() && m_control->state() == QLowEnergyController::DiscoveredState &&
|
||||
gattCommunicationChannelService && gattWriteCharacteristic.isValid() &&
|
||||
gattNotify1Characteristic.isValid() && initDone) {
|
||||
QSettings settings;
|
||||
update_metrics(true, watts());
|
||||
|
||||
{
|
||||
if (requestResistance != -1) {
|
||||
if (requestResistance < 1)
|
||||
requestResistance = 1;
|
||||
if (requestResistance != currentResistance().value() && requestResistance >= 1 &&
|
||||
requestResistance <= 15) {
|
||||
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
|
||||
forceResistance(requestResistance);
|
||||
}
|
||||
requestResistance = -1;
|
||||
} else {
|
||||
uint8_t noOpData1[] = {0xf0, 0xa2, 0x01, 0xe7, 0x7a};
|
||||
writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp"));
|
||||
}
|
||||
}
|
||||
|
||||
// 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 tapeStarted();
|
||||
}*/
|
||||
if (requestStop != -1) {
|
||||
emit debug(QStringLiteral("stopping..."));
|
||||
// writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
|
||||
requestStop = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void trxappgateusbrower::changeInclinationRequested(double grade, double percentage) {
|
||||
if (percentage < 0)
|
||||
percentage = 0;
|
||||
changeInclination(grade, percentage);
|
||||
}
|
||||
|
||||
void trxappgateusbrower::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString());
|
||||
}
|
||||
|
||||
double trxappgateusbrower::GetSpeedFromPacket(const QByteArray &packet) {
|
||||
|
||||
uint16_t convertedData = (packet.at(7) - 1) + ((packet.at(6) - 1) * 100);
|
||||
double data = (double)(convertedData) / 10.0f;
|
||||
return data;
|
||||
}
|
||||
|
||||
double trxappgateusbrower::GetCadenceFromPacket(const QByteArray &packet) {
|
||||
|
||||
uint16_t convertedData = ((uint16_t)packet.at(20)) - 1;
|
||||
return convertedData;
|
||||
}
|
||||
|
||||
double trxappgateusbrower::GetWattFromPacket(const QByteArray &packet) {
|
||||
|
||||
uint16_t convertedData = ((packet.at(17) - 1) * 100) + (packet.at(18) - 1);
|
||||
double data = ((double)(convertedData)) / 10.0f;
|
||||
return data;
|
||||
}
|
||||
|
||||
void trxappgateusbrower::characteristicChanged(const QLowEnergyCharacteristic &characteristic,
|
||||
const QByteArray &newValue) {
|
||||
// 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();
|
||||
double weight = settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
|
||||
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();
|
||||
|
||||
emit debug(QStringLiteral(" << ") + newValue.toHex(' '));
|
||||
|
||||
lastPacket = newValue;
|
||||
|
||||
if(newValue.length() != 23) {
|
||||
return;
|
||||
}
|
||||
|
||||
Resistance = newValue.at(18) - 1;
|
||||
Speed = GetSpeedFromPacket(newValue);
|
||||
Cadence = (GetCadenceFromPacket(newValue) * cadence_gain) + cadence_offset;
|
||||
m_watt = GetWattFromPacket(newValue);
|
||||
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
|
||||
// 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())));
|
||||
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
|
||||
#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"))) {
|
||||
update_hr_from_external();
|
||||
}
|
||||
}
|
||||
|
||||
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
|
||||
emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
|
||||
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
|
||||
emit debug(QStringLiteral("Current Calculate Distance: ") + QString::number(Distance.value()));
|
||||
// debug("Current Distance: " + QString::number(distance));
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
|
||||
|
||||
if (m_control->error() != QLowEnergyController::NoError) {
|
||||
qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString();
|
||||
}
|
||||
}
|
||||
|
||||
void trxappgateusbrower::btinit() {
|
||||
|
||||
uint8_t initData1[] = {0xf0, 0xa0, 0x01, 0x00, 0x91};
|
||||
uint8_t initData2[] = {0xf0, 0xa0, 0x01, 0xe7, 0x78};
|
||||
uint8_t initData3[] = {0xf0, 0xa1, 0x01, 0xe7, 0x79};
|
||||
uint8_t initData4[] = {0xf0, 0xa3, 0x01, 0xe7, 0x01, 0x7c};
|
||||
uint8_t initData5[] = {0xf0, 0xa4, 0x01, 0xe7, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x83};
|
||||
uint8_t initData6[] = {0xf0, 0xa5, 0x01, 0xe7, 0x02, 0x7f};
|
||||
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, true);
|
||||
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
void trxappgateusbrower::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
QBluetoothUuid _gattWriteCharacteristicId((quint16)0xfff2);
|
||||
QBluetoothUuid _gattNotify1CharacteristicId((quint16)0xfff1);
|
||||
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
|
||||
emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
|
||||
|
||||
if (state == QLowEnergyService::ServiceDiscovered) {
|
||||
// qDebug() << gattCommunicationChannelService->characteristics();
|
||||
|
||||
QString uuidWrite = QStringLiteral("0000fff2-0000-1000-8000-00805f9b34fb");
|
||||
QString uuidNotify1 = QStringLiteral("0000fff1-0000-1000-8000-00805f9b34fb");
|
||||
QString uuidNotify2 = QStringLiteral("49535343-4c8a-39b3-2f49-511cff073b7e");
|
||||
|
||||
|
||||
QBluetoothUuid _gattWriteCharacteristicId(uuidWrite);
|
||||
QBluetoothUuid _gattNotify1CharacteristicId(uuidNotify1);
|
||||
QBluetoothUuid _gattNotify2CharacteristicId(uuidNotify2);
|
||||
|
||||
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
|
||||
gattNotify1Characteristic = gattCommunicationChannelService->characteristic(_gattNotify1CharacteristicId);
|
||||
|
||||
if (!gattWriteCharacteristic.isValid()) {
|
||||
uuidWrite = QStringLiteral("49535343-8841-43f4-a8d4-ecbe34729bb3");
|
||||
uuidNotify1 = QStringLiteral("49535343-1E4D-4BD9-BA61-23C647249616");
|
||||
uuidNotify2 = QStringLiteral("49535343-4c8a-39b3-2f49-511cff073b7e");
|
||||
|
||||
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
|
||||
gattNotify1Characteristic = gattCommunicationChannelService->characteristic(_gattNotify1CharacteristicId);
|
||||
}
|
||||
|
||||
|
||||
// establish hook into notifications
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this,
|
||||
&trxappgateusbrower::characteristicChanged);
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this,
|
||||
&trxappgateusbrower::characteristicWritten);
|
||||
connect(gattCommunicationChannelService,
|
||||
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
|
||||
this, &trxappgateusbrower::errorService);
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this,
|
||||
&trxappgateusbrower::descriptorWritten);
|
||||
|
||||
// ******************************************* virtual treadmill init *************************************
|
||||
QSettings settings;
|
||||
if (!firstStateChanged && !this->hasVirtualDevice()) {
|
||||
bool virtual_device_enabled =
|
||||
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
|
||||
bool virtual_device_force_bike =
|
||||
settings.value(QZSettings::virtual_device_force_bike, QZSettings::default_virtual_device_force_bike)
|
||||
.toBool();
|
||||
if (virtual_device_enabled) {
|
||||
if (!virtual_device_force_bike) {
|
||||
debug("creating virtual treadmill interface...");
|
||||
auto virtualTreadmill = new virtualtreadmill(this, noHeartService);
|
||||
connect(virtualTreadmill, &virtualtreadmill::debug, this, &trxappgateusbrower::debug);
|
||||
connect(virtualTreadmill, &virtualtreadmill::changeInclination, this,
|
||||
&trxappgateusbrower::changeInclinationRequested);
|
||||
this->setVirtualDevice(virtualTreadmill, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
} else {
|
||||
debug("creating virtual bike interface...");
|
||||
auto virtualBike = new virtualbike(this);
|
||||
connect(virtualBike, &virtualbike::changeInclination, this,
|
||||
&trxappgateusbrower::changeInclinationRequested);
|
||||
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::ALTERNATIVE);
|
||||
}
|
||||
firstStateChanged = 1;
|
||||
}
|
||||
}
|
||||
// ********************************************************************************************************
|
||||
|
||||
QByteArray descriptor;
|
||||
descriptor.append((char)0x01);
|
||||
descriptor.append((char)0x00);
|
||||
gattCommunicationChannelService->writeDescriptor(
|
||||
gattNotify1Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
void trxappgateusbrower::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
|
||||
emit debug(QStringLiteral("descriptorWritten ") + descriptor.name() + QStringLiteral(" ") + newValue.toHex(' '));
|
||||
|
||||
initRequest = true;
|
||||
emit connectedAndDiscovered();
|
||||
}
|
||||
|
||||
void trxappgateusbrower::characteristicWritten(const QLowEnergyCharacteristic &characteristic,
|
||||
const QByteArray &newValue) {
|
||||
Q_UNUSED(characteristic);
|
||||
emit debug(QStringLiteral("characteristicWritten ") + newValue.toHex(' '));
|
||||
}
|
||||
|
||||
void trxappgateusbrower::serviceScanDone(void) {
|
||||
emit debug(QStringLiteral("serviceScanDone"));
|
||||
|
||||
QString uuid = QStringLiteral("0000fff0-0000-1000-8000-00805f9b34fb");
|
||||
QString uuid2 = QStringLiteral("49535343-FE7D-4AE5-8FA9-9FAFD205E455");
|
||||
QString uuid3 = QStringLiteral("0000fff0-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
QBluetoothUuid _gattCommunicationChannelServiceId(uuid);
|
||||
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
|
||||
|
||||
if (gattCommunicationChannelService == nullptr) {
|
||||
qDebug() << QStringLiteral("invalid service") << uuid;
|
||||
uuid = uuid2;
|
||||
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
|
||||
return;
|
||||
}
|
||||
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &trxappgateusbrower::stateChanged);
|
||||
gattCommunicationChannelService->discoverDetails();
|
||||
}
|
||||
|
||||
void trxappgateusbrower::errorService(QLowEnergyService::ServiceError err) {
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
|
||||
emit debug(QStringLiteral("trxappgateusbrower::errorService") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
|
||||
m_control->errorString());
|
||||
}
|
||||
|
||||
void trxappgateusbrower::error(QLowEnergyController::Error err) {
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
|
||||
emit debug(QStringLiteral("trxappgateusbrower::error") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
|
||||
m_control->errorString());
|
||||
}
|
||||
|
||||
void trxappgateusbrower::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit debug(QStringLiteral("Found new device: ") + device.name() + " (" + device.address().toString() + ')');
|
||||
|
||||
{
|
||||
bluetoothDevice = device;
|
||||
|
||||
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
|
||||
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &trxappgateusbrower::serviceDiscovered);
|
||||
connect(m_control, &QLowEnergyController::discoveryFinished, this, &trxappgateusbrower::serviceScanDone);
|
||||
connect(m_control,
|
||||
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
|
||||
this, &trxappgateusbrower::error);
|
||||
connect(m_control, &QLowEnergyController::stateChanged, this, &trxappgateusbrower::controllerStateChanged);
|
||||
|
||||
connect(m_control,
|
||||
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
|
||||
this, [this](QLowEnergyController::Error error) {
|
||||
Q_UNUSED(error);
|
||||
Q_UNUSED(this);
|
||||
emit debug(QStringLiteral("Cannot connect to remote device."));
|
||||
emit disconnected();
|
||||
});
|
||||
connect(m_control, &QLowEnergyController::connected, this, [this]() {
|
||||
Q_UNUSED(this);
|
||||
emit debug(QStringLiteral("Controller connected. Search services..."));
|
||||
m_control->discoverServices();
|
||||
});
|
||||
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
|
||||
Q_UNUSED(this);
|
||||
emit debug(QStringLiteral("LowEnergy controller disconnected"));
|
||||
emit disconnected();
|
||||
});
|
||||
|
||||
// Connect
|
||||
m_control->connectToDevice();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool trxappgateusbrower::connected() {
|
||||
if (!m_control) {
|
||||
return false;
|
||||
}
|
||||
return m_control->state() == QLowEnergyController::DiscoveredState;
|
||||
}
|
||||
|
||||
void trxappgateusbrower::controllerStateChanged(QLowEnergyController::ControllerState state) {
|
||||
qDebug() << QStringLiteral("controllerStateChanged") << state;
|
||||
if (state == QLowEnergyController::UnconnectedState && m_control) {
|
||||
qDebug() << QStringLiteral("trying to connect back again...");
|
||||
initDone = false;
|
||||
m_control->connectToDevice();
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t trxappgateusbrower::watts() { return m_watt.value(); }
|
||||
|
||||
|
||||
void trxappgateusbrower::searchingStop() { searchStopped = true; }
|
||||
|
||||
107
src/devices/trxappgateusbrower/trxappgateusbrower.h
Normal file
107
src/devices/trxappgateusbrower/trxappgateusbrower.h
Normal file
@@ -0,0 +1,107 @@
|
||||
#ifndef TRXAPPGATEUSBROWER_H
|
||||
#define TRXAPPGATEUSBROWER_H
|
||||
|
||||
#include <QBluetoothDeviceDiscoveryAgent>
|
||||
#include <QtBluetooth/qlowenergyadvertisingdata.h>
|
||||
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristic.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristicdata.h>
|
||||
#include <QtBluetooth/qlowenergycontroller.h>
|
||||
#include <QtBluetooth/qlowenergydescriptordata.h>
|
||||
#include <QtBluetooth/qlowenergyservice.h>
|
||||
#include <QtBluetooth/qlowenergyservicedata.h>
|
||||
#include <QtCore/qbytearray.h>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#else
|
||||
#include <QtGui/qguiapplication.h>
|
||||
#endif
|
||||
#include <QtCore/qlist.h>
|
||||
#include <QtCore/qmutex.h>
|
||||
#include <QtCore/qscopedpointer.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include "devices/rower.h"
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "ios/lockscreen.h"
|
||||
#endif
|
||||
|
||||
class trxappgateusbrower : public rower {
|
||||
Q_OBJECT
|
||||
public:
|
||||
trxappgateusbrower(bool noWriteResistance = false, bool noHeartService = false, int8_t bikeResistanceOffset = 4,
|
||||
double bikeResistanceGain = 1.0);
|
||||
bool connected() override;
|
||||
|
||||
private:
|
||||
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
|
||||
bool wait_for_response = false);
|
||||
void startDiscover();
|
||||
uint16_t watts() override;
|
||||
void forceResistance(resistance_t requestResistance);
|
||||
void btinit();
|
||||
double GetSpeedFromPacket(const QByteArray &packet);
|
||||
double GetCadenceFromPacket(const QByteArray &packet);
|
||||
double GetWattFromPacket(const QByteArray &packet);
|
||||
|
||||
QTimer *refresh;
|
||||
|
||||
QLowEnergyService* gattCommunicationChannelService;
|
||||
QLowEnergyCharacteristic gattWriteCharacteristic;
|
||||
QLowEnergyCharacteristic gattNotify1Characteristic;
|
||||
|
||||
uint8_t sec1Update = 0;
|
||||
QByteArray lastPacket;
|
||||
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
uint8_t firstStateChanged = 0;
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
const uint8_t max_resistance = 72; // 24;
|
||||
const uint8_t default_resistance = 6;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = false;
|
||||
|
||||
uint8_t counterPoll = 0;
|
||||
bool searchStopped = false;
|
||||
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
lockscreen *h = 0;
|
||||
#endif
|
||||
|
||||
Q_SIGNALS:
|
||||
void disconnected();
|
||||
void debug(QString string);
|
||||
|
||||
public slots:
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
void searchingStop();
|
||||
|
||||
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);
|
||||
void changeInclinationRequested(double grade, double percentage);
|
||||
// void ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
};
|
||||
|
||||
#endif // TRXAPPGATEUSBROWER_H
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "homeform.h"
|
||||
#include "wahookickrheadwind.h"
|
||||
#include <QBluetoothLocalDevice>
|
||||
#include <QDateTime>
|
||||
@@ -279,6 +280,10 @@ void wahookickrheadwind::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
QSettings settings;
|
||||
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
|
||||
device.address().toString() + ')');
|
||||
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested(device.name() + QStringLiteral(" connected!"));
|
||||
|
||||
{
|
||||
bluetoothDevice = device;
|
||||
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
|
||||
|
||||
@@ -306,17 +306,22 @@ resistance_t wahookickrsnapbike::pelotonToBikeResistance(int pelotonResistance)
|
||||
settings.value(QZSettings::schwinn_bike_resistance_v2, QZSettings::default_schwinn_bike_resistance_v2).toBool();
|
||||
if (!schwinn_bike_resistance_v2) {
|
||||
if (pelotonResistance > 54)
|
||||
return pelotonResistance;
|
||||
return (pelotonResistance * settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
|
||||
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
|
||||
if (pelotonResistance < 26)
|
||||
return pelotonResistance / 5;
|
||||
return ((pelotonResistance / 5) * settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
|
||||
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
|
||||
|
||||
// y = 0,04x2 - 1,32x + 11,8
|
||||
return ((0.04 * pow(pelotonResistance, 2)) - (1.32 * pelotonResistance) + 11.8);
|
||||
return (((0.04 * pow(pelotonResistance, 2)) - (1.32 * pelotonResistance) + 11.8) * settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
|
||||
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
|
||||
} else {
|
||||
if (pelotonResistance > 20)
|
||||
return (((double)pelotonResistance - 20.0) * 1.25);
|
||||
return ((((double)pelotonResistance - 20.0) * 1.25) * settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
|
||||
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
|
||||
else
|
||||
return 1;
|
||||
return (1 * settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
|
||||
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
111
src/homeform.cpp
111
src/homeform.cpp
@@ -1563,6 +1563,12 @@ void homeform::sortTiles() {
|
||||
target_power->setGridId(i);
|
||||
dataList.append(target_power);
|
||||
}
|
||||
|
||||
if (settings.value(QZSettings::tile_target_zone_enabled, false).toBool() &&
|
||||
settings.value(QZSettings::tile_target_zone_order, 24).toInt() == i) {
|
||||
target_zone->setGridId(i);
|
||||
dataList.append(target_zone);
|
||||
}
|
||||
}
|
||||
} else if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
@@ -1898,13 +1904,13 @@ void homeform::sortTiles() {
|
||||
}
|
||||
|
||||
if (settings.value(QZSettings::tile_biggears_enabled, false).toBool() &&
|
||||
settings.value(QZSettings::tile_biggears_order, 54).toInt() == i) {
|
||||
settings.value(QZSettings::tile_biggears_order, 54).toInt() == i + (settings.value(QZSettings::tile_biggears_swap, QZSettings::default_tile_biggears_swap).toBool() ? 1 : 0)) {
|
||||
biggearsPlus->setGridId(i);
|
||||
dataList.append(biggearsPlus);
|
||||
}
|
||||
|
||||
if (settings.value(QZSettings::tile_biggears_enabled, false).toBool() &&
|
||||
settings.value(QZSettings::tile_biggears_order, 54).toInt() + 1 == i) {
|
||||
settings.value(QZSettings::tile_biggears_order, 54).toInt() == i + (settings.value(QZSettings::tile_biggears_swap, QZSettings::default_tile_biggears_swap).toBool() ? 0 : 1)) {
|
||||
biggearsMinus->setGridId(i);
|
||||
dataList.append(biggearsMinus);
|
||||
}
|
||||
@@ -4088,31 +4094,35 @@ void homeform::update() {
|
||||
targetMets->setValue(QString::number(trainProgram->currentTargetMets(), 'f', 1));
|
||||
trainrow next = trainProgram->getRowFromCurrent(1);
|
||||
trainrow next_1 = trainProgram->getRowFromCurrent(2);
|
||||
if (next.duration.second() != 0 || next.duration.minute() != 0 || next.duration.hour() != 0) {
|
||||
if (next.requested_peloton_resistance != -1)
|
||||
nextRows->setValue(QStringLiteral("PR") + QString::number(next.requested_peloton_resistance) +
|
||||
QStringLiteral(" ") + next.duration.toString(QStringLiteral("mm:ss")));
|
||||
else if (next.resistance != -1)
|
||||
nextRows->setValue(QStringLiteral("R") + QString::number(next.resistance) + QStringLiteral(" ") +
|
||||
next.duration.toString(QStringLiteral("mm:ss")));
|
||||
else if (next.zoneHR != -1)
|
||||
nextRows->setValue(QStringLiteral("HR") + QString::number(next.zoneHR) + QStringLiteral(" ") +
|
||||
next.duration.toString(QStringLiteral("mm:ss")));
|
||||
else if (next.HRmin != -1 && next.HRmax != -1)
|
||||
if (next.duration.second() != 0 || next.duration.minute() != 0 || next.duration.hour() != 0 || next.distance != -1) {
|
||||
QString duration = next.duration.toString(QStringLiteral("mm:ss"));
|
||||
if(next.distance != -1) {
|
||||
duration = QString::number(next.distance, 'f' , 1);
|
||||
}
|
||||
if (next.requested_peloton_resistance != -1) {
|
||||
nextRows->setValue(QStringLiteral("PR") + QString::number(next.requested_peloton_resistance));
|
||||
nextRows->setSecondLine(duration);
|
||||
} else if (next.resistance != -1) {
|
||||
nextRows->setValue(QStringLiteral("R") + QString::number(next.resistance));
|
||||
nextRows->setSecondLine(duration);
|
||||
} else if (next.zoneHR != -1) {
|
||||
nextRows->setValue(QStringLiteral("HR") + QString::number(next.zoneHR));
|
||||
nextRows->setSecondLine(duration);
|
||||
} else if (next.HRmin != -1 && next.HRmax != -1) {
|
||||
nextRows->setValue(QStringLiteral("HR") + QString::number(next.HRmin) + QStringLiteral("-") +
|
||||
QString::number(next.HRmax) + QStringLiteral(" ") +
|
||||
next.duration.toString(QStringLiteral("mm:ss")));
|
||||
else if (next.speed != -1 && next.inclination != -200)
|
||||
QString::number(next.HRmax));
|
||||
nextRows->setSecondLine(duration);
|
||||
} else if (next.speed != -1 && next.inclination != -200) {
|
||||
nextRows->setValue(QStringLiteral("S") + QString::number(next.speed, 'f' , 1) + QStringLiteral("I") +
|
||||
QString::number(next.inclination, 'f' , 1) + QStringLiteral(" ") +
|
||||
next.duration.toString(QStringLiteral("mm:ss")));
|
||||
else if (next.speed != -1)
|
||||
nextRows->setValue(QStringLiteral("S") + QString::number(next.speed, 'f' , 1) + QStringLiteral(" ") +
|
||||
next.duration.toString(QStringLiteral("mm:ss")));
|
||||
else if (next.inclination != -200)
|
||||
nextRows->setValue(QStringLiteral("I") + QString::number(next.inclination, 'f' , 1) + QStringLiteral(" ") +
|
||||
next.duration.toString(QStringLiteral("mm:ss")));
|
||||
else if (next.power != -1) {
|
||||
QString::number(next.inclination, 'f' , 1));
|
||||
nextRows->setSecondLine(duration);
|
||||
} else if (next.speed != -1) {
|
||||
nextRows->setValue(QStringLiteral("S") + QString::number(next.speed, 'f' , 1));
|
||||
nextRows->setSecondLine(duration);
|
||||
} else if (next.inclination != -200) {
|
||||
nextRows->setValue(QStringLiteral("I") + QString::number(next.inclination, 'f' , 1));
|
||||
nextRows->setSecondLine(duration);
|
||||
} else if (next.power != -1) {
|
||||
double ftpPerc = (next.power / ftpSetting) * 100.0;
|
||||
uint8_t ftpZone = 1;
|
||||
if (ftpPerc < 56) {
|
||||
@@ -4131,16 +4141,20 @@ void homeform::update() {
|
||||
ftpZone = 7;
|
||||
}
|
||||
nextRows->setValue(QStringLiteral("Z") + QString::number(ftpZone) + QStringLiteral(" ") +
|
||||
next.duration.toString(QStringLiteral("mm:ss")));
|
||||
if (next_1.duration.second() != 0 || next_1.duration.minute() != 0 || next_1.duration.hour() != 0) {
|
||||
duration);
|
||||
if (next_1.duration.second() != 0 || next_1.duration.minute() != 0 || next_1.duration.hour() != 0 || next_1.distance != -1) {
|
||||
QString duration_1 = next_1.duration.toString(QStringLiteral("mm:ss"));
|
||||
if(next_1.distance != -1) {
|
||||
duration_1 = QString::number(next_1.distance, 'f' , 1);
|
||||
}
|
||||
if (next_1.requested_peloton_resistance != -1)
|
||||
nextRows->setSecondLine(
|
||||
QStringLiteral("PR") + QString::number(next_1.requested_peloton_resistance) +
|
||||
QStringLiteral(" ") + next_1.duration.toString(QStringLiteral("mm:ss")));
|
||||
QStringLiteral(" ") + duration_1);
|
||||
else if (next_1.resistance != -1)
|
||||
nextRows->setSecondLine(QStringLiteral("R") + QString::number(next_1.resistance) +
|
||||
QStringLiteral(" ") +
|
||||
next_1.duration.toString(QStringLiteral("mm:ss")));
|
||||
duration_1);
|
||||
else if (next_1.power != -1) {
|
||||
double ftpPerc = (next_1.power / ftpSetting) * 100.0;
|
||||
uint8_t ftpZone = 1;
|
||||
@@ -4161,7 +4175,7 @@ void homeform::update() {
|
||||
}
|
||||
nextRows->setSecondLine(QStringLiteral("Z") + QString::number(ftpZone) +
|
||||
QStringLiteral(" ") +
|
||||
next_1.duration.toString(QStringLiteral("mm:ss")));
|
||||
duration_1);
|
||||
}
|
||||
} else {
|
||||
nextRows->setSecondLine(QStringLiteral("N/A"));
|
||||
@@ -4344,17 +4358,23 @@ void homeform::update() {
|
||||
this->pace->setValueFontColor(QStringLiteral("red"));
|
||||
}
|
||||
} else {
|
||||
if (bluetoothManager->device()->currentSpeed().value() <= trainProgram->currentRow().upper_speed &&
|
||||
bluetoothManager->device()->currentSpeed().value() >= trainProgram->currentRow().lower_speed) {
|
||||
// Round speeds to 1 decimal place before comparison to avoid overly strict matching
|
||||
double currentSpeed = round(bluetoothManager->device()->currentSpeed().value() * 10.0) / 10.0;
|
||||
double upperSpeed = round(trainProgram->currentRow().upper_speed * 10.0) / 10.0;
|
||||
double lowerSpeed = round(trainProgram->currentRow().lower_speed * 10.0) / 10.0;
|
||||
|
||||
// Check if speed is in target zone (green)
|
||||
if (currentSpeed <= upperSpeed && currentSpeed >= lowerSpeed) {
|
||||
this->target_zone->setValueFontColor(QStringLiteral("limegreen"));
|
||||
this->pace->setValueFontColor(QStringLiteral("limegreen"));
|
||||
} else if (bluetoothManager->device()->currentSpeed().value() <=
|
||||
(trainProgram->currentRow().upper_speed + 0.2) &&
|
||||
bluetoothManager->device()->currentSpeed().value() >=
|
||||
(trainProgram->currentRow().lower_speed - 0.2)) {
|
||||
}
|
||||
// Check if speed is close to target zone (orange)
|
||||
else if (currentSpeed <= (upperSpeed + 0.2) && currentSpeed >= (lowerSpeed - 0.2)) {
|
||||
this->target_zone->setValueFontColor(QStringLiteral("orange"));
|
||||
this->pace->setValueFontColor(QStringLiteral("orange"));
|
||||
} else {
|
||||
}
|
||||
// Speed is out of range (red)
|
||||
else {
|
||||
this->target_zone->setValueFontColor(QStringLiteral("red"));
|
||||
this->pace->setValueFontColor(QStringLiteral("red"));
|
||||
}
|
||||
@@ -4387,8 +4407,21 @@ void homeform::update() {
|
||||
break;
|
||||
}
|
||||
|
||||
this->target_pace->setValue(
|
||||
((treadmill *)bluetoothManager->device())->lastRequestedPace().toString(QStringLiteral("m:ss")));
|
||||
if (trainProgram) {
|
||||
// in order to see the target pace of a peloton workout even if the speed force for treadmill is disabled
|
||||
this->target_pace->setValue(
|
||||
((treadmill *)bluetoothManager->device())->speedToPace(trainProgram->currentRow().speed).toString(QStringLiteral("m:ss")));
|
||||
this->target_pace->setSecondLine(((treadmill *)bluetoothManager->device())
|
||||
->speedToPace(trainProgram->currentRow().lower_speed)
|
||||
.toString(QStringLiteral("m:ss")) +
|
||||
" - " +
|
||||
((treadmill *)bluetoothManager->device())
|
||||
->speedToPace(trainProgram->currentRow().upper_speed)
|
||||
.toString(QStringLiteral("m:ss")));
|
||||
} else {
|
||||
this->target_pace->setValue(
|
||||
((treadmill *)bluetoothManager->device())->lastRequestedPace().toString(QStringLiteral("m:ss")));
|
||||
}
|
||||
this->target_speed->setValue(QString::number(
|
||||
((treadmill *)bluetoothManager->device())->lastRequestedSpeed().value() * unit_conversion, 'f', 1));
|
||||
this->target_speed->setSecondLine(QString::number(bluetoothManager->device()->difficult() * 100.0, 'f', 0) +
|
||||
|
||||
366
src/inner_templates/chartjs/dotreadmillchartlive.js
Normal file
366
src/inner_templates/chartjs/dotreadmillchartlive.js
Normal file
@@ -0,0 +1,366 @@
|
||||
window.chartColors = {
|
||||
red: 'rgb(255, 29, 0)',
|
||||
redt: 'rgb(255, 29, 0, 0.55)',
|
||||
orange: 'rgb(255, 159, 64)',
|
||||
oranget: 'rgb(255, 159, 64, 0.55)',
|
||||
darkorange: 'rgb(255, 140, 0)',
|
||||
darkoranget: 'rgb(255, 140, 0, 0.55)',
|
||||
orangered: 'rgb(255, 69, 0)',
|
||||
orangeredt: 'rgb(255, 69, 0, 0.55)',
|
||||
yellow: 'rgb(255, 205, 86)',
|
||||
yellowt: 'rgb(255, 205, 86, 0.55)',
|
||||
green: 'rgb(75, 192, 192)',
|
||||
greent: 'rgb(75, 192, 192, 0.55)',
|
||||
limegreen: 'rgb(50, 205, 50)',
|
||||
limegreent: 'rgb(50, 205, 50, 0.55)',
|
||||
gold: 'rgb(255, 215, 0)',
|
||||
goldt: 'rgb(255, 215, 0, 0.55)',
|
||||
grey: 'rgb(201, 203, 207)',
|
||||
greyt: 'rgb(201, 203, 207, 0.55)',
|
||||
black: 'rgb(0, 0, 0)',
|
||||
blackt: 'rgb(0, 0, 0, 0.55)',
|
||||
};
|
||||
|
||||
var treadmillChart = null;
|
||||
var speed_max = 0;
|
||||
var incline_max = 0;
|
||||
|
||||
// Define speed zones
|
||||
const speedZones = [6, 8, 10, 12, 14, 16]; // km/h
|
||||
|
||||
function process_arr(arr) {
|
||||
let ctx = document.getElementById('canvas').getContext('2d');
|
||||
let div = document.getElementById('divcanvas');
|
||||
|
||||
let speed = [];
|
||||
let targetSpeed = [];
|
||||
let inclination = [];
|
||||
let targetInclination = [];
|
||||
let maxEl = 0;
|
||||
|
||||
for (let el of arr) {
|
||||
let time = el.elapsed_s + el.elapsed_m * 60 + el.elapsed_h * 3600;
|
||||
maxEl = time;
|
||||
|
||||
if (el.speed !== undefined) {
|
||||
speed.push({x: time, y: el.speed});
|
||||
if (speed_max < el.speed) speed_max = el.speed;
|
||||
}
|
||||
|
||||
if (el.target_speed !== undefined && el.target_speed !== -1) {
|
||||
targetSpeed.push({x: time, y: el.target_speed});
|
||||
if (speed_max < el.target_speed) speed_max = el.target_speed;
|
||||
}
|
||||
|
||||
if (el.inclination !== undefined) {
|
||||
inclination.push({x: time, y: el.inclination});
|
||||
if (incline_max < el.inclination) incline_max = el.inclination;
|
||||
}
|
||||
|
||||
if (el.target_inclination !== undefined && el.target_inclination !== -200) {
|
||||
targetInclination.push({x: time, y: el.target_inclination});
|
||||
if (incline_max < el.target_inclination) incline_max = el.target_inclination;
|
||||
}
|
||||
}
|
||||
|
||||
speed_max = Math.ceil(speed_max * 1.1);
|
||||
incline_max = Math.ceil(incline_max * 1.1);
|
||||
|
||||
const backgroundFill = {
|
||||
id: 'custom_canvas_background_color',
|
||||
beforeDraw: (chart) => {
|
||||
const ctx = chart.canvas.getContext('2d');
|
||||
ctx.save();
|
||||
ctx.globalCompositeOperation = 'destination-over';
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fillRect(0, 0, chart.width, chart.height);
|
||||
ctx.restore();
|
||||
}
|
||||
};
|
||||
|
||||
let config = {
|
||||
type: 'line',
|
||||
plugins: [backgroundFill],
|
||||
data: {
|
||||
datasets: [{
|
||||
label: 'Speed',
|
||||
backgroundColor: window.chartColors.red,
|
||||
borderColor: window.chartColors.red,
|
||||
data: speed,
|
||||
fill: false,
|
||||
pointRadius: 0,
|
||||
borderWidth: 2,
|
||||
yAxisID: 'y-speed',
|
||||
segment: {
|
||||
borderColor: ctx => {
|
||||
const y = ctx.p0.parsed.y;
|
||||
if (y < speedZones[0]) return window.chartColors.grey;
|
||||
if (y < speedZones[1]) return window.chartColors.limegreen;
|
||||
if (y < speedZones[2]) return window.chartColors.gold;
|
||||
if (y < speedZones[3]) return window.chartColors.orange;
|
||||
if (y < speedZones[4]) return window.chartColors.darkorange;
|
||||
if (y < speedZones[5]) return window.chartColors.orangered;
|
||||
return window.chartColors.red;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
label: 'Target Speed',
|
||||
backgroundColor: window.chartColors.black,
|
||||
borderColor: window.chartColors.black,
|
||||
data: targetSpeed,
|
||||
fill: false,
|
||||
pointRadius: 0,
|
||||
borderWidth: 2,
|
||||
yAxisID: 'y-speed',
|
||||
borderDash: [5, 5]
|
||||
}, {
|
||||
label: 'Incline',
|
||||
backgroundColor: window.chartColors.orange,
|
||||
borderColor: window.chartColors.orange,
|
||||
data: inclination,
|
||||
fill: false,
|
||||
pointRadius: 0,
|
||||
borderWidth: 2,
|
||||
yAxisID: 'y-incline'
|
||||
}, {
|
||||
label: 'Target Incline',
|
||||
backgroundColor: window.chartColors.grey,
|
||||
borderColor: window.chartColors.grey,
|
||||
data: targetInclination,
|
||||
fill: false,
|
||||
pointRadius: 0,
|
||||
borderWidth: 2,
|
||||
yAxisID: 'y-incline',
|
||||
borderDash: [5, 5]
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
aspectRatio: div.width / div.height,
|
||||
interaction: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
},
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
annotation: {
|
||||
annotations: {
|
||||
box1: {
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
yMin: 0,
|
||||
yMax: speedZones[0],
|
||||
backgroundColor: "#d6d6d620",
|
||||
yScaleID: 'y-speed',
|
||||
},
|
||||
box2: {
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
yMin: speedZones[0],
|
||||
yMax: speedZones[1],
|
||||
backgroundColor: window.chartColors.limegreent,
|
||||
yScaleID: 'y-speed',
|
||||
},
|
||||
box3: {
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
yMin: speedZones[1],
|
||||
yMax: speedZones[2],
|
||||
backgroundColor: window.chartColors.goldt,
|
||||
yScaleID: 'y-speed',
|
||||
},
|
||||
box4: {
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
yMin: speedZones[2],
|
||||
yMax: speedZones[3],
|
||||
backgroundColor: window.chartColors.oranget,
|
||||
yScaleID: 'y-speed',
|
||||
},
|
||||
box5: {
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
yMin: speedZones[3],
|
||||
yMax: speedZones[4],
|
||||
backgroundColor: window.chartColors.darkoranget,
|
||||
yScaleID: 'y-speed',
|
||||
},
|
||||
box6: {
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
yMin: speedZones[4],
|
||||
yMax: speedZones[5],
|
||||
backgroundColor: window.chartColors.orangeredt,
|
||||
yScaleID: 'y-speed',
|
||||
},
|
||||
box7: {
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
yMin: speedZones[5],
|
||||
yMax: speed_max,
|
||||
backgroundColor: window.chartColors.redt,
|
||||
yScaleID: 'y-speed',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
title: {
|
||||
display: false
|
||||
},
|
||||
ticks: {
|
||||
callback: function(value) {
|
||||
return value !== 0 ?
|
||||
Math.floor(value / 3600).toString().padStart(2, "0") + ":" +
|
||||
Math.floor((value / 60) - (Math.floor(value / 3600) * 60)).toString().padStart(2, "0") :
|
||||
"";
|
||||
},
|
||||
padding: -20,
|
||||
align: "end",
|
||||
},
|
||||
},
|
||||
'y-speed': {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: 'left',
|
||||
title: {
|
||||
display: false
|
||||
},
|
||||
min: 0,
|
||||
max: speed_max,
|
||||
ticks: {
|
||||
stepSize: 1,
|
||||
autoSkip: false,
|
||||
callback: value => speedZones.includes(value) ?
|
||||
'Speed z' + (speedZones.indexOf(value) + 1) : undefined,
|
||||
color: 'black',
|
||||
padding: -70,
|
||||
align: 'end',
|
||||
}
|
||||
},
|
||||
'y-incline': {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: 'right',
|
||||
title: {
|
||||
display: false
|
||||
},
|
||||
min: 0,
|
||||
max: incline_max,
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
treadmillChart = new Chart(ctx, config);
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
el = new MainWSQueueElement({
|
||||
msg: null
|
||||
}, function(msg) {
|
||||
if (msg.msg === 'workout') {
|
||||
return msg.content;
|
||||
}
|
||||
return null;
|
||||
}, 2000, 1);
|
||||
el.enqueue().then(process_workout).catch(function(err) {
|
||||
console.error('Error is ' + err);
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
|
||||
function process_workout(arr) {
|
||||
// Update speed data
|
||||
treadmillChart.data.datasets[0].data.push({
|
||||
x: arr.elapsed_s + (arr.elapsed_m * 60) + (arr.elapsed_h * 3600),
|
||||
y: arr.speed
|
||||
});
|
||||
if (speed_max < arr.speed) {
|
||||
speed_max = Math.ceil(arr.speed * 1.1);
|
||||
treadmillChart.options.scales['y-speed'].max = speed_max;
|
||||
}
|
||||
|
||||
// Update target speed
|
||||
if (arr.target_speed !== undefined && arr.target_speed !== -1) {
|
||||
treadmillChart.data.datasets[1].data.push({
|
||||
x: arr.elapsed_s + (arr.elapsed_m * 60) + (arr.elapsed_h * 3600),
|
||||
y: arr.target_speed
|
||||
});
|
||||
if (speed_max < arr.target_speed) {
|
||||
speed_max = Math.ceil(arr.target_speed * 1.1);
|
||||
treadmillChart.options.scales['y-speed'].max = speed_max;
|
||||
}
|
||||
}
|
||||
|
||||
// Update inclination data
|
||||
treadmillChart.data.datasets[2].data.push({
|
||||
x: arr.elapsed_s + (arr.elapsed_m * 60) + (arr.elapsed_h * 3600),
|
||||
y: arr.inclination
|
||||
});
|
||||
if (incline_max < arr.inclination) {
|
||||
incline_max = Math.ceil(arr.inclination * 1.1);
|
||||
treadmillChart.options.scales['y-incline'].max = incline_max;
|
||||
}
|
||||
|
||||
// Update target inclination
|
||||
if (arr.target_inclination !== undefined && arr.target_inclination !== -200) {
|
||||
treadmillChart.data.datasets[3].data.push({
|
||||
x: arr.elapsed_s + (arr.elapsed_m * 60) + (arr.elapsed_h * 3600),
|
||||
y: arr.target_inclination
|
||||
});
|
||||
if (incline_max < arr.target_inclination) {
|
||||
incline_max = Math.ceil(arr.target_inclination * 1.1);
|
||||
treadmillChart.options.scales['y-incline'].max = incline_max;
|
||||
}
|
||||
}
|
||||
|
||||
treadmillChart.update();
|
||||
refresh();
|
||||
}
|
||||
|
||||
function dochart_init() {
|
||||
el = new MainWSQueueElement({
|
||||
msg: 'getsessionarray'
|
||||
}, function(msg) {
|
||||
if (msg.msg === 'R_getsessionarray') {
|
||||
return msg.content;
|
||||
}
|
||||
return null;
|
||||
}, 15000, 3);
|
||||
el.enqueue().then(process_arr).catch(function(err) {
|
||||
console.error('Error is ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
$(window).on('load', function() {
|
||||
dochart_init();
|
||||
|
||||
/*
|
||||
let testData = [
|
||||
{'speed': 5, 'target_speed': 5, 'inclination': 1, 'target_inclination': 1, 'elapsed_s': 0, 'elapsed_m': 0, 'elapsed_h': 0},
|
||||
{'speed': 8, 'target_speed': 3, 'inclination': 2, 'target_inclination': 2, 'elapsed_s': 0, 'elapsed_m': 5, 'elapsed_h': 0},
|
||||
{'speed': 10, 'target_speed': 5, 'inclination': 4, 'target_inclination': 4, 'elapsed_s': 0, 'elapsed_m': 10, 'elapsed_h': 0},
|
||||
{'speed': 12, 'target_speed': 7, 'inclination': 6, 'target_inclination': 8, 'elapsed_s': 0, 'elapsed_m': 15, 'elapsed_h': 0},
|
||||
{'speed': 14, 'target_speed': 14, 'inclination': 8, 'target_inclination': 10, 'elapsed_s': 0, 'elapsed_m': 20, 'elapsed_h': 0},
|
||||
{'speed': 16, 'target_speed': 16, 'inclination': 10, 'target_inclination': 12, 'elapsed_s': 0, 'elapsed_m': 25, 'elapsed_h': 0},
|
||||
{'speed': 8, 'target_speed': 8, 'inclination': 2, 'target_inclination': 4, 'elapsed_s': 0, 'elapsed_m': 30, 'elapsed_h': 0}
|
||||
];
|
||||
process_arr(testData);
|
||||
*/
|
||||
});
|
||||
|
||||
$(document).ready(function () {
|
||||
$('#loading').hide();
|
||||
});
|
||||
53
src/inner_templates/chartjs/treadmillchartlive.htm
Normal file
53
src/inner_templates/chartjs/treadmillchartlive.htm
Normal file
@@ -0,0 +1,53 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Treadmill Chart</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,height=device-height,initial-scale=1">
|
||||
<script src="resize-observer.min.js"></script>
|
||||
<script src="jquery-3.6.0.min.js"></script>
|
||||
<script src="chartjs.3.4.1.min.js"></script>
|
||||
<script src="moment.js"></script>
|
||||
<script src="chartjs-adapter-moment.js"></script>
|
||||
<script src="chartjs-plugin-annotation.min.js"></script>
|
||||
<script src="globals.js"></script>
|
||||
<script src="main_ws_manager.js"></script>
|
||||
<script src="dotreadmillchartlive.js"></script>
|
||||
<script src="dochartliveheart.js"></script>
|
||||
<script src="html2canvas.min.js"></script>
|
||||
<style>
|
||||
canvas {
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
html, body {
|
||||
-ms-content-zooming: none;
|
||||
touch-action: none;
|
||||
content-zooming: none;
|
||||
overflow-y: hidden;
|
||||
overflow-x: hidden;
|
||||
overflow-y: none;
|
||||
overflow-x: none;
|
||||
margin: 0px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="background-color:#1d2330">
|
||||
<table style="border-spacing: 0px">
|
||||
<tr>
|
||||
<td>
|
||||
<div id="divcanvas" style="width:50vw;height:100vh; background-color:white; border: 0px solid #aaa; overflow: hidden;">
|
||||
<canvas id="canvas"></canvas>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="divcanvasheart" style="width:50vw;height:100vh; background-color:white; border: 0px solid #aaa; overflow: hidden;">
|
||||
<canvas id="canvasheart"></canvas>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -41,7 +41,7 @@ class lockscreen {
|
||||
int virtualrower_getLastFTMSMessage(unsigned char *message);
|
||||
|
||||
// virtualtreadmill
|
||||
void virtualtreadmill_zwift_ios();
|
||||
void virtualtreadmill_zwift_ios(bool garmin_bluetooth_compatibility);
|
||||
void virtualtreadmill_setHeartRate(unsigned char heartRate);
|
||||
double virtualtreadmill_getCurrentSlope();
|
||||
uint64_t virtualtreadmill_lastChangeCurrentSlope();
|
||||
|
||||
@@ -169,9 +169,9 @@ void lockscreen::virtualrower_setHeartRate(unsigned char heartRate)
|
||||
|
||||
|
||||
// virtual treadmill
|
||||
void lockscreen::virtualtreadmill_zwift_ios()
|
||||
void lockscreen::virtualtreadmill_zwift_ios(bool garmin_bluetooth_compatibility)
|
||||
{
|
||||
_virtualtreadmill_zwift = [[virtualtreadmill_zwift alloc] init];
|
||||
_virtualtreadmill_zwift = [[virtualtreadmill_zwift alloc] initWithGarmin_bluetooth_compatibility:garmin_bluetooth_compatibility];
|
||||
}
|
||||
|
||||
void lockscreen::virtualtreadmill_setHeartRate(unsigned char heartRate)
|
||||
|
||||
@@ -845,7 +845,7 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
|
||||
}
|
||||
} else if(self.serviceToggle == 3) {
|
||||
if(watt_bike_emulator) {
|
||||
WattBikeSequence = WattBikeSequence + 1
|
||||
WattBikeSequence = (WattBikeSequence + 1) % 255
|
||||
let WattBikeArray : [UInt8] = [ WattBikeSequence, 0x03, 0xB6, (UInt8)(self.CurrentGears) ]
|
||||
let WattBikeData = Data(bytes: WattBikeArray, count: 4)
|
||||
let ok = self.peripheralManager.updateValue(WattBikeData, for: self.WattBikeReadCharacteristic, onSubscribedCentrals: nil)
|
||||
|
||||
@@ -11,9 +11,9 @@ let treadmilldataUuid = CBUUID(string: "0x2ACD");
|
||||
@objc public class virtualtreadmill_zwift: NSObject {
|
||||
private var peripheralManager: BLEPeripheralManagerTreadmillZwift!
|
||||
|
||||
@objc public override init() {
|
||||
@objc public init(garmin_bluetooth_compatibility: Bool) {
|
||||
super.init()
|
||||
peripheralManager = BLEPeripheralManagerTreadmillZwift()
|
||||
peripheralManager = BLEPeripheralManagerTreadmillZwift(garmin_bluetooth_compatibility: garmin_bluetooth_compatibility)
|
||||
}
|
||||
|
||||
@objc public func updateHeartRate(HeartRate: UInt8)
|
||||
@@ -50,6 +50,7 @@ let treadmilldataUuid = CBUUID(string: "0x2ACD");
|
||||
}
|
||||
|
||||
class BLEPeripheralManagerTreadmillZwift: NSObject, CBPeripheralManagerDelegate {
|
||||
private var garmin_bluetooth_compatibility: Bool = false
|
||||
private var peripheralManager: CBPeripheralManager!
|
||||
|
||||
private var heartRateService: CBMutableService!
|
||||
@@ -87,8 +88,9 @@ class BLEPeripheralManagerTreadmillZwift: NSObject, CBPeripheralManagerDelegate
|
||||
private var notificationTimer: Timer! = nil
|
||||
//var delegate: BLEPeripheralManagerDelegate?
|
||||
|
||||
override init() {
|
||||
init(garmin_bluetooth_compatibility: Bool) {
|
||||
super.init()
|
||||
self.garmin_bluetooth_compatibility = garmin_bluetooth_compatibility
|
||||
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
|
||||
}
|
||||
|
||||
@@ -97,78 +99,79 @@ class BLEPeripheralManagerTreadmillZwift: 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, .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 treadmilldataProperties: CBCharacteristicProperties = [.notify, .read]
|
||||
let treadmilldataPermissions: CBAttributePermissions = [.readable]
|
||||
self.treadmilldataCharacteristic = CBMutableCharacteristic(type: treadmilldataUuid,
|
||||
properties: treadmilldataProperties,
|
||||
value: nil,
|
||||
permissions: treadmilldataPermissions)
|
||||
|
||||
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,
|
||||
treadmilldataCharacteristic,
|
||||
FitnessMachinestatusCharacteristic,
|
||||
TrainingStatusCharacteristic ]
|
||||
|
||||
self.peripheralManager.add(FitnessMachineService)
|
||||
if(!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: 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, .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 treadmilldataProperties: CBCharacteristicProperties = [.notify, .read]
|
||||
let treadmilldataPermissions: CBAttributePermissions = [.readable]
|
||||
self.treadmilldataCharacteristic = CBMutableCharacteristic(type: treadmilldataUuid,
|
||||
properties: treadmilldataProperties,
|
||||
value: nil,
|
||||
permissions: treadmilldataPermissions)
|
||||
|
||||
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,
|
||||
treadmilldataCharacteristic,
|
||||
FitnessMachinestatusCharacteristic,
|
||||
TrainingStatusCharacteristic ]
|
||||
|
||||
self.peripheralManager.add(FitnessMachineService)
|
||||
}
|
||||
|
||||
self.rscService = CBMutableService(type: RSCServiceUuid, primary: true)
|
||||
|
||||
@@ -215,9 +218,16 @@ class BLEPeripheralManagerTreadmillZwift: NSObject, CBPeripheralManagerDelegate
|
||||
return
|
||||
}
|
||||
|
||||
let advertisementData = [CBAdvertisementDataLocalNameKey: "QZ",
|
||||
CBAdvertisementDataServiceUUIDsKey: [heartRateServiceUUID, FitnessMachineServiceUuid, RSCServiceUuid]] as [String : Any]
|
||||
peripheralManager.startAdvertising(advertisementData)
|
||||
if(!garmin_bluetooth_compatibility) {
|
||||
let advertisementData = [CBAdvertisementDataLocalNameKey: "QZ",
|
||||
CBAdvertisementDataServiceUUIDsKey: [heartRateServiceUUID, FitnessMachineServiceUuid, RSCServiceUuid]] as [String : Any]
|
||||
peripheralManager.startAdvertising(advertisementData)
|
||||
} else {
|
||||
let advertisementData = [CBAdvertisementDataLocalNameKey: "QZ",
|
||||
CBAdvertisementDataServiceUUIDsKey: [RSCServiceUuid]] as [String : Any]
|
||||
peripheralManager.startAdvertising(advertisementData)
|
||||
}
|
||||
|
||||
print("Successfully added service")
|
||||
}
|
||||
|
||||
@@ -361,21 +371,21 @@ class BLEPeripheralManagerTreadmillZwift: NSObject, CBPeripheralManagerDelegate
|
||||
let treadmillData = self.calculateTreadmillData()
|
||||
let rscMeasurementData = self.calculateRSCMeasurement()
|
||||
|
||||
if(self.serviceToggle == 0)
|
||||
if(self.serviceToggle == 0 && !garmin_bluetooth_compatibility)
|
||||
{
|
||||
let ok = self.peripheralManager.updateValue(heartRateData, for: self.heartRateCharacteristic, onSubscribedCentrals: nil)
|
||||
if(ok) {
|
||||
self.serviceToggle = 1;
|
||||
}
|
||||
}
|
||||
else if(self.serviceToggle == 1)
|
||||
else if(self.serviceToggle == 1 && !garmin_bluetooth_compatibility)
|
||||
{
|
||||
let ok = self.peripheralManager.updateValue(treadmillData, for: self.treadmilldataCharacteristic, onSubscribedCentrals: nil)
|
||||
if(ok) {
|
||||
self.serviceToggle = 2;
|
||||
}
|
||||
}
|
||||
else if(self.serviceToggle == 2)
|
||||
else if(self.serviceToggle == 2 || garmin_bluetooth_compatibility)
|
||||
{
|
||||
let ok = self.peripheralManager.updateValue(rscMeasurementData, for: self.rscMeasurementCharacteristic, onSubscribedCentrals: nil)
|
||||
if(ok) {
|
||||
|
||||
15
src/main.cpp
15
src/main.cpp
@@ -41,6 +41,8 @@
|
||||
#include "ios/lockscreen.h"
|
||||
#endif
|
||||
|
||||
#include "osc.h"
|
||||
|
||||
#include "handleurl.h"
|
||||
|
||||
bool logs = true;
|
||||
@@ -68,6 +70,8 @@ bool battery_service = false;
|
||||
bool service_changed = false;
|
||||
bool bike_wheel_revs = false;
|
||||
bool run_cadence_sensor = false;
|
||||
bool horizon_treadmill_7_8 = false;
|
||||
bool horizon_treadmill_force_ftms = false;
|
||||
bool nordictrack_10_treadmill = false;
|
||||
bool reebok_fr30_treadmill = false;
|
||||
bool zwift_play = false;
|
||||
@@ -140,6 +144,10 @@ QCoreApplication *createApplication(int &argc, char *argv[]) {
|
||||
bike_wheel_revs = true;
|
||||
if (!qstrcmp(argv[i], "-run-cadence-sensor"))
|
||||
run_cadence_sensor = true;
|
||||
if (!qstrcmp(argv[i], "-horizon-treadmill-7-8"))
|
||||
horizon_treadmill_7_8 = true;
|
||||
if (!qstrcmp(argv[i], "-horizon-treadmill-force-ftms"))
|
||||
horizon_treadmill_force_ftms = true;
|
||||
if (!qstrcmp(argv[i], "-nordictrack-10-treadmill"))
|
||||
nordictrack_10_treadmill = true;
|
||||
if (!qstrcmp(argv[i], "-reebok_fr30_treadmill"))
|
||||
@@ -404,6 +412,8 @@ int main(int argc, char *argv[]) {
|
||||
settings.setValue(QZSettings::service_changed, service_changed);
|
||||
settings.setValue(QZSettings::bike_wheel_revs, bike_wheel_revs);
|
||||
settings.setValue(QZSettings::run_cadence_sensor, run_cadence_sensor);
|
||||
settings.setValue(QZSettings::horizon_treadmill_7_8, horizon_treadmill_7_8);
|
||||
settings.setValue(QZSettings::horizon_treadmill_force_ftms, horizon_treadmill_force_ftms);
|
||||
settings.setValue(QZSettings::nordictrack_10_treadmill, nordictrack_10_treadmill);
|
||||
settings.setValue(QZSettings::reebok_fr30_treadmill, reebok_fr30_treadmill);
|
||||
settings.setValue(QZSettings::zwift_click, zwift_click);
|
||||
@@ -598,6 +608,11 @@ int main(int argc, char *argv[]) {
|
||||
MQTTPublisher* mqtt = new MQTTPublisher(mqtt_host, mqtt_port, mqtt_username, mqtt_password, &bl);
|
||||
}
|
||||
|
||||
QString OSC_ip = settings.value(QZSettings::OSC_ip, QZSettings::default_OSC_ip).toString();
|
||||
if(OSC_ip.length() > 0) {
|
||||
OSC* osc = new OSC(&bl);
|
||||
}
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
lockscreen h;
|
||||
|
||||
@@ -777,7 +777,7 @@ ApplicationWindow {
|
||||
}
|
||||
|
||||
ItemDelegate {
|
||||
text: "version 2.18.9"
|
||||
text: "version 2.18.17"
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
|
||||
247
src/osc.cpp
Normal file
247
src/osc.cpp
Normal file
@@ -0,0 +1,247 @@
|
||||
#include "osc.h"
|
||||
|
||||
OSC::OSC(bluetooth* manager, QObject *parent)
|
||||
: QObject{parent}
|
||||
{
|
||||
bluetoothManager = manager;
|
||||
// Setup timer for periodic publishing
|
||||
m_timer = new QTimer();
|
||||
m_timer->setInterval(1000);
|
||||
connect(m_timer, &QTimer::timeout, this, &OSC::publishWorkoutData);
|
||||
|
||||
OSC_recvSocket->bind(9001);
|
||||
|
||||
m_timer->start();
|
||||
}
|
||||
|
||||
void OSC::publishWorkoutData() {
|
||||
if(!bluetoothManager->device()) return;
|
||||
QSettings settings;
|
||||
QString OSC_ip = settings.value(QZSettings::OSC_ip, QZSettings::default_OSC_ip).toString();
|
||||
int OSC_port = settings.value(QZSettings::OSC_port, QZSettings::default_OSC_port).toInt();
|
||||
QByteArray osc_read = OSC_recvSocket->readAll();
|
||||
if(!osc_read.isEmpty()) {
|
||||
OSC_handlePacket(OSCPP::Server::Packet(osc_read.data(), osc_read.length()));
|
||||
}
|
||||
char osc_buffer[3000];
|
||||
int osc_len = OSC_makePacket(osc_buffer, sizeof(osc_buffer));
|
||||
int osc_ret_len = OSC_sendSocket->writeDatagram(osc_buffer, osc_len, QHostAddress(OSC_ip), OSC_port);
|
||||
qDebug() << "OSC >> " << osc_ret_len << QByteArray::fromRawData(osc_buffer, osc_len).toHex(' ');
|
||||
}
|
||||
|
||||
size_t OSC::OSC_makePacket(void* buffer, size_t size)
|
||||
{
|
||||
// Construct a packet
|
||||
OSCPP::Client::Packet packet(buffer, size);
|
||||
packet
|
||||
// Open a bundle with a timetag
|
||||
.openBundle(1234ULL)
|
||||
// Add a message with two arguments and an array with 6 elements;
|
||||
// for efficiency this needs to be known in advance.
|
||||
.openMessage("/QZ/Resistance", 1)
|
||||
.int32(bluetoothManager->device()->currentResistance().value())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/Heart", 1)
|
||||
.int32(bluetoothManager->device()->currentHeart().value())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/Speed", 1)
|
||||
.float32(bluetoothManager->device()->currentSpeed().value())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/Pace", 1)
|
||||
.string(bluetoothManager->device()->currentPace().toString(QStringLiteral("m:ss")).toLatin1())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/Inclination", 1)
|
||||
.float32(bluetoothManager->device()->currentInclination().value())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/AveragePace", 1)
|
||||
.string(bluetoothManager->device()->averagePace().toString(QStringLiteral("m:ss")).toLatin1())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/MaxPace", 1)
|
||||
.string(bluetoothManager->device()->maxPace().toString(QStringLiteral("m:ss")).toLatin1())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/Odometer", 1)
|
||||
.float32(bluetoothManager->device()->odometer())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/OdometerFromStartup", 1)
|
||||
.float32(bluetoothManager->device()->odometerFromStartup())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/Distance", 1)
|
||||
.float32(bluetoothManager->device()->currentDistance().value())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/Distance1s", 1)
|
||||
.float32(bluetoothManager->device()->currentDistance1s().value())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/Calories", 1)
|
||||
.float32(bluetoothManager->device()->calories().value())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/Joules", 1)
|
||||
.float32(bluetoothManager->device()->jouls().value())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/FanSpeed", 1)
|
||||
.int32(bluetoothManager->device()->fanSpeed())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/ElapsedTime", 1)
|
||||
.string(bluetoothManager->device()->elapsedTime().toString(QStringLiteral("m:ss")).toLatin1())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/MovingTime", 1)
|
||||
.string(bluetoothManager->device()->movingTime().toString(QStringLiteral("m:ss")).toLatin1())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/LapElapsedTime", 1)
|
||||
.string(bluetoothManager->device()->lapElapsedTime().toString(QStringLiteral("m:ss")).toLatin1())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/Connected", 1)
|
||||
.int32(bluetoothManager->device()->connected())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/Resistance", 1)
|
||||
.int32(bluetoothManager->device()->currentResistance().value())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/Cadence", 1)
|
||||
.float32(bluetoothManager->device()->currentCadence().value())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/CrankRevolutions", 1)
|
||||
.float32(bluetoothManager->device()->currentCrankRevolutions())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/Coordinate", 2)
|
||||
.float32(bluetoothManager->device()->currentCordinate().latitude())
|
||||
.float32(bluetoothManager->device()->currentCordinate().longitude())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/Azimuth", 1)
|
||||
.float32(bluetoothManager->device()->currentAzimuth())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/AverageAzimuthNext300m", 1)
|
||||
.float32(bluetoothManager->device()->averageAzimuthNext300m())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/LastCrankEventTime", 1)
|
||||
.int32(bluetoothManager->device()->lastCrankEventTime())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/Watts", 1)
|
||||
.int32(bluetoothManager->device()->wattsMetric().value())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/ElevationGain", 1)
|
||||
.float32(bluetoothManager->device()->elevationGain().value())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/Paused", 1)
|
||||
.int32(bluetoothManager->device()->isPaused())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/AutoResistance", 1)
|
||||
.int32(bluetoothManager->device()->autoResistance())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/Difficulty", 1)
|
||||
.float32(bluetoothManager->device()->difficult())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/InclinationDifficulty", 1)
|
||||
.float32(bluetoothManager->device()->inclinationDifficult())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/DifficultyOffset", 1)
|
||||
.float32(bluetoothManager->device()->difficultOffset())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/InclinationDifficultyOffset", 1)
|
||||
.float32(bluetoothManager->device()->inclinationDifficultOffset())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/WeightLoss", 1)
|
||||
.float32(bluetoothManager->device()->weightLoss())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/WattKg", 1)
|
||||
.float32(bluetoothManager->device()->wattKg().value())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/METS", 1)
|
||||
.float32(bluetoothManager->device()->currentMETS().value())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/HeartZone", 1)
|
||||
.int32(bluetoothManager->device()->currentHeartZone().value())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/MaxHeartZone", 1)
|
||||
.int32(bluetoothManager->device()->maxHeartZone())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/PowerZone", 1)
|
||||
.int32(bluetoothManager->device()->currentPowerZone().value())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/TargetPowerZone", 1)
|
||||
.int32(bluetoothManager->device()->targetPowerZone().value())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/DeviceType", 1)
|
||||
.int32(bluetoothManager->device()->deviceType())
|
||||
.closeMessage()
|
||||
|
||||
.openMessage("/QZ/MaxResistance", 1)
|
||||
.int32(bluetoothManager->device()->maxResistance())
|
||||
.closeMessage()
|
||||
.closeBundle();
|
||||
return packet.size();
|
||||
}
|
||||
|
||||
void OSC::OSC_handlePacket(const OSCPP::Server::Packet& packet)
|
||||
{
|
||||
if (packet.isBundle()) {
|
||||
// Convert to bundle
|
||||
OSCPP::Server::Bundle bundle(packet);
|
||||
|
||||
// Print the time
|
||||
std::cout << "#bundle " << bundle.time() << std::endl;
|
||||
|
||||
// Get packet stream
|
||||
OSCPP::Server::PacketStream packets(bundle.packets());
|
||||
|
||||
// Iterate over all the packets and call handlePacket recursively.
|
||||
// Cuidado: Might lead to stack overflow!
|
||||
while (!packets.atEnd()) {
|
||||
OSC_handlePacket(packets.next());
|
||||
}
|
||||
} else {
|
||||
// Convert to message
|
||||
OSCPP::Server::Message msg(packet);
|
||||
|
||||
// Get argument stream
|
||||
OSCPP::Server::ArgStream args(msg.args());
|
||||
|
||||
if (msg == "/QZ/Resistance") {
|
||||
const float value = args.int32();
|
||||
qDebug() << "OSC" << value;
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
|
||||
((bike*)bluetoothManager->device())->changeResistance(value);
|
||||
} else {
|
||||
// Simply print unknown messages
|
||||
std::cout << "Unknown message: " << msg << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/osc.h
Normal file
44
src/osc.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef OSC_H
|
||||
#define OSC_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QSettings>
|
||||
#include <QHash>
|
||||
#include <QVariant>
|
||||
#include "bluetoothdevice.h"
|
||||
#include "devices/bike.h"
|
||||
#include "devices/treadmill.h"
|
||||
#include "devices/rower.h"
|
||||
#include "homeform.h"
|
||||
#include "bluetooth.h"
|
||||
#include <oscpp/client.hpp>
|
||||
#include <oscpp/server.hpp>
|
||||
#include <oscpp/print.hpp>
|
||||
#include <iostream>
|
||||
|
||||
class OSC : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OSC(bluetooth* manager, QObject *parent = nullptr);
|
||||
|
||||
private:
|
||||
QTimer* m_timer;
|
||||
bluetooth* bluetoothManager;
|
||||
|
||||
size_t OSC_makePacket(void* buffer, size_t size);
|
||||
void OSC_handlePacket(const OSCPP::Server::Packet& packet);
|
||||
QUdpSocket* OSC_sendSocket = new QUdpSocket(this);
|
||||
QUdpSocket* OSC_recvSocket = new QUdpSocket(this);
|
||||
|
||||
private slots:
|
||||
void publishWorkoutData();
|
||||
|
||||
signals:
|
||||
|
||||
};
|
||||
|
||||
#endif // OSC_H
|
||||
368
src/oscpp/client.hpp
Normal file
368
src/oscpp/client.hpp
Normal file
@@ -0,0 +1,368 @@
|
||||
// oscpp library
|
||||
//
|
||||
// Copyright (c) 2004-2013 Stefan Kersten <sk@k-hornz.de>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person or organization
|
||||
// obtaining a copy of the software and accompanying documentation covered by
|
||||
// this license (the "Software") to use, reproduce, display, distribute,
|
||||
// execute, and transmit the Software, and to prepare derivative works of the
|
||||
// Software, and to permit third-parties to whom the Software is furnished to
|
||||
// do so, all subject to the following:
|
||||
//
|
||||
// The copyright notices in the Software and this entire statement, including
|
||||
// the above license grant, this restriction and the following disclaimer,
|
||||
// must be included in all copies of the Software, in whole or in part, and
|
||||
// all derivative works of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#ifndef OSCPP_CLIENT_HPP_INCLUDED
|
||||
#define OSCPP_CLIENT_HPP_INCLUDED
|
||||
|
||||
#include <oscpp/detail/host.hpp>
|
||||
#include <oscpp/detail/stream.hpp>
|
||||
#include <oscpp/util.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
namespace OSCPP { namespace Client {
|
||||
|
||||
//! OSC packet construction.
|
||||
/*!
|
||||
* Construct a valid OSC packet for transmitting over a transport
|
||||
* medium.
|
||||
*/
|
||||
class Packet
|
||||
{
|
||||
int32_t ptrDiff(const char* a, const char* b)
|
||||
{
|
||||
// Make sure pointer difference fits into int32_t
|
||||
const intptr_t diff = a - b;
|
||||
if (diff < std::numeric_limits<int32_t>::min() ||
|
||||
diff > std::numeric_limits<int32_t>::max())
|
||||
{
|
||||
std::stringstream s;
|
||||
s << "Pointer difference " << diff
|
||||
<< " can't be represented by int32_t";
|
||||
throw std::logic_error(s.str());
|
||||
}
|
||||
return static_cast<int32_t>(diff);
|
||||
}
|
||||
|
||||
int32_t calcSize(const char* begin, const char* end)
|
||||
{
|
||||
const int32_t size = ptrDiff(end, begin) - 4;
|
||||
if (size < 0)
|
||||
{
|
||||
throw std::logic_error("Calculated size is negative");
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
public:
|
||||
//! Constructor.
|
||||
/*!
|
||||
*/
|
||||
Packet()
|
||||
{
|
||||
reset(0, 0);
|
||||
}
|
||||
|
||||
//! Constructor.
|
||||
/*!
|
||||
*/
|
||||
Packet(void* buffer, size_t size)
|
||||
{
|
||||
reset(buffer, size);
|
||||
}
|
||||
|
||||
//! Destructor.
|
||||
virtual ~Packet()
|
||||
{}
|
||||
|
||||
//! Get packet buffer address.
|
||||
/*!
|
||||
* Return the start address of the packet currently under
|
||||
* construction.
|
||||
*/
|
||||
void* data() const
|
||||
{
|
||||
return m_buffer;
|
||||
}
|
||||
|
||||
size_t capacity() const
|
||||
{
|
||||
return m_capacity;
|
||||
}
|
||||
|
||||
//! Get packet content size.
|
||||
/*!
|
||||
* Return the size of the packet currently under construction.
|
||||
*/
|
||||
size_t size() const
|
||||
{
|
||||
return m_args.consumed();
|
||||
}
|
||||
|
||||
//! Reset packet state.
|
||||
void reset(void* buffer, size_t size)
|
||||
{
|
||||
checkAlignment(&m_buffer, kAlignment);
|
||||
m_buffer = buffer;
|
||||
m_capacity = size;
|
||||
m_args = WriteStream(m_buffer, m_capacity);
|
||||
m_sizePosM = m_sizePosB = nullptr;
|
||||
m_inBundle = 0;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
reset(m_buffer, m_capacity);
|
||||
}
|
||||
|
||||
Packet& openBundle(uint64_t time)
|
||||
{
|
||||
if (m_inBundle > 0)
|
||||
{
|
||||
assert(m_sizePosB != nullptr || m_inBundle == 1);
|
||||
// Remember previous size pos offset
|
||||
const int32_t offset =
|
||||
m_sizePosB == nullptr ? 0 : ptrDiff(m_sizePosB, m_args.begin());
|
||||
char* curPos = m_args.pos();
|
||||
m_args.skip(4);
|
||||
// Record size pos
|
||||
std::memcpy(curPos, &offset, 4);
|
||||
m_sizePosB = curPos;
|
||||
}
|
||||
else if (m_args.pos() != m_args.begin())
|
||||
{
|
||||
throw std::logic_error(
|
||||
"Cannot open toplevel bundle in non-empty packet");
|
||||
}
|
||||
|
||||
m_inBundle++;
|
||||
m_args.putString("#bundle");
|
||||
m_args.putUInt64(time);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& closeBundle()
|
||||
{
|
||||
if (m_inBundle > 0)
|
||||
{
|
||||
if (m_inBundle > 1)
|
||||
{
|
||||
// Get current stream pos
|
||||
char* curPos = m_args.pos();
|
||||
|
||||
// Get previous bundle size stream pos
|
||||
int32_t offset;
|
||||
memcpy(&offset, m_sizePosB, 4);
|
||||
// Get previous size pos
|
||||
char* prevPos = m_args.begin() + offset;
|
||||
|
||||
const int32_t bundleSize = calcSize(m_sizePosB, curPos);
|
||||
assert(bundleSize >= 0 &&
|
||||
(size_t)bundleSize >= Size::bundle(0));
|
||||
// Write bundle size
|
||||
m_args.setPos(m_sizePosB);
|
||||
m_args.putInt32(bundleSize);
|
||||
m_args.setPos(curPos);
|
||||
|
||||
// record outer bundle size pos
|
||||
m_sizePosB = prevPos;
|
||||
}
|
||||
m_inBundle--;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::logic_error(
|
||||
"closeBundle() without matching openBundle()");
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& openMessage(const char* addr, size_t numTags)
|
||||
{
|
||||
if (m_inBundle > 0)
|
||||
{
|
||||
// record message size pos
|
||||
m_sizePosM = m_args.pos();
|
||||
// advance arg stream
|
||||
m_args.skip(4);
|
||||
}
|
||||
m_args.putString(addr);
|
||||
size_t sigLen = numTags + 2;
|
||||
m_tags = WriteStream(m_args, sigLen);
|
||||
m_args.zero(align(sigLen));
|
||||
m_tags.putChar(',');
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& closeMessage()
|
||||
{
|
||||
if (m_inBundle > 0)
|
||||
{
|
||||
// Get current stream pos
|
||||
char* curPos = m_args.pos();
|
||||
// write message size
|
||||
m_args.setPos(m_sizePosM);
|
||||
m_args.putInt32(calcSize(m_sizePosM, curPos));
|
||||
// restore stream pos
|
||||
m_args.setPos(curPos);
|
||||
// reset tag stream
|
||||
m_tags = WriteStream();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
//! Write integer message argument.
|
||||
/*!
|
||||
* Write a 32 bit integer message argument.
|
||||
*
|
||||
* \param arg 32 bit integer argument.
|
||||
*
|
||||
* \pre openMessage must have been called before with no intervening
|
||||
* closeMessage.
|
||||
*
|
||||
* \throw OSCPP::XRunError stream buffer xrun.
|
||||
*/
|
||||
Packet& int32(int32_t arg)
|
||||
{
|
||||
m_tags.putChar('i');
|
||||
m_args.putInt32(arg);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& float32(float arg)
|
||||
{
|
||||
m_tags.putChar('f');
|
||||
m_args.putFloat32(arg);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& string(const char* arg)
|
||||
{
|
||||
m_tags.putChar('s');
|
||||
m_args.putString(arg);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// @throw std::invalid_argument if blob size is greater than
|
||||
// std::numeric_limits<int32_t>::max()
|
||||
Packet& blob(const Blob& arg)
|
||||
{
|
||||
if (arg.size() > (size_t)std::numeric_limits<int32_t>::max())
|
||||
{
|
||||
throw std::invalid_argument("Blob size greater than maximum "
|
||||
"value representable by int32_t");
|
||||
}
|
||||
m_tags.putChar('b');
|
||||
m_args.putInt32(static_cast<int32_t>(arg.size()));
|
||||
m_args.putData(arg.data(), arg.size());
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& openArray()
|
||||
{
|
||||
m_tags.putChar('[');
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& closeArray()
|
||||
{
|
||||
m_tags.putChar(']');
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T> Packet& put(T)
|
||||
{
|
||||
T::OSC_Client_Packet_put_unimplemented;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename InputIterator>
|
||||
Packet& put(InputIterator begin, InputIterator end)
|
||||
{
|
||||
for (auto it = begin; it != end; it++)
|
||||
{
|
||||
put(*it);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename InputIterator>
|
||||
Packet& putArray(InputIterator begin, InputIterator end)
|
||||
{
|
||||
openArray();
|
||||
put<InputIterator>(begin, end);
|
||||
closeArray();
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
void* m_buffer;
|
||||
size_t m_capacity;
|
||||
WriteStream m_args; // packet stream
|
||||
WriteStream m_tags; // current tag stream
|
||||
char* m_sizePosM; // last message size position
|
||||
char* m_sizePosB; // last bundle size position
|
||||
size_t m_inBundle; // bundle nesting depth
|
||||
};
|
||||
|
||||
template <> inline Packet& Packet::put<int32_t>(int32_t x)
|
||||
{
|
||||
return int32(x);
|
||||
}
|
||||
template <> inline Packet& Packet::put<float>(float x)
|
||||
{
|
||||
return float32(x);
|
||||
}
|
||||
template <> inline Packet& Packet::put<const char*>(const char* x)
|
||||
{
|
||||
return string(x);
|
||||
}
|
||||
template <> inline Packet& Packet::put<Blob>(Blob x)
|
||||
{
|
||||
return blob(x);
|
||||
}
|
||||
|
||||
template <size_t buffer_size> class StaticPacket : public Packet
|
||||
{
|
||||
public:
|
||||
StaticPacket()
|
||||
: Packet(reinterpret_cast<char*>(&m_buffer), buffer_size)
|
||||
{}
|
||||
|
||||
private:
|
||||
typedef typename std::aligned_storage<buffer_size, kAlignment>::type
|
||||
AlignedBuffer;
|
||||
AlignedBuffer m_buffer;
|
||||
};
|
||||
|
||||
class DynamicPacket : public Packet
|
||||
{
|
||||
public:
|
||||
DynamicPacket(size_t buffer_size)
|
||||
: Packet(static_cast<char*>(new char[buffer_size]), buffer_size)
|
||||
{}
|
||||
|
||||
~DynamicPacket()
|
||||
{
|
||||
delete[] static_cast<char*>(data());
|
||||
}
|
||||
};
|
||||
|
||||
}} // namespace OSCPP::Client
|
||||
|
||||
#endif // OSCPP_CLIENT_HPP_INCLUDED
|
||||
82
src/oscpp/detail/endian.hpp
Normal file
82
src/oscpp/detail/endian.hpp
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2005 Caleb Epstein
|
||||
// Copyright 2006 John Maddock
|
||||
// Copyright 2010 Rene Rivera
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompany-
|
||||
// ing file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
/*
|
||||
* Copyright (c) 1997
|
||||
* Silicon Graphics Computer Systems, Inc.
|
||||
*
|
||||
* Permission to use, copy, modify, distribute and sell this software
|
||||
* and its documentation for any purpose is hereby granted without fee,
|
||||
* provided that the above copyright notice appear in all copies and
|
||||
* that both that copyright notice and this permission notice appear
|
||||
* in supporting documentation. Silicon Graphics makes no
|
||||
* representations about the suitability of this software for any
|
||||
* purpose. It is provided "as is" without express or implied warranty.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright notice reproduced from <boost/detail/limits.hpp>, from
|
||||
* which this code was originally taken.
|
||||
*
|
||||
* Modified by Caleb Epstein to use <endian.h> with GNU libc and to
|
||||
* defined the BOOST_ENDIAN macro.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Modifications for oscpp by Stefan Kersten
|
||||
* - Change prefix from BOOST to OSCPP
|
||||
* - Remove PDP endianness
|
||||
* - Add OSCPP_BYTE_ORDER_* macros
|
||||
*/
|
||||
|
||||
#ifndef OSCPP_ENDIAN_HPP_INCLUDED
|
||||
#define OSCPP_ENDIAN_HPP_INCLUDED
|
||||
|
||||
#define OSCPP_BYTE_ORDER_BIG_ENDIAN 4321
|
||||
#define OSCPP_BYTE_ORDER_LITTLE_ENDIAN 1234
|
||||
|
||||
// GNU libc offers the helpful header <endian.h> which defines
|
||||
// __BYTE_ORDER
|
||||
|
||||
#if defined(__GLIBC__) || defined(__ANDROID__)
|
||||
# include <endian.h>
|
||||
# if (__BYTE_ORDER == __LITTLE_ENDIAN)
|
||||
# define OSCPP_LITTLE_ENDIAN
|
||||
# elif (__BYTE_ORDER == __BIG_ENDIAN)
|
||||
# define OSCPP_BIG_ENDIAN
|
||||
# else
|
||||
# error Unknown machine endianness detected.
|
||||
# endif
|
||||
# define OSCPP_BYTE_ORDER __BYTE_ORDER
|
||||
#elif defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) || \
|
||||
defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) || \
|
||||
defined(_STLP_BIG_ENDIAN) && !defined(_STLP_LITTLE_ENDIAN)
|
||||
# define OSCPP_BIG_ENDIAN
|
||||
# define OSCPP_BYTE_ORDER OSCPP_BYTE_ORDER_BIG_ENDIAN
|
||||
#elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN) || \
|
||||
defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__) || \
|
||||
defined(_STLP_LITTLE_ENDIAN) && !defined(_STLP_BIG_ENDIAN)
|
||||
# define OSCPP_LITTLE_ENDIAN
|
||||
# define OSCPP_BYTE_ORDER OSCPP_BYTE_ORDER_LITTLE_ENDIAN
|
||||
#elif defined(__sparc) || defined(__sparc__) || defined(_POWER) || \
|
||||
defined(__powerpc__) || defined(__ppc__) || defined(__hpux) || \
|
||||
defined(__hppa) || defined(_MIPSEB) || defined(_POWER) || \
|
||||
defined(__s390__)
|
||||
# define OSCPP_BIG_ENDIAN
|
||||
# define OSCPP_BYTE_ORDER OSCPP_BYTE_ORDER_BIG_ENDIAN
|
||||
#elif defined(__i386__) || defined(__alpha__) || defined(__ia64) || \
|
||||
defined(__ia64__) || defined(_M_IX86) || defined(_M_IA64) || \
|
||||
defined(_M_ALPHA) || defined(__amd64) || defined(__amd64__) || \
|
||||
defined(_M_AMD64) || defined(__x86_64) || defined(__x86_64__) || \
|
||||
defined(_M_X64) || defined(__bfin__)
|
||||
|
||||
# define OSCPP_LITTLE_ENDIAN
|
||||
# define OSCPP_BYTE_ORDER OSCPP_BYTE_ORDER_LITTLE_ENDIAN
|
||||
#else
|
||||
# error The file oscpp/endian.hpp needs to be set up for your CPU type.
|
||||
#endif
|
||||
|
||||
#endif // OSCPP_ENDIAN_HPP_INCLUDED
|
||||
118
src/oscpp/detail/host.hpp
Normal file
118
src/oscpp/detail/host.hpp
Normal file
@@ -0,0 +1,118 @@
|
||||
// oscpp library
|
||||
//
|
||||
// Copyright (c) 2004-2013 Stefan Kersten <sk@k-hornz.de>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person or organization
|
||||
// obtaining a copy of the software and accompanying documentation covered by
|
||||
// this license (the "Software") to use, reproduce, display, distribute,
|
||||
// execute, and transmit the Software, and to prepare derivative works of the
|
||||
// Software, and to permit third-parties to whom the Software is furnished to
|
||||
// do so, all subject to the following:
|
||||
//
|
||||
// The copyright notices in the Software and this entire statement, including
|
||||
// the above license grant, this restriction and the following disclaimer,
|
||||
// must be included in all copies of the Software, in whole or in part, and
|
||||
// all derivative works of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#ifndef OSCPP_HOST_HPP_INCLUDED
|
||||
#define OSCPP_HOST_HPP_INCLUDED
|
||||
|
||||
#include <oscpp/detail/endian.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace OSCPP {
|
||||
#if defined(__GNUC__)
|
||||
inline static uint32_t bswap32(uint32_t x)
|
||||
{
|
||||
return __builtin_bswap32(x);
|
||||
}
|
||||
inline static uint64_t bswap64(uint64_t x)
|
||||
{
|
||||
return __builtin_bswap64(x);
|
||||
}
|
||||
#elif defined(_WINDOWS_) || defined(_WIN32)
|
||||
# include <stdlib.h>
|
||||
inline static uint32_t bswap32(uint32_t x)
|
||||
{
|
||||
return _byteswap_ulong(x);
|
||||
}
|
||||
inline static uint64_t bswap64(uint64_t x)
|
||||
{
|
||||
return _byteswap_uint64(x);
|
||||
}
|
||||
#else
|
||||
// Fallback implementation
|
||||
# warning Using unoptimized byte swap functions
|
||||
|
||||
inline static uint32_t bswap32(uint32_t x)
|
||||
{
|
||||
const uint32_t b1 = x << 24;
|
||||
const uint32_t b2 = (x & 0x0000FF00) << 8;
|
||||
const uint32_t b3 = (x & 0x00FF0000) >> 8;
|
||||
const uint32_t b4 = x >> 24;
|
||||
return b1 | b2 | b3 | b4;
|
||||
}
|
||||
inline static uint64_t bswap64(int64_t x)
|
||||
{
|
||||
const uint64_t w1 = oscpp_bswap(uint32_t(x & 0x00000000FFFFFFFF)) << 32;
|
||||
const uint64_t w2 = oscpp_bswap(uint32_t(x >> 32));
|
||||
return w1 | w2;
|
||||
}
|
||||
#endif
|
||||
|
||||
enum ByteOrder
|
||||
{
|
||||
NetworkByteOrder,
|
||||
HostByteOrder
|
||||
};
|
||||
|
||||
template <ByteOrder B> inline uint32_t convert32(uint32_t)
|
||||
{
|
||||
throw std::logic_error("Invalid byte order");
|
||||
}
|
||||
|
||||
template <> inline uint32_t convert32<NetworkByteOrder>(uint32_t x)
|
||||
{
|
||||
#if defined(OSCPP_LITTLE_ENDIAN)
|
||||
return bswap32(x);
|
||||
#else
|
||||
return x;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <> inline uint32_t convert32<HostByteOrder>(uint32_t x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
template <ByteOrder B> inline uint64_t convert64(uint64_t)
|
||||
{
|
||||
throw std::logic_error("Invalid byte order");
|
||||
}
|
||||
|
||||
template <> inline uint64_t convert64<NetworkByteOrder>(uint64_t x)
|
||||
{
|
||||
#if defined(OSCPP_LITTLE_ENDIAN)
|
||||
return bswap64(x);
|
||||
#else
|
||||
return x;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <> inline uint64_t convert64<HostByteOrder>(uint64_t x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
} // namespace OSCPP
|
||||
|
||||
#endif // OSCPP_HOST_HPP_INCLUDED
|
||||
379
src/oscpp/detail/stream.hpp
Normal file
379
src/oscpp/detail/stream.hpp
Normal file
@@ -0,0 +1,379 @@
|
||||
// oscpp library
|
||||
//
|
||||
// Copyright (c) 2004-2013 Stefan Kersten <sk@k-hornz.de>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person or organization
|
||||
// obtaining a copy of the software and accompanying documentation covered by
|
||||
// this license (the "Software") to use, reproduce, display, distribute,
|
||||
// execute, and transmit the Software, and to prepare derivative works of the
|
||||
// Software, and to permit third-parties to whom the Software is furnished to
|
||||
// do so, all subject to the following:
|
||||
//
|
||||
// The copyright notices in the Software and this entire statement, including
|
||||
// the above license grant, this restriction and the following disclaimer,
|
||||
// must be included in all copies of the Software, in whole or in part, and
|
||||
// all derivative works of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#ifndef OSCPP_STREAM_HPP_INCLUDED
|
||||
#define OSCPP_STREAM_HPP_INCLUDED
|
||||
|
||||
#include <oscpp/detail/host.hpp>
|
||||
#include <oscpp/error.hpp>
|
||||
#include <oscpp/types.hpp>
|
||||
#include <oscpp/util.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
namespace OSCPP {
|
||||
|
||||
class Stream
|
||||
{
|
||||
public:
|
||||
Stream()
|
||||
{
|
||||
m_begin = m_end = m_pos = 0;
|
||||
}
|
||||
|
||||
Stream(void* data, size_t size)
|
||||
{
|
||||
m_begin = static_cast<char*>(data);
|
||||
m_end = m_begin + size;
|
||||
m_pos = m_begin;
|
||||
}
|
||||
|
||||
Stream(const Stream& stream)
|
||||
{
|
||||
m_begin = m_pos = stream.m_pos;
|
||||
m_end = stream.m_end;
|
||||
}
|
||||
|
||||
Stream(const Stream& stream, size_t size)
|
||||
{
|
||||
m_begin = m_pos = stream.m_pos;
|
||||
m_end = m_begin + size;
|
||||
if (m_end > stream.m_end)
|
||||
throw UnderrunError();
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
m_pos = m_begin;
|
||||
}
|
||||
|
||||
const char* begin() const
|
||||
{
|
||||
return m_begin;
|
||||
}
|
||||
|
||||
char* begin()
|
||||
{
|
||||
return m_begin;
|
||||
}
|
||||
|
||||
const char* end() const
|
||||
{
|
||||
return m_end;
|
||||
}
|
||||
|
||||
size_t capacity() const
|
||||
{
|
||||
return end() - begin();
|
||||
}
|
||||
|
||||
const char* pos() const
|
||||
{
|
||||
return m_pos;
|
||||
}
|
||||
|
||||
char* pos()
|
||||
{
|
||||
return m_pos;
|
||||
}
|
||||
|
||||
void setPos(char* pos)
|
||||
{
|
||||
assert((pos >= m_begin) && (pos <= m_end));
|
||||
m_pos = pos;
|
||||
}
|
||||
|
||||
void advance(size_t n)
|
||||
{
|
||||
m_pos += n;
|
||||
}
|
||||
|
||||
bool atEnd() const
|
||||
{
|
||||
return pos() == end();
|
||||
}
|
||||
|
||||
size_t consumed() const
|
||||
{
|
||||
return pos() - begin();
|
||||
}
|
||||
|
||||
size_t consumable() const
|
||||
{
|
||||
return end() - pos();
|
||||
}
|
||||
|
||||
inline void checkAlignment(size_t n) const
|
||||
{
|
||||
OSCPP::checkAlignment(pos(), n);
|
||||
}
|
||||
|
||||
protected:
|
||||
char* m_begin;
|
||||
char* m_end;
|
||||
char* m_pos;
|
||||
};
|
||||
|
||||
template <ByteOrder B> class BasicWriteStream : public Stream
|
||||
{
|
||||
public:
|
||||
BasicWriteStream()
|
||||
: Stream()
|
||||
{}
|
||||
|
||||
BasicWriteStream(void* data, size_t size)
|
||||
: Stream(data, size)
|
||||
{}
|
||||
|
||||
BasicWriteStream(const BasicWriteStream& stream)
|
||||
: Stream(stream)
|
||||
{}
|
||||
|
||||
BasicWriteStream(const BasicWriteStream& stream, size_t size)
|
||||
: Stream(stream, size)
|
||||
{}
|
||||
|
||||
// throw (OverflowError)
|
||||
inline void checkWritable(size_t n) const
|
||||
{
|
||||
if (consumable() < n)
|
||||
throw OverflowError(n - consumable());
|
||||
}
|
||||
|
||||
void skip(size_t n)
|
||||
{
|
||||
checkWritable(n);
|
||||
advance(n);
|
||||
}
|
||||
|
||||
void zero(size_t n)
|
||||
{
|
||||
checkWritable(n);
|
||||
std::memset(m_pos, 0, n);
|
||||
advance(n);
|
||||
}
|
||||
|
||||
void putChar(char c)
|
||||
{
|
||||
checkWritable(1);
|
||||
*pos() = c;
|
||||
advance(1);
|
||||
}
|
||||
|
||||
void putInt32(int32_t x)
|
||||
{
|
||||
checkWritable(4);
|
||||
checkAlignment(4);
|
||||
uint32_t uh;
|
||||
memcpy(&uh, &x, 4);
|
||||
const uint32_t un = convert32<B>(uh);
|
||||
std::memcpy(pos(), &un, 4);
|
||||
advance(4);
|
||||
}
|
||||
|
||||
void putUInt64(uint64_t x)
|
||||
{
|
||||
checkWritable(8);
|
||||
const uint64_t un = convert64<B>(x);
|
||||
std::memcpy(pos(), &un, 8);
|
||||
advance(8);
|
||||
}
|
||||
|
||||
void putFloat32(float f)
|
||||
{
|
||||
checkWritable(4);
|
||||
checkAlignment(4);
|
||||
uint32_t uh;
|
||||
std::memcpy(&uh, &f, 4);
|
||||
const uint32_t un = convert32<B>(uh);
|
||||
std::memcpy(pos(), &un, 4);
|
||||
advance(4);
|
||||
}
|
||||
|
||||
void putFloat64(double f)
|
||||
{
|
||||
checkWritable(8);
|
||||
checkAlignment(4);
|
||||
uint64_t uh;
|
||||
std::memcpy(&uh, &f, 8);
|
||||
const uint64_t un = convert64<B>(uh);
|
||||
std::memcpy(pos(), &un, 8);
|
||||
advance(8);
|
||||
}
|
||||
|
||||
void putData(const void* data, size_t size)
|
||||
{
|
||||
const size_t padding = OSCPP::padding(size);
|
||||
const size_t n = size + padding;
|
||||
checkWritable(n);
|
||||
std::memcpy(pos(), data, size);
|
||||
std::memset(pos() + size, 0, padding);
|
||||
advance(n);
|
||||
}
|
||||
|
||||
void putString(const char* s)
|
||||
{
|
||||
putData(s, strlen(s) + 1);
|
||||
}
|
||||
};
|
||||
|
||||
typedef BasicWriteStream<NetworkByteOrder> WriteStream;
|
||||
|
||||
template <ByteOrder B> class BasicReadStream : public Stream
|
||||
{
|
||||
public:
|
||||
BasicReadStream()
|
||||
{}
|
||||
|
||||
BasicReadStream(const void* data, size_t size)
|
||||
: Stream(const_cast<void*>(data), size)
|
||||
{}
|
||||
|
||||
BasicReadStream(const BasicReadStream& stream)
|
||||
: Stream(stream)
|
||||
{}
|
||||
|
||||
BasicReadStream(const BasicReadStream& stream, size_t size)
|
||||
: Stream(stream, size)
|
||||
{}
|
||||
|
||||
// throw (UnderrunError)
|
||||
void checkReadable(size_t n) const
|
||||
{
|
||||
if (consumable() < n)
|
||||
throw UnderrunError();
|
||||
}
|
||||
|
||||
// throw (UnderrunError)
|
||||
void skip(size_t n)
|
||||
{
|
||||
checkReadable(n);
|
||||
advance(n);
|
||||
}
|
||||
|
||||
// throw (UnderrunError)
|
||||
inline char peekChar() const
|
||||
{
|
||||
checkReadable(1);
|
||||
return *pos();
|
||||
}
|
||||
|
||||
// throw (UnderrunError)
|
||||
inline char getChar()
|
||||
{
|
||||
const char x = peekChar();
|
||||
advance(1);
|
||||
return x;
|
||||
}
|
||||
|
||||
// throw (UnderrunError)
|
||||
inline int32_t peekInt32() const
|
||||
{
|
||||
checkReadable(4);
|
||||
checkAlignment(4);
|
||||
uint32_t un;
|
||||
std::memcpy(&un, pos(), 4);
|
||||
const uint32_t uh = convert32<B>(un);
|
||||
int32_t x;
|
||||
std::memcpy(&x, &uh, 4);
|
||||
return x;
|
||||
}
|
||||
|
||||
// throw (UnderrunError)
|
||||
inline int32_t getInt32()
|
||||
{
|
||||
const int32_t x = peekInt32();
|
||||
advance(4);
|
||||
return x;
|
||||
}
|
||||
|
||||
// throw (UnderrunError)
|
||||
inline uint64_t getUInt64()
|
||||
{
|
||||
checkReadable(8);
|
||||
uint64_t un;
|
||||
std::memcpy(&un, pos(), 8);
|
||||
advance(8);
|
||||
return convert64<B>(un);
|
||||
}
|
||||
|
||||
// throw (UnderrunError)
|
||||
inline float getFloat32()
|
||||
{
|
||||
checkReadable(4);
|
||||
checkAlignment(4);
|
||||
uint32_t un;
|
||||
std::memcpy(&un, pos(), 4);
|
||||
advance(4);
|
||||
const uint32_t uh = convert32<B>(un);
|
||||
float f;
|
||||
std::memcpy(&f, &uh, 4);
|
||||
return f;
|
||||
}
|
||||
|
||||
// throw (UnderrunError)
|
||||
inline double getFloat64()
|
||||
{
|
||||
checkReadable(8);
|
||||
checkAlignment(4);
|
||||
uint64_t un;
|
||||
std::memcpy(&un, pos(), 8);
|
||||
advance(8);
|
||||
const uint64_t uh = convert64<B>(un);
|
||||
double f;
|
||||
std::memcpy(&f, &uh, 8);
|
||||
return f;
|
||||
}
|
||||
|
||||
// throw (UnderrunError, ParseError)
|
||||
const char* getString()
|
||||
{
|
||||
checkReadable(4); // min string length
|
||||
|
||||
const char* ptr = static_cast<const char*>(pos()) + 3;
|
||||
const char* end = static_cast<const char*>(this->end());
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (ptr >= end)
|
||||
throw UnderrunError();
|
||||
if (*ptr == '\0')
|
||||
break;
|
||||
ptr += 4;
|
||||
}
|
||||
|
||||
const char* x = pos();
|
||||
advance(ptr - pos() + 1);
|
||||
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
typedef BasicReadStream<NetworkByteOrder> ReadStream;
|
||||
} // namespace OSCPP
|
||||
|
||||
#endif // OSCPP_STREAM_HPP_INCLUDED
|
||||
87
src/oscpp/error.hpp
Normal file
87
src/oscpp/error.hpp
Normal file
@@ -0,0 +1,87 @@
|
||||
// oscpp library
|
||||
//
|
||||
// Copyright (c) 2004-2013 Stefan Kersten <sk@k-hornz.de>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person or organization
|
||||
// obtaining a copy of the software and accompanying documentation covered by
|
||||
// this license (the "Software") to use, reproduce, display, distribute,
|
||||
// execute, and transmit the Software, and to prepare derivative works of the
|
||||
// Software, and to permit third-parties to whom the Software is furnished to
|
||||
// do so, all subject to the following:
|
||||
//
|
||||
// The copyright notices in the Software and this entire statement, including
|
||||
// the above license grant, this restriction and the following disclaimer,
|
||||
// must be included in all copies of the Software, in whole or in part, and
|
||||
// all derivative works of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#ifndef OSCPP_ERROR_HPP_INCLUDED
|
||||
#define OSCPP_ERROR_HPP_INCLUDED
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
|
||||
namespace OSCPP {
|
||||
|
||||
class Error : public std::exception
|
||||
{
|
||||
public:
|
||||
Error(const std::string& what)
|
||||
: m_what(what)
|
||||
{}
|
||||
|
||||
virtual ~Error() noexcept
|
||||
{}
|
||||
|
||||
const char* what() const noexcept override
|
||||
{
|
||||
return m_what.c_str();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_what;
|
||||
};
|
||||
|
||||
class UnderrunError : public Error
|
||||
{
|
||||
public:
|
||||
UnderrunError()
|
||||
: Error(std::string("Buffer underrun"))
|
||||
{}
|
||||
};
|
||||
|
||||
class OverflowError : public Error
|
||||
{
|
||||
public:
|
||||
OverflowError(size_t bytes)
|
||||
: Error(std::string("Buffer overflow"))
|
||||
, m_bytes(bytes)
|
||||
{}
|
||||
|
||||
size_t numBytes() const
|
||||
{
|
||||
return m_bytes;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t m_bytes;
|
||||
};
|
||||
|
||||
class ParseError : public Error
|
||||
{
|
||||
public:
|
||||
ParseError(const std::string& what = "Parse error")
|
||||
: Error(what)
|
||||
{}
|
||||
};
|
||||
|
||||
} // namespace OSCPP
|
||||
|
||||
#endif // OSCPP_ERROR_HPP_INCLUDED
|
||||
183
src/oscpp/print.hpp
Normal file
183
src/oscpp/print.hpp
Normal file
@@ -0,0 +1,183 @@
|
||||
// OSCpp library
|
||||
//
|
||||
// Copyright (c) 2004-2011 Stefan Kersten <sk@k-hornz.de>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person or organization
|
||||
// obtaining a copy of the software and accompanying documentation covered by
|
||||
// this license (the "Software") to use, reproduce, display, distribute,
|
||||
// execute, and transmit the Software, and to prepare derivative works of the
|
||||
// Software, and to permit third-parties to whom the Software is furnished to
|
||||
// do so, all subject to the following:
|
||||
//
|
||||
// The copyright notices in the Software and this entire statement, including
|
||||
// the above license grant, this restriction and the following disclaimer,
|
||||
// must be included in all copies of the Software, in whole or in part, and
|
||||
// all derivative works of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#ifndef OSCPP_PRINT_HPP_INCLUDED
|
||||
#define OSCPP_PRINT_HPP_INCLUDED
|
||||
|
||||
#include <oscpp/client.hpp>
|
||||
#include <oscpp/server.hpp>
|
||||
|
||||
#include <ostream>
|
||||
|
||||
namespace OSCPP { namespace detail {
|
||||
|
||||
const size_t kDefaultIndentWidth = 4;
|
||||
|
||||
class Indent
|
||||
{
|
||||
public:
|
||||
Indent(size_t w)
|
||||
: m_width(w)
|
||||
, m_indent(0)
|
||||
{}
|
||||
Indent(size_t w, size_t n)
|
||||
: m_width(w)
|
||||
, m_indent(n)
|
||||
{}
|
||||
Indent(const Indent&) = default;
|
||||
|
||||
operator size_t() const
|
||||
{
|
||||
return m_indent;
|
||||
}
|
||||
Indent inc() const
|
||||
{
|
||||
return Indent(m_width, m_indent + m_width);
|
||||
}
|
||||
|
||||
private:
|
||||
size_t m_width;
|
||||
size_t m_indent;
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& out, const Indent& indent)
|
||||
{
|
||||
size_t n = indent;
|
||||
while (n-- > 0)
|
||||
out << ' ';
|
||||
return out;
|
||||
}
|
||||
|
||||
inline void printArgs(std::ostream& out, Server::ArgStream args)
|
||||
{
|
||||
while (!args.atEnd())
|
||||
{
|
||||
const char t = args.tag();
|
||||
switch (t)
|
||||
{
|
||||
case 'i':
|
||||
out << "i:" << args.int32();
|
||||
break;
|
||||
case 'f':
|
||||
out << "f:" << args.float32();
|
||||
break;
|
||||
case 's':
|
||||
out << "s:" << args.string();
|
||||
break;
|
||||
case 'b':
|
||||
out << "b:" << args.blob().size();
|
||||
break;
|
||||
case '[':
|
||||
out << "[ ";
|
||||
printArgs(out, args.array());
|
||||
out << " ]";
|
||||
break;
|
||||
default:
|
||||
out << t << ":?";
|
||||
args.drop();
|
||||
break;
|
||||
}
|
||||
out << ' ';
|
||||
}
|
||||
}
|
||||
|
||||
inline void printMessage(std::ostream& out, const Server::Message& msg,
|
||||
const Indent& indent)
|
||||
{
|
||||
out << indent << msg.address() << ' ';
|
||||
printArgs(out, msg.args());
|
||||
}
|
||||
|
||||
inline void printBundle(std::ostream& out, const Server::Bundle& bundle,
|
||||
const Indent& indent)
|
||||
{
|
||||
out << indent << "# " << bundle.time() << " [" << std::endl;
|
||||
Indent nextIndent = indent.inc();
|
||||
auto packets = bundle.packets();
|
||||
while (!packets.atEnd())
|
||||
{
|
||||
auto packet = packets.next();
|
||||
if (packet.isMessage())
|
||||
{
|
||||
printMessage(out, packet, nextIndent);
|
||||
}
|
||||
else
|
||||
{
|
||||
printBundle(out, packet, nextIndent);
|
||||
}
|
||||
out << std::endl;
|
||||
}
|
||||
out << indent << "]";
|
||||
}
|
||||
|
||||
inline void printPacket(std::ostream& out, const Server::Packet& packet,
|
||||
const Indent& indent)
|
||||
{
|
||||
if (packet.isMessage())
|
||||
{
|
||||
printMessage(out, packet, indent);
|
||||
}
|
||||
else
|
||||
{
|
||||
printBundle(out, packet, indent);
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace OSCPP::detail
|
||||
|
||||
namespace OSCPP { namespace Server {
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& out, const Packet& packet)
|
||||
{
|
||||
detail::printPacket(out, packet,
|
||||
detail::Indent(detail::kDefaultIndentWidth));
|
||||
return out;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& out, const Bundle& packet)
|
||||
{
|
||||
detail::printBundle(out, packet,
|
||||
detail::Indent(detail::kDefaultIndentWidth));
|
||||
return out;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& out, const Message& packet)
|
||||
{
|
||||
detail::printMessage(out, packet,
|
||||
detail::Indent(detail::kDefaultIndentWidth));
|
||||
return out;
|
||||
}
|
||||
|
||||
}} // namespace OSCPP::Server
|
||||
|
||||
namespace OSCPP { namespace Client {
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& out, const Packet& packet)
|
||||
{
|
||||
return out << Server::Packet(packet.data(), packet.size());
|
||||
}
|
||||
|
||||
}} // namespace OSCPP::Client
|
||||
|
||||
#endif // OSCPP_PRINT_HPP_INCLUDED
|
||||
493
src/oscpp/server.hpp
Normal file
493
src/oscpp/server.hpp
Normal file
@@ -0,0 +1,493 @@
|
||||
// oscpp library
|
||||
//
|
||||
// Copyright (c) 2004-2013 Stefan Kersten <sk@k-hornz.de>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person or organization
|
||||
// obtaining a copy of the software and accompanying documentation covered by
|
||||
// this license (the "Software") to use, reproduce, display, distribute,
|
||||
// execute, and transmit the Software, and to prepare derivative works of the
|
||||
// Software, and to permit third-parties to whom the Software is furnished to
|
||||
// do so, all subject to the following:
|
||||
//
|
||||
// The copyright notices in the Software and this entire statement, including
|
||||
// the above license grant, this restriction and the following disclaimer,
|
||||
// must be included in all copies of the Software, in whole or in part, and
|
||||
// all derivative works of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#ifndef OSCPP_SERVER_HPP_INCLUDED
|
||||
#define OSCPP_SERVER_HPP_INCLUDED
|
||||
|
||||
#include <oscpp/detail/stream.hpp>
|
||||
#include <oscpp/util.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <tuple>
|
||||
|
||||
namespace OSCPP { namespace Server {
|
||||
|
||||
//! OSC Message Argument Iterator.
|
||||
/*!
|
||||
* Retrieve typed arguments from an incoming message.
|
||||
*
|
||||
* Supported tags and their correspondong types are:
|
||||
*
|
||||
* i -- 32 bit signed integer number<br>
|
||||
* f -- 32 bit floating point number<br>
|
||||
* s -- NULL-terminated string padded to 4-byte boundary<br>
|
||||
* b -- 32-bit integer size followed by 4-byte aligned data
|
||||
*
|
||||
* \sa getArgInt32
|
||||
* \sa getArgFloat32
|
||||
* \sa getArgString
|
||||
*/
|
||||
class ArgStream
|
||||
{
|
||||
public:
|
||||
//* Empty argument stream.
|
||||
ArgStream() = default;
|
||||
|
||||
//* Construct argument stream from tag and value streams.
|
||||
ArgStream(const ReadStream& tags, const ReadStream& args)
|
||||
: m_tags(tags)
|
||||
, m_args(args)
|
||||
{}
|
||||
|
||||
//! Constructor.
|
||||
/*!
|
||||
* Read arguments from stream, which has to point to the start of a
|
||||
* message type signature.
|
||||
*
|
||||
* \throw OSCPP::UnderrunError stream buffer underrun.
|
||||
* \throw OSCPP::ParseError error while parsing input stream.
|
||||
*/
|
||||
ArgStream(const ReadStream& stream)
|
||||
{
|
||||
m_args = stream;
|
||||
const char* tags = m_args.getString();
|
||||
if (tags[0] != ',')
|
||||
throw ParseError("Tag string doesn't start with ','");
|
||||
m_tags = ReadStream(tags + 1, strlen(tags) - 1);
|
||||
}
|
||||
|
||||
//* Return the number of arguments that can be read from the stream.
|
||||
size_t size() const
|
||||
{
|
||||
return m_tags.capacity();
|
||||
}
|
||||
|
||||
//* Return true if no more arguments can be read from the stream.
|
||||
bool atEnd() const
|
||||
{
|
||||
return m_tags.atEnd();
|
||||
}
|
||||
|
||||
//* Return tag and argument streams.
|
||||
std::tuple<ReadStream, ReadStream> state() const
|
||||
{
|
||||
return std::make_tuple(m_tags, m_args);
|
||||
}
|
||||
|
||||
//* Return the type tag corresponding to the next message argument.
|
||||
char tag() const
|
||||
{
|
||||
return m_tags.peekChar();
|
||||
}
|
||||
|
||||
//* Drop next argument.
|
||||
void drop()
|
||||
{
|
||||
drop(m_tags.getChar());
|
||||
}
|
||||
|
||||
//! Get next integer argument.
|
||||
/*!
|
||||
* Read next numerical argument from the input stream and convert it
|
||||
* to an integer.
|
||||
*
|
||||
* \exception OSCPP::UnderrunError stream buffer underrun.
|
||||
* \exception OSCPP::ParseError argument could not be converted.
|
||||
*/
|
||||
int32_t int32()
|
||||
{
|
||||
const char t = m_tags.getChar();
|
||||
if (t == 'i')
|
||||
return m_args.getInt32();
|
||||
if (t == 'f')
|
||||
return (int32_t)m_args.getFloat32();
|
||||
throw ParseError("Cannot convert argument to int");
|
||||
}
|
||||
|
||||
//! Get next float argument.
|
||||
/*!
|
||||
* Read next numerical argument from the input stream and convert it
|
||||
* to a float.
|
||||
*
|
||||
* \exception OSCPP::UnderrunError stream buffer underrun.
|
||||
* \exception OSCPP::ParseError argument could not be converted.
|
||||
*/
|
||||
float float32()
|
||||
{
|
||||
const char t = m_tags.getChar();
|
||||
if (t == 'f')
|
||||
return m_args.getFloat32();
|
||||
if (t == 'i')
|
||||
return (float)m_args.getInt32();
|
||||
throw ParseError("Cannot convert argument to float");
|
||||
}
|
||||
|
||||
//! Get next string argument.
|
||||
/*!
|
||||
* Read next string argument and return it as a NULL-terminated
|
||||
* string.
|
||||
*
|
||||
* \exception OSCPP::UnderrunError stream buffer underrun.
|
||||
* \exception OSCPP::ParseError argument could not be converted or
|
||||
* is not a valid string.
|
||||
*/
|
||||
const char* string()
|
||||
{
|
||||
if (m_tags.getChar() == 's')
|
||||
{
|
||||
return m_args.getString();
|
||||
}
|
||||
throw ParseError("Cannot convert argument to string");
|
||||
}
|
||||
|
||||
//* Get next blob argument.
|
||||
//
|
||||
// @throw OSCPP::UnderrunError stream buffer underrun.
|
||||
// @throw OSCPP::ParseError argument is not a valid blob
|
||||
Blob blob()
|
||||
{
|
||||
if (m_tags.getChar() == 'b')
|
||||
{
|
||||
return parseBlob();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ParseError("Cannot convert argument to blob");
|
||||
}
|
||||
}
|
||||
|
||||
//* Return a stream corresponding to an array argument.
|
||||
ArgStream array()
|
||||
{
|
||||
if (m_tags.getChar() == '[')
|
||||
{
|
||||
const char* tags = m_tags.pos();
|
||||
const char* args = m_args.pos();
|
||||
dropArray();
|
||||
// m_tags.pos() points right after the closing ']'.
|
||||
return ArgStream(ReadStream(tags, m_tags.pos() - tags - 1),
|
||||
ReadStream(args, m_args.pos() - args));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ParseError("Expected array");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> T next()
|
||||
{
|
||||
return T::OSC_Server_ArgStream_next_unimplemented;
|
||||
}
|
||||
|
||||
private:
|
||||
// Parse a blob (type tag already consumed).
|
||||
Blob parseBlob()
|
||||
{
|
||||
int32_t size = m_args.getInt32();
|
||||
if (size < 0)
|
||||
{
|
||||
throw ParseError("Invalid blob size is less than zero");
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(
|
||||
sizeof(size_t) >= sizeof(int32_t),
|
||||
"Size of size_t must be greater than size of int32_t");
|
||||
const void* data = m_args.pos();
|
||||
m_args.skip(align(size));
|
||||
return Blob(data, static_cast<size_t>(size));
|
||||
}
|
||||
}
|
||||
// Drop an atomic value of type t (type tag already consumed).
|
||||
void dropAtom(char t)
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case 'i':
|
||||
m_args.skip(4);
|
||||
break;
|
||||
case 'f':
|
||||
m_args.skip(4);
|
||||
break;
|
||||
case 's':
|
||||
m_args.getString();
|
||||
break;
|
||||
case 'b':
|
||||
parseBlob();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Drop a possibly nested array.
|
||||
void dropArray()
|
||||
{
|
||||
unsigned int level = 0;
|
||||
for (;;)
|
||||
{
|
||||
char t = m_tags.getChar();
|
||||
if (t == ']')
|
||||
{
|
||||
if (level == 0)
|
||||
break;
|
||||
else
|
||||
level--;
|
||||
}
|
||||
else if (t == '[')
|
||||
{
|
||||
level++;
|
||||
}
|
||||
else
|
||||
{
|
||||
dropAtom(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Drop the next argument of type t (type tag already consumed).
|
||||
void drop(char t)
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case '[':
|
||||
dropArray();
|
||||
break;
|
||||
default:
|
||||
dropAtom(t);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ReadStream m_tags;
|
||||
ReadStream m_args;
|
||||
};
|
||||
|
||||
class Message
|
||||
{
|
||||
public:
|
||||
Message(const char* address, const ReadStream& stream)
|
||||
: m_address(address)
|
||||
, m_args(ArgStream(stream))
|
||||
{}
|
||||
|
||||
const char* address() const
|
||||
{
|
||||
return m_address;
|
||||
}
|
||||
|
||||
ArgStream args() const
|
||||
{
|
||||
return m_args;
|
||||
}
|
||||
|
||||
private:
|
||||
const char* m_address;
|
||||
ArgStream m_args;
|
||||
};
|
||||
|
||||
class PacketStream;
|
||||
|
||||
class Bundle
|
||||
{
|
||||
public:
|
||||
Bundle(uint64_t time, const ReadStream& stream)
|
||||
: m_time(time)
|
||||
, m_stream(stream)
|
||||
{}
|
||||
|
||||
uint64_t time() const
|
||||
{
|
||||
return m_time;
|
||||
}
|
||||
|
||||
inline PacketStream packets() const;
|
||||
|
||||
private:
|
||||
uint64_t m_time;
|
||||
ReadStream m_stream;
|
||||
};
|
||||
|
||||
class Packet
|
||||
{
|
||||
public:
|
||||
Packet()
|
||||
: m_isBundle(false)
|
||||
{}
|
||||
|
||||
Packet(const ReadStream& stream)
|
||||
: m_stream(stream)
|
||||
, m_isBundle(isBundle(stream))
|
||||
{
|
||||
// Skip over #bundle header
|
||||
if (m_isBundle)
|
||||
m_stream.skip(8);
|
||||
}
|
||||
|
||||
Packet(const void* data, size_t size)
|
||||
: Packet(ReadStream(data, size))
|
||||
{}
|
||||
|
||||
const void* data() const
|
||||
{
|
||||
return m_stream.begin();
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return m_stream.capacity();
|
||||
}
|
||||
|
||||
bool isBundle() const
|
||||
{
|
||||
return m_isBundle;
|
||||
}
|
||||
|
||||
bool isMessage() const
|
||||
{
|
||||
return !isBundle();
|
||||
}
|
||||
|
||||
operator Bundle() const
|
||||
{
|
||||
if (!isBundle())
|
||||
throw ParseError("Packet is not a bundle");
|
||||
ReadStream stream(m_stream);
|
||||
uint64_t time = stream.getUInt64();
|
||||
return Bundle(time, std::move(stream));
|
||||
}
|
||||
|
||||
operator Message() const
|
||||
{
|
||||
if (!isMessage())
|
||||
throw ParseError("Packet is not a message");
|
||||
ReadStream stream(m_stream);
|
||||
const char* address = stream.getString();
|
||||
return Message(address, std::move(stream));
|
||||
}
|
||||
|
||||
static bool isMessage(const void* data, size_t size)
|
||||
{
|
||||
return (size > 3) && (static_cast<const char*>(data)[0] != '#');
|
||||
}
|
||||
|
||||
static bool isMessage(const ReadStream& stream)
|
||||
{
|
||||
return isMessage(stream.pos(), stream.consumable());
|
||||
}
|
||||
|
||||
static bool isBundle(const void* data, size_t size)
|
||||
{
|
||||
return (size > 15) && (std::memcmp(data, "#bundle", 8) == 0);
|
||||
}
|
||||
|
||||
static bool isBundle(const ReadStream& stream)
|
||||
{
|
||||
return isBundle(stream.pos(), stream.consumable());
|
||||
}
|
||||
|
||||
private:
|
||||
ReadStream m_stream;
|
||||
bool m_isBundle;
|
||||
};
|
||||
|
||||
class PacketStream
|
||||
{
|
||||
public:
|
||||
PacketStream(const ReadStream& stream)
|
||||
: m_stream(stream)
|
||||
{}
|
||||
|
||||
bool atEnd() const
|
||||
{
|
||||
return m_stream.atEnd();
|
||||
}
|
||||
|
||||
Packet next()
|
||||
{
|
||||
size_t size = m_stream.getInt32();
|
||||
ReadStream stream(m_stream, size);
|
||||
m_stream.skip(size);
|
||||
return Packet(stream);
|
||||
}
|
||||
|
||||
private:
|
||||
ReadStream m_stream;
|
||||
};
|
||||
|
||||
template <> inline int32_t ArgStream::next<int32_t>()
|
||||
{
|
||||
return int32();
|
||||
}
|
||||
|
||||
template <> inline float ArgStream::next<float>()
|
||||
{
|
||||
return float32();
|
||||
}
|
||||
|
||||
template <> inline const char* ArgStream::next<const char*>()
|
||||
{
|
||||
return string();
|
||||
}
|
||||
|
||||
template <> inline Blob ArgStream::next<Blob>()
|
||||
{
|
||||
return blob();
|
||||
}
|
||||
|
||||
template <> inline ArgStream ArgStream::next<ArgStream>()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
PacketStream Bundle::packets() const
|
||||
{
|
||||
return PacketStream(m_stream);
|
||||
}
|
||||
|
||||
}} // namespace OSCPP::Server
|
||||
|
||||
static inline bool operator==(const OSCPP::Server::Message& msg,
|
||||
const char* str)
|
||||
{
|
||||
return strcmp(msg.address(), str) == 0;
|
||||
}
|
||||
|
||||
static inline bool operator==(const char* str,
|
||||
const OSCPP::Server::Message& msg)
|
||||
{
|
||||
return msg == str;
|
||||
}
|
||||
|
||||
static inline bool operator!=(const OSCPP::Server::Message& msg,
|
||||
const char* str)
|
||||
{
|
||||
return !(msg == str);
|
||||
}
|
||||
|
||||
static inline bool operator!=(const char* str,
|
||||
const OSCPP::Server::Message& msg)
|
||||
{
|
||||
return msg != str;
|
||||
}
|
||||
|
||||
#endif // OSCPP_SERVER_HPP_INCLUDED
|
||||
59
src/oscpp/types.hpp
Normal file
59
src/oscpp/types.hpp
Normal file
@@ -0,0 +1,59 @@
|
||||
// oscpp library
|
||||
//
|
||||
// Copyright (c) 2004-2013 Stefan Kersten <sk@k-hornz.de>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person or organization
|
||||
// obtaining a copy of the software and accompanying documentation covered by
|
||||
// this license (the "Software") to use, reproduce, display, distribute,
|
||||
// execute, and transmit the Software, and to prepare derivative works of the
|
||||
// Software, and to permit third-parties to whom the Software is furnished to
|
||||
// do so, all subject to the following:
|
||||
//
|
||||
// The copyright notices in the Software and this entire statement, including
|
||||
// the above license grant, this restriction and the following disclaimer,
|
||||
// must be included in all copies of the Software, in whole or in part, and
|
||||
// all derivative works of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#ifndef OSCPP_TYPES_HPP_INCLUDED
|
||||
#define OSCPP_TYPES_HPP_INCLUDED
|
||||
|
||||
namespace OSCPP {
|
||||
|
||||
class Blob
|
||||
{
|
||||
public:
|
||||
Blob()
|
||||
: m_size(0)
|
||||
, m_data(nullptr)
|
||||
{}
|
||||
Blob(const void* data, size_t size)
|
||||
: m_size(size)
|
||||
, m_data(data)
|
||||
{}
|
||||
Blob(const Blob& other) = default;
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
const void* data() const
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t m_size;
|
||||
const void* m_data;
|
||||
};
|
||||
|
||||
} // namespace OSCPP
|
||||
|
||||
#endif // OSCPP_TYPES_HPP_INCLUDED
|
||||
159
src/oscpp/util.hpp
Normal file
159
src/oscpp/util.hpp
Normal file
@@ -0,0 +1,159 @@
|
||||
// oscpp library
|
||||
//
|
||||
// Copyright (c) 2004-2013 Stefan Kersten <sk@k-hornz.de>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person or organization
|
||||
// obtaining a copy of the software and accompanying documentation covered by
|
||||
// this license (the "Software") to use, reproduce, display, distribute,
|
||||
// execute, and transmit the Software, and to prepare derivative works of the
|
||||
// Software, and to permit third-parties to whom the Software is furnished to
|
||||
// do so, all subject to the following:
|
||||
//
|
||||
// The copyright notices in the Software and this entire statement, including
|
||||
// the above license grant, this restriction and the following disclaimer,
|
||||
// must be included in all copies of the Software, in whole or in part, and
|
||||
// all derivative works of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#ifndef OSCPP_UTIL_HPP_INCLUDED
|
||||
#define OSCPP_UTIL_HPP_INCLUDED
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
namespace OSCPP {
|
||||
|
||||
static const size_t kAlignment = 4;
|
||||
|
||||
inline bool isAligned(const void* ptr, size_t alignment)
|
||||
{
|
||||
return (reinterpret_cast<uintptr_t>(ptr) & (alignment - 1)) == 0;
|
||||
}
|
||||
|
||||
constexpr bool isAligned(size_t n)
|
||||
{
|
||||
return (n & 3) == 0;
|
||||
}
|
||||
|
||||
constexpr size_t align(size_t n)
|
||||
{
|
||||
return (n + 3) & -4;
|
||||
}
|
||||
|
||||
constexpr size_t padding(size_t n)
|
||||
{
|
||||
return align(n) - n;
|
||||
}
|
||||
|
||||
inline void checkAlignment(const void* ptr, size_t n)
|
||||
{
|
||||
if (!isAligned(ptr, n))
|
||||
{
|
||||
throw std::runtime_error("Unaligned pointer");
|
||||
}
|
||||
}
|
||||
|
||||
namespace Tags {
|
||||
|
||||
constexpr size_t int32()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
constexpr size_t float32()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
constexpr size_t string()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
constexpr size_t blob()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
constexpr size_t array(size_t numElems)
|
||||
{
|
||||
return numElems + 2;
|
||||
}
|
||||
} // namespace Tags
|
||||
|
||||
namespace Size {
|
||||
|
||||
class String
|
||||
{
|
||||
public:
|
||||
String(const char* x)
|
||||
: m_value(x)
|
||||
{}
|
||||
|
||||
operator const char*() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
private:
|
||||
const char* m_value;
|
||||
};
|
||||
|
||||
inline size_t string(const String& x)
|
||||
{
|
||||
return align(std::strlen(x) + 1);
|
||||
}
|
||||
|
||||
template <size_t N> constexpr size_t string(char const (&)[N])
|
||||
{
|
||||
return align(N);
|
||||
}
|
||||
|
||||
constexpr size_t bundle(size_t numPackets)
|
||||
{
|
||||
return 8 /* #bundle */ + 8 /* timestamp */ +
|
||||
4 * numPackets /* size prefix */;
|
||||
}
|
||||
|
||||
inline size_t message(const String& address, size_t numArgs)
|
||||
{
|
||||
return string(address) + align(numArgs + 2);
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
constexpr size_t message(char const (&address)[N], size_t numArgs)
|
||||
{
|
||||
return string(address) + align(numArgs + 2);
|
||||
}
|
||||
|
||||
constexpr size_t int32(size_t n = 1)
|
||||
{
|
||||
return n * 4;
|
||||
}
|
||||
|
||||
constexpr size_t float32(size_t n = 1)
|
||||
{
|
||||
return n * 4;
|
||||
}
|
||||
|
||||
constexpr size_t float64(size_t n = 1)
|
||||
{
|
||||
return n * 8;
|
||||
}
|
||||
|
||||
constexpr size_t string(size_t n)
|
||||
{
|
||||
return align(n + 1);
|
||||
}
|
||||
|
||||
constexpr size_t blob(size_t size)
|
||||
{
|
||||
return 4 + align(size);
|
||||
}
|
||||
} // namespace Size
|
||||
} // namespace OSCPP
|
||||
|
||||
#endif // OSCPP_UTIL_HPP_INCLUDED
|
||||
608
src/peloton.cpp
608
src/peloton.cpp
@@ -245,203 +245,339 @@ peloton::peloton(bluetooth *bl, QObject *parent) : QObject(parent) {
|
||||
|
||||
treadmill_pace[0].value = 0;
|
||||
treadmill_pace[0].display_name = QStringLiteral("Recovery");
|
||||
treadmill_pace[0].levels[0].speed = 4.83; // 3.0 mph
|
||||
treadmill_pace[0].levels[1].speed = 5.15; // 3.2 mph
|
||||
treadmill_pace[0].levels[2].speed = 5.63; // 3.5 mph
|
||||
treadmill_pace[0].levels[3].speed = 5.95; // 3.7 mph
|
||||
treadmill_pace[0].levels[4].speed = 6.60; // 4.1 mph
|
||||
treadmill_pace[0].levels[5].speed = 7.24; // 4.5 mph
|
||||
treadmill_pace[0].levels[6].speed = 8.05; // 5.0 mph
|
||||
treadmill_pace[0].levels[7].speed = 9.17; // 5.7 mph
|
||||
treadmill_pace[0].levels[8].speed = 10.46; // 6.5 mph
|
||||
treadmill_pace[0].levels[9].speed = 12.23; // 7.6 mph
|
||||
for (int i = 0; i < 10; i++) {
|
||||
treadmill_pace[0].levels[i].display_name = QStringLiteral("Level %1").arg(i+1);
|
||||
treadmill_pace[0].levels[i].slug = QStringLiteral("level_%1").arg(i+1);
|
||||
}
|
||||
|
||||
treadmill_pace[0].levels[0].slow_pace = 1.60934; // 1.0 mph
|
||||
treadmill_pace[0].levels[0].fast_pace = 4.82802; // 3.0 mph
|
||||
treadmill_pace[0].levels[0].speed = (treadmill_pace[0].levels[0].slow_pace + treadmill_pace[0].levels[0].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[0].levels[1].slow_pace = 1.60934; // 1.0 mph
|
||||
treadmill_pace[0].levels[1].fast_pace = 5.14989; // 3.2 mph
|
||||
treadmill_pace[0].levels[1].speed = (treadmill_pace[0].levels[1].slow_pace + treadmill_pace[0].levels[1].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[0].levels[2].slow_pace = 1.60934; // 1.0 mph
|
||||
treadmill_pace[0].levels[2].fast_pace = 5.63269; // 3.5 mph
|
||||
treadmill_pace[0].levels[2].speed = (treadmill_pace[0].levels[2].slow_pace + treadmill_pace[0].levels[2].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[0].levels[3].slow_pace = 1.60934; // 1.0 mph
|
||||
treadmill_pace[0].levels[3].fast_pace = 5.95456; // 3.7 mph
|
||||
treadmill_pace[0].levels[3].speed = (treadmill_pace[0].levels[3].slow_pace + treadmill_pace[0].levels[3].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[0].levels[4].slow_pace = 1.60934; // 1.0 mph
|
||||
treadmill_pace[0].levels[4].fast_pace = 6.59829; // 4.1 mph
|
||||
treadmill_pace[0].levels[4].speed = (treadmill_pace[0].levels[4].slow_pace + treadmill_pace[0].levels[4].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[0].levels[5].slow_pace = 1.60934; // 1.0 mph
|
||||
treadmill_pace[0].levels[5].fast_pace = 7.24203; // 4.5 mph
|
||||
treadmill_pace[0].levels[5].speed = (treadmill_pace[0].levels[5].slow_pace + treadmill_pace[0].levels[5].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[0].levels[6].slow_pace = 1.60934; // 1.0 mph
|
||||
treadmill_pace[0].levels[6].fast_pace = 8.0467; // 5.0 mph
|
||||
treadmill_pace[0].levels[6].speed = (treadmill_pace[0].levels[6].slow_pace + treadmill_pace[0].levels[6].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[0].levels[7].slow_pace = 1.60934; // 1.0 mph
|
||||
treadmill_pace[0].levels[7].fast_pace = 9.17324; // 5.7 mph
|
||||
treadmill_pace[0].levels[7].speed = (treadmill_pace[0].levels[7].slow_pace + treadmill_pace[0].levels[7].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[0].levels[8].slow_pace = 1.60934; // 1.0 mph
|
||||
treadmill_pace[0].levels[8].fast_pace = 10.46071; // 6.5 mph
|
||||
treadmill_pace[0].levels[8].speed = (treadmill_pace[0].levels[8].slow_pace + treadmill_pace[0].levels[8].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[0].levels[9].slow_pace = 1.60934; // 1.0 mph
|
||||
treadmill_pace[0].levels[9].fast_pace = 12.23098; // 7.6 mph
|
||||
treadmill_pace[0].levels[9].speed = (treadmill_pace[0].levels[9].slow_pace + treadmill_pace[0].levels[9].fast_pace) / 2.0;
|
||||
|
||||
// Easy
|
||||
treadmill_pace[1].value = 1;
|
||||
treadmill_pace[1].display_name = QStringLiteral("Easy");
|
||||
treadmill_pace[1].levels[0].speed = 5.15; // 3.2 mph
|
||||
treadmill_pace[1].levels[0].display_name = QStringLiteral("Level 1");
|
||||
treadmill_pace[1].levels[0].slug = QStringLiteral("level_1");
|
||||
treadmill_pace[1].levels[1].speed = 5.47; // 3.4 mph
|
||||
treadmill_pace[1].levels[1].display_name = QStringLiteral("Level 2");
|
||||
treadmill_pace[1].levels[1].slug = QStringLiteral("level_2");
|
||||
treadmill_pace[1].levels[2].speed = 5.95; // 3.7 mph
|
||||
treadmill_pace[1].levels[2].display_name = QStringLiteral("Level 3");
|
||||
treadmill_pace[1].levels[2].slug = QStringLiteral("level_3");
|
||||
treadmill_pace[1].levels[3].speed = 6.28; // 3.9 mph
|
||||
treadmill_pace[1].levels[3].display_name = QStringLiteral("Level 4");
|
||||
treadmill_pace[1].levels[3].slug = QStringLiteral("level_4");
|
||||
treadmill_pace[1].levels[4].speed = 6.92; // 4.3 mph
|
||||
treadmill_pace[1].levels[4].display_name = QStringLiteral("Level 5");
|
||||
treadmill_pace[1].levels[4].slug = QStringLiteral("level_5");
|
||||
treadmill_pace[1].levels[5].speed = 7.56; // 4.7 mph
|
||||
treadmill_pace[1].levels[5].display_name = QStringLiteral("Level 6");
|
||||
treadmill_pace[1].levels[5].slug = QStringLiteral("level_6");
|
||||
treadmill_pace[1].levels[6].speed = 8.37; // 5.2 mph
|
||||
treadmill_pace[1].levels[6].display_name = QStringLiteral("Level 7");
|
||||
treadmill_pace[1].levels[6].slug = QStringLiteral("level_7");
|
||||
treadmill_pace[1].levels[7].speed = 9.66; // 6.0 mph
|
||||
treadmill_pace[1].levels[7].display_name = QStringLiteral("Level 8");
|
||||
treadmill_pace[1].levels[7].slug = QStringLiteral("level_8");
|
||||
treadmill_pace[1].levels[8].speed = 11.10; // 6.9 mph
|
||||
treadmill_pace[1].levels[8].display_name = QStringLiteral("Level 9");
|
||||
treadmill_pace[1].levels[8].slug = QStringLiteral("level_9");
|
||||
treadmill_pace[1].levels[9].speed = 12.87; // 8.0 mph
|
||||
treadmill_pace[1].levels[9].display_name = QStringLiteral("Level 10");
|
||||
treadmill_pace[1].levels[9].slug = QStringLiteral("level_10");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
treadmill_pace[1].levels[i].display_name = QStringLiteral("Level %1").arg(i+1);
|
||||
treadmill_pace[1].levels[i].slug = QStringLiteral("level_%1").arg(i+1);
|
||||
}
|
||||
|
||||
treadmill_pace[1].levels[0].slow_pace = 4.98895; // 3.1 mph
|
||||
treadmill_pace[1].levels[0].fast_pace = 5.30802; // 3.3 mph
|
||||
treadmill_pace[1].levels[0].speed = (treadmill_pace[1].levels[0].slow_pace + treadmill_pace[1].levels[0].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[1].levels[1].slow_pace = 5.30802; // 3.3 mph
|
||||
treadmill_pace[1].levels[1].fast_pace = 5.79362; // 3.6 mph
|
||||
treadmill_pace[1].levels[1].speed = (treadmill_pace[1].levels[1].slow_pace + treadmill_pace[1].levels[1].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[1].levels[2].slow_pace = 5.79362; // 3.6 mph
|
||||
treadmill_pace[1].levels[2].fast_pace = 6.27642; // 3.9 mph
|
||||
treadmill_pace[1].levels[2].speed = (treadmill_pace[1].levels[2].slow_pace + treadmill_pace[1].levels[2].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[1].levels[3].slow_pace = 6.11549; // 3.8 mph
|
||||
treadmill_pace[1].levels[3].fast_pace = 6.59829; // 4.1 mph
|
||||
treadmill_pace[1].levels[3].speed = (treadmill_pace[1].levels[3].slow_pace + treadmill_pace[1].levels[3].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[1].levels[4].slow_pace = 6.75923; // 4.2 mph
|
||||
treadmill_pace[1].levels[4].fast_pace = 7.24203; // 4.5 mph
|
||||
treadmill_pace[1].levels[4].speed = (treadmill_pace[1].levels[4].slow_pace + treadmill_pace[1].levels[4].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[1].levels[5].slow_pace = 7.40296; // 4.6 mph
|
||||
treadmill_pace[1].levels[5].fast_pace = 7.88576; // 4.9 mph
|
||||
treadmill_pace[1].levels[5].speed = (treadmill_pace[1].levels[5].slow_pace + treadmill_pace[1].levels[5].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[1].levels[6].slow_pace = 8.20763; // 5.1 mph
|
||||
treadmill_pace[1].levels[6].fast_pace = 8.85137; // 5.5 mph
|
||||
treadmill_pace[1].levels[6].speed = (treadmill_pace[1].levels[6].slow_pace + treadmill_pace[1].levels[6].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[1].levels[7].slow_pace = 9.33417; // 5.8 mph
|
||||
treadmill_pace[1].levels[7].fast_pace = 9.97791; // 6.2 mph
|
||||
treadmill_pace[1].levels[7].speed = (treadmill_pace[1].levels[7].slow_pace + treadmill_pace[1].levels[7].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[1].levels[8].slow_pace = 10.61884; // 6.6 mph
|
||||
treadmill_pace[1].levels[8].fast_pace = 11.58725; // 7.2 mph
|
||||
treadmill_pace[1].levels[8].speed = (treadmill_pace[1].levels[8].slow_pace + treadmill_pace[1].levels[8].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[1].levels[9].slow_pace = 12.39192; // 7.7 mph
|
||||
treadmill_pace[1].levels[9].fast_pace = 13.51846; // 8.4 mph
|
||||
treadmill_pace[1].levels[9].speed = (treadmill_pace[1].levels[9].slow_pace + treadmill_pace[1].levels[9].fast_pace) / 2.0;
|
||||
|
||||
// Moderate
|
||||
treadmill_pace[2].value = 2;
|
||||
treadmill_pace[2].display_name = QStringLiteral("Moderate");
|
||||
treadmill_pace[2].levels[0].speed = 5.63; // 3.5 mph
|
||||
treadmill_pace[2].levels[0].display_name = QStringLiteral("Level 1");
|
||||
treadmill_pace[2].levels[0].slug = QStringLiteral("level_1");
|
||||
treadmill_pace[2].levels[1].speed = 5.95; // 3.7 mph
|
||||
treadmill_pace[2].levels[1].display_name = QStringLiteral("Level 2");
|
||||
treadmill_pace[2].levels[1].slug = QStringLiteral("level_2");
|
||||
treadmill_pace[2].levels[2].speed = 6.44; // 4.0 mph
|
||||
treadmill_pace[2].levels[2].display_name = QStringLiteral("Level 3");
|
||||
treadmill_pace[2].levels[2].slug = QStringLiteral("level_3");
|
||||
treadmill_pace[2].levels[3].speed = 6.92; // 4.3 mph
|
||||
treadmill_pace[2].levels[3].display_name = QStringLiteral("Level 4");
|
||||
treadmill_pace[2].levels[3].slug = QStringLiteral("level_4");
|
||||
treadmill_pace[2].levels[4].speed = 7.56; // 4.7 mph
|
||||
treadmill_pace[2].levels[4].display_name = QStringLiteral("Level 5");
|
||||
treadmill_pace[2].levels[4].slug = QStringLiteral("level_5");
|
||||
treadmill_pace[2].levels[5].speed = 8.37; // 5.2 mph
|
||||
treadmill_pace[2].levels[5].display_name = QStringLiteral("Level 6");
|
||||
treadmill_pace[2].levels[5].slug = QStringLiteral("level_6");
|
||||
treadmill_pace[2].levels[6].speed = 9.17; // 5.7 mph
|
||||
treadmill_pace[2].levels[6].display_name = QStringLiteral("Level 7");
|
||||
treadmill_pace[2].levels[6].slug = QStringLiteral("level_7");
|
||||
treadmill_pace[2].levels[7].speed = 10.46; // 6.5 mph
|
||||
treadmill_pace[2].levels[7].display_name = QStringLiteral("Level 8");
|
||||
treadmill_pace[2].levels[7].slug = QStringLiteral("level_8");
|
||||
treadmill_pace[2].levels[8].speed = 12.07; // 7.5 mph
|
||||
treadmill_pace[2].levels[8].display_name = QStringLiteral("Level 9");
|
||||
treadmill_pace[2].levels[8].slug = QStringLiteral("level_9");
|
||||
treadmill_pace[2].levels[9].speed = 14.00; // 8.7 mph
|
||||
treadmill_pace[2].levels[9].display_name = QStringLiteral("Level 10");
|
||||
treadmill_pace[2].levels[9].slug = QStringLiteral("level_10");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
treadmill_pace[2].levels[i].display_name = QStringLiteral("Level %1").arg(i+1);
|
||||
treadmill_pace[2].levels[i].slug = QStringLiteral("level_%1").arg(i+1);
|
||||
}
|
||||
|
||||
treadmill_pace[2].levels[0].slow_pace = 5.47176; // 3.4 mph
|
||||
treadmill_pace[2].levels[0].fast_pace = 5.79362; // 3.6 mph
|
||||
treadmill_pace[2].levels[0].speed = (treadmill_pace[2].levels[0].slow_pace + treadmill_pace[2].levels[0].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[2].levels[1].slow_pace = 5.95456; // 3.7 mph
|
||||
treadmill_pace[2].levels[1].fast_pace = 6.27642; // 3.9 mph
|
||||
treadmill_pace[2].levels[1].speed = (treadmill_pace[2].levels[1].slow_pace + treadmill_pace[2].levels[1].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[2].levels[2].slow_pace = 6.43736; // 4.0 mph
|
||||
treadmill_pace[2].levels[2].fast_pace = 6.75923; // 4.2 mph
|
||||
treadmill_pace[2].levels[2].speed = (treadmill_pace[2].levels[2].slow_pace + treadmill_pace[2].levels[2].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[2].levels[3].slow_pace = 6.75923; // 4.2 mph
|
||||
treadmill_pace[2].levels[3].fast_pace = 7.24203; // 4.5 mph
|
||||
treadmill_pace[2].levels[3].speed = (treadmill_pace[2].levels[3].slow_pace + treadmill_pace[2].levels[3].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[2].levels[4].slow_pace = 7.40296; // 4.6 mph
|
||||
treadmill_pace[2].levels[4].fast_pace = 7.88576; // 4.9 mph
|
||||
treadmill_pace[2].levels[4].speed = (treadmill_pace[2].levels[4].slow_pace + treadmill_pace[2].levels[4].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[2].levels[5].slow_pace = 8.0467; // 5.0 mph
|
||||
treadmill_pace[2].levels[5].fast_pace = 8.69043; // 5.4 mph
|
||||
treadmill_pace[2].levels[5].speed = (treadmill_pace[2].levels[5].slow_pace + treadmill_pace[2].levels[5].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[2].levels[6].slow_pace = 9.01230; // 5.6 mph
|
||||
treadmill_pace[2].levels[6].fast_pace = 9.65604; // 6.0 mph
|
||||
treadmill_pace[2].levels[6].speed = (treadmill_pace[2].levels[6].slow_pace + treadmill_pace[2].levels[6].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[2].levels[7].slow_pace = 10.13884; // 6.3 mph
|
||||
treadmill_pace[2].levels[7].fast_pace = 10.94351; // 6.8 mph
|
||||
treadmill_pace[2].levels[7].speed = (treadmill_pace[2].levels[7].slow_pace + treadmill_pace[2].levels[7].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[2].levels[8].slow_pace = 11.74818; // 7.3 mph
|
||||
treadmill_pace[2].levels[8].fast_pace = 12.55285; // 7.8 mph
|
||||
treadmill_pace[2].levels[8].speed = (treadmill_pace[2].levels[8].slow_pace + treadmill_pace[2].levels[8].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[2].levels[9].slow_pace = 13.67939; // 8.5 mph
|
||||
treadmill_pace[2].levels[9].fast_pace = 14.48406; // 9.0 mph
|
||||
treadmill_pace[2].levels[9].speed = (treadmill_pace[2].levels[9].slow_pace + treadmill_pace[2].levels[9].fast_pace) / 2.0;
|
||||
|
||||
// Challenging
|
||||
treadmill_pace[3].value = 3;
|
||||
treadmill_pace[3].display_name = QStringLiteral("Challenging");
|
||||
treadmill_pace[3].levels[0].speed = 6.11; // 3.8 mph
|
||||
treadmill_pace[3].levels[0].display_name = QStringLiteral("Level 1");
|
||||
treadmill_pace[3].levels[0].slug = QStringLiteral("level_1");
|
||||
treadmill_pace[3].levels[1].speed = 6.60; // 4.1 mph
|
||||
treadmill_pace[3].levels[1].display_name = QStringLiteral("Level 2");
|
||||
treadmill_pace[3].levels[1].slug = QStringLiteral("level_2");
|
||||
treadmill_pace[3].levels[2].speed = 7.08; // 4.4 mph
|
||||
treadmill_pace[3].levels[2].display_name = QStringLiteral("Level 3");
|
||||
treadmill_pace[3].levels[2].slug = QStringLiteral("level_3");
|
||||
treadmill_pace[3].levels[3].speed = 7.56; // 4.7 mph
|
||||
treadmill_pace[3].levels[3].display_name = QStringLiteral("Level 4");
|
||||
treadmill_pace[3].levels[3].slug = QStringLiteral("level_4");
|
||||
treadmill_pace[3].levels[4].speed = 8.37; // 5.2 mph
|
||||
treadmill_pace[3].levels[4].display_name = QStringLiteral("Level 5");
|
||||
treadmill_pace[3].levels[4].slug = QStringLiteral("level_5");
|
||||
treadmill_pace[3].levels[5].speed = 9.17; // 5.7 mph
|
||||
treadmill_pace[3].levels[5].display_name = QStringLiteral("Level 6");
|
||||
treadmill_pace[3].levels[5].slug = QStringLiteral("level_6");
|
||||
treadmill_pace[3].levels[6].speed = 10.30; // 6.4 mph
|
||||
treadmill_pace[3].levels[6].display_name = QStringLiteral("Level 7");
|
||||
treadmill_pace[3].levels[6].slug = QStringLiteral("level_7");
|
||||
treadmill_pace[3].levels[7].speed = 11.59; // 7.2 mph
|
||||
treadmill_pace[3].levels[7].display_name = QStringLiteral("Level 8");
|
||||
treadmill_pace[3].levels[7].slug = QStringLiteral("level_8");
|
||||
treadmill_pace[3].levels[8].speed = 13.20; // 8.2 mph
|
||||
treadmill_pace[3].levels[8].display_name = QStringLiteral("Level 9");
|
||||
treadmill_pace[3].levels[8].slug = QStringLiteral("level_9");
|
||||
treadmill_pace[3].levels[9].speed = 15.29; // 9.5 mph
|
||||
treadmill_pace[3].levels[9].display_name = QStringLiteral("Level 10");
|
||||
treadmill_pace[3].levels[9].slug = QStringLiteral("level_10");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
treadmill_pace[3].levels[i].display_name = QStringLiteral("Level %1").arg(i+1);
|
||||
treadmill_pace[3].levels[i].slug = QStringLiteral("level_%1").arg(i+1);
|
||||
}
|
||||
|
||||
treadmill_pace[3].levels[0].slow_pace = 5.95456; // 3.7 mph
|
||||
treadmill_pace[3].levels[0].fast_pace = 6.43736; // 4.0 mph
|
||||
treadmill_pace[3].levels[0].speed = (treadmill_pace[3].levels[0].slow_pace + treadmill_pace[3].levels[0].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[3].levels[1].slow_pace = 6.43736; // 4.0 mph
|
||||
treadmill_pace[3].levels[1].fast_pace = 6.92016; // 4.3 mph
|
||||
treadmill_pace[3].levels[1].speed = (treadmill_pace[3].levels[1].slow_pace + treadmill_pace[3].levels[1].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[3].levels[2].slow_pace = 6.92016; // 4.3 mph
|
||||
treadmill_pace[3].levels[2].fast_pace = 7.40296; // 4.6 mph
|
||||
treadmill_pace[3].levels[2].speed = (treadmill_pace[3].levels[2].slow_pace + treadmill_pace[3].levels[2].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[3].levels[3].slow_pace = 7.40296; // 4.6 mph
|
||||
treadmill_pace[3].levels[3].fast_pace = 8.0467; // 5.0 mph
|
||||
treadmill_pace[3].levels[3].speed = (treadmill_pace[3].levels[3].slow_pace + treadmill_pace[3].levels[3].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[3].levels[4].slow_pace = 8.0467; // 5.0 mph
|
||||
treadmill_pace[3].levels[4].fast_pace = 8.69043; // 5.4 mph
|
||||
treadmill_pace[3].levels[4].speed = (treadmill_pace[3].levels[4].slow_pace + treadmill_pace[3].levels[4].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[3].levels[5].slow_pace = 8.85137; // 5.5 mph
|
||||
treadmill_pace[3].levels[5].fast_pace = 9.65604; // 6.0 mph
|
||||
treadmill_pace[3].levels[5].speed = (treadmill_pace[3].levels[5].slow_pace + treadmill_pace[3].levels[5].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[3].levels[6].slow_pace = 9.81697; // 6.1 mph
|
||||
treadmill_pace[3].levels[6].fast_pace = 10.78258; // 6.7 mph
|
||||
treadmill_pace[3].levels[6].speed = (treadmill_pace[3].levels[6].slow_pace + treadmill_pace[3].levels[6].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[3].levels[7].slow_pace = 11.10445; // 6.9 mph
|
||||
treadmill_pace[3].levels[7].fast_pace = 12.07005; // 7.5 mph
|
||||
treadmill_pace[3].levels[7].speed = (treadmill_pace[3].levels[7].slow_pace + treadmill_pace[3].levels[7].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[3].levels[8].slow_pace = 12.71379; // 7.9 mph
|
||||
treadmill_pace[3].levels[8].fast_pace = 13.84032; // 8.6 mph
|
||||
treadmill_pace[3].levels[8].speed = (treadmill_pace[3].levels[8].slow_pace + treadmill_pace[3].levels[8].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[3].levels[9].slow_pace = 14.64499; // 9.1 mph
|
||||
treadmill_pace[3].levels[9].fast_pace = 16.0934; // 10.0 mph
|
||||
treadmill_pace[3].levels[9].speed = (treadmill_pace[3].levels[9].slow_pace + treadmill_pace[3].levels[9].fast_pace) / 2.0;
|
||||
|
||||
// Hard
|
||||
treadmill_pace[4].value = 4;
|
||||
treadmill_pace[4].display_name = QStringLiteral("Hard");
|
||||
treadmill_pace[4].levels[0].speed = 6.76; // 4.2 mph
|
||||
treadmill_pace[4].levels[0].display_name = QStringLiteral("Level 1");
|
||||
treadmill_pace[4].levels[0].slug = QStringLiteral("level_1");
|
||||
treadmill_pace[4].levels[1].speed = 7.24; // 4.5 mph
|
||||
treadmill_pace[4].levels[1].display_name = QStringLiteral("Level 2");
|
||||
treadmill_pace[4].levels[1].slug = QStringLiteral("level_2");
|
||||
treadmill_pace[4].levels[2].speed = 7.88; // 4.9 mph
|
||||
treadmill_pace[4].levels[2].display_name = QStringLiteral("Level 3");
|
||||
treadmill_pace[4].levels[2].slug = QStringLiteral("level_3");
|
||||
treadmill_pace[4].levels[3].speed = 8.37; // 5.2 mph
|
||||
treadmill_pace[4].levels[3].display_name = QStringLiteral("Level 4");
|
||||
treadmill_pace[4].levels[3].slug = QStringLiteral("level_4");
|
||||
treadmill_pace[4].levels[4].speed = 9.17; // 5.7 mph
|
||||
treadmill_pace[4].levels[4].display_name = QStringLiteral("Level 5");
|
||||
treadmill_pace[4].levels[4].slug = QStringLiteral("level_5");
|
||||
treadmill_pace[4].levels[5].speed = 10.14; // 6.3 mph
|
||||
treadmill_pace[4].levels[5].display_name = QStringLiteral("Level 6");
|
||||
treadmill_pace[4].levels[5].slug = QStringLiteral("level_6");
|
||||
treadmill_pace[4].levels[6].speed = 11.27; // 7.0 mph
|
||||
treadmill_pace[4].levels[6].display_name = QStringLiteral("Level 7");
|
||||
treadmill_pace[4].levels[6].slug = QStringLiteral("level_7");
|
||||
treadmill_pace[4].levels[7].speed = 12.55; // 7.8 mph
|
||||
treadmill_pace[4].levels[7].display_name = QStringLiteral("Level 8");
|
||||
treadmill_pace[4].levels[7].slug = QStringLiteral("level_8");
|
||||
treadmill_pace[4].levels[8].speed = 14.48; // 9.0 mph
|
||||
treadmill_pace[4].levels[8].display_name = QStringLiteral("Level 9");
|
||||
treadmill_pace[4].levels[8].slug = QStringLiteral("level_9");
|
||||
treadmill_pace[4].levels[9].speed = 16.90; // 10.5 mph
|
||||
treadmill_pace[4].levels[9].display_name = QStringLiteral("Level 10");
|
||||
treadmill_pace[4].levels[9].slug = QStringLiteral("level_10");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
treadmill_pace[4].levels[i].display_name = QStringLiteral("Level %1").arg(i+1);
|
||||
treadmill_pace[4].levels[i].slug = QStringLiteral("level_%1").arg(i+1);
|
||||
}
|
||||
|
||||
treadmill_pace[4].levels[0].slow_pace = 6.59829; // 4.1 mph
|
||||
treadmill_pace[4].levels[0].fast_pace = 7.08110; // 4.4 mph
|
||||
treadmill_pace[4].levels[0].speed = (treadmill_pace[4].levels[0].slow_pace + treadmill_pace[4].levels[0].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[4].levels[1].slow_pace = 7.08110; // 4.4 mph
|
||||
treadmill_pace[4].levels[1].fast_pace = 7.56390; // 4.7 mph
|
||||
treadmill_pace[4].levels[1].speed = (treadmill_pace[4].levels[1].slow_pace + treadmill_pace[4].levels[1].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[4].levels[2].slow_pace = 7.56390; // 4.7 mph
|
||||
treadmill_pace[4].levels[2].fast_pace = 8.20763; // 5.1 mph
|
||||
treadmill_pace[4].levels[2].speed = (treadmill_pace[4].levels[2].slow_pace + treadmill_pace[4].levels[2].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[4].levels[3].slow_pace = 8.20763; // 5.1 mph
|
||||
treadmill_pace[4].levels[3].fast_pace = 8.69043; // 5.4 mph
|
||||
treadmill_pace[4].levels[3].speed = (treadmill_pace[4].levels[3].slow_pace + treadmill_pace[4].levels[3].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[4].levels[4].slow_pace = 8.85137; // 5.5 mph
|
||||
treadmill_pace[4].levels[4].fast_pace = 9.65604; // 6.0 mph
|
||||
treadmill_pace[4].levels[4].speed = (treadmill_pace[4].levels[4].slow_pace + treadmill_pace[4].levels[4].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[4].levels[5].slow_pace = 9.81697; // 6.1 mph
|
||||
treadmill_pace[4].levels[5].fast_pace = 10.62164; // 6.6 mph
|
||||
treadmill_pace[4].levels[5].speed = (treadmill_pace[4].levels[5].slow_pace + treadmill_pace[4].levels[5].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[4].levels[6].slow_pace = 10.94351; // 6.8 mph
|
||||
treadmill_pace[4].levels[6].fast_pace = 11.74818; // 7.3 mph
|
||||
treadmill_pace[4].levels[6].speed = (treadmill_pace[4].levels[6].slow_pace + treadmill_pace[4].levels[6].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[4].levels[7].slow_pace = 12.23098; // 7.6 mph
|
||||
treadmill_pace[4].levels[7].fast_pace = 13.19659; // 8.2 mph
|
||||
treadmill_pace[4].levels[7].speed = (treadmill_pace[4].levels[7].slow_pace + treadmill_pace[4].levels[7].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[4].levels[8].slow_pace = 14.00126; // 8.7 mph
|
||||
treadmill_pace[4].levels[8].fast_pace = 15.12780; // 9.4 mph
|
||||
treadmill_pace[4].levels[8].speed = (treadmill_pace[4].levels[8].slow_pace + treadmill_pace[4].levels[8].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[4].levels[9].slow_pace = 16.25433; // 10.1 mph
|
||||
treadmill_pace[4].levels[9].fast_pace = 17.54180; // 10.9 mph
|
||||
treadmill_pace[4].levels[9].speed = (treadmill_pace[4].levels[9].slow_pace + treadmill_pace[4].levels[9].fast_pace) / 2.0;
|
||||
|
||||
// Very Hard
|
||||
treadmill_pace[5].value = 5;
|
||||
treadmill_pace[5].display_name = QStringLiteral("Very Hard");
|
||||
treadmill_pace[5].levels[0].speed = 7.56; // 4.7 mph
|
||||
treadmill_pace[5].levels[0].display_name = QStringLiteral("Level 1");
|
||||
treadmill_pace[5].levels[0].slug = QStringLiteral("level_1");
|
||||
treadmill_pace[5].levels[1].speed = 8.05; // 5.0 mph
|
||||
treadmill_pace[5].levels[1].display_name = QStringLiteral("Level 2");
|
||||
treadmill_pace[5].levels[1].slug = QStringLiteral("level_2");
|
||||
treadmill_pace[5].levels[2].speed = 8.69; // 5.4 mph
|
||||
treadmill_pace[5].levels[2].display_name = QStringLiteral("Level 3");
|
||||
treadmill_pace[5].levels[2].slug = QStringLiteral("level_3");
|
||||
treadmill_pace[5].levels[3].speed = 9.17; // 5.7 mph
|
||||
treadmill_pace[5].levels[3].display_name = QStringLiteral("Level 4");
|
||||
treadmill_pace[5].levels[3].slug = QStringLiteral("level_4");
|
||||
treadmill_pace[5].levels[4].speed = 10.14; // 6.3 mph
|
||||
treadmill_pace[5].levels[4].display_name = QStringLiteral("Level 5");
|
||||
treadmill_pace[5].levels[4].slug = QStringLiteral("level_5");
|
||||
treadmill_pace[5].levels[5].speed = 11.27; // 7.0 mph
|
||||
treadmill_pace[5].levels[5].display_name = QStringLiteral("Level 6");
|
||||
treadmill_pace[5].levels[5].slug = QStringLiteral("level_6");
|
||||
treadmill_pace[5].levels[6].speed = 12.39; // 7.7 mph
|
||||
treadmill_pace[5].levels[6].display_name = QStringLiteral("Level 7");
|
||||
treadmill_pace[5].levels[6].slug = QStringLiteral("level_7");
|
||||
treadmill_pace[5].levels[7].speed = 13.84; // 8.6 mph
|
||||
treadmill_pace[5].levels[7].display_name = QStringLiteral("Level 8");
|
||||
treadmill_pace[5].levels[7].slug = QStringLiteral("level_8");
|
||||
treadmill_pace[5].levels[8].speed = 15.93; // 9.9 mph
|
||||
treadmill_pace[5].levels[8].display_name = QStringLiteral("Level 9");
|
||||
treadmill_pace[5].levels[8].slug = QStringLiteral("level_9");
|
||||
treadmill_pace[5].levels[9].speed = 18.51; // 11.5 mph
|
||||
treadmill_pace[5].levels[9].display_name = QStringLiteral("Level 10");
|
||||
treadmill_pace[5].levels[9].slug = QStringLiteral("level_10");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
treadmill_pace[5].levels[i].display_name = QStringLiteral("Level %1").arg(i+1);
|
||||
treadmill_pace[5].levels[i].slug = QStringLiteral("level_%1").arg(i+1);
|
||||
}
|
||||
|
||||
treadmill_pace[5].levels[0].slow_pace = 7.24203; // 4.5 mph
|
||||
treadmill_pace[5].levels[0].fast_pace = 7.88576; // 4.9 mph
|
||||
treadmill_pace[5].levels[0].speed = (treadmill_pace[5].levels[0].slow_pace + treadmill_pace[5].levels[0].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[5].levels[1].slow_pace = 7.72483; // 4.8 mph
|
||||
treadmill_pace[5].levels[1].fast_pace = 8.36857; // 5.2 mph
|
||||
treadmill_pace[5].levels[1].speed = (treadmill_pace[5].levels[1].slow_pace + treadmill_pace[5].levels[1].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[5].levels[2].slow_pace = 8.36857; // 5.2 mph
|
||||
treadmill_pace[5].levels[2].fast_pace = 9.01230; // 5.6 mph
|
||||
treadmill_pace[5].levels[2].speed = (treadmill_pace[5].levels[2].slow_pace + treadmill_pace[5].levels[2].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[5].levels[3].slow_pace = 8.85137; // 5.5 mph
|
||||
treadmill_pace[5].levels[3].fast_pace = 9.81697; // 6.1 mph
|
||||
treadmill_pace[5].levels[3].speed = (treadmill_pace[5].levels[3].slow_pace + treadmill_pace[5].levels[3].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[5].levels[4].slow_pace = 9.81697; // 6.1 mph
|
||||
treadmill_pace[5].levels[4].fast_pace = 10.62164; // 6.6 mph
|
||||
treadmill_pace[5].levels[4].speed = (treadmill_pace[5].levels[4].slow_pace + treadmill_pace[5].levels[4].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[5].levels[5].slow_pace = 10.78258; // 6.7 mph
|
||||
treadmill_pace[5].levels[5].fast_pace = 11.74818; // 7.3 mph
|
||||
treadmill_pace[5].levels[5].speed = (treadmill_pace[5].levels[5].slow_pace + treadmill_pace[5].levels[5].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[5].levels[6].slow_pace = 11.90912; // 7.4 mph
|
||||
treadmill_pace[5].levels[6].fast_pace = 13.03565; // 8.1 mph
|
||||
treadmill_pace[5].levels[6].speed = (treadmill_pace[5].levels[6].slow_pace + treadmill_pace[5].levels[6].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[5].levels[7].slow_pace = 13.35752; // 8.3 mph
|
||||
treadmill_pace[5].levels[7].fast_pace = 14.64499; // 9.1 mph
|
||||
treadmill_pace[5].levels[7].speed = (treadmill_pace[5].levels[7].slow_pace + treadmill_pace[5].levels[7].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[5].levels[8].slow_pace = 15.28873; // 9.5 mph
|
||||
treadmill_pace[5].levels[8].fast_pace = 16.73714; // 10.4 mph
|
||||
treadmill_pace[5].levels[8].speed = (treadmill_pace[5].levels[8].slow_pace + treadmill_pace[5].levels[8].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[5].levels[9].slow_pace = 17.70274; // 11.0 mph
|
||||
treadmill_pace[5].levels[9].fast_pace = 19.63395; // 12.2 mph
|
||||
treadmill_pace[5].levels[9].speed = (treadmill_pace[5].levels[9].slow_pace + treadmill_pace[5].levels[9].fast_pace) / 2.0;
|
||||
|
||||
// Max
|
||||
treadmill_pace[6].value = 6;
|
||||
treadmill_pace[6].display_name = QStringLiteral("Max");
|
||||
treadmill_pace[6].levels[0].speed = 8.05; // 5.0 mph
|
||||
treadmill_pace[6].levels[1].speed = 8.53; // 5.3 mph
|
||||
treadmill_pace[6].levels[2].speed = 9.17; // 5.7 mph
|
||||
treadmill_pace[6].levels[3].speed = 9.98; // 6.2 mph
|
||||
treadmill_pace[6].levels[4].speed = 10.78; // 6.7 mph
|
||||
treadmill_pace[6].levels[5].speed = 11.91; // 7.4 mph
|
||||
treadmill_pace[6].levels[6].speed = 13.20; // 8.2 mph
|
||||
treadmill_pace[6].levels[7].speed = 14.81; // 9.2 mph
|
||||
treadmill_pace[6].levels[8].speed = 16.90; // 10.5 mph
|
||||
treadmill_pace[6].levels[9].speed = 19.80; // 12.3 mph
|
||||
for (int i = 0; i < 10; i++) {
|
||||
treadmill_pace[6].levels[i].display_name = QStringLiteral("Level %1").arg(i+1);
|
||||
treadmill_pace[6].levels[i].slug = QStringLiteral("level_%1").arg(i+1);
|
||||
}
|
||||
|
||||
treadmill_pace[6].levels[0].slow_pace = 8.0467; // 5.0 mph
|
||||
treadmill_pace[6].levels[0].fast_pace = 20.11675; // 12.5 mph
|
||||
treadmill_pace[6].levels[0].speed = (treadmill_pace[6].levels[0].slow_pace + treadmill_pace[6].levels[0].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[6].levels[1].slow_pace = 8.52950; // 5.3 mph
|
||||
treadmill_pace[6].levels[1].fast_pace = 20.11675; // 12.5 mph
|
||||
treadmill_pace[6].levels[1].speed = (treadmill_pace[6].levels[1].slow_pace + treadmill_pace[6].levels[1].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[6].levels[2].slow_pace = 9.17324; // 5.7 mph
|
||||
treadmill_pace[6].levels[2].fast_pace = 20.11675; // 12.5 mph
|
||||
treadmill_pace[6].levels[2].speed = (treadmill_pace[6].levels[2].slow_pace + treadmill_pace[6].levels[2].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[6].levels[3].slow_pace = 9.97791; // 6.2 mph
|
||||
treadmill_pace[6].levels[3].fast_pace = 20.11675; // 12.5 mph
|
||||
treadmill_pace[6].levels[3].speed = (treadmill_pace[6].levels[3].slow_pace + treadmill_pace[6].levels[3].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[6].levels[4].slow_pace = 10.78258; // 6.7 mph
|
||||
treadmill_pace[6].levels[4].fast_pace = 20.11675; // 12.5 mph
|
||||
treadmill_pace[6].levels[4].speed = (treadmill_pace[6].levels[4].slow_pace + treadmill_pace[6].levels[4].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[6].levels[5].slow_pace = 11.90912; // 7.4 mph
|
||||
treadmill_pace[6].levels[5].fast_pace = 20.11675; // 12.5 mph
|
||||
treadmill_pace[6].levels[5].speed = (treadmill_pace[6].levels[5].slow_pace + treadmill_pace[6].levels[5].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[6].levels[6].slow_pace = 13.19659; // 8.2 mph
|
||||
treadmill_pace[6].levels[6].fast_pace = 20.11675; // 12.5 mph
|
||||
treadmill_pace[6].levels[6].speed = (treadmill_pace[6].levels[6].slow_pace + treadmill_pace[6].levels[6].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[6].levels[7].slow_pace = 14.80593; // 9.2 mph
|
||||
treadmill_pace[6].levels[7].fast_pace = 20.11675; // 12.5 mph
|
||||
treadmill_pace[6].levels[7].speed = (treadmill_pace[6].levels[7].slow_pace + treadmill_pace[6].levels[7].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[6].levels[8].slow_pace = 16.89993; // 10.5 mph
|
||||
treadmill_pace[6].levels[8].fast_pace = 20.11675; // 12.5 mph
|
||||
treadmill_pace[6].levels[8].speed = (treadmill_pace[6].levels[8].slow_pace + treadmill_pace[6].levels[8].fast_pace) / 2.0;
|
||||
|
||||
treadmill_pace[6].levels[9].slow_pace = 19.79488; // 12.3 mph
|
||||
treadmill_pace[6].levels[9].fast_pace = 20.11675; // 12.5 mph
|
||||
treadmill_pace[6].levels[9].speed = (treadmill_pace[6].levels[9].slow_pace + treadmill_pace[6].levels[9].fast_pace) / 2.0;
|
||||
|
||||
connect(timer, &QTimer::timeout, this, &peloton::startEngine);
|
||||
|
||||
PZP = new powerzonepack(bl, this);
|
||||
@@ -1089,6 +1225,138 @@ void peloton::ride_onfinish(QNetworkReply *reply) {
|
||||
trainrows.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (trainrows.empty() && !target_metrics_data_list.isEmpty() && bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL) {
|
||||
QJsonObject target_metrics_data = ride["target_metrics_data"].toObject();
|
||||
QJsonArray target_metrics = target_metrics_data["target_metrics"].toArray();
|
||||
|
||||
if (!target_metrics.isEmpty()) {
|
||||
bool treadmill_force_speed = settings.value(QZSettings::treadmill_force_speed,
|
||||
QZSettings::default_treadmill_force_speed).toBool();
|
||||
int peloton_treadmill_level = settings.value(QZSettings::peloton_treadmill_level,
|
||||
QZSettings::default_peloton_treadmill_level).toInt() - 1;
|
||||
|
||||
if (peloton_treadmill_level < 0 || peloton_treadmill_level > 9)
|
||||
peloton_treadmill_level = 0;
|
||||
|
||||
double miles = 1.0;
|
||||
if (settings.value(QZSettings::miles_unit, QZSettings::default_miles_unit).toBool()) { // i didn't find the unit in the json
|
||||
miles = 1.60934;
|
||||
}
|
||||
|
||||
for (const QJsonValue& segment : target_metrics) {
|
||||
QJsonObject segmentObj = segment.toObject();
|
||||
QJsonArray metrics = segmentObj["metrics"].toArray();
|
||||
QJsonObject offsets = segmentObj["offsets"].toObject();
|
||||
QString segment_type = segmentObj["segment_type"].toString();
|
||||
|
||||
// Skip if no metrics or invalid offsets
|
||||
if (metrics.isEmpty() || offsets.isEmpty())
|
||||
continue;
|
||||
|
||||
double speed_lower = -1;
|
||||
double speed_upper = -1;
|
||||
double inc_lower = -100;
|
||||
double inc_upper = -100;
|
||||
int pace_intensity_lower = -1;
|
||||
int pace_intensity_upper = -1;
|
||||
|
||||
// Process metrics (speed, incline, pace_intensity)
|
||||
for (const QJsonValue& metric : metrics) {
|
||||
QJsonObject metricObj = metric.toObject();
|
||||
QString metricName = metricObj["name"].toString().toLower();
|
||||
|
||||
if (metricName == "pace_intensity") {
|
||||
pace_intensity_lower = metricObj["lower"].toInt();
|
||||
pace_intensity_upper = metricObj["upper"].toInt();
|
||||
|
||||
speed_lower = treadmill_pace[pace_intensity_lower].levels[peloton_treadmill_level].slow_pace;
|
||||
speed_upper = treadmill_pace[pace_intensity_upper].levels[peloton_treadmill_level].fast_pace;
|
||||
|
||||
miles = 1; // the pace intensity are always in km/h
|
||||
}
|
||||
else if (metricName == "speed") {
|
||||
speed_lower = metricObj["lower"].toDouble();
|
||||
speed_upper = metricObj["upper"].toDouble();
|
||||
}
|
||||
else if (metricName == "incline") {
|
||||
inc_lower = metricObj["lower"].toDouble();
|
||||
inc_upper = metricObj["upper"].toDouble();
|
||||
}
|
||||
}
|
||||
|
||||
// Create training row
|
||||
trainrow row;
|
||||
if (speed_lower != -1)
|
||||
row.forcespeed = treadmill_force_speed;
|
||||
|
||||
// Set duration
|
||||
int start = offsets["start"].toInt();
|
||||
int end = offsets["end"].toInt();
|
||||
row.duration = QTime(0, 0, 0).addSecs(end - start + 1);
|
||||
|
||||
// Apply difficulty settings
|
||||
if (difficulty.toUpper() == "LOWER") {
|
||||
if (speed_lower != -1)
|
||||
row.speed = speed_lower * miles;
|
||||
if (inc_lower != -100)
|
||||
row.inclination = inc_lower;
|
||||
if (pace_intensity_lower != -1)
|
||||
row.pace_intensity = pace_intensity_lower;
|
||||
}
|
||||
else if (difficulty.toUpper() == "UPPER") {
|
||||
if (speed_lower != -1)
|
||||
row.speed = speed_upper * miles;
|
||||
if (inc_lower != -100)
|
||||
row.inclination = inc_upper;
|
||||
if (pace_intensity_upper != -1)
|
||||
row.pace_intensity = pace_intensity_upper;
|
||||
}
|
||||
else { // AVERAGE
|
||||
if (speed_lower != -1)
|
||||
row.speed = (speed_lower + speed_upper) / 2.0 * miles;
|
||||
if (inc_lower != -100)
|
||||
row.inclination = (inc_lower + inc_upper) / 2.0;
|
||||
if (pace_intensity_lower != -1)
|
||||
row.pace_intensity = (pace_intensity_lower + pace_intensity_upper) / 2;
|
||||
}
|
||||
|
||||
// Store range values
|
||||
if (speed_lower != -1) {
|
||||
row.lower_speed = speed_lower * miles;
|
||||
row.average_speed = (speed_lower + speed_upper) / 2.0 * miles;
|
||||
row.upper_speed = speed_upper * miles;
|
||||
}
|
||||
|
||||
if (inc_lower != -100) {
|
||||
// Apply inclination adjustments
|
||||
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();
|
||||
|
||||
row.lower_inclination = inc_lower * gain + offset;
|
||||
row.average_inclination = (inc_lower + inc_upper) / 2.0 * gain + offset;
|
||||
row.upper_inclination = inc_upper * gain + offset;
|
||||
row.inclination = row.inclination * gain + offset;
|
||||
}
|
||||
|
||||
qDebug() << row.duration << row.speed << row.inclination;
|
||||
trainrows.append(row);
|
||||
}
|
||||
|
||||
QTime duration(0,0,0,0);
|
||||
foreach(trainrow r, trainrows) {
|
||||
duration = duration.addSecs(QTime(0,0,0,0).secsTo(r.duration));
|
||||
qDebug() << duration << r.duration;
|
||||
}
|
||||
if(QTime(0,0,0,0).secsTo(duration) < current_pedaling_duration) {
|
||||
qDebug() << "peloton sends less metrics than expected, let's remove this and fallback on HFB" << QTime(0,0,0,0).secsTo(duration) << current_pedaling_duration;
|
||||
trainrows.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (log_request) {
|
||||
qDebug() << "peloton::ride_onfinish" << trainrows.length() << ride;
|
||||
|
||||
@@ -104,7 +104,9 @@ class peloton : public QObject {
|
||||
|
||||
typedef struct _peloton_treadmill_pace_intensities_level {
|
||||
QString display_name;
|
||||
double speed;
|
||||
double fast_pace;
|
||||
double slow_pace;
|
||||
double speed; // Average of fast_pace and slow_pace
|
||||
QString slug;
|
||||
}_peloton_treadmill_pace_intensities_level;
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
AccordionElement {
|
||||
StaticAccordionElement {
|
||||
title: qsTr("Profiles")
|
||||
indicatRectColor: Material.color(Material.Grey)
|
||||
textColor: Material.color(Material.Grey)
|
||||
|
||||
@@ -77,16 +77,21 @@ DEFINES += QT_DEPRECATED_WARNINGS IO_UNDER_QT SMTP_BUILD NOMINMAX
|
||||
SOURCES += \
|
||||
$$PWD/devices/antbike/antbike.cpp \
|
||||
$$PWD/devices/crossrope/crossrope.cpp \
|
||||
$$PWD/devices/cycleopsphantombike/cycleopsphantombike.cpp \
|
||||
$$PWD/devices/deeruntreadmill/deerruntreadmill.cpp \
|
||||
$$PWD/devices/focustreadmill/focustreadmill.cpp \
|
||||
$$PWD/devices/jumprope.cpp \
|
||||
$$PWD/devices/kineticinroadbike/SmartControl.cpp \
|
||||
$$PWD/devices/kineticinroadbike/kineticinroadbike.cpp \
|
||||
$$PWD/devices/lifespantreadmill/lifespantreadmill.cpp \
|
||||
$$PWD/devices/nordictrackifitadbelliptical/nordictrackifitadbelliptical.cpp \
|
||||
$$PWD/devices/pitpatbike/pitpatbike.cpp \
|
||||
$$PWD/devices/sportsplusrower/sportsplusrower.cpp \
|
||||
$$PWD/devices/sportstechelliptical/sportstechelliptical.cpp \
|
||||
$$PWD/devices/sramAXSController/sramAXSController.cpp \
|
||||
$$PWD/devices/technogymbike/technogymbike.cpp \
|
||||
$$PWD/devices/trxappgateusbelliptical/trxappgateusbelliptical.cpp \
|
||||
$$PWD/devices/trxappgateusbrower/trxappgateusbrower.cpp \
|
||||
$$PWD/mqtt/qmqttauthenticationproperties.cpp \
|
||||
$$PWD/mqtt/qmqttclient.cpp \
|
||||
$$PWD/mqtt/qmqttconnection.cpp \
|
||||
@@ -99,6 +104,7 @@ SOURCES += \
|
||||
$$PWD/mqtt/qmqtttopicfilter.cpp \
|
||||
$$PWD/mqtt/qmqtttopicname.cpp \
|
||||
$$PWD/mqtt/qmqtttype.cpp \
|
||||
$$PWD/osc.cpp \
|
||||
QTelnet.cpp \
|
||||
devices/bkoolbike/bkoolbike.cpp \
|
||||
devices/csafe/csafe.cpp \
|
||||
@@ -327,17 +333,32 @@ HEADERS += \
|
||||
$$PWD/EventHandler.h \
|
||||
$$PWD/devices/antbike/antbike.h \
|
||||
$$PWD/devices/crossrope/crossrope.h \
|
||||
$$PWD/devices/cycleopsphantombike/cycleopsphantombike.h \
|
||||
$$PWD/devices/deeruntreadmill/deerruntreadmill.h \
|
||||
$$PWD/devices/focustreadmill/focustreadmill.h \
|
||||
$$PWD/devices/jumprope.h \
|
||||
$$PWD/devices/kineticinroadbike/SmartControl.h \
|
||||
$$PWD/devices/kineticinroadbike/kineticinroadbike.h \
|
||||
$$PWD/devices/lifespantreadmill/lifespantreadmill.h \
|
||||
$$PWD/devices/nordictrackifitadbelliptical/nordictrackifitadbelliptical.h \
|
||||
$$PWD/devices/pitpatbike/pitpatbike.h \
|
||||
$$PWD/devices/sportsplusrower/sportsplusrower.h \
|
||||
$$PWD/devices/sportstechelliptical/sportstechelliptical.h \
|
||||
$$PWD/devices/sramAXSController/sramAXSController.h \
|
||||
$$PWD/devices/technogymbike/technogymbike.h \
|
||||
$$PWD/devices/trxappgateusbelliptical/trxappgateusbelliptical.h \
|
||||
$$PWD/devices/trxappgateusbrower/trxappgateusbrower.h \
|
||||
$$PWD/ergtable.h \
|
||||
$$PWD/osc.h \
|
||||
$$PWD/oscpp/client.hpp \
|
||||
$$PWD/oscpp/detail/endian.hpp \
|
||||
$$PWD/oscpp/detail/host.hpp \
|
||||
$$PWD/oscpp/detail/stream.hpp \
|
||||
$$PWD/oscpp/error.hpp \
|
||||
$$PWD/oscpp/print.hpp \
|
||||
$$PWD/oscpp/server.hpp \
|
||||
$$PWD/oscpp/types.hpp \
|
||||
$$PWD/oscpp/util.hpp \
|
||||
$$PWD/mqtt/qmqttauthenticationproperties.h \
|
||||
$$PWD/mqtt/qmqttclient.h \
|
||||
$$PWD/mqtt/qmqttclient_p.h \
|
||||
@@ -928,4 +949,4 @@ INCLUDEPATH += purchasing/inapp
|
||||
|
||||
WINRT_MANIFEST = AppxManifest.xml
|
||||
|
||||
VERSION = 2.18.9
|
||||
VERSION = 2.18.17
|
||||
|
||||
50
src/qfit.cpp
50
src/qfit.cpp
@@ -3,6 +3,7 @@
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <ostream>
|
||||
#include <QDir>
|
||||
|
||||
#include "QSettings"
|
||||
|
||||
@@ -13,6 +14,12 @@
|
||||
#include "fit_developer_field_description.hpp"
|
||||
#include "fit_mesg_broadcaster.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
|
||||
qfit::qfit(QObject *parent) : QObject(parent) {}
|
||||
@@ -22,6 +29,8 @@ void qfit::save(const QString &filename, QList<SessionLine> session, bluetoothde
|
||||
QSettings settings;
|
||||
bool strava_virtual_activity =
|
||||
settings.value(QZSettings::strava_virtual_activity, QZSettings::default_strava_virtual_activity).toBool();
|
||||
bool strava_treadmill =
|
||||
settings.value(QZSettings::strava_treadmill, QZSettings::default_strava_treadmill).toBool();
|
||||
bool powr_sensor_running_cadence_half_on_strava =
|
||||
settings
|
||||
.value(QZSettings::powr_sensor_running_cadence_half_on_strava,
|
||||
@@ -46,25 +55,35 @@ void qfit::save(const QString &filename, QList<SessionLine> session, bluetoothde
|
||||
startingDistanceOffset = session.at(firstRealIndex).distance;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
file.open(QString(filename).toLocal8Bit().constData(), std::ios::in | std::ios::out | std::ios::binary | std::ios::trunc);
|
||||
#else
|
||||
file.open(filename.toStdString(), std::ios::in | std::ios::out | std::ios::binary | std::ios::trunc);
|
||||
#endif
|
||||
|
||||
if (!file.is_open()) {
|
||||
|
||||
printf("Error opening file ExampleActivity.fit\n");
|
||||
qDebug() << "Error opening file stream";
|
||||
return;
|
||||
}
|
||||
|
||||
QFile output(filename);
|
||||
output.open(QIODevice::WriteOnly);
|
||||
|
||||
bool fit_file_garmin_device_training_effect = settings.value(QZSettings::fit_file_garmin_device_training_effect, QZSettings::default_fit_file_garmin_device_training_effect).toBool();
|
||||
fit::FileIdMesg fileIdMesg; // Every FIT file requires a File ID message
|
||||
fileIdMesg.SetType(FIT_FILE_ACTIVITY);
|
||||
if(bluetooth_device_name.toUpper().startsWith("DOMYOS"))
|
||||
fileIdMesg.SetManufacturer(FIT_MANUFACTURER_DECATHLON);
|
||||
else
|
||||
fileIdMesg.SetManufacturer(FIT_MANUFACTURER_DEVELOPMENT);
|
||||
fileIdMesg.SetProduct(1);
|
||||
fileIdMesg.SetSerialNumber(12345);
|
||||
else {
|
||||
if(fit_file_garmin_device_training_effect)
|
||||
fileIdMesg.SetManufacturer(FIT_MANUFACTURER_GARMIN);
|
||||
else
|
||||
fileIdMesg.SetManufacturer(FIT_MANUFACTURER_DEVELOPMENT);
|
||||
}
|
||||
if(fit_file_garmin_device_training_effect) {
|
||||
fileIdMesg.SetProduct(FIT_GARMIN_PRODUCT_EDGE_1030_PLUS);
|
||||
fileIdMesg.SetSerialNumber(3313379353);
|
||||
} else {
|
||||
fileIdMesg.SetProduct(1);
|
||||
fileIdMesg.SetSerialNumber(12345);
|
||||
}
|
||||
fileIdMesg.SetTimeCreated(session.at(firstRealIndex).time.toSecsSinceEpoch() - 631065600L);
|
||||
|
||||
bool gps_data = false;
|
||||
@@ -138,7 +157,8 @@ void qfit::save(const QString &filename, QList<SessionLine> session, bluetoothde
|
||||
if (strava_virtual_activity) {
|
||||
sessionMesg.SetSubSport(FIT_SUB_SPORT_VIRTUAL_ACTIVITY);
|
||||
} else {
|
||||
sessionMesg.SetSubSport(FIT_SUB_SPORT_TREADMILL);
|
||||
if(strava_treadmill)
|
||||
sessionMesg.SetSubSport(FIT_SUB_SPORT_TREADMILL);
|
||||
}
|
||||
} else if (type == bluetoothdevice::ELLIPTICAL) {
|
||||
|
||||
@@ -218,7 +238,9 @@ void qfit::save(const QString &filename, QList<SessionLine> session, bluetoothde
|
||||
fit::WorkoutMesg workout;
|
||||
workout.SetSport(sessionMesg.GetSport());
|
||||
workout.SetSubSport(sessionMesg.GetSubSport());
|
||||
#ifndef _WIN32
|
||||
workout.SetWktName(workoutName.toStdWString());
|
||||
#endif
|
||||
workout.SetNumValidSteps(1);
|
||||
encode.Write(workout);
|
||||
|
||||
@@ -611,7 +633,11 @@ class Listener : public fit::FileIdMesgListener,
|
||||
|
||||
void qfit::open(const QString &filename, QList<SessionLine> *output) {
|
||||
std::fstream file;
|
||||
file.open(filename.toStdString(), std::ios::in);
|
||||
#ifdef _WIN32
|
||||
file.open(QString(filename).toLocal8Bit().constData(), std::ios::in | std::ios::binary);
|
||||
#else
|
||||
file.open(filename.toStdString(), std::ios::in | std::ios::binary);
|
||||
#endif
|
||||
|
||||
if (!file.is_open()) {
|
||||
|
||||
@@ -633,4 +659,6 @@ void qfit::open(const QString &filename, QList<SessionLine> *output) {
|
||||
mesgBroadcaster.AddListener((fit::RecordMesgListener &)listener);
|
||||
mesgBroadcaster.AddListener((fit::MesgListener &)listener);
|
||||
decode.Read(&s, &mesgBroadcaster, &mesgBroadcaster, &listener);
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
@@ -110,5 +110,9 @@
|
||||
<file>inner_templates/chartjs/dochartliveheart.js</file>
|
||||
<file>Wizard.qml</file>
|
||||
<file>gears.qml</file>
|
||||
<file>IndicatorOnlySwitch.qml</file>
|
||||
<file>StaticAccordionElement.qml</file>
|
||||
<file>inner_templates/chartjs/dotreadmillchartlive.js</file>
|
||||
<file>inner_templates/chartjs/treadmillchartlive.htm</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -801,8 +801,20 @@ const QString QZSettings::peloton_auto_start_without_intro = QStringLiteral("pel
|
||||
const QString QZSettings::nordictrack_tseries5_treadmill = QStringLiteral("nordictrack_tseries5_treadmill");
|
||||
const QString QZSettings::proform_carbon_tl_PFTL59722c = QStringLiteral("proform_carbon_tl_PFTL59722c");
|
||||
const QString QZSettings::nordictrack_gx_44_pro = QStringLiteral("nordictrack_gx_44_pro");
|
||||
const QString QZSettings::OSC_ip = QStringLiteral("osc_ip");
|
||||
const QString QZSettings::default_OSC_ip = QStringLiteral("");
|
||||
const QString QZSettings::OSC_port = QStringLiteral("osc_port");
|
||||
const QString QZSettings::strava_treadmill = QStringLiteral("strava_treadmill");
|
||||
const QString QZSettings::iconsole_rower = QStringLiteral("iconsole_rower");
|
||||
const QString QZSettings::proform_treadmill_1500_pro = QStringLiteral("proform_treadmill_1500_pro");
|
||||
const QString QZSettings::proform_505_cst_80_44 = QStringLiteral("proform_505_cst_80_44");
|
||||
const QString QZSettings::proform_trainer_8_0 = QStringLiteral("proform_trainer_8_0");
|
||||
const QString QZSettings::tile_biggears_swap = QStringLiteral("tile_biggears_swap");
|
||||
const QString QZSettings::treadmill_follow_wattage = QStringLiteral("treadmill_follow_wattage");
|
||||
const QString QZSettings::fit_file_garmin_device_training_effect = QStringLiteral("fit_file_garmin_device_training_effect");
|
||||
const QString QZSettings::proform_treadmill_705_cst_V80_44 = QStringLiteral("proform_treadmill_705_cst_V80_44");
|
||||
|
||||
const uint32_t allSettingsCount = 676;
|
||||
const uint32_t allSettingsCount = 687;
|
||||
|
||||
QVariant allSettings[allSettingsCount][2] = {
|
||||
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
|
||||
@@ -1485,6 +1497,17 @@ QVariant allSettings[allSettingsCount][2] = {
|
||||
{QZSettings::nordictrack_tseries5_treadmill, QZSettings::default_nordictrack_tseries5_treadmill},
|
||||
{QZSettings::proform_carbon_tl_PFTL59722c, QZSettings::default_proform_carbon_tl_PFTL59722c},
|
||||
{QZSettings::nordictrack_gx_44_pro, QZSettings::default_nordictrack_gx_44_pro},
|
||||
{QZSettings::OSC_ip, QZSettings::default_OSC_ip},
|
||||
{QZSettings::OSC_port, QZSettings::default_OSC_port},
|
||||
{QZSettings::strava_treadmill, QZSettings::default_strava_treadmill},
|
||||
{QZSettings::iconsole_rower, QZSettings::default_iconsole_rower},
|
||||
{QZSettings::proform_treadmill_1500_pro, QZSettings::default_proform_treadmill_1500_pro},
|
||||
{QZSettings::proform_505_cst_80_44, QZSettings::default_proform_505_cst_80_44},
|
||||
{QZSettings::proform_trainer_8_0, QZSettings::default_proform_trainer_8_0},
|
||||
{QZSettings::tile_biggears_swap, QZSettings::default_tile_biggears_swap},
|
||||
{QZSettings::treadmill_follow_wattage, QZSettings::default_treadmill_follow_wattage},
|
||||
{QZSettings::fit_file_garmin_device_training_effect, QZSettings::default_fit_file_garmin_device_training_effect},
|
||||
{QZSettings::proform_treadmill_705_cst_V80_44, QZSettings::default_proform_treadmill_705_cst_V80_44},
|
||||
};
|
||||
|
||||
void QZSettings::qDebugAllSettings(bool showDefaults) {
|
||||
|
||||
@@ -2228,6 +2228,39 @@ class QZSettings {
|
||||
static const QString nordictrack_gx_44_pro;
|
||||
static constexpr bool default_nordictrack_gx_44_pro = false;
|
||||
|
||||
static const QString OSC_ip;
|
||||
static const QString default_OSC_ip;
|
||||
|
||||
static const QString OSC_port;
|
||||
static constexpr int default_OSC_port = 9000;
|
||||
|
||||
static const QString strava_treadmill;
|
||||
static constexpr bool default_strava_treadmill = true;
|
||||
|
||||
static const QString iconsole_rower;
|
||||
static constexpr bool default_iconsole_rower = false;
|
||||
|
||||
static const QString proform_treadmill_1500_pro;
|
||||
static constexpr bool default_proform_treadmill_1500_pro = false;
|
||||
|
||||
static const QString proform_505_cst_80_44;
|
||||
static constexpr bool default_proform_505_cst_80_44 = false;
|
||||
|
||||
static const QString proform_trainer_8_0;
|
||||
static constexpr bool default_proform_trainer_8_0 = false;
|
||||
|
||||
static const QString tile_biggears_swap;
|
||||
static constexpr bool default_tile_biggears_swap = false;
|
||||
|
||||
static const QString treadmill_follow_wattage;
|
||||
static constexpr bool default_treadmill_follow_wattage = false;
|
||||
|
||||
static const QString fit_file_garmin_device_training_effect;
|
||||
static constexpr bool default_fit_file_garmin_device_training_effect = false;
|
||||
|
||||
static const QString proform_treadmill_705_cst_V80_44;
|
||||
static constexpr bool default_proform_treadmill_705_cst_V80_44 = 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.
|
||||
|
||||
@@ -195,6 +195,7 @@ ScrollView {
|
||||
property int tile_rss_order: 53
|
||||
property bool tile_biggears_enabled: false
|
||||
property int tile_biggears_order: 54
|
||||
property bool tile_biggears_swap: false
|
||||
}
|
||||
|
||||
|
||||
@@ -1624,27 +1625,42 @@ ScrollView {
|
||||
title: qsTr("Gears Big Buttons")
|
||||
linkedBoolSetting: "tile_biggears_enabled"
|
||||
settings: settings
|
||||
accordionContent: RowLayout {
|
||||
spacing: 10
|
||||
Label {
|
||||
text: qsTr("order index:")
|
||||
accordionContent: ColumnLayout {
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
Label {
|
||||
text: qsTr("order index:")
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
ComboBox {
|
||||
id: biggearsOrderTextField
|
||||
model: rootItem.tile_order
|
||||
displayText: settings.tile_biggears_order
|
||||
Layout.fillHeight: false
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
onActivated: {
|
||||
displayText = biggearsOrderTextField.currentValue
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: "OK"
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
onClicked: {settings.tile_biggears_order = biggearsOrderTextField.displayText; toast.show("Setting saved!"); }
|
||||
}
|
||||
}
|
||||
SwitchDelegate {
|
||||
text: qsTr("Swap Buttons")
|
||||
spacing: 0
|
||||
bottomPadding: 0
|
||||
topPadding: 0
|
||||
rightPadding: 0
|
||||
leftPadding: 0
|
||||
clip: false
|
||||
checked: settings.tile_biggears_swap
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
ComboBox {
|
||||
id: biggearsOrderTextField
|
||||
model: rootItem.tile_order
|
||||
displayText: settings.tile_biggears_order
|
||||
Layout.fillHeight: false
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
onActivated: {
|
||||
displayText = biggearsOrderTextField.currentValue
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: "OK"
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
onClicked: {settings.tile_biggears_order = biggearsOrderTextField.displayText; toast.show("Setting saved!"); }
|
||||
onClicked: settings.tile_biggears_swap = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6953
src/settings.qml
6953
src/settings.qml
File diff suppressed because it is too large
Load Diff
@@ -1401,14 +1401,38 @@ trainprogram *trainprogram::load(const QString &filename, bluetooth *b, QString
|
||||
}
|
||||
|
||||
QList<trainrow> trainprogram::loadXML(const QString &filename, bluetoothdevice::BLUETOOTH_TYPE device_type) {
|
||||
|
||||
QList<trainrow> list;
|
||||
QFile input(filename);
|
||||
input.open(QIODevice::ReadOnly);
|
||||
QXmlStreamReader stream(&input);
|
||||
while (!stream.atEnd()) {
|
||||
|
||||
QList<trainrow> repeatRows;
|
||||
int repeatTimes = 0;
|
||||
bool insideRepeat = false;
|
||||
|
||||
while (!stream.atEnd()) {
|
||||
stream.readNext();
|
||||
|
||||
// Handle repeat tag start
|
||||
if (stream.isStartElement() && stream.name() == "repeat") {
|
||||
insideRepeat = true;
|
||||
repeatRows.clear();
|
||||
QXmlStreamAttributes attrs = stream.attributes();
|
||||
if (attrs.hasAttribute("times")) {
|
||||
repeatTimes = attrs.value("times").toInt();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle repeat tag end
|
||||
if (stream.isEndElement() && stream.name() == "repeat") {
|
||||
insideRepeat = false;
|
||||
for (int i = 0; i < repeatTimes; i++) {
|
||||
list.append(repeatRows);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
trainrow row;
|
||||
QXmlStreamAttributes atts = stream.attributes();
|
||||
bool ramp = false;
|
||||
@@ -1447,7 +1471,7 @@ QList<trainrow> trainprogram::loadXML(const QString &filename, bluetoothdevice::
|
||||
row.longitude = atts.value(QStringLiteral("longitude")).toDouble();
|
||||
}
|
||||
if (atts.hasAttribute(QStringLiteral("altitude"))) {
|
||||
row.longitude = atts.value(QStringLiteral("altitude")).toDouble();
|
||||
row.altitude = atts.value(QStringLiteral("altitude")).toDouble();
|
||||
}
|
||||
if (atts.hasAttribute(QStringLiteral("azimuth"))) {
|
||||
row.azimuth = atts.value(QStringLiteral("azimuth")).toDouble();
|
||||
@@ -1459,12 +1483,10 @@ QList<trainrow> trainprogram::loadXML(const QString &filename, bluetoothdevice::
|
||||
row.requested_peloton_resistance = atts.value(QStringLiteral("requested_peloton_resistance")).toInt();
|
||||
}
|
||||
if (atts.hasAttribute(QStringLiteral("lower_requested_peloton_resistance"))) {
|
||||
row.lower_requested_peloton_resistance =
|
||||
atts.value(QStringLiteral("lower_requested_peloton_resistance")).toInt();
|
||||
row.lower_requested_peloton_resistance = atts.value(QStringLiteral("lower_requested_peloton_resistance")).toInt();
|
||||
}
|
||||
if (atts.hasAttribute(QStringLiteral("upper_requested_peloton_resistance"))) {
|
||||
row.upper_requested_peloton_resistance =
|
||||
atts.value(QStringLiteral("upper_requested_peloton_resistance")).toInt();
|
||||
row.upper_requested_peloton_resistance = atts.value(QStringLiteral("upper_requested_peloton_resistance")).toInt();
|
||||
}
|
||||
if (atts.hasAttribute(QStringLiteral("pace_intensity"))) {
|
||||
row.pace_intensity = atts.value(QStringLiteral("pace_intensity")).toInt();
|
||||
@@ -1505,12 +1527,15 @@ QList<trainrow> trainprogram::loadXML(const QString &filename, bluetoothdevice::
|
||||
if (atts.hasAttribute(QStringLiteral("powerzone"))) {
|
||||
QSettings settings;
|
||||
if(device_type == bluetoothdevice::TREADMILL) {
|
||||
row.power = atts.value(QStringLiteral("powerzone")).toDouble() * settings.value(QZSettings::ftp_run, QZSettings::default_ftp_run).toDouble();
|
||||
row.power = atts.value(QStringLiteral("powerzone")).toDouble() *
|
||||
settings.value(QZSettings::ftp_run, QZSettings::default_ftp_run).toDouble();
|
||||
} else {
|
||||
row.power = atts.value(QStringLiteral("powerzone")).toDouble() * settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble();
|
||||
row.power = atts.value(QStringLiteral("powerzone")).toDouble() *
|
||||
settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble();
|
||||
}
|
||||
}
|
||||
if (atts.hasAttribute(QStringLiteral("speedfrom")) && atts.hasAttribute(QStringLiteral("speedto")) && atts.hasAttribute(QStringLiteral("duration"))) {
|
||||
if (atts.hasAttribute(QStringLiteral("speedfrom")) && atts.hasAttribute(QStringLiteral("speedto")) &&
|
||||
atts.hasAttribute(QStringLiteral("duration"))) {
|
||||
double speedFrom = atts.value(QStringLiteral("speedfrom")).toDouble();
|
||||
double speedTo = atts.value(QStringLiteral("speedto")).toDouble();
|
||||
QTime duration = QTime::fromString(atts.value(QStringLiteral("duration")).toString(), QStringLiteral("hh:mm:ss"));
|
||||
@@ -1550,11 +1575,16 @@ QList<trainrow> trainprogram::loadXML(const QString &filename, bluetoothdevice::
|
||||
rowI.speed = speedFrom - (speedStep * i);
|
||||
}
|
||||
qDebug() << "TrainRow" << rowI.toString();
|
||||
list.append(rowI);
|
||||
if (insideRepeat) {
|
||||
repeatRows.append(rowI);
|
||||
} else {
|
||||
list.append(rowI);
|
||||
}
|
||||
}
|
||||
ramp = true;
|
||||
}
|
||||
if (atts.hasAttribute(QStringLiteral("powerzonefrom")) && atts.hasAttribute(QStringLiteral("powerzoneto")) && atts.hasAttribute(QStringLiteral("duration"))) {
|
||||
if (atts.hasAttribute(QStringLiteral("powerzonefrom")) && atts.hasAttribute(QStringLiteral("powerzoneto")) &&
|
||||
atts.hasAttribute(QStringLiteral("duration"))) {
|
||||
QSettings settings;
|
||||
double speedFrom = atts.value(QStringLiteral("powerzonefrom")).toDouble();
|
||||
double speedTo = atts.value(QStringLiteral("powerzoneto")).toDouble();
|
||||
@@ -1593,25 +1623,37 @@ QList<trainrow> trainprogram::loadXML(const QString &filename, bluetoothdevice::
|
||||
rowI.forcespeed = 1;
|
||||
if (speedFrom < speedTo) {
|
||||
if(device_type == bluetoothdevice::TREADMILL) {
|
||||
rowI.power = (speedFrom + (speedStep * i)) * settings.value(QZSettings::ftp_run, QZSettings::default_ftp_run).toDouble();
|
||||
rowI.power = (speedFrom + (speedStep * i)) *
|
||||
settings.value(QZSettings::ftp_run, QZSettings::default_ftp_run).toDouble();
|
||||
} else {
|
||||
rowI.power = (speedFrom + (speedStep * i)) * settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble();
|
||||
rowI.power = (speedFrom + (speedStep * i)) *
|
||||
settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble();
|
||||
}
|
||||
} else {
|
||||
if(device_type == bluetoothdevice::TREADMILL) {
|
||||
rowI.power = (speedFrom - (speedStep * i)) * settings.value(QZSettings::ftp_run, QZSettings::default_ftp_run).toDouble();
|
||||
rowI.power = (speedFrom - (speedStep * i)) *
|
||||
settings.value(QZSettings::ftp_run, QZSettings::default_ftp_run).toDouble();
|
||||
} else {
|
||||
rowI.power = (speedFrom - (speedStep * i)) * settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble();
|
||||
rowI.power = (speedFrom - (speedStep * i)) *
|
||||
settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble();
|
||||
}
|
||||
}
|
||||
qDebug() << "TrainRow" << rowI.toString();
|
||||
list.append(rowI);
|
||||
if (insideRepeat) {
|
||||
repeatRows.append(rowI);
|
||||
} else {
|
||||
list.append(rowI);
|
||||
}
|
||||
}
|
||||
ramp = true;
|
||||
}
|
||||
|
||||
if(!ramp) {
|
||||
list.append(row);
|
||||
if (insideRepeat) {
|
||||
repeatRows.append(row);
|
||||
} else {
|
||||
list.append(row);
|
||||
}
|
||||
qDebug() << row.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,10 +45,11 @@ virtualtreadmill::virtualtreadmill(bluetoothdevice *t, bool noHeartService) {
|
||||
#ifndef IO_UNDER_QT
|
||||
bool ios_peloton_workaround =
|
||||
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
|
||||
bool garmin_bluetooth_compatibility = settings.value(QZSettings::garmin_bluetooth_compatibility, QZSettings::default_garmin_bluetooth_compatibility).toBool();
|
||||
if (ios_peloton_workaround) {
|
||||
qDebug() << "ios_zwift_workaround activated!";
|
||||
h = new lockscreen();
|
||||
h->virtualtreadmill_zwift_ios();
|
||||
h->virtualtreadmill_zwift_ios(garmin_bluetooth_compatibility);
|
||||
} else
|
||||
#endif
|
||||
#endif
|
||||
@@ -56,7 +57,11 @@ virtualtreadmill::virtualtreadmill(bluetoothdevice *t, bool noHeartService) {
|
||||
//! [Advertising Data]
|
||||
advertisingData.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral);
|
||||
advertisingData.setIncludePowerLevel(true);
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
advertisingData.setLocalName(QStringLiteral("KICKR RUN"));
|
||||
#else
|
||||
advertisingData.setLocalName(QStringLiteral("DomyosBridge"));
|
||||
#endif
|
||||
QList<QBluetoothUuid> services;
|
||||
|
||||
// Add Wahoo Run Service UUID
|
||||
@@ -230,6 +235,49 @@ virtualtreadmill::virtualtreadmill(bluetoothdevice *t, bool noHeartService) {
|
||||
serviceDataFTMS.addCharacteristic(charDataFIT2);
|
||||
}
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
qDebug() << "Raspberry workaround for sending metrics to the peloton app";
|
||||
QLowEnergyCharacteristicData charDataFIT;
|
||||
charDataFIT.setUuid((QBluetoothUuid::CharacteristicType)0x2A00);
|
||||
QByteArray valueFIT;
|
||||
valueFIT.append((char)'K'); // average speed, cadence and resistance level supported
|
||||
valueFIT.append((char)'I'); // heart rate and elapsed time
|
||||
valueFIT.append((char)'C');
|
||||
valueFIT.append((char)'K');
|
||||
valueFIT.append((char)'R'); // resistance and power target supported
|
||||
valueFIT.append((char)' '); // indoor simulation, wheel and spin down supported
|
||||
valueFIT.append((char)'R');
|
||||
valueFIT.append((char)'U');
|
||||
valueFIT.append((char)'N');
|
||||
valueFIT.append((char)0x00);
|
||||
charDataFIT.setValue(valueFIT);
|
||||
charDataFIT.setProperties(QLowEnergyCharacteristic::Read);
|
||||
|
||||
QLowEnergyCharacteristicData charDataFIT2;
|
||||
charDataFIT2.setUuid((QBluetoothUuid::CharacteristicType)0x2A01);
|
||||
QByteArray valueFIT2;
|
||||
valueFIT2.append((char)0x00);
|
||||
charDataFIT2.setValue(valueFIT2);
|
||||
charDataFIT2.setProperties(QLowEnergyCharacteristic::Read);
|
||||
|
||||
genericAccessServerData.setUuid((QBluetoothUuid::ServiceClassUuid)0x1800);
|
||||
genericAccessServerData.addCharacteristic(charDataFIT);
|
||||
genericAccessServerData.addCharacteristic(charDataFIT2);
|
||||
|
||||
QLowEnergyCharacteristicData charDataFIT3;
|
||||
charDataFIT3.setUuid((QBluetoothUuid::CharacteristicType)0x2A05);
|
||||
charDataFIT3.setProperties(QLowEnergyCharacteristic::Indicate);
|
||||
QByteArray descriptor33;
|
||||
descriptor33.append((char)0x02);
|
||||
descriptor33.append((char)0x00);
|
||||
const QLowEnergyDescriptorData clientConfig43(QBluetoothUuid::ClientCharacteristicConfiguration,
|
||||
descriptor33);
|
||||
charDataFIT3.addDescriptor(clientConfig43);
|
||||
|
||||
genericAttributeServiceData.setUuid((QBluetoothUuid::ServiceClassUuid)0x1801);
|
||||
genericAttributeServiceData.addCharacteristic(charDataFIT3);
|
||||
#endif
|
||||
|
||||
if (RSCEnable()) {
|
||||
QLowEnergyCharacteristicData charData;
|
||||
charData.setUuid(QBluetoothUuid::CharacteristicType::RSCFeature);
|
||||
@@ -252,6 +300,7 @@ virtualtreadmill::virtualtreadmill(bluetoothdevice *t, bool noHeartService) {
|
||||
QLowEnergyCharacteristicData charData3;
|
||||
charData3.setUuid(QBluetoothUuid::CharacteristicType::RSCMeasurement);
|
||||
charData3.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Notify);
|
||||
charData3.setValue(valueLocaltion);
|
||||
QByteArray descriptor;
|
||||
descriptor.append((char)0x01);
|
||||
descriptor.append((char)0x00);
|
||||
@@ -305,6 +354,11 @@ virtualtreadmill::virtualtreadmill(bluetoothdevice *t, bool noHeartService) {
|
||||
|
||||
serviceWahoo = leController->addService(serviceDataWahoo);
|
||||
QThread::msleep(100);
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
genericAccessServer = leController->addService(genericAccessServerData);
|
||||
genericAttributeService = leController->addService(genericAttributeServiceData);
|
||||
#endif
|
||||
|
||||
if (noHeartService == false) {
|
||||
serviceHR = leController->addService(serviceDataHR);
|
||||
@@ -322,14 +376,20 @@ virtualtreadmill::virtualtreadmill(bluetoothdevice *t, bool noHeartService) {
|
||||
settings.value(QZSettings::bluetooth_relaxed, QZSettings::default_bluetooth_relaxed).toBool();
|
||||
QLowEnergyAdvertisingParameters pars = QLowEnergyAdvertisingParameters();
|
||||
if (!bluetooth_relaxed) {
|
||||
#if !defined(Q_OS_LINUX) || defined(Q_OS_ANDROID)
|
||||
pars.setInterval(100, 100);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/BleAdvertiser",
|
||||
"startAdvertisingTreadmill",
|
||||
"(Landroid/content/Context;)V",
|
||||
QtAndroid::androidContext().object());
|
||||
|
||||
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
pars.setInterval(30, 50);
|
||||
leController->startAdvertising(pars, advertisingData);
|
||||
#else
|
||||
leController->startAdvertising(pars, advertisingData, advertisingData);
|
||||
#endif
|
||||
@@ -427,6 +487,11 @@ void virtualtreadmill::reconnect() {
|
||||
|
||||
serviceWahoo = leController->addService(serviceDataWahoo);
|
||||
QThread::msleep(100);
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
genericAccessServer = leController->addService(genericAccessServerData);
|
||||
genericAttributeService = leController->addService(genericAttributeServiceData);
|
||||
#endif
|
||||
|
||||
if (noHeartService == false) {
|
||||
serviceHR = leController->addService(serviceDataHR);
|
||||
|
||||
@@ -44,6 +44,11 @@ class virtualtreadmill : public virtualdevice {
|
||||
QLowEnergyService *serviceDIS = nullptr;
|
||||
QLowEnergyService *serviceWahoo = nullptr;
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
QLowEnergyService *genericAccessServer = nullptr;
|
||||
QLowEnergyService *genericAttributeService = nullptr;
|
||||
#endif
|
||||
|
||||
QLowEnergyAdvertisingData advertisingData;
|
||||
|
||||
QLowEnergyServiceData serviceDataFTMS;
|
||||
@@ -51,6 +56,11 @@ class virtualtreadmill : public virtualdevice {
|
||||
QLowEnergyServiceData serviceDataHR;
|
||||
QLowEnergyServiceData serviceDataDIS;
|
||||
QLowEnergyServiceData serviceDataWahoo;
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
QLowEnergyServiceData genericAccessServerData;
|
||||
QLowEnergyServiceData genericAttributeServiceData;
|
||||
#endif
|
||||
|
||||
QTimer treadmillTimer;
|
||||
bluetoothdevice *treadMill;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <QDebug>
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
#include <QDateTime>
|
||||
//#include "localKeyProvider.h"
|
||||
//#include "zapCrypto.h"
|
||||
#include "zapConstants.h"
|
||||
@@ -28,16 +29,23 @@ class AbstractZapDevice: public QObject {
|
||||
QByteArray REQUEST_START;
|
||||
QByteArray RESPONSE_START;
|
||||
|
||||
AbstractZapDevice() : autoRepeatTimer(new QTimer(this)) {
|
||||
AbstractZapDevice() {
|
||||
RIDE_ON = QByteArray::fromRawData("\x52\x69\x64\x65\x4F\x6E", 6); // "RideOn"
|
||||
REQUEST_START = QByteArray::fromRawData("\x00\x09", 2); // {0, 9}
|
||||
RESPONSE_START = QByteArray::fromRawData("\x01\x03", 2); // {1, 3}
|
||||
|
||||
// Setup auto-repeat
|
||||
autoRepeatTimer = new QTimer();
|
||||
autoRepeatTimer->setInterval(500);
|
||||
connect(autoRepeatTimer, &QTimer::timeout, this, &AbstractZapDevice::handleAutoRepeat);
|
||||
}
|
||||
|
||||
~AbstractZapDevice() {
|
||||
if (autoRepeatTimer) {
|
||||
autoRepeatTimer->stop();
|
||||
}
|
||||
}
|
||||
|
||||
int processCharacteristic(const QString& characteristicName, const QByteArray& bytes, ZWIFT_PLAY_TYPE zapType) {
|
||||
if (bytes.isEmpty()) return 0;
|
||||
|
||||
@@ -45,7 +53,7 @@ class AbstractZapDevice: public QObject {
|
||||
bool gears_volume_debouncing = settings.value(QZSettings::gears_volume_debouncing, QZSettings::default_gears_volume_debouncing).toBool();
|
||||
bool zwiftplay_swap = settings.value(QZSettings::zwiftplay_swap, QZSettings::default_zwiftplay_swap).toBool();
|
||||
|
||||
qDebug() << zapType << characteristicName << bytes.toHex() << zwiftplay_swap << gears_volume_debouncing << risingEdge;
|
||||
qDebug() << zapType << characteristicName << bytes.toHex() << zwiftplay_swap << gears_volume_debouncing << risingEdge << lastFrame;
|
||||
|
||||
#define DEBOUNCE (!gears_volume_debouncing || risingEdge <= 0)
|
||||
|
||||
@@ -68,6 +76,7 @@ class AbstractZapDevice: public QObject {
|
||||
#else
|
||||
switch(bytes[0]) {
|
||||
case 0x37:
|
||||
lastFrame = QDateTime::currentDateTime();
|
||||
if(bytes.length() == 5) {
|
||||
if(bytes[2] == 0) {
|
||||
if(DEBOUNCE) {
|
||||
@@ -107,6 +116,7 @@ class AbstractZapDevice: public QObject {
|
||||
}
|
||||
break;
|
||||
case 0x07: // zwift play
|
||||
lastFrame = QDateTime::currentDateTime();
|
||||
if(bytes.length() > 5 && bytes[bytes.length() - 5] == 0x40 && (
|
||||
(((uint8_t)bytes[bytes.length() - 4]) == 0xc7 && zapType == RIGHT) ||
|
||||
(((uint8_t)bytes[bytes.length() - 4]) == 0xc8 && zapType == LEFT)
|
||||
@@ -182,6 +192,7 @@ class AbstractZapDevice: public QObject {
|
||||
qDebug() << "ignoring this frame";
|
||||
return 1;
|
||||
case 0x23: // zwift ride
|
||||
lastFrame = QDateTime::currentDateTime();
|
||||
if(bytes.length() > 12 &&
|
||||
((((uint8_t)bytes[12]) == 0xc7 && zapType == RIGHT) ||
|
||||
(((uint8_t)bytes[12]) == 0xc8 && zapType == LEFT))
|
||||
@@ -318,11 +329,19 @@ class AbstractZapDevice: public QObject {
|
||||
private:
|
||||
QByteArray devicePublicKeyBytes;
|
||||
static volatile int8_t risingEdge;
|
||||
QTimer* autoRepeatTimer; // Timer for auto-repeat
|
||||
bool lastButtonPlus = false; // Track which button was last pressed
|
||||
static QTimer* autoRepeatTimer; // Static timer for auto-repeat
|
||||
static bool lastButtonPlus; // Static track of which button was last pressed
|
||||
static QDateTime lastFrame;
|
||||
|
||||
private slots:
|
||||
void handleAutoRepeat() {
|
||||
uint64_t delta = lastFrame.msecsTo(QDateTime::currentDateTime());
|
||||
qDebug() << "gear auto repeat" << lastButtonPlus << lastFrame << delta;
|
||||
if(delta > 400) {
|
||||
qDebug() << "stopping repeat timer";
|
||||
autoRepeatTimer->stop();
|
||||
return;
|
||||
}
|
||||
if(lastButtonPlus)
|
||||
emit plus();
|
||||
else
|
||||
|
||||
@@ -15,6 +15,9 @@ extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
|
||||
#endif
|
||||
|
||||
volatile int8_t AbstractZapDevice::risingEdge = 0;
|
||||
QTimer* AbstractZapDevice::autoRepeatTimer = nullptr;
|
||||
bool AbstractZapDevice::lastButtonPlus = false;
|
||||
QDateTime AbstractZapDevice::lastFrame = QDateTime::currentDateTime();
|
||||
|
||||
zwiftclickremote::zwiftclickremote(bluetoothdevice *parentDevice, AbstractZapDevice::ZWIFT_PLAY_TYPE typeZap) {
|
||||
#ifdef Q_OS_IOS
|
||||
@@ -31,10 +34,10 @@ zwiftclickremote::zwiftclickremote(bluetoothdevice *parentDevice, AbstractZapDev
|
||||
void zwiftclickremote::update() {
|
||||
if (initRequest && !initDone) {
|
||||
initRequest = false;
|
||||
initDone = true;
|
||||
QByteArray s = playDevice->buildHandshakeStart();
|
||||
qDebug() << s.length();
|
||||
writeCharacteristic(gattWrite1Service, &gattWrite1Characteristic, (uint8_t *) s.data(), s.length(), "handshakeStart");
|
||||
initDone = true;
|
||||
} else if(initDone) {
|
||||
countRxTimeout++;
|
||||
if(countRxTimeout == 10) {
|
||||
@@ -118,7 +121,8 @@ void zwiftclickremote::writeCharacteristic(QLowEnergyService *service, QLowEnerg
|
||||
qDebug() << QStringLiteral(" >> ") + writeBuffer->toHex(' ') + QStringLiteral(" // ") + info;
|
||||
}
|
||||
|
||||
loop.exec();
|
||||
if(wait_for_response) // without this, it crashes on ios after sometimes
|
||||
loop.exec();
|
||||
}
|
||||
|
||||
void zwiftclickremote::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
@@ -310,3 +314,10 @@ void zwiftclickremote::controllerStateChanged(QLowEnergyController::ControllerSt
|
||||
m_control->connectToDevice();
|
||||
}
|
||||
}
|
||||
|
||||
void zwiftclickremote::vibrate(uint8_t pattern) {
|
||||
if(!initDone) return;
|
||||
QByteArray s = QByteArray::fromHex("1212080A060802100018");
|
||||
s.append(pattern);
|
||||
writeCharacteristic(gattWrite1Service, &gattWrite1Characteristic, (uint8_t *) s.data(), s.length(), "vibrate", false, false);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ class zwiftclickremote : public bluetoothdevice {
|
||||
zwiftclickremote(bluetoothdevice *parentDevice, AbstractZapDevice::ZWIFT_PLAY_TYPE typeZap);
|
||||
bool connected() override;
|
||||
ZwiftPlayDevice* playDevice = new ZwiftPlayDevice();
|
||||
void vibrate(uint8_t pattern);
|
||||
AbstractZapDevice::ZWIFT_PLAY_TYPE typeZap = AbstractZapDevice::NONE;
|
||||
|
||||
private:
|
||||
QList<QLowEnergyService *> gattCommunicationChannelService;
|
||||
@@ -45,13 +47,12 @@ class zwiftclickremote : public bluetoothdevice {
|
||||
|
||||
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);
|
||||
bool wait_for_response = false);
|
||||
|
||||
bluetoothdevice *parentDevice = nullptr;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
AbstractZapDevice::ZWIFT_PLAY_TYPE typeZap = AbstractZapDevice::NONE;
|
||||
|
||||
QTimer *refresh;
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ DEFINE_DEVICE(CSCBike, "CSC Bike");
|
||||
DEFINE_DEVICE(Chronobike, "Chronobike");
|
||||
DEFINE_DEVICE(ComputrainerBike, "Computrainer Bike");
|
||||
DEFINE_DEVICE(Concept2SkiErg, "Concept2 Ski Erg");
|
||||
DEFINE_DEVICE(CyclopsPhantomBike, "Cyclops Phantom Bike");
|
||||
DEFINE_DEVICE(DeerRunTreadmill, "DeerRun Treadmill");
|
||||
DEFINE_DEVICE(DomyosBike, "Domyos Bike");
|
||||
DEFINE_DEVICE(DomyosElliptical, "Domyos Elliptical");
|
||||
@@ -107,6 +108,7 @@ DEFINE_DEVICE(OctaneElliptical, "Octane Elliptical");
|
||||
DEFINE_DEVICE(OctaneTreadmill, "Octane Treadmill");
|
||||
DEFINE_DEVICE(PafersBike, "Pafers Bike");
|
||||
DEFINE_DEVICE(PafersTreadmill, "Pafers Treadmill");
|
||||
DEFINE_DEVICE(PitpatBike, "Pitpat Bike");
|
||||
DEFINE_DEVICE(ProFormRower, "ProForm Rower");
|
||||
DEFINE_DEVICE(ProFormTreadmill, "ProForm Treadmill");
|
||||
DEFINE_DEVICE(ProFormWifiBike, "ProForm Wifi Bike");
|
||||
|
||||
@@ -55,6 +55,7 @@ public:
|
||||
DEFINE_DEVICE(Chronobike, "Chronobike");
|
||||
DEFINE_DEVICE(ComputrainerBike, "Computrainer Bike");
|
||||
DEFINE_DEVICE(Concept2SkiErg, "Concept2 Ski Erg");
|
||||
DEFINE_DEVICE(CyclopsPhantomBike, "Cyclops Phantom Bike");
|
||||
DEFINE_DEVICE(DeerRunTreadmill, "DeerRun Treadmill");
|
||||
DEFINE_DEVICE(DomyosBike, "Domyos Bike");
|
||||
DEFINE_DEVICE(DomyosElliptical, "Domyos Elliptical");
|
||||
@@ -112,6 +113,7 @@ public:
|
||||
DEFINE_DEVICE(OctaneTreadmill, "Octane Treadmill");
|
||||
DEFINE_DEVICE(PafersBike, "Pafers Bike");
|
||||
DEFINE_DEVICE(PafersTreadmill, "Pafers Treadmill");
|
||||
DEFINE_DEVICE(PitpatBike, "Pitpat Bike");
|
||||
DEFINE_DEVICE(ProFormRower, "ProForm Rower");
|
||||
DEFINE_DEVICE(ProFormTreadmill, "ProForm Treadmill");
|
||||
DEFINE_DEVICE(ProFormWifiBike, "ProForm Wifi Bike");
|
||||
|
||||
@@ -218,10 +218,15 @@ void DeviceTestDataIndex::Initialize() {
|
||||
}
|
||||
});
|
||||
|
||||
// Cyclops Phantom Bike
|
||||
RegisterNewDeviceTestData(DeviceIndex::CyclopsPhantomBike)
|
||||
->expectDevice<cycleopsphantombike>()
|
||||
->acceptDeviceName("INDOORCYCLE", DeviceNameComparison::StartsWithIgnoreCase);
|
||||
|
||||
// DeerRun Treadmill
|
||||
RegisterNewDeviceTestData(DeviceIndex::DeerRunTreadmill)
|
||||
->expectDevice<deerruntreadmill>()
|
||||
->acceptDeviceName("PITPAT", DeviceNameComparison::StartsWithIgnoreCase);
|
||||
->acceptDeviceName("PITPAT-T", DeviceNameComparison::StartsWithIgnoreCase);
|
||||
|
||||
// Domyos bike
|
||||
RegisterNewDeviceTestData(DeviceIndex::DomyosBike)
|
||||
@@ -923,6 +928,10 @@ void DeviceTestDataIndex::Initialize() {
|
||||
}
|
||||
});
|
||||
|
||||
// Pitpat Bike
|
||||
RegisterNewDeviceTestData(DeviceIndex::PitpatBike)
|
||||
->expectDevice<pitpatbike>()
|
||||
->acceptDeviceName("PITPAT-S", DeviceNameComparison::StartsWithIgnoreCase);
|
||||
|
||||
// Proform Bike
|
||||
RegisterNewDeviceTestData(DeviceIndex::ProformBike)
|
||||
|
||||
Reference in New Issue
Block a user