Compare commits

..

4 Commits

Author SHA1 Message Date
Roberto Viola
53a41b35e2 build fix 2022-12-16 11:57:51 +01:00
Roberto Viola
1f178ff3fa using lastrunstate in order to set speed and inclination 2022-12-16 11:44:09 +01:00
Sunguk Lee
fd3cc25732 Initial send device state signal of kingsmith r2 to homeform 2022-12-16 11:12:02 +01:00
Sunguk Lee
64e9052f25 Initial implement start/stop control of KingSmith R2 when press start/pause/stop buttons
This patch is not perfect.
If a user doesn't do workout, treadmill will stop automatically,
but an UI of the application couldn't update anything.

Also in the timing issue, sometimes changeing `ControlMode` or `runState` command
just return `Error` packet.
I think, sending message too early (before finishing handshake) could
make the problem.

Fully implement needs passing the signal to homeform or MainApp.
And also it needs more complex state machine for handling buttons.
(eg; press the start button & press the pause button before
finish start treadmill action, pause action never call)
2022-12-16 11:11:40 +01:00
283 changed files with 2753 additions and 23708 deletions

View File

@@ -13,8 +13,8 @@ on:
branches: [ master, github-workflow-playground ]
pull_request:
branches: [ master ]
# schedule:
# - cron: "0 */12 * * *"
schedule:
- cron: "0 */12 * * *"
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
@@ -38,14 +38,6 @@ jobs:
path: "src/qmdnsengine/"
ref: "zwift"
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: google/googletest
path: "tst/googletest/"
ref: "release-1.12.1"
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
@@ -58,12 +50,12 @@ jobs:
with:
install: mingw-w64-x86_64-toolchain
msystem: mingw64
release: false
release: false
- name: Setup cmake
uses: jwlawson/actions-setup-cmake@v1.9
with:
cmake-version: '3.20.x'
cmake-version: '3.20.x'
- name: Install Qt
uses: jurplel/install-qt-action@v2
@@ -74,19 +66,18 @@ jobs:
target: "desktop"
arch: win64_mingw81
dir: "${{github.workspace}}/qt/"
install-deps: "true"
install-deps: "true"
- name: Build
run: |
qmake
run: |
cd src
qmake
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
cd ..
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
make -j8
cd src/debug
cd debug
mkdir output
mkdir appx
cp qdomyos-zwift.exe output/
@@ -94,100 +85,21 @@ jobs:
windeployqt --qmldir ../../ qdomyos-zwift.exe
cp "${{github.workspace}}/qt/Qt/5.15.2/mingw81_64/bin/libwinpthread-1.dll" .
cp "${{github.workspace}}/qt/Qt/5.15.2/mingw81_64/bin/libgcc_s_seh-1.dll" .
cp "${{github.workspace}}/qt/Qt/5.15.2/mingw81_64/bin/libstdc++-6.dll" .
cp ../../../icons/iOS/iTunesArtwork@2x.png .
cp "${{github.workspace}}/qt/Qt/5.15.2/mingw81_64/bin/libstdc++-6.dll" .
cp ../../../icons/iOS/iTunesArtwork@2x.png .
cp ../../AppxManifest.xml .
mkdir adb
cp ../../adb/* adb/
cd ..
cd appx
#../../MSIX-Toolkit/WindowsSDK/10/10.0.20348.0/x64/makeappx.exe pack /d ../output/ /p qz
- name: Archive windows binary
uses: actions/upload-artifact@v2
with:
name: windows-binary
path: src/debug/output
# window-steam-build:
# runs-on: windows-latest
#
# steps:
# - uses: actions/checkout@v2
# - name: Checkout submodule repo
# uses: actions/checkout@v2
# with:
# repository: bluetiger9/SmtpClient-for-Qt
# path: "src/smtpclient/"
# ref: 3fa4a0fe5797070339422cf18b5e9ed8dcb91f9c
#
# - uses: actions/checkout@v2
# - name: Checkout submodule repo
# uses: actions/checkout@v2
# with:
# repository: cagnulein/qmdnsengine
# path: "src/qmdnsengine/"
# ref: "zwift"
#
# - uses: msys2/setup-msys2@v2
# with:
# install: mingw-w64-x86_64-toolchain
# msystem: mingw64
# release: false
#
# - name: Setup cmake
# uses: jwlawson/actions-setup-cmake@v1.9
# with:
# cmake-version: '3.20.x'
#
# - name: Install Qt
# uses: jurplel/install-qt-action@v2
# with:
# version: '5.15.2'
# host: 'windows'
# modules: 'qtnetworkauth qtcharts'
# target: "desktop"
# arch: win64_mingw81
# dir: "${{github.workspace}}/qt/"
# install-deps: "true"
#
# - name: Build
# run: |
# qmake
# cd src
# echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
# echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
# echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
# echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
# echo "#define STEAM_STORE" >> secret.h
# cd ..
# make -j8
# cd src/debug
# mkdir output
# mkdir appx
# cp qdomyos-zwift.exe output/
# cd output
# windeployqt --qmldir ../../ qdomyos-zwift.exe
# cp "${{github.workspace}}/qt/Qt/5.15.2/mingw81_64/bin/libwinpthread-1.dll" .
# cp "${{github.workspace}}/qt/Qt/5.15.2/mingw81_64/bin/libgcc_s_seh-1.dll" .
# cp "${{github.workspace}}/qt/Qt/5.15.2/mingw81_64/bin/libstdc++-6.dll" .
#
# - uses: game-ci/steam-deploy@v1
# with:
# username: ${{ secrets.STEAM_USERNAME }}
# password: ${{ secrets.STEAM_PASSWORD }}
# configVdf: ${{ secrets.STEAM_CONFIG_VDF}}
# ssfnFileName: ${{ secrets.STEAM_SSFN_FILE_NAME }}
# ssfnFileContents: ${{ secrets.STEAM_SSFN_FILE_CONTENTS }}
# appId: 2267200
# buildDescription: 2.12
# rootPath: src/debug/output
# depot1Path: ./
# #depot2Path: StandaloneLinux64
# releaseBranch: prerelease
# This workflow contains a single job called "build"
linux-x86-build:
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
@@ -210,7 +122,7 @@ jobs:
- name: Xvfb install and run
run: |
sudo apt-get install -y xvfb
Xvfb -ac ${{ env.DISPLAY }} -screen 0 1280x780x24 &
Xvfb -ac ${{ env.DISPLAY }} -screen 0 1280x780x24 &
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
@@ -219,7 +131,7 @@ jobs:
with:
repository: bluetiger9/SmtpClient-for-Qt
path: "src/smtpclient/"
ref: 3fa4a0fe5797070339422cf18b5e9ed8dcb91f9c
ref: 3fa4a0fe5797070339422cf18b5e9ed8dcb91f9c
- uses: actions/checkout@v2
- name: Checkout submodule repo
@@ -229,42 +141,24 @@ jobs:
path: "src/qmdnsengine/"
ref: "zwift"
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: google/googletest
path: "tst/googletest/"
ref: "release-1.12.1"
- name: Install packages required to run QZ inside workflow
run: sudo apt update -y && sudo apt-get install -y qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 libqt5networkauth5-dev libqt5websockets5* libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev
- name: Install Qt
uses: jurplel/install-qt-action@v2
with:
version: '5.15.2'
host: 'linux'
modules: 'qtnetworkauth qtcharts'
- name: Compile Linux Desktop
run: qmake; make -j8
run: cd src; qmake; make -j8
- name: Archive linux-desktop binary
uses: actions/upload-artifact@v2
with:
name: linux-desktop-binary
path: src/qdomyos-zwift
- name: Test
run: cd tst; GTEST_OUTPUT=xml:test-results/ GTEST_COLOR=1 ./qdomyos-zwift-tests; cd ..
- name: Upload test results
uses: actions/upload-artifact@v2
if: failure()
with:
name: test_results_xml
path: tst/test-results/**/*.xml
path: src/qdomyos-zwift
# - name: Test Peloton API
# if: github.event_name == 'push' || github.event_name == 'schedule'
@@ -305,7 +199,7 @@ jobs:
# modules: 'qtcharts debug_info'
# dir: '${{ github.workspace }}/output/android/'
# cached: ${{ steps.cache-qt-android.outputs.cache-hit }}
# - name: Compile Android
# run: cd src; qmake; make -j4
@@ -317,124 +211,6 @@ jobs:
# target: 'desktop'
# modules: 'qtcharts debug_info'
# dir: '${{ github.workspace }}/output/macos/'
# - name: Compile MacOS
# run: cd src; qmake; make -j4
# This workflow contains a single job called "build"
android-build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# - name: Cache Qt Linux Desktop
# id: cache-qt-linux-desktop
# uses: actions/cache@v1
# with:
# path: '${{ github.workspace }}/output/linux-desktop/'
# key: ${{ runner.os }}-QtCache-Linux-Desktop
# - name: Cache Qt Linux Android
# id: cache-qt-android
# uses: actions/cache@v1
# with:
# path: '${{ github.workspace }}/output/android/'
# key: ${{ runner.os }}-QtCache-Android
- name: Xvfb install and run
run: |
sudo apt-get install -y xvfb
Xvfb -ac ${{ env.DISPLAY }} -screen 0 1280x780x24 &
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: bluetiger9/SmtpClient-for-Qt
path: "src/smtpclient/"
ref: 3fa4a0fe5797070339422cf18b5e9ed8dcb91f9c
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: cagnulein/qmdnsengine
path: "src/qmdnsengine/"
ref: "zwift"
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: google/googletest
path: "tst/googletest/"
ref: "release-1.12.1"
- name: Install packages required to run QZ inside workflow
run: sudo apt update -y && sudo apt-get install -y qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 libqt5networkauth5-dev libqt5websockets5* libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev
# - name: Test Peloton API
# if: github.event_name == 'push' || github.event_name == 'schedule'
# run: cd /home/runner/work/qdomyos-zwift/qdomyos-zwift/src/; ./qdomyos-zwift -test-peloton -peloton-username ${{ secrets.peloton_username }} -peloton-password ${{ secrets.peloton_password }}
# timeout-minutes: 2
# - name: Test Home Fitness Buddy API
# run: cd /home/runner/work/qdomyos-zwift/qdomyos-zwift/src/; ./qdomyos-zwift -test-hfb
# timeout-minutes: 2
# - uses: actions/checkout@v2
# with:
# repository: nttld/setup-ndk
# path: setup-ndk
# The packages.json in nttld/setup-ndk has already been updated,
# https://github.com/nttld/setup-ndk/commit/831db5b02a0f0cab80614619efe461a3dcc140e6
# but `dist/*` has not been rebuilt yet. Build it.
# https://github.com/nttld/setup-ndk/tree/main/dist
# - name: Locally rebuilt setup-ndk
# run: |
# npm -prefix ./setup-ndk install
# npm -prefix ./setup-ndk run all
# Install using locally rebuilt setup-ndk
# - name: Setup Android NDK r21d
# uses: ./setup-ndk
#- uses: nttld/setup-ndk@v1
# with:
# ndk-version: r21d
# waiting github.com/jurplel/install-qt-action/issues/63
- name: Install Qt Android
uses: jurplel/install-qt-action@v2
with:
version: '5.15.2'
host: 'linux'
target: 'android'
arch: 'android'
modules: 'qtcharts qtnetworkauth'
dir: '${{ github.workspace }}/output/android/'
- name: Install Java
uses: actions/setup-java@v3
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '11'
- name: Set Android NDK 21 && build
run: |
# Install NDK 21 after GitHub update
# https://github.com/actions/virtual-environments/issues/5595
ANDROID_ROOT="/usr/local/lib/android"
ANDROID_SDK_ROOT="${ANDROID_ROOT}/sdk"
SDKMANAGER="${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager"
echo "y" | $SDKMANAGER "ndk;21.4.7075529"
export ANDROID_NDK="${ANDROID_SDK_ROOT}/ndk-bundle"
export ANDROID_NDK_ROOT="${ANDROID_NDK}"
ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK
rm -rf /usr/local/lib/android/sdk/ndk/25.1.8937393
qmake -spec android-clang 'ANDROID_ABIS=armeabi-v7a arm64-v8a x86 x86_64' 'ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk/21.4.7075529' && make -j4 && make INSTALL_ROOT=${{ github.workspace }}/output/android/ install
- name: Build APK (not usable for production due to unpatched QT library)
run: cd src; androiddeployqt --input android-qdomyos-zwift-deployment-settings.json --output ${{ github.workspace }}/output/android/ --android-platform android-31 --gradle --aab

3
.gitignore vendored
View File

@@ -40,12 +40,9 @@ template-examples/train-program-saver/.eslintrc.js
template-examples/train-program-saver/.jshintrc
template-examples/train-program-saver/debug.js
google_test/*
# Qt-es
*.pro.user
*build-*
!build-qdomyos-zwift-Qt_*_for_iOS-Debug # Needed for Apple Watch
src/inner_templates/googlemaps/cesium-key.js
*.autosave
.vscode/settings.json

4
.gitmodules vendored
View File

@@ -9,7 +9,3 @@
path = src/qmdnsengine
url = https://github.com/cagnulein/qmdnsengine.git
branch = zwift
[submodule "tst/googletest"]
path = tst/googletest
url = https://github.com/google/googletest.git
branch = tags/release-1.12.1

View File

@@ -249,8 +249,6 @@
87440FBF2640292900E4DC0B /* moc_fitplusbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87440FBE2640292900E4DC0B /* moc_fitplusbike.cpp */; };
87473A9627ECA9EE00C203F5 /* proformrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87473A9527ECA9EE00C203F5 /* proformrower.cpp */; };
87473A9827ECAA0500C203F5 /* moc_proformrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87473A9727ECAA0500C203F5 /* moc_proformrower.cpp */; };
874D272029AFA11F0007C079 /* apexbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 874D271E29AFA11F0007C079 /* apexbike.cpp */; };
874D272229AFA13B0007C079 /* moc_apexbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 874D272129AFA13B0007C079 /* moc_apexbike.cpp */; };
8752B4CD27F43D9200E2EC6C /* qz.storekit in Copy Bundle Resources */ = {isa = PBXBuildFile; fileRef = 8752B4CC27F43D9200E2EC6C /* qz.storekit */; };
87540FAD2848FD70005E0D44 /* libqtexttospeech_speechios.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 87540FAC2848FD70005E0D44 /* libqtexttospeech_speechios.a */; };
8754D24C27F786F0003D7054 /* virtualrower.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8754D24B27F786F0003D7054 /* virtualrower.swift */; };
@@ -340,8 +338,6 @@
879F740F2893D592009A64C8 /* libqtmedia_audioengine.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 879F740E2893D591009A64C8 /* libqtmedia_audioengine.a */; };
879F74112893D5B8009A64C8 /* libqavfcamera.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 879F74102893D5B7009A64C8 /* libqavfcamera.a */; };
879F74152893D732009A64C8 /* CoreMedia.framework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 879F74142893D732009A64C8 /* CoreMedia.framework */; };
87A0771029B641D600A368BF /* wahookickrheadwind.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A0770F29B641D500A368BF /* wahookickrheadwind.cpp */; };
87A0771229B6420200A368BF /* moc_wahookickrheadwind.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A0771129B6420200A368BF /* moc_wahookickrheadwind.cpp */; };
87A0C4BB262329A600121A76 /* npecablebike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A0C4B7262329A600121A76 /* npecablebike.cpp */; };
87A0C4BC262329A600121A76 /* cscbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A0C4B9262329A600121A76 /* cscbike.cpp */; };
87A0C4BF262329B500121A76 /* moc_cscbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A0C4BD262329B500121A76 /* moc_cscbike.cpp */; };
@@ -356,8 +352,6 @@
87ADD2BB27634C1500B7A0AB /* technogymmyruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87ADD2B927634C1400B7A0AB /* technogymmyruntreadmill.cpp */; };
87ADD2BD27634C2100B7A0AB /* moc_technogymmyruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87ADD2BC27634C2100B7A0AB /* moc_technogymmyruntreadmill.cpp */; };
87AE0CB227760DCB00E547E9 /* virtualtreadmill_zwift.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87AE0CB127760DCB00E547E9 /* virtualtreadmill_zwift.swift */; };
87B187BB29B8C552007EEF9D /* ziprotreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B187B929B8C552007EEF9D /* ziprotreadmill.cpp */; };
87B187BD29B8C577007EEF9D /* moc_ziprotreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B187BC29B8C577007EEF9D /* moc_ziprotreadmill.cpp */; };
87B617EC25F25FED0094A1CB /* screencapture.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B617E725F25FEC0094A1CB /* screencapture.cpp */; };
87B617ED25F25FED0094A1CB /* fitshowtreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B617EA25F25FED0094A1CB /* fitshowtreadmill.cpp */; };
87B617EE25F25FED0094A1CB /* snodebike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B617EB25F25FED0094A1CB /* snodebike.cpp */; };
@@ -368,8 +362,6 @@
87BB1776269E987100F46A1C /* libQt5HttpServer.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 87BB1775269E987000F46A1C /* libQt5HttpServer.a */; };
87BE6FDC272D2A3100C35795 /* horizongr7bike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87BE6FDA272D2A3100C35795 /* horizongr7bike.cpp */; };
87BE6FDE272D2A3E00C35795 /* moc_horizongr7bike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87BE6FDD272D2A3E00C35795 /* moc_horizongr7bike.cpp */; };
87BF116D298E28CA00B5B6E7 /* pelotonbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87BF116C298E28CA00B5B6E7 /* pelotonbike.cpp */; };
87BF116F298E28EC00B5B6E7 /* moc_pelotonbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87BF116E298E28EC00B5B6E7 /* moc_pelotonbike.cpp */; };
87C481FA26DFA7C3006211AD /* eliterizer.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87C481F926DFA7C3006211AD /* eliterizer.cpp */; };
87C481FC26DFA7D1006211AD /* moc_eliterizer.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87C481FB26DFA7D1006211AD /* moc_eliterizer.cpp */; };
87C5F0B526285E5F0067A1B5 /* mimemessage.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87C5F09726285E5A0067A1B5 /* mimemessage.cpp */; };
@@ -981,9 +973,6 @@
87473A9427ECA9EE00C203F5 /* proformrower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformrower.h; path = ../src/proformrower.h; sourceTree = "<group>"; };
87473A9527ECA9EE00C203F5 /* proformrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = proformrower.cpp; path = ../src/proformrower.cpp; sourceTree = "<group>"; };
87473A9727ECAA0500C203F5 /* moc_proformrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_proformrower.cpp; sourceTree = "<group>"; };
874D271E29AFA11F0007C079 /* apexbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = apexbike.cpp; path = ../src/apexbike.cpp; sourceTree = "<group>"; };
874D271F29AFA11F0007C079 /* apexbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = apexbike.h; path = ../src/apexbike.h; sourceTree = "<group>"; };
874D272129AFA13B0007C079 /* moc_apexbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_apexbike.cpp; sourceTree = "<group>"; };
8752B4CC27F43D9200E2EC6C /* qz.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = qz.storekit; sourceTree = "<group>"; };
87540FAC2848FD70005E0D44 /* libqtexttospeech_speechios.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtexttospeech_speechios.a; path = ../../Qt/5.15.2/ios/plugins/texttospeech/libqtexttospeech_speechios.a; sourceTree = "<group>"; };
8754D24B27F786F0003D7054 /* virtualrower.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = virtualrower.swift; path = ../src/ios/virtualrower.swift; sourceTree = "<group>"; };
@@ -1117,9 +1106,6 @@
879F74102893D5B7009A64C8 /* libqavfcamera.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqavfcamera.a; path = ../../Qt/5.15.2/ios/plugins/mediaservice/libqavfcamera.a; sourceTree = "<group>"; };
879F74122893D705009A64C8 /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; };
879F74142893D732009A64C8 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
87A0770E29B641D500A368BF /* wahookickrheadwind.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wahookickrheadwind.h; path = ../src/wahookickrheadwind.h; sourceTree = "<group>"; };
87A0770F29B641D500A368BF /* wahookickrheadwind.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = wahookickrheadwind.cpp; path = ../src/wahookickrheadwind.cpp; sourceTree = "<group>"; };
87A0771129B6420200A368BF /* moc_wahookickrheadwind.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_wahookickrheadwind.cpp; sourceTree = "<group>"; };
87A0C4B7262329A600121A76 /* npecablebike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = npecablebike.cpp; path = ../src/npecablebike.cpp; sourceTree = "<group>"; };
87A0C4B8262329A600121A76 /* cscbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = cscbike.h; path = ../src/cscbike.h; sourceTree = "<group>"; };
87A0C4B9262329A600121A76 /* cscbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = cscbike.cpp; path = ../src/cscbike.cpp; sourceTree = "<group>"; };
@@ -1144,9 +1130,6 @@
87ADD2BA27634C1400B7A0AB /* technogymmyruntreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = technogymmyruntreadmill.h; path = ../src/technogymmyruntreadmill.h; sourceTree = "<group>"; };
87ADD2BC27634C2100B7A0AB /* moc_technogymmyruntreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_technogymmyruntreadmill.cpp; sourceTree = "<group>"; };
87AE0CB127760DCB00E547E9 /* virtualtreadmill_zwift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = virtualtreadmill_zwift.swift; path = ../src/ios/virtualtreadmill_zwift.swift; sourceTree = "<group>"; };
87B187B929B8C552007EEF9D /* ziprotreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ziprotreadmill.cpp; path = ../src/ziprotreadmill.cpp; sourceTree = "<group>"; };
87B187BA29B8C552007EEF9D /* ziprotreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ziprotreadmill.h; path = ../src/ziprotreadmill.h; sourceTree = "<group>"; };
87B187BC29B8C577007EEF9D /* moc_ziprotreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_ziprotreadmill.cpp; sourceTree = "<group>"; };
87B617E625F25FEC0094A1CB /* fitshowtreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fitshowtreadmill.h; path = ../src/fitshowtreadmill.h; sourceTree = "<group>"; };
87B617E725F25FEC0094A1CB /* screencapture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = screencapture.cpp; path = ../src/screencapture.cpp; sourceTree = "<group>"; };
87B617E825F25FEC0094A1CB /* screencapture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = screencapture.h; path = ../src/screencapture.h; sourceTree = "<group>"; };
@@ -1161,9 +1144,6 @@
87BE6FDA272D2A3100C35795 /* horizongr7bike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = horizongr7bike.cpp; path = ../src/horizongr7bike.cpp; sourceTree = "<group>"; };
87BE6FDB272D2A3100C35795 /* horizongr7bike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = horizongr7bike.h; path = ../src/horizongr7bike.h; sourceTree = "<group>"; };
87BE6FDD272D2A3E00C35795 /* moc_horizongr7bike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_horizongr7bike.cpp; sourceTree = "<group>"; };
87BF116B298E28CA00B5B6E7 /* pelotonbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = pelotonbike.h; path = ../src/pelotonbike.h; sourceTree = "<group>"; };
87BF116C298E28CA00B5B6E7 /* pelotonbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pelotonbike.cpp; path = ../src/pelotonbike.cpp; sourceTree = "<group>"; };
87BF116E298E28EC00B5B6E7 /* moc_pelotonbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_pelotonbike.cpp; sourceTree = "<group>"; };
87C481F826DFA7C3006211AD /* eliterizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = eliterizer.h; path = ../src/eliterizer.h; sourceTree = "<group>"; };
87C481F926DFA7C3006211AD /* eliterizer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = eliterizer.cpp; path = ../src/eliterizer.cpp; sourceTree = "<group>"; };
87C481FB26DFA7D1006211AD /* moc_eliterizer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_eliterizer.cpp; sourceTree = "<group>"; };
@@ -1674,10 +1654,6 @@
25B08E2869634E9BCBA333A2 /* Generated Sources */ = {
isa = PBXGroup;
children = (
87B187BC29B8C577007EEF9D /* moc_ziprotreadmill.cpp */,
87A0771129B6420200A368BF /* moc_wahookickrheadwind.cpp */,
874D272129AFA13B0007C079 /* moc_apexbike.cpp */,
87BF116E298E28EC00B5B6E7 /* moc_pelotonbike.cpp */,
87CF516A293C87AF00A7CABC /* moc_characteristicwriteprocessore005.cpp */,
87E2F85E291ED315002BDC65 /* moc_lifefitnesstreadmill.cpp */,
87F02E4129178545000DB52C /* moc_octaneelliptical.cpp */,
@@ -1835,14 +1811,6 @@
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
isa = PBXGroup;
children = (
87B187B929B8C552007EEF9D /* ziprotreadmill.cpp */,
87B187BA29B8C552007EEF9D /* ziprotreadmill.h */,
87A0770F29B641D500A368BF /* wahookickrheadwind.cpp */,
87A0770E29B641D500A368BF /* wahookickrheadwind.h */,
874D271E29AFA11F0007C079 /* apexbike.cpp */,
874D271F29AFA11F0007C079 /* apexbike.h */,
87BF116C298E28CA00B5B6E7 /* pelotonbike.cpp */,
87BF116B298E28CA00B5B6E7 /* pelotonbike.h */,
8767EF1D29448D6700810C0F /* characteristicwriteprocessor.cpp */,
87CF5167293C879700A7CABC /* characteristicwriteprocessore005.cpp */,
87CF5168293C879700A7CABC /* characteristicwriteprocessore005.h */,
@@ -2897,7 +2865,6 @@
87FFA13927BBE40A00924E4E /* moc_solebike.cpp in Compile Sources */,
F1F4043967BC815770C8BEEA /* domyostreadmill.cpp in Compile Sources */,
87C5F0BF26285E5F0067A1B5 /* smtpclient.cpp in Compile Sources */,
87BF116F298E28EC00B5B6E7 /* moc_pelotonbike.cpp in Compile Sources */,
873CD20827EF8D8A000131BC /* inappstore.cpp in Compile Sources */,
87FA11AB27C5ECD1008AC5D1 /* ultrasportbike.cpp in Compile Sources */,
87C5F0D826285E7E0067A1B5 /* moc_quotedprintable.cpp in Compile Sources */,
@@ -2965,8 +2932,6 @@
873824B627E64707004F1B46 /* moc_hostname_p.cpp in Compile Sources */,
873824EA27E647A8004F1B46 /* browser.cpp in Compile Sources */,
87E34C2B2886F95400CEDE4B /* octanetreadmill.cpp in Compile Sources */,
87B187BB29B8C552007EEF9D /* ziprotreadmill.cpp in Compile Sources */,
87BF116D298E28CA00B5B6E7 /* pelotonbike.cpp in Compile Sources */,
87DAE16A26E9FF5000B0527E /* moc_kingsmithr2treadmill.cpp in Compile Sources */,
87D91F9C2800B9B90026D43C /* moc_proformwifibike.cpp in Compile Sources */,
87C5F0D226285E7E0067A1B5 /* moc_mimemultipart.cpp in Compile Sources */,
@@ -3187,11 +3152,8 @@
87D269A025F535200076AA48 /* skandikawiribike.cpp in Compile Sources */,
8738249427E646E3004F1B46 /* characteristicnotifier2a5b.cpp in Compile Sources */,
8768D1FB285081FE00F58E3A /* nordictrackifitadbtreadmill.cpp in Compile Sources */,
87B187BD29B8C577007EEF9D /* moc_ziprotreadmill.cpp in Compile Sources */,
877FBA2B276E684E00F6C0C9 /* moc_bowflextreadmill.cpp in Compile Sources */,
874D272229AFA13B0007C079 /* moc_apexbike.cpp in Compile Sources */,
8738248127E646C1004F1B46 /* characteristicnotifier2ad2.cpp in Compile Sources */,
87A0771029B641D600A368BF /* wahookickrheadwind.cpp in Compile Sources */,
8791A8AB25C861BD003B50B2 /* inspirebike.cpp in Compile Sources */,
876BFC9D27BE35C5001D7645 /* bowflext216treadmill.cpp in Compile Sources */,
871235C126B297720012D0F2 /* moc_kingsmithr1protreadmill.cpp in Compile Sources */,
@@ -3206,7 +3168,6 @@
87A0C4C0262329B500121A76 /* moc_npecablebike.cpp in Compile Sources */,
87DAE16B26E9FF5000B0527E /* moc_solef80treadmill.cpp in Compile Sources */,
8762D50F2601F7EA00F6F049 /* M3iNS.mm in Compile Sources */,
874D272029AFA11F0007C079 /* apexbike.cpp in Compile Sources */,
8798C8872733E103003148B3 /* strydrunpowersensor.cpp in Compile Sources */,
87C5F0B626285E5F0067A1B5 /* quotedprintable.cpp in Compile Sources */,
87310B23266FBB78008BA0D6 /* moc_smartrowrower.cpp in Compile Sources */,
@@ -3219,7 +3180,6 @@
74C43649C9C4E2E5F9378019 /* moc_domyosbike.cpp in Compile Sources */,
87E0761D277A081A00FDA0F9 /* technogymmyruntreadmillrfcomm.cpp in Compile Sources */,
873824B327E64707004F1B46 /* moc_dirconprocessor.cpp in Compile Sources */,
87A0771229B6420200A368BF /* moc_wahookickrheadwind.cpp in Compile Sources */,
87EB918827EE5FE7002535E1 /* moc_inappstoreqmltype.cpp in Compile Sources */,
87083D9626678EFA0072410D /* zwiftworkout.cpp in Compile Sources */,
87C5F0B826285E5F0067A1B5 /* stagesbike.cpp in Compile Sources */,
@@ -3576,7 +3536,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 514;
CURRENT_PROJECT_VERSION = 2.12.10;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
HEADER_SEARCH_PATHS = (
@@ -3651,7 +3611,7 @@
/Users/cagnulein/Qt/5.15.2/ios/plugins/playlistformats,
/Users/cagnulein/Qt/5.15.2/ios/plugins/audio,
);
MARKETING_VERSION = 2.13;
MARKETING_VERSION = 2.12;
OTHER_CFLAGS = (
"-pipe",
"-g",
@@ -3744,7 +3704,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 514;
CURRENT_PROJECT_VERSION = 2.12.10;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -3821,7 +3781,7 @@
/Users/cagnulein/Qt/5.15.2/ios/plugins/playlistformats,
/Users/cagnulein/Qt/5.15.2/ios/plugins/audio,
);
MARKETING_VERSION = 2.13;
MARKETING_VERSION = 2.12;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = (
"-pipe",
@@ -3948,7 +3908,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 514;
CURRENT_PROJECT_VERSION = 2.12.10;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -3969,7 +3929,7 @@
IBSC_MODULE = watchkit_Extension;
INFOPLIST_FILE = watchkit/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
MARKETING_VERSION = 2.13;
MARKETING_VERSION = 2.12;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
@@ -4040,7 +4000,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 514;
CURRENT_PROJECT_VERSION = 2.12.10;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -4057,7 +4017,7 @@
IBSC_MODULE = watchkit_Extension;
INFOPLIST_FILE = watchkit/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
MARKETING_VERSION = 2.13;
MARKETING_VERSION = 2.12;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
@@ -4127,7 +4087,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 514;
CURRENT_PROJECT_VERSION = 2.12.10;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -4168,7 +4128,7 @@
);
INFOPLIST_FILE = "watchkit Extension/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 2.13;
MARKETING_VERSION = 2.12;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
@@ -4237,7 +4197,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 514;
CURRENT_PROJECT_VERSION = 2.12.10;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
@@ -4274,7 +4234,7 @@
);
INFOPLIST_FILE = "watchkit Extension/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 2.13;
MARKETING_VERSION = 2.12;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";

View File

@@ -1,14 +0,0 @@
QT += gui bluetooth widgets xml positioning quick networkauth websockets texttospeech location multimedia
QTPLUGIN += qavfmediaplayer
QT+= charts
unix:android: QT += androidextras gui-private
android: include(android_openssl/openssl.pri)
INCLUDEPATH += $$PWD/src/qmdnsengine/src/include
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/src/android
ANDROID_ABIS = armeabi-v7a arm64-v8a x86 x86_64

View File

@@ -4,20 +4,17 @@ QDomyos-Zwift can be installed from source on MacOs, Linux, Android and IOS.
Once you've installed QDomyos-Zwift, you can access the [operation guide](30_usage.md) for more information.
These instructions build the app itself, not the test project.
## On a Linux System (from source)
```buildoutcfg
$ sudo apt update && sudo apt upgrade # this is very important on raspberry pi: you need the bluetooth firmware updated!
$ sudo apt install git qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qml-module* libqt5texttospeech5-dev libqt5texttospeech5 libqt5location5-plugins qtlocation5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 g++ make
$ sudo apt install git qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qml-module* libqt5texttospeech5-dev libqt5texttospeech5 libqt5location5-plugins qtlocation5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5
$ git clone https://github.com/cagnulein/qdomyos-zwift.git
$ cd qdomyos-zwift
$ git submodule update --init src/smtpclient/
$ git submodule update --init src/qmdnsengine/
$ git submodule update --init tst/googletest/
$ cd src
$ qmake qdomyos-zwift.pro
$ qmake
$ make -j4
$ sudo ./qdomyos-zwift
```
@@ -105,17 +102,15 @@ This operation takes a moment to complete.
#### Install qdomyos-zwift from sources
```bash
sudo apt install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 qtlocation5-dev qtquickcontrols2-5-dev libqt5texttospeech5-dev libqt5texttospeech5 g++ make
git clone https://github.com/cagnulein/qdomyos-zwift.git
cd qdomyos-zwift
git submodule update --init src/smtpclient/
git submodule update --init src/qmdnsengine/
git submodule update --init tst/googletest/
cd src
qmake qdomyos-zwift.pro
make
```
`sudo apt install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 qtlocation5-dev qtquickcontrols2-5-dev libqt5texttospeech5-dev libqt5texttospeech5`
`git clone https://github.com/cagnulein/qdomyos-zwift.git`
`cd qdomyos-zwift`
`git submodule update --init src/smtpclient/`
`git submodule update --init src/qmdnsengine/`
`cd src`
`qmake`
`make`
Please note :
- Don't build the application with `-j4` option (this will fail)

View File

@@ -36,7 +36,7 @@ An android device is required for this operation.
8. Disable the option Enable Bluetooth HCI snoop log
9. in Developer Options: Bug report->Full report
10. wait a random amount of time (10-20 seconds)
11. A notification will appear at the top of the device. Click on it, share, email it to yourself. If it doesn't appear you need to use ADB to pull the file from the phone itself
11. A notification will appear at the top of the device. Click on it, share, email it to yourself
12. You'll get a zip file with the entire report. In the FS/Data/Log/bt directory of the zipfile is the file you want.
13. attach the log file in a new issue with a short description of the steps you did in the app when you used it

View File

@@ -1,18 +0,0 @@
TEMPLATE = subdirs
CONFIG+=ordered
!android: {
SUBDIRS = \
src/qdomyos-zwift-lib.pro \
src/qdomyos-zwift.pro \
tst/qdomyos-zwift-tests.pro
tst.depends = src/qdomyos-zwift-lib.pro
}
android: {
SUBDIRS = \
src/qdomyos-zwift.pro
}

View File

@@ -26,12 +26,6 @@ ColumnLayout {
}
}
Timer {
id: chartJscheckStartFromWeb
interval: 200; running: true; repeat: true
onTriggered: {if(rootItem.startRequested) {rootItem.startRequested = false; rootItem.stopRequested = false; stackView.pop(); }}
}
Button {
id: closeButton
height: 50

File diff suppressed because it is too large Load Diff

View File

@@ -1,215 +0,0 @@
/*
* Copyright (c) 2009 Mark Liversedge (liversedge@gmail.com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
// I have consciously avoided putting things like data logging, lap marking,
// intervals or any load management functions in this class. It is restricted
// to controlling an reading telemetry from the device
//
// I expect higher order classes to implement such functions whilst
// other devices (e.g. ANT+ devices) may be implemented with the same basic
// interface
//
// I have avoided a base abstract class at this stage since I am uncertain
// what core methods would be required by say, ANT+ or Tacx devices
#ifndef _Computrainer_h
#define _Computrainer_h 1
#include <QDebug>
#include <QFile>
#include <QMutex>
#include <QString>
#include <QThread>
#ifdef WIN32
#include <windef.h>
#endif
#ifdef WIN32
#include <winbase.h>
#include <windows.h>
#else
#include <sys/ioctl.h>
#include <termios.h> // unix!!
#include <unistd.h> // unix!!
#ifndef N_TTY // for OpenBSD, this is a hack XXX
#define N_TTY 0
#endif
#endif
#ifdef Q_OS_ANDROID
#include "keepawakehelper.h"
#include <QAndroidJniObject>
#endif
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
/* Some CT Microcontroller / Protocol Constants */
/* read timeouts in microseconds */
#define CT_READTIMEOUT 1000
#define CT_WRITETIMEOUT 2000
// message type
#define CT_SPEED 0x01
#define CT_POWER 0x02
#define CT_HEARTRATE 0x03
#define CT_CADENCE 0x06
#define CT_RRC 0x09
#define CT_SENSOR 0x0b
// buttons
#define CT_RESET 0x01
#define CT_F1 0x02
#define CT_F3 0x04
#define CT_PLUS 0x08
#define CT_F2 0x10
#define CT_MINUS 0x20
#define CT_SSS 0x40 // spinscan sync is not a button!
#define CT_NONE 0x80
/* Device operation mode */
#define CT_ERGOMODE 0x01
#define CT_SSMODE 0x02
#define CT_CALIBRATE 0x04
/* UI operation mode */
#define UI_MANUAL 0x01 // using +/- keys to adjust
#define UI_ERG 0x02 // running an erg file!
/* Control status */
#define CT_RUNNING 0x01
#define CT_PAUSED 0x02
/* default operation mode */
#define DEFAULT_MODE CT_ERGOMODE
#define DEFAULT_LOAD 100.00
#define DEFAULT_GRADIENT 2.00
class Computrainer : public QThread {
public:
Computrainer(QObject *parent = 0, QString deviceFilename = 0); // pass device
~Computrainer();
QObject *parent;
// HIGH-LEVEL FUNCTIONS
int start(); // Calls QThread to start
int restart(); // restart after paused
int pause(); // pauses data collection, inbound telemetry is discarded
int stop(); // stops data collection thread
int quit(int error); // called by thread before exiting
bool discover(QString deviceFilename); // confirm CT is attached to device
// SET
void setDevice(QString deviceFilename); // setup the device filename
void setLoad(double load); // set the load to generate in ERGOMODE
void setGradient(double gradient); // set the load to generate in SSMODE
void setMode(int mode,
double load = DEFAULT_LOAD, // set mode to CT_ERGOMODE or CT_SSMODE
double gradient = DEFAULT_GRADIENT);
// GET TELEMETRY AND STATUS
// direct access to class variables is not allowed because we need to use wait conditions
// to sync data read/writes between the run() thread and the main gui thread
bool isCalibrated();
bool isHRConnected();
bool isCADConnected();
void getTelemetry(double &Power, double &HeartRate, double &Cadence, double &Speed, double &RRC, bool &calibration,
int &Buttons, uint8_t *ss, int &Status);
void getSpinScan(double spinData[]);
int getMode();
double getGradient();
double getLoad();
private:
void run(); // called by start to kick off the CT comtrol thread
// 56 bytes comprise of 8 7byte command messages, where
// the last is the set load / gradient respectively
uint8_t ERGO_Command[56], SS_Command[56];
// Utility and BG Thread functions
int openPort();
int closePort();
// Protocol encoding
void prepareCommand(int mode, double value); // sets up the command packet according to current settings
int sendCommand(int mode); // writes a command to the device
int calcCRC(int value); // calculates the checksum for the current command
// Protocol decoding
int readMessage();
void unpackTelemetry(int &b1, int &b2, int &b3, int &buttons, int &type, int &value8, int &value12);
// Mutex for controlling accessing private data
QMutex pvars;
// INBOUND TELEMETRY - all volatile since it is updated by the run() thread
volatile double devicePower; // current output power in Watts
volatile double deviceHeartRate; // current heartrate in BPM
volatile double deviceCadence; // current cadence in RPM
volatile double deviceSpeed; // current speed in KPH
volatile double deviceRRC; // calibrated Rolling Resistance
volatile bool deviceCalibrated; // is it calibrated?
volatile uint8_t spinScan[24]; // SS values only in SS_MODE
volatile int deviceButtons; // Button status
volatile bool deviceHRConnected; // HR jack is connected
volatile bool deviceCADConnected; // Cadence jack is connected
volatile int deviceStatus; // Device status running, paused, disconnected
// OUTBOUND COMMANDS - all volatile since it is updated by the GUI thread
volatile int mode;
volatile double load;
volatile double gradient;
// i/o message holder
uint8_t buf[7];
// device port
QString deviceFilename;
#ifdef WIN32
HANDLE devicePort; // file descriptor for reading from com3
DCB deviceSettings; // serial port settings baud rate et al
#else
int devicePort; // unix!!
struct termios deviceSettings; // unix!!
#endif
// raw device utils
int rawWrite(uint8_t *bytes, int size); // unix!!
int rawRead(uint8_t *bytes, int size); // unix!!
#ifdef Q_OS_ANDROID
QList<jbyte> bufRX;
bool cleanFrame = false;
#endif
};
class CTsleeper : public QThread {
public:
static void msleep(unsigned long msecs); // inherited from QThread
};
#endif // _GC_Computrainer_h

View File

@@ -1,165 +0,0 @@
#include "CrossQFile.h"
#include <QFileInfo>
#ifdef __ANDROID__
#include <jni.h>
#include <QtAndroidExtras/QtAndroid>
#include <QtAndroidExtras/qandroidjnienvironment.h>
#endif
#include <QCoreApplication>
CrossQFile::CrossQFile(const QString& nameOrUri, const bool isUri) : QFile(nameOrUri), isWorkingWithUri(isUri){
#ifdef __ANDROID__
mainActivityObj = QtAndroid::androidActivity();
contentResolverObj = mainActivityObj.callObjectMethod
("getContentResolver","()Landroid/content/ContentResolver;");
checkJenvExceptions();
#endif
}
#ifdef __ANDROID__
bool CrossQFile::checkJenvExceptions() const{
QAndroidJniEnvironment env;
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
return true;
}
return false;
}
QAndroidJniObject CrossQFile::parseUriString(const QString& uriString) const{
return QAndroidJniObject::callStaticObjectMethod
("android/net/Uri" , "parse",
"(Ljava/lang/String;)Landroid/net/Uri;",
QAndroidJniObject::fromString(uriString).object());
}
#endif
void CrossQFile::setFileName(const QString& nameOrUri, bool isUri){
QFile::setFileName(nameOrUri);
isWorkingWithUri = isUri;
}
qint64 CrossQFile::size() const{
#ifdef __ANDROID__
if(isWorkingWithUri){
QAndroidJniObject cursorObj {contentResolverObj.callObjectMethod
("query",
"(Landroid/net/Uri;[Ljava/lang/String;Landroid/os/Bundle;Landroid/os/CancellationSignal;)Landroid/database/Cursor;",
parseUriString(fileName()).object(), QAndroidJniObject().object(), QAndroidJniObject().object()
, QAndroidJniObject().object(), QAndroidJniObject().object())};
int sizeIndex {cursorObj.callMethod<jint>
("getColumnIndex","(Ljava/lang/String;)I",
QAndroidJniObject::getStaticObjectField<jstring>
("android/provider/OpenableColumns","SIZE").object())};
cursorObj.callMethod<jboolean>("moveToFirst");
qint64 ret {cursorObj.callMethod<jlong>("getLong","(I)J",sizeIndex)};
if(checkJenvExceptions()){
ret = 0;
}
return ret;
}
#endif
return QFile::size();
}
bool CrossQFile::open(CrossQFile::OpenMode openMode){
#ifdef __ANDROID__
if(isWorkingWithUri){
QAndroidJniObject jopenMode {QAndroidJniObject::fromString("rw")};
switch (openMode){
case QFile::ReadOnly:
jopenMode = QAndroidJniObject::fromString("r");
break;
case QFile::WriteOnly:
jopenMode = QAndroidJniObject::fromString("w");
break;
default:
jopenMode = QAndroidJniObject::fromString("rw");
}
QAndroidJniObject pfdObj{contentResolverObj.callObjectMethod
("openFileDescriptor", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;",
parseUriString(fileName()).object(), jopenMode.object())};
int fd{pfdObj.callMethod<jint>("detachFd")};
bool ret {false};
if(QFile::open(fd, openMode)){
ret = true;
}
if(checkJenvExceptions()){
ret = false;
}
return ret;
}
#endif
return QFile::open(openMode);
}
QString CrossQFile::displayName(){
#ifdef __ANDROID__
if(isWorkingWithUri){
QAndroidJniObject cursorObj {contentResolverObj.callObjectMethod
("query",
"(Landroid/net/Uri;[Ljava/lang/String;Landroid/os/Bundle;Landroid/os/CancellationSignal;)Landroid/database/Cursor;",
parseUriString(fileName()).object(), QAndroidJniObject().object(), QAndroidJniObject().object(),
QAndroidJniObject().object(), QAndroidJniObject().object())};
cursorObj.callMethod<jboolean>("moveToFirst");
QAndroidJniObject retObj{cursorObj.callObjectMethod
("getString","(I)Ljava/lang/String;", cursorObj.callMethod<jint>
("getColumnIndex","(Ljava/lang/String;)I",
QAndroidJniObject::getStaticObjectField<jstring>
("android/provider/OpenableColumns","DISPLAY_NAME").object()))};
QString ret {retObj.toString()};
if(checkJenvExceptions()){
ret = "";
}
return ret;
}
#endif
QFileInfo fileInfo(fileName());
return fileInfo.fileName();
}
bool CrossQFile::remove(){
#ifdef __ANDROID__
if(isWorkingWithUri){
bool ret {static_cast<bool>(QAndroidJniObject::callStaticMethod<jboolean>
("android/provider/DocumentsContract", "deleteDocument",
"(Landroid/content/ContentResolver;Landroid/net/Uri;)Z",
contentResolverObj.object(), parseUriString(fileName()).object()))};
if(checkJenvExceptions()){
ret = false;
}
return ret;
}
#endif
return QFile::remove();
}
bool CrossQFile::rename(const QString& newName){
if(!isWorkingWithUri){
return QFile::rename(newName);
}else{
return false;
}
}
bool CrossQFile::exists() const{
#ifdef __ANDROID__
if(isWorkingWithUri){
bool ret {static_cast<bool>(QAndroidJniObject::callStaticMethod<jboolean>
("android/provider/DocumentsContract", "isDocumentUri",
"(Landroid/content/Context;Landroid/net/Uri;)Z",
mainActivityObj.object(), parseUriString(fileName()).object()))};
if(checkJenvExceptions()){
ret = false;
}
return ret;
}
#endif
return QFile::exists();
}

View File

@@ -1,40 +0,0 @@
#ifndef CROSSQFILE_H
#define CROSSQFILE_H
#include <QObject>
#include <QFile>
#include <QSharedPointer>
#ifdef __ANDROID__
#include <QtAndroidExtras/QAndroidJniObject>
#endif
class CrossQFile : public QFile
{
public:
CrossQFile(const QString& nameOrUri, const bool isUri = false);
virtual qint64 size() const override;
//if working with uri, it uses QAndroidjniObject to open the file using the uri.
//otherwise it would act like a normal QFile.
bool open(CrossQFile::OpenMode openMode) override;
bool remove();
//if working with uri, it does nothing.
//otherwise it would act like a normal QFile.
bool rename(const QString& newName);
bool exists() const;
//returns the display name of the file.
QString displayName();
void setFileName(const QString& nameOrUri, bool isUri = false);
private:
bool isWorkingWithUri{false};
#ifdef __ANDROID__
QAndroidJniObject mainActivityObj;
QAndroidJniObject contentResolverObj;
bool checkJenvExceptions() const;
QAndroidJniObject parseUriString(const QString& uriString) const;
#endif
};
#endif // CROSSQFILE_H

View File

@@ -68,13 +68,8 @@ HomeForm{
onTriggered: popupLap.close();
}
Timer {
id: checkStartStopFromWeb
interval: 200; running: true; repeat: true
onTriggered: {if(rootItem.stopRequested) {rootItem.stopRequested = false; inner_stop(); }}
}
function inner_stop() {
start.onClicked: { start_clicked(); }
stop.onClicked: {
stop_clicked();
rootItem.save_screenshot();
if(CHARTJS)
@@ -82,11 +77,6 @@ HomeForm{
else
stackView.push("ChartsEndWorkout.qml")
}
start.onClicked: { start_clicked(); }
stop.onClicked: {
inner_stop();
}
lap.onClicked: { lap_clicked(); popupLap.open(); popupLapAutoClose.running = true; }
Component.onCompleted: { console.log("completed"); }
@@ -265,7 +255,7 @@ HomeForm{
color: largeButtonColor
radius: 20
}
font.pointSize: 20 * settings.ui_zoom / 100
font.pointSize: 16 * settings.ui_zoom / 100
//width: 48 * settings.ui_zoom / 100
//height: 48 * settings.ui_zoom / 100
}

View File

@@ -157,7 +157,7 @@ Page {
width: parent.width
anchors.top: row1.bottom
anchors.topMargin: 30
text: "This app should automatically connect to your bike/treadmill/rower. <b>If it doesn't, please check</b>:<br>1) your Echelon/Domyos App MUST be closed while qdomyos-zwift is running;<br>2) bluetooth and bluetooth permission MUST be on<br>3) your bike/treadmill/rower should be turned on BEFORE starting this app<br>4) try to restart your device<br><br>If your bike/treadmill disconnects every 30 seconds try to disable the 'virtual device' setting on the left bar.<br><br>In case of issues, please feel free to contact me at roberto.viola83@gmail.com.<br><br><b>Have a nice ride!</b><br/ ><i>QZ specifically disclaims liability for<br>incidental or consequential damages and assumes<br>no responsibility or liability for any loss<br>or damage suffered by any person as a result of<br>the use or misuse of the app.</i><br><br>Roberto Viola"
text: "This app should automatically connects to your bike/treadmill. <b>If it doesn't, please check</b>:<br>1) your Echelon/Domyos App MUST be closed while qdomyos-zwift is running;<br>2) bluetooth and bluetooth permission MUST be on<br>3) your bike/treadmill should be turned on BEFORE starting this app<br>4) try to restart your device<br><br>If your bike/treadmill disconnects every 30 seconds try to disable the 'virtual device' setting on the left bar.<br><br>In case of issue, please, feel free to contact me to roberto.viola83@gmail.com.<br><br><b>Have a nice ride!</b><br/ ><i>QZ specifically disclaims liability for<br>incidental or consequential damages and assumes<br>no responsibility or liability for any loss<br>or damage suffered by any person as a result of<br>the use or misuse of the app.</i><br><br>Roberto Viola"
wrapMode: Label.WordWrap
visible: rootItem.labelHelp
}

View File

@@ -1,58 +0,0 @@
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Controls.Material 2.12
import QtQuick.Dialogs 1.0
import QtGraphicalEffects 1.12
import Qt.labs.settings 1.0
import QtMultimedia 5.15
import QtQuick.Layouts 1.3
import QtWebView 1.1
Item {
anchors.fill: parent
height: parent.height
width: parent.width
visible: true
WebView {
anchors.fill: parent
height: parent.height
width: parent.width
visible: !rootItem.generalPopupVisible
url: rootItem.getStravaAuthUrl
}
Popup {
id: popupStravaConnectedWeb
parent: Overlay.overlay
enabled: rootItem.generalPopupVisible
onEnabledChanged: { if(rootItem.generalPopupVisible) popupStravaConnectedWeb.open() }
onClosed: { rootItem.generalPopupVisible = false; }
x: Math.round((parent.width - width) / 2)
y: Math.round((parent.height - height) / 2)
width: 380
height: 120
modal: true
focus: true
palette.text: "white"
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
enter: Transition
{
NumberAnimation { property: "opacity"; from: 0.0; to: 1.0 }
}
exit: Transition
{
NumberAnimation { property: "opacity"; from: 1.0; to: 0.0 }
}
Column {
anchors.horizontalCenter: parent.horizontalCenter
Label {
anchors.horizontalCenter: parent.horizontalCenter
width: 370
height: 120
text: qsTr("Your Strava account is now connected!<br><br>When you will press STOP on QZ a file<br>will be automatically uploaded to Strava!")
}
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,5 +1,5 @@
<?xml version="1.0"?>
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.13.18" android:versionCode="523" android:installLocation="auto">
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="2.12.13" android:versionCode="443" 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 -->
@@ -10,7 +10,7 @@
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<application android:hardwareAccelerated="true" android:debuggable="false" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="qdomyos-zwift" android:extractNativeLibs="true" android:icon="@drawable/icon" android:usesCleartextTraffic="true">
<activity android:theme="@style/Theme.AppCompat" android:exported="true" android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="QZ" android:launchMode="singleTop">
<activity android:theme="@style/Theme.AppCompat" android:exported="true" android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="qdomyos-zwift" android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@@ -75,20 +75,6 @@
android:exported="true"></service>
<service android:name=".ChannelService"></service>
<service android:name=".FloatingWindowGFG" android:enabled="true" android:exported="true"/>
<service android:name="com.cgutman.androidremotedebugger.service.ShellService"
android:enabled="true"
android:exported="false" >
</service>
<service
android:name=".ScreenCaptureService"
android:foregroundServiceType="mediaProjection" />
<meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES"
android:value="ocr" />
<activity android:name="org.cagnulen.qdomyoszwift.MyActivity" />
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
</application>
@@ -106,9 +92,4 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="com.android.vending.BILLING"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.things.permission.USE_PERIPHERAL_IO" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions" />
</manifest>

View File

@@ -12,31 +12,12 @@ buildscript {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
maven { url 'https://dl.bintray.com/rvalerio/maven' }
}
apply plugin: 'com.android.application'
def amazon = System.getenv('AMAZON')
println(amazon)
dependencies {
compile 'com.rvalerio:fgchecker:1.1.0'
implementation "androidx.core:core-ktx:+"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0"
if(amazon == "1") {
// amazon app store
implementation 'com.google.mlkit:text-recognition:16.0.0-beta6'
} else {
// google play store
implementation 'com.google.android.gms:play-services-mlkit-text-recognition:18.0.2'
}
implementation 'com.google.android.play:core:1.8.2'
def appcompat_version = "1.3.1"
def appcompat_version = "1.1.0"
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation "com.android.billingclient:billing:4.0.0"
implementation 'com.android.support:appcompat-v7:28.0.0'
@@ -44,7 +25,6 @@ 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:3.4.6'
androidTestImplementation "com.android.support:support-annotations:28.0.0"
}

View File

@@ -116,9 +116,6 @@ public class ChannelService extends Service {
if (null != powerChannelController) {
powerChannelController.cadence = cadence;
}
if (null != speedChannelController) {
speedChannelController.cadence = cadence;
}
}
int getHeart() {

View File

@@ -25,25 +25,15 @@ import androidx.appcompat.app.AppCompatActivity;
public class FloatingHandler {
static Context _context;
static public int _port = 0;
static Intent _intent = null;
static public int _width;
static public int _height;
static public int _alpha;
public static void show(Context context, int port, int width, int height, int transparency) {
public static void show(Context context, int port) {
_context = context;
_port = port;
_width = width;
_height = height;
_alpha = transparency;
// First it confirms whether the
// 'Display over other apps' permission in given
if (checkOverlayDisplayPermission()) {
if(_intent == null)
_intent = new Intent(context, FloatingWindowGFG.class);
// FloatingWindowGFG service is started
context.startService(_intent);
context.startService(new Intent(context, FloatingWindowGFG.class));
// The MainActivity closes here
//finish();
} else {
@@ -56,11 +46,6 @@ public class FloatingHandler {
}
}
public static void hide() {
if(_intent != null)
_context.stopService(_intent);
}
private static boolean checkOverlayDisplayPermission() {
// Android Version is lesser than Marshmallow
// or the API is lesser than 23

View File

@@ -25,7 +25,8 @@ import android.webkit.WebView;
import android.webkit.WebSettings;
import android.webkit.WebViewClient;
import android.util.Log;
import android.content.SharedPreferences;
import androidx.annotation.Nullable;
public class FloatingWindowGFG extends Service {
@@ -38,15 +39,9 @@ public class FloatingWindowGFG extends Service {
private WindowManager windowManager;
private Button maximizeBtn;
// Retrieve the user preference node for the package com.mycompany
SharedPreferences sharedPreferences;
// Preference key name
final String PREF_NAME_X = "floatWindowLayoutUpdateParamX";
final String PREF_NAME_Y = "floatWindowLayoutUpdateParamY";
// As FloatingWindowGFG inherits Service class,
// it actually overrides the onBind method
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
@@ -58,9 +53,9 @@ public class FloatingWindowGFG extends Service {
// The screen height and width are calculated, cause
// the height and width of the floating window is set depending on this
/*DisplayMetrics metrics = getApplicationContext().getResources().getDisplayMetrics();
DisplayMetrics metrics = getApplicationContext().getResources().getDisplayMetrics();
int width = metrics.widthPixels;
int height = metrics.heightPixels;*/
int height = metrics.heightPixels;
// To obtain a WindowManager of a different Display,
// we need a Context for that display, so WINDOW_SERVICE is used
@@ -85,7 +80,7 @@ public class FloatingWindowGFG extends Service {
wv.loadUrl("http://localhost:" + FloatingHandler._port + "/floating/floating.htm");
wv.clearView();
wv.measure(100, 100);
wv.setAlpha(Float.valueOf(FloatingHandler._alpha) / 100.0f);
wv.setAlpha(0.6f);
settings.setBuiltInZoomControls(true);
settings.setUseWideViewPort(true);
settings.setDomStorageEnabled(true);
@@ -115,10 +110,9 @@ public class FloatingWindowGFG extends Service {
// This problem is solved later.
// 5) Next parameter is Layout_Format. System chooses a format that supports
// translucency by PixelFormat.TRANSLUCENT
floatWindowLayoutParam = new WindowManager.LayoutParams(
(int) (FloatingHandler._width ),
(int) (FloatingHandler._height ),
(int) (width * (0.30f)),
(int) (height * (0.22f)),
LAYOUT_TYPE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
@@ -132,10 +126,6 @@ public class FloatingWindowGFG extends Service {
floatWindowLayoutParam.x = 0;
floatWindowLayoutParam.y = 0;
sharedPreferences = getSharedPreferences("FloatingWindowGFG",MODE_PRIVATE);
floatWindowLayoutParam.x = sharedPreferences.getInt(PREF_NAME_X, floatWindowLayoutParam.x);
floatWindowLayoutParam.y = sharedPreferences.getInt(PREF_NAME_Y, floatWindowLayoutParam.y);
// The ViewGroup that inflates the floating_layout.xml is
// added to the WindowManager with all the parameters
windowManager.addView(floatView, floatWindowLayoutParam);
@@ -177,11 +167,6 @@ public class FloatingWindowGFG extends Service {
floatWindowLayoutUpdateParam.x = (int) ((x + event.getRawX()) - px);
floatWindowLayoutUpdateParam.y = (int) ((y + event.getRawY()) - py);
SharedPreferences.Editor myEdit = sharedPreferences.edit();
myEdit.putInt(PREF_NAME_X, floatWindowLayoutUpdateParam.x);
myEdit.putInt(PREF_NAME_Y, floatWindowLayoutUpdateParam.y);
myEdit.commit();
// updated parameter is applied to the WindowManager
windowManager.updateViewLayout(floatView, floatWindowLayoutUpdateParam);
break;

View File

@@ -8,6 +8,7 @@ import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
public class ForegroundService extends Service {
@@ -38,7 +39,7 @@ public class ForegroundService extends Service {
public void onDestroy() {
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;

View File

@@ -1,108 +0,0 @@
package org.cagnulen.qdomyoszwift;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.media.projection.MediaProjectionManager;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.util.DisplayMetrics;
import android.os.Build;
import android.provider.Settings;
import android.app.AppOpsManager;
import android.util.Log;
import android.annotation.TargetApi;
import com.rvalerio.fgchecker.AppChecker;
public class MediaProjection {
private static final int REQUEST_CODE = 100;
private static Context _context;
private static String _packageName = "";
/*private static MediaProjection m_instance;
public MediaProjection() {
m_instance = this;
startProjection();
}*/
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE) {
if (resultCode != Activity.RESULT_OK) {
return;
}
//startService(org.cagnulen.qdomyoszwift.ScreenCaptureService.getStartIntent(this, resultCode, data));
}
}
static boolean isLandscape(){
boolean landscape = false;
DisplayMetrics metrics = _context.getResources().getDisplayMetrics();
int width = metrics.widthPixels;
int height = metrics.heightPixels;
if(width<height){
landscape = false;
} else {
landscape = true;
}
return landscape;
}
static void requestUsageStatsPermission() {
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& !hasUsageStatsPermission(_context)) {
_context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
}
}
@TargetApi(Build.VERSION_CODES.KITKAT)
static boolean hasUsageStatsPermission(Context context) {
AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
int mode = appOps.checkOpNoThrow("android:get_usage_stats",
android.os.Process.myUid(), context.getPackageName());
boolean granted = mode == AppOpsManager.MODE_ALLOWED;
return granted;
}
public static String getPackageName() {
return _packageName;
}
public static void startService(Context context, int resultCode, Intent data) {
_context = context;
requestUsageStatsPermission();
context.startService(org.cagnulen.qdomyoszwift.ScreenCaptureService.getStartIntent(context, resultCode, data));
AppChecker appChecker = new AppChecker();
appChecker
.whenAny(new AppChecker.Listener() {
@Override
public void onForeground(String packageName) {
_packageName = packageName;
/*Log.e("MediaProjection", packageName);
if(isLandscape())
Log.e("MediaProjection", "Landscape");
else
Log.e("MediaProjection", "Portrait");*/
}
})
.timeout(1000)
.start(context);
}
public void startProjection(Context context) {
_context = context;
MediaProjectionManager mProjectionManager =
(MediaProjectionManager) context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
//Activity a = (Activity)_context;
//this.startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE);
}
public void stopProjection() {
//startService(com.mtsahakis.mediaprojectiondemo.ScreenCaptureService.getStopIntent(this));
}
}

View File

@@ -13,14 +13,11 @@ public class NotificationClient
{
private static NotificationManager m_notificationManager;
private static Notification.Builder m_builder;
private static Context _context;
private static Intent serviceIntent = null;
public NotificationClient() {}
public static void notify(Context context, String message) {
_context = context;
serviceIntent = new Intent(context, ForegroundService.class);
Intent serviceIntent = new Intent(context, ForegroundService.class);
serviceIntent.putExtra("inputExtra", "QZ is Running");
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent);
@@ -28,9 +25,4 @@ public class NotificationClient
context.startService(serviceIntent);
}
}
public static void hide() {
if(serviceIntent != null)
_context.stopService(serviceIntent);
}
}

View File

@@ -1,56 +0,0 @@
package org.cagnulen.qdomyoszwift;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
import androidx.core.app.NotificationCompat;
import androidx.core.util.Pair;
public class NotificationUtils {
public static final int NOTIFICATION_ID = 1337;
private static final String NOTIFICATION_CHANNEL_ID = "org.cagnulen.qdomyoszwift";
private static final String NOTIFICATION_CHANNEL_NAME = "org.cagnulen.qdomyoszwift";
public static Pair<Integer, Notification> getNotification(Context context) {
createNotificationChannel(context);
Notification notification = createNotification(context);
NotificationManager notificationManager
= (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFICATION_ID, notification);
return new Pair<>(NOTIFICATION_ID, notification);
}
@TargetApi(Build.VERSION_CODES.O)
private static void createNotificationChannel(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
NOTIFICATION_CHANNEL_ID,
NOTIFICATION_CHANNEL_NAME,
NotificationManager.IMPORTANCE_LOW
);
channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.createNotificationChannel(channel);
}
}
private static Notification createNotification(Context context) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
builder.setSmallIcon(R.drawable.icon);
builder.setContentTitle("QZ Peloton Sync");
builder.setContentText("Active!");
builder.setOngoing(true);
builder.setCategory(Notification.CATEGORY_SERVICE);
builder.setPriority(Notification.PRIORITY_LOW);
builder.setShowWhen(true);
return builder.build();
}
}

View File

@@ -1,215 +0,0 @@
package org.cagnulen.qdomyoszwift;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.os.PowerManager;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import java.util.logging.Logger;
import static android.content.ContentValues.TAG;
import androidx.core.app.NotificationCompat;
import com.cgutman.androidremotedebugger.AdbUtils;
import com.cgutman.androidremotedebugger.console.ConsoleBuffer;
import com.cgutman.androidremotedebugger.devconn.DeviceConnection;
import com.cgutman.androidremotedebugger.devconn.DeviceConnectionListener;
import com.cgutman.androidremotedebugger.service.ShellService;
import com.cgutman.adblib.AdbCrypto;
public class QZAdbRemote implements DeviceConnectionListener {
private static ShellService.ShellServiceBinder binder;
private static DeviceConnection connection;
private static Intent service;
private static final String LOG_TAG = "QZ:AdbRemote";
private static String lastCommand = "";
private static boolean ADBConnected = false;
private static String _address = "127.0.0.1";
private static Context _context;
private static QZAdbRemote INSTANCE;
public static QZAdbRemote getInstance() {
if(INSTANCE == null) {
INSTANCE = new QZAdbRemote();
}
return INSTANCE;
}
@Override
public void notifyConnectionEstablished(DeviceConnection devConn) {
ADBConnected = true;
Log.i(LOG_TAG, "notifyConnectionEstablished" + lastCommand);
}
@Override
public void notifyConnectionFailed(DeviceConnection devConn, Exception e) {
ADBConnected = false;
Log.e(LOG_TAG, e.getMessage());
}
@Override
public void notifyStreamFailed(DeviceConnection devConn, Exception e) {
ADBConnected = false;
Log.e(LOG_TAG, e.getMessage());
}
@Override
public void notifyStreamClosed(DeviceConnection devConn) {
ADBConnected = false;
Log.e(LOG_TAG, "notifyStreamClosed");
}
@Override
public AdbCrypto loadAdbCrypto(DeviceConnection devConn) {
return AdbUtils.readCryptoConfig(_context.getFilesDir());
}
@Override
public boolean canReceiveData() {
return true;
}
@Override
public void receivedData(DeviceConnection devConn, byte[] data, int offset, int length) {
Log.i(LOG_TAG, data.toString());
}
@Override
public boolean isConsole() {
return false;
}
@Override
public void consoleUpdated(DeviceConnection devConn, ConsoleBuffer console) {
}
private DeviceConnection startConnection(String host, int port) {
/* Create the connection object */
DeviceConnection conn = binder.createConnection(host, port);
/* Add this activity as a connection listener */
binder.addListener(conn, this);
/* Begin the async connection process */
conn.startConnect();
return conn;
}
private DeviceConnection connectOrLookupConnection(String host, int port) {
DeviceConnection conn = binder.findConnection(host, port);
if (conn == null) {
/* No existing connection, so start the connection process */
conn = startConnection(host, port);
}
else {
/* Add ourselves as a new listener of this connection */
binder.addListener(conn, this);
}
return conn;
}
public ServiceConnection serviceConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName arg0, IBinder arg1) {
binder = (ShellService.ShellServiceBinder)arg1;
if (connection != null) {
binder.removeListener(connection, QZAdbRemote.getInstance());
}
connection = connectOrLookupConnection(_address, 5555);
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
binder = null;
}
};
static public void createConnection(String ip, Context context) {
_address = ip;
_context = context;
/* If we have old RSA keys, just use them */
AdbCrypto crypto = AdbUtils.readCryptoConfig(_context.getFilesDir());
if (crypto == null)
{
/* We need to make a new pair */
Log.i(LOG_TAG,
"This will only be done once.");
new Thread(new Runnable() {
@Override
public void run() {
AdbCrypto crypto;
crypto = AdbUtils.writeNewCryptoConfig(_context.getFilesDir());
if (crypto == null)
{
Log.e(LOG_TAG,
"Unable to generate and save RSA key pair");
return;
}
}
}).start();
}
if (binder == null) {
service = new Intent(_context, ShellService.class);
/* Bind the service if we're not bound already. After binding, the callback will
* perform the initial connection. */
_context.bindService(service, QZAdbRemote.getInstance().serviceConn, Service.BIND_AUTO_CREATE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
_context.startForegroundService(service);
}
else {
_context.startService(service);
}
}
}
static public void sendCommand(String command) {
Log.d(LOG_TAG, "sendCommand " + ADBConnected + " " + command);
if(ADBConnected) {
StringBuilder commandBuffer = new StringBuilder();
commandBuffer.append(command);
/* Append a newline since it's not included in the command itself */
commandBuffer.append('\n');
/* Send it to the device */
connection.queueCommand(commandBuffer.toString());
} else {
Log.e(LOG_TAG, "sendCommand ADB is not connected!");
}
}
}

View File

@@ -1,365 +0,0 @@
package org.cagnulen.qdomyoszwift;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Notification;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.Image;
import android.media.ImageReader;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.view.Display;
import android.view.OrientationEventListener;
import android.view.WindowManager;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Objects;
import com.google.mlkit.vision.common.InputImage;
import com.google.mlkit.vision.text.Text;
import com.google.mlkit.vision.text.TextRecognition;
import com.google.mlkit.vision.text.TextRecognizer;
import com.google.mlkit.vision.text.latin.TextRecognizerOptions;
import android.media.ImageReader.OnImageAvailableListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import android.graphics.Rect;
import android.graphics.Point;
import androidx.core.util.Pair;
public class ScreenCaptureService extends Service {
private static final String TAG = "ScreenCaptureService";
private static final String RESULT_CODE = "RESULT_CODE";
private static final String DATA = "DATA";
private static final String ACTION = "ACTION";
private static final String START = "START";
private static final String STOP = "STOP";
private static final String SCREENCAP_NAME = "screencap";
private static int IMAGES_PRODUCED;
private MediaProjection mMediaProjection;
private String mStoreDir;
private ImageReader mImageReader;
private Handler mHandler;
private Display mDisplay;
private VirtualDisplay mVirtualDisplay;
private int mDensity;
private int mWidth;
private int mHeight;
private static int mWidthImage;
private static int mHeightImage;
private int mRotation;
private OrientationChangeCallback mOrientationChangeCallback;
private TextRecognizer recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS);
private static String lastText = "";
private static String lastTextExtended = "";
private static boolean isRunning = false;
public static String getLastText() {
return lastText;
}
public static String getLastTextExtended() {
return lastTextExtended;
}
public static int getImageWidth() {
return mWidthImage;
}
public static int getImageHeight() {
return mHeightImage;
}
public static Intent getStartIntent(Context context, int resultCode, Intent data) {
Intent intent = new Intent(context, ScreenCaptureService.class);
intent.putExtra(ACTION, START);
intent.putExtra(RESULT_CODE, resultCode);
intent.putExtra(DATA, data);
return intent;
}
public static Intent getStopIntent(Context context) {
Intent intent = new Intent(context, ScreenCaptureService.class);
intent.putExtra(ACTION, STOP);
return intent;
}
private static boolean isStartCommand(Intent intent) {
return intent.hasExtra(RESULT_CODE) && intent.hasExtra(DATA)
&& intent.hasExtra(ACTION) && Objects.equals(intent.getStringExtra(ACTION), START);
}
private static boolean isStopCommand(Intent intent) {
return intent.hasExtra(ACTION) && Objects.equals(intent.getStringExtra(ACTION), STOP);
}
private static int getVirtualDisplayFlags() {
return DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
}
private class ImageAvailableListener implements ImageReader.OnImageAvailableListener {
@Override
public void onImageAvailable(ImageReader reader) {
FileOutputStream fos = null;
try (Image image = mImageReader.acquireLatestImage()) {
if (image != null) {
if(!isRunning) {
Image.Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
int pixelStride = planes[0].getPixelStride();
int rowStride = planes[0].getRowStride();
int rowPadding = rowStride - pixelStride * mWidth;
//Log.e(TAG, "Image reviewing");
isRunning = true;
// create bitmap
mWidthImage = mWidth + rowPadding / pixelStride;
mHeightImage = mHeight;
final Bitmap bitmap = Bitmap.createBitmap(mWidth + rowPadding / pixelStride, mHeight, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
/*
// write bitmap to a file
fos = new FileOutputStream(mStoreDir + "/myscreen.png");
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
IMAGES_PRODUCED++;
Log.e(TAG, "captured image: " + IMAGES_PRODUCED);
*/
InputImage inputImage = InputImage.fromBitmap(bitmap, 0);
/*InputImage inputImage = InputImage.fromByteBuffer(buffer,
mWidth + rowPadding / pixelStride, mHeight,
0,
InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);*/
Task<Text> result =
recognizer.process(inputImage)
.addOnSuccessListener(new OnSuccessListener<Text>() {
@Override
public void onSuccess(Text result) {
// Task completed successfully
//Log.e(TAG, "Image done!");
String resultText = result.getText();
lastText = resultText;
lastTextExtended = "";
for (Text.TextBlock block : result.getTextBlocks()) {
String blockText = block.getText();
Point[] blockCornerPoints = block.getCornerPoints();
Rect blockFrame = block.getBoundingBox();
lastTextExtended = lastTextExtended + blockText + "$$" + blockFrame.toString() + "§§";
/*for (Text.Line line : block.getLines()) {
String lineText = line.getText();
Point[] lineCornerPoints = line.getCornerPoints();
Rect lineFrame = line.getBoundingBox();
for (Text.Element element : line.getElements()) {
String elementText = element.getText();
Point[] elementCornerPoints = element.getCornerPoints();
Rect elementFrame = element.getBoundingBox();
for (Text.Symbol symbol : element.getSymbols()) {
String symbolText = symbol.getText();
Point[] symbolCornerPoints = symbol.getCornerPoints();
Rect symbolFrame = symbol.getBoundingBox();
}
}
}*/
}
bitmap.recycle();
isRunning = false;
}
})
.addOnFailureListener(
new OnFailureListener() {
@Override
public void onFailure(Exception e) {
// Task failed with an exception
//Log.e(TAG, "Image fail");
isRunning = false;
}
});
} else {
//Log.e(TAG, "Image ignored");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private class OrientationChangeCallback extends OrientationEventListener {
OrientationChangeCallback(Context context) {
super(context);
}
@Override
public void onOrientationChanged(int orientation) {
final int rotation = mDisplay.getRotation();
if (rotation != mRotation) {
mRotation = rotation;
try {
// clean up
if (mVirtualDisplay != null) mVirtualDisplay.release();
if (mImageReader != null) mImageReader.setOnImageAvailableListener(null, null);
// re-create virtual display depending on device width / height
createVirtualDisplay();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
private class MediaProjectionStopCallback extends MediaProjection.Callback {
@Override
public void onStop() {
Log.e(TAG, "stopping projection.");
mHandler.post(new Runnable() {
@Override
public void run() {
if (mVirtualDisplay != null) mVirtualDisplay.release();
if (mImageReader != null) mImageReader.setOnImageAvailableListener(null, null);
if (mOrientationChangeCallback != null) mOrientationChangeCallback.disable();
mMediaProjection.unregisterCallback(MediaProjectionStopCallback.this);
}
});
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
// create store dir
File externalFilesDir = getExternalFilesDir(null);
if (externalFilesDir != null) {
mStoreDir = externalFilesDir.getAbsolutePath() + "/screenshots/";
File storeDirectory = new File(mStoreDir);
if (!storeDirectory.exists()) {
boolean success = storeDirectory.mkdirs();
if (!success) {
Log.e(TAG, "failed to create file storage directory.");
stopSelf();
}
}
} else {
Log.e(TAG, "failed to create file storage directory, getExternalFilesDir is null.");
stopSelf();
}
// start capture handling thread
new Thread() {
@Override
public void run() {
Looper.prepare();
mHandler = new Handler();
Looper.loop();
}
}.start();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (isStartCommand(intent)) {
// create notification
Pair<Integer, Notification> notification = NotificationUtils.getNotification(this);
startForeground(notification.first, notification.second);
// start projection
int resultCode = intent.getIntExtra(RESULT_CODE, Activity.RESULT_CANCELED);
Intent data = intent.getParcelableExtra(DATA);
startProjection(resultCode, data);
} else if (isStopCommand(intent)) {
stopProjection();
stopSelf();
} else {
stopSelf();
}
return START_NOT_STICKY;
}
private void startProjection(int resultCode, Intent data) {
MediaProjectionManager mpManager =
(MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
if (mMediaProjection == null) {
mMediaProjection = mpManager.getMediaProjection(resultCode, data);
if (mMediaProjection != null) {
// display metrics
mDensity = Resources.getSystem().getDisplayMetrics().densityDpi;
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
mDisplay = windowManager.getDefaultDisplay();
// create virtual display depending on device width / height
createVirtualDisplay();
// register orientation change callback
mOrientationChangeCallback = new OrientationChangeCallback(this);
if (mOrientationChangeCallback.canDetectOrientation()) {
mOrientationChangeCallback.enable();
}
// register media projection stop callback
mMediaProjection.registerCallback(new MediaProjectionStopCallback(), mHandler);
}
}
}
private void stopProjection() {
if (mHandler != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
if (mMediaProjection != null) {
mMediaProjection.stop();
}
}
});
}
}
@SuppressLint("WrongConstant")
private void createVirtualDisplay() {
// get width and height
mWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
mHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
// start capture reader
mImageReader = ImageReader.newInstance(mWidth, mHeight, PixelFormat.RGBA_8888, 2);
mVirtualDisplay = mMediaProjection.createVirtualDisplay(SCREENCAP_NAME, mWidth, mHeight,
mDensity, getVirtualDisplayFlags(), mImageReader.getSurface(), null, mHandler);
mImageReader.setOnImageAvailableListener(new ImageAvailableListener(), mHandler);
}
}

View File

@@ -16,7 +16,6 @@
package org.cagnulen.qdomyoszwift;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import com.dsi.ant.channel.AntChannel;
@@ -33,17 +32,15 @@ import java.util.Random;
public class SpeedChannelController {
// The device type and transmission type to be part of the channel ID message
private static final int CHANNEL_SPEED_DEVICE_TYPE = 0x79;
private static final int CHANNEL_SPEED_DEVICE_TYPE = 0x7B;
private static final int CHANNEL_SPEED_TRANSMISSION_TYPE = 1;
// The period and frequency values the channel will be configured to
private static final int CHANNEL_SPEED_PERIOD = 8086; // 1 Hz
private static final int CHANNEL_SPEED_PERIOD = 8118; // 1 Hz
private static final int CHANNEL_SPEED_FREQUENCY = 57;
private static final String TAG = SpeedChannelController.class.getSimpleName();
public static final int SPEED_SENSOR_ID = 0x9e3d4b33;
private static final double MILLISECOND_TO_1_1024_CONVERSION = 0.9765625;
public static final int SPEED_SENSOR_ID = 0x9e3d4b65;
private AntChannel mAntChannel;
@@ -51,7 +48,6 @@ public class SpeedChannelController {
private boolean mIsOpen;
double speed = 0.0;
int cadence = 0;
public SpeedChannelController(AntChannel antChannel) {
mAntChannel = antChannel;
@@ -165,14 +161,14 @@ public class SpeedChannelController {
* received and channel death events can be handled.
*/
public class ChannelEventCallback implements IAntChannelEventHandler {
int revCounts = 0;
int ucMessageCount = 0;
byte ucPageChange = 0;
byte ucExtMesgType = 1;
long lastTime = 0;
double totalWay = 0.0;
double totalRotations = 0.0;
long lastSpeedEventTime = 0;
long lastCadenceEventTime = 0;
long elapsedMillis = 0;
int rotations;
double way;
int rev;
double remWay;
double wheel = 0.1;
@Override
@@ -206,31 +202,58 @@ public class SpeedChannelController {
// Switching on event code to handle the different types of channel events
switch (code) {
case TX:
long realtimeMillis = SystemClock.elapsedRealtime();
long unixTime = System.currentTimeMillis() / 1000L;
if (lastTime != 0) {
elapsedMillis = realtimeMillis - lastTime;
totalWay += speed * elapsedMillis / 3_600L;
totalRotations += (double) cadence * elapsedMillis / 60_000L;
rev = (int) (totalWay / wheel);
rotations = (int) totalRotations;
lastCadenceEventTime = realtimeMillis - (long) ((totalRotations - rotations) / cadence * 60_000);
lastSpeedEventTime = realtimeMillis - (long) ((totalWay - (rev * wheel)) / speed * 3_600);
way = speed * (unixTime - lastTime) / 3.6 + remWay;
rev = (int) (way / wheel + 0.5);
remWay = way - rev * wheel;
revCounts += rev;
}
lastTime = realtimeMillis;
lastTime = unixTime;
ucPageChange += 0x20;
ucPageChange &= 0xF0;
ucMessageCount += 1;
byte[] payload = new byte[8];
int lastCadenceEventTime1024 = (int) ((double) lastCadenceEventTime / MILLISECOND_TO_1_1024_CONVERSION);
int lastSpeedEventTime1024 = (int) ((double) lastSpeedEventTime / MILLISECOND_TO_1_1024_CONVERSION);
payload[0] = (byte) (lastCadenceEventTime1024 & 0xFF);
payload[1] = (byte) ((lastCadenceEventTime1024 >> 8) & 0xFF);
payload[2] = (byte) (rotations & 0xFF);
payload[3] = (byte) ((rotations >> 8) & 0xFF);
payload[4] = (byte) (lastSpeedEventTime1024 & 0xFF);
payload[5] = (byte) ((lastSpeedEventTime1024 >> 8) & 0xFF);
payload[6] = (byte) (rev & 0xFF);
payload[7] = (byte) ((rev >> 8) & 0xFF);
if (ucMessageCount >= 65) {
if (ucExtMesgType >= 4)
ucExtMesgType = 1;
if (ucExtMesgType == 1) {
int halfunixTime = (int) (unixTime / 2L);
payload[0] = (byte) ((byte) 0x01 | (byte) (ucPageChange & (byte) 0x80));
payload[1] = (byte) (halfunixTime & 0xFF);
payload[2] = (byte) ((halfunixTime >> 8) & 0xFF);
payload[3] = (byte) ((halfunixTime >> 16) & 0xFF);
} else if (ucExtMesgType == 2) {
payload[0] = (byte) ((byte) 0x02 | (byte) (ucPageChange & (byte) 0x80));
payload[1] = (byte) 0xFF;
payload[2] = (byte) ((SPEED_SENSOR_ID >> 16) & 0xFF);
payload[3] = (byte) ((SPEED_SENSOR_ID >> 24) & 0xFF);
} else if (ucExtMesgType == 3) {
payload[0] = (byte) ((byte) 0x03 | (byte) (ucPageChange & (byte) 0x80));
payload[1] = (byte) 0x01;
payload[2] = (byte) 0x01;
payload[3] = (byte) 0x01;
}
if (ucMessageCount >= 68) {
ucMessageCount = 0;
ucExtMesgType += 1;
}
} else {
payload[0] = (byte) (ucPageChange & 0x80);
payload[1] = (byte) 0xFF;
payload[2] = (byte) 0xFF;
payload[3] = (byte) 0xFF;
}
int unixTime1024 = (int) (unixTime * 1024);
payload[4] = (byte) (unixTime1024 & 0xFF);
payload[5] = (byte) ((unixTime1024 >> 8) & 0xFF);
payload[6] = (byte) (revCounts & 0xFF);
payload[7] = (byte) ((revCounts >> 8) & 0xFF);
if (mIsOpen) {
try {
@@ -242,6 +265,9 @@ public class SpeedChannelController {
}
break;
case CHANNEL_COLLISION:
ucPageChange += 0x20;
ucPageChange &= 0xF0;
ucMessageCount += 1;
break;
case RX_SEARCH_TIMEOUT:
// TODO May want to keep searching

View File

@@ -1,136 +0,0 @@
package org.cagnulen.qdomyoszwift;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.util.Log;
import android.app.Service;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import com.hoho.android.usbserial.driver.CdcAcmSerialDriver;
import com.hoho.android.usbserial.driver.Ch34xSerialDriver;
import com.hoho.android.usbserial.driver.CommonUsbSerialPort;
import com.hoho.android.usbserial.driver.Cp21xxSerialDriver;
import com.hoho.android.usbserial.driver.FtdiSerialDriver;
import com.hoho.android.usbserial.driver.ProlificSerialDriver;
import com.hoho.android.usbserial.driver.UsbId;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import com.hoho.android.usbserial.driver.UsbSerialProber;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Deque;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.concurrent.Callable;
import java.util.ArrayList;
import java.util.List;
import java.nio.charset.StandardCharsets;
public class Usbserial {
static UsbSerialPort port = null;
static byte[] receiveData = new byte[4096];
static int lastReadLen = 0;
public static void open(Context context) {
Log.d("QZ","UsbSerial open");
// Find all available drivers from attached devices.
UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
List<UsbSerialDriver> availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager);
if (availableDrivers.isEmpty()) {
Log.d("QZ","UsbSerial no available drivers");
return;
}
// Open a connection to the first available driver.
UsbSerialDriver driver = availableDrivers.get(0);
if (!manager.hasPermission(driver.getDevice())) {
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
RingtoneManager.getRingtone(context, notification).play();
Log.d("QZ","USB permission ...");
final Boolean[] granted = {null};
BroadcastReceiver usbReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
granted[0] = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
}
};
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0;
PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0, new Intent("org.cagnulen.qdomyoszwift.USB_PERMISSION"), flags);
IntentFilter filter = new IntentFilter("org.cagnulen.qdomyoszwift.USB_PERMISSION");
context.registerReceiver(usbReceiver, filter);
manager.requestPermission(driver.getDevice(), permissionIntent);
for(int i=0; i<5000; i++) {
if(granted[0] != null) break;
try {
Thread.sleep(1);
}
catch (InterruptedException e) {
// Do something here
}
}
Log.d("QZ","USB permission "+granted[0]);
}
UsbDeviceConnection connection = manager.openDevice(driver.getDevice());
if (connection == null) {
Log.d("QZ","UsbSerial no permissions");
// add UsbManager.requestPermission(driver.getDevice(), ..) handling here
return;
}
port = driver.getPorts().get(0); // Most devices have just one port (port 0)
try {
port.open(connection);
port.setParameters(2400, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
}
catch (IOException e) {
// Do something here
}
Log.d("QZ","UsbSerial port opened");
}
public static void write (byte[] bytes) {
if(port == null)
return;
Log.d("QZ","UsbSerial writing " + new String(bytes, StandardCharsets.UTF_8));
try {
port.write(bytes, 2000);
}
catch (IOException e) {
// Do something here
}
}
public static int readLen() {
return lastReadLen;
}
public static byte[] read() {
if(port == null) {
lastReadLen = 0;
return receiveData;
}
try {
lastReadLen = port.read(receiveData, 2000);
Log.d("QZ","UsbSerial reading " + lastReadLen + new String(receiveData, StandardCharsets.UTF_8));
}
catch (IOException e) {
// Do something here
}
return receiveData;
}
}

View File

@@ -1,16 +0,0 @@
package com.cgutman.adblib;
/**
* This interface specifies the required functions for AdbCrypto to
* perform Base64 encoding of its public key.
* @author Cameron Gutman
*/
public interface AdbBase64 {
/**
* This function must encoded the specified data as a base 64 string, without
* appending any extra newlines or other characters.
* @param data Data to encode
* @return String containing base 64 encoded data
*/
public String encodeToString(byte[] data);
}

View File

@@ -1,374 +0,0 @@
package com.cgutman.adblib;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.Socket;
import java.util.HashMap;
/**
* This class represents an ADB connection.
* @author Cameron Gutman
*/
public class AdbConnection implements Closeable {
/** The underlying socket that this class uses to
* communicate with the target device.
*/
private Socket socket;
/** The last allocated local stream ID. The ID
* chosen for the next stream will be this value + 1.
*/
private int lastLocalId;
/**
* The input stream that this class uses to read from
* the socket.
*/
private InputStream inputStream;
/**
* The output stream that this class uses to read from
* the socket.
*/
OutputStream outputStream;
/**
* The backend thread that handles responding to ADB packets.
*/
private Thread connectionThread;
/**
* Specifies whether a connect has been attempted
*/
private boolean connectAttempted;
/**
* Specifies whether a CNXN packet has been received from the peer.
*/
private boolean connected;
/**
* Specifies the maximum amount data that can be sent to the remote peer.
* This is only valid after connect() returns successfully.
*/
private int maxData;
/**
* An initialized ADB crypto object that contains a key pair.
*/
private AdbCrypto crypto;
/**
* Specifies whether this connection has already sent a signed token.
*/
private boolean sentSignature;
/**
* A hash map of our open streams indexed by local ID.
**/
private HashMap<Integer, AdbStream> openStreams;
/**
* Internal constructor to initialize some internal state
*/
private AdbConnection()
{
openStreams = new HashMap<Integer, AdbStream>();
lastLocalId = 0;
connectionThread = createConnectionThread();
}
/**
* Creates a AdbConnection object associated with the socket and
* crypto object specified.
* @param socket The socket that the connection will use for communcation.
* @param crypto The crypto object that stores the key pair for authentication.
* @return A new AdbConnection object.
* @throws IOException If there is a socket error
*/
public static AdbConnection create(Socket socket, AdbCrypto crypto) throws IOException
{
AdbConnection newConn = new AdbConnection();
newConn.crypto = crypto;
newConn.socket = socket;
newConn.inputStream = socket.getInputStream();
newConn.outputStream = socket.getOutputStream();
/* Disable Nagle because we're sending tiny packets */
socket.setTcpNoDelay(true);
return newConn;
}
/**
* Creates a new connection thread.
* @return A new connection thread.
*/
private Thread createConnectionThread()
{
@SuppressWarnings("resource")
final AdbConnection conn = this;
return new Thread(new Runnable() {
@Override
public void run() {
while (!connectionThread.isInterrupted())
{
try {
/* Read and parse a message off the socket's input stream */
AdbProtocol.AdbMessage msg = AdbProtocol.AdbMessage.parseAdbMessage(inputStream);
/* Verify magic and checksum */
if (!AdbProtocol.validateMessage(msg))
continue;
switch (msg.command)
{
/* Stream-oriented commands */
case AdbProtocol.CMD_OKAY:
case AdbProtocol.CMD_WRTE:
case AdbProtocol.CMD_CLSE:
/* We must ignore all packets when not connected */
if (!conn.connected)
continue;
/* Get the stream object corresponding to the packet */
AdbStream waitingStream = openStreams.get(msg.arg1);
if (waitingStream == null)
continue;
synchronized (waitingStream) {
if (msg.command == AdbProtocol.CMD_OKAY)
{
/* We're ready for writes */
waitingStream.updateRemoteId(msg.arg0);
waitingStream.readyForWrite();
/* Unwait an open/write */
waitingStream.notify();
}
else if (msg.command == AdbProtocol.CMD_WRTE)
{
/* Got some data from our partner */
waitingStream.addPayload(msg.payload);
/* Tell it we're ready for more */
waitingStream.sendReady();
}
else if (msg.command == AdbProtocol.CMD_CLSE)
{
/* He doesn't like us anymore :-( */
conn.openStreams.remove(msg.arg1);
/* Notify readers and writers */
waitingStream.notifyClose();
}
}
break;
case AdbProtocol.CMD_AUTH:
byte[] packet;
if (msg.arg0 == AdbProtocol.AUTH_TYPE_TOKEN)
{
/* This is an authentication challenge */
if (conn.sentSignature)
{
/* We've already tried our signature, so send our public key */
packet = AdbProtocol.generateAuth(AdbProtocol.AUTH_TYPE_RSA_PUBLIC,
conn.crypto.getAdbPublicKeyPayload());
}
else
{
/* We'll sign the token */
packet = AdbProtocol.generateAuth(AdbProtocol.AUTH_TYPE_SIGNATURE,
conn.crypto.signAdbTokenPayload(msg.payload));
conn.sentSignature = true;
}
/* Write the AUTH reply */
conn.outputStream.write(packet);
conn.outputStream.flush();
}
break;
case AdbProtocol.CMD_CNXN:
synchronized (conn) {
/* We need to store the max data size */
conn.maxData = msg.arg1;
/* Mark us as connected and unwait anyone waiting on the connection */
conn.connected = true;
conn.notifyAll();
}
break;
default:
/* Unrecognized packet, just drop it */
break;
}
} catch (Exception e) {
/* The cleanup is taken care of by a combination of this thread
* and close() */
break;
}
}
/* This thread takes care of cleaning up pending streams */
synchronized (conn) {
cleanupStreams();
conn.notifyAll();
conn.connectAttempted = false;
}
}
});
}
/**
* Gets the max data size that the remote client supports.
* A connection must have been attempted before calling this routine.
* This routine will block if a connection is in progress.
* @return The maximum data size indicated in the connect packet.
* @throws InterruptedException If a connection cannot be waited on.
* @throws IOException if the connection fails
*/
public int getMaxData() throws InterruptedException, IOException
{
if (!connectAttempted)
throw new IllegalStateException("connect() must be called first");
synchronized (this) {
/* Block if a connection is pending, but not yet complete */
if (!connected)
wait();
if (!connected) {
throw new IOException("Connection failed");
}
}
return maxData;
}
/**
* Connects to the remote device. This routine will block until the connection
* completes.
* @throws IOException If the socket fails while connecting
* @throws InterruptedException If we are unable to wait for the connection to finish
*/
public void connect() throws IOException, InterruptedException
{
if (connected)
throw new IllegalStateException("Already connected");
/* Write the CONNECT packet */
outputStream.write(AdbProtocol.generateConnect());
outputStream.flush();
/* Start the connection thread to respond to the peer */
connectAttempted = true;
connectionThread.start();
/* Wait for the connection to go live */
synchronized (this) {
if (!connected)
wait();
if (!connected) {
throw new IOException("Connection failed");
}
}
}
/**
* Opens an AdbStream object corresponding to the specified destination.
* This routine will block until the connection completes.
* @param destination The destination to open on the target
* @return AdbStream object corresponding to the specified destination
* @throws UnsupportedEncodingException If the destination cannot be encoded to UTF-8
* @throws IOException If the stream fails while sending the packet
* @throws InterruptedException If we are unable to wait for the connection to finish
*/
public AdbStream open(String destination) throws UnsupportedEncodingException, IOException, InterruptedException
{
int localId = ++lastLocalId;
if (!connectAttempted)
throw new IllegalStateException("connect() must be called first");
/* Wait for the connect response */
synchronized (this) {
if (!connected)
wait();
if (!connected) {
throw new IOException("Connection failed");
}
}
/* Add this stream to this list of half-open streams */
AdbStream stream = new AdbStream(this, localId);
openStreams.put(localId, stream);
/* Send the open */
outputStream.write(AdbProtocol.generateOpen(localId, destination));
outputStream.flush();
/* Wait for the connection thread to receive the OKAY */
synchronized (stream) {
stream.wait();
}
/* Check if the open was rejected */
if (stream.isClosed())
throw new ConnectException("Stream open actively rejected by remote peer");
/* We're fully setup now */
return stream;
}
/**
* This function terminates all I/O on streams associated with this ADB connection
*/
private void cleanupStreams() {
/* Close all streams on this connection */
for (AdbStream s : openStreams.values()) {
/* We handle exceptions for each close() call to avoid
* terminating cleanup for one failed close(). */
try {
s.close();
} catch (IOException e) {}
}
/* No open streams anymore */
openStreams.clear();
}
/** This routine closes the Adb connection and underlying socket
* @throws IOException if the socket fails to close
*/
@Override
public void close() throws IOException {
/* If the connection thread hasn't spawned yet, there's nothing to do */
if (connectionThread == null)
return;
/* Closing the socket will kick the connection thread */
socket.close();
/* Wait for the connection thread to die */
connectionThread.interrupt();
try {
connectionThread.join();
} catch (InterruptedException e) { }
}
}

View File

@@ -1,249 +0,0 @@
package com.cgutman.adblib;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
/**
* This class encapsulates the ADB cryptography functions and provides
* an interface for the storage and retrieval of keys.
* @author Cameron Gutman
*/
public class AdbCrypto {
/** An RSA keypair encapsulated by the AdbCrypto object */
private KeyPair keyPair;
/** The base 64 conversion interface to use */
private AdbBase64 base64;
/** The ADB RSA key length in bits */
public static final int KEY_LENGTH_BITS = 2048;
/** The ADB RSA key length in bytes */
public static final int KEY_LENGTH_BYTES = KEY_LENGTH_BITS / 8;
/** The ADB RSA key length in words */
public static final int KEY_LENGTH_WORDS = KEY_LENGTH_BYTES / 4;
/** The RSA signature padding as an int array */
public static final int[] SIGNATURE_PADDING_AS_INT = new int[]
{
0x00,0x01,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,
0x30,0x21,0x30,0x09,0x06,0x05,0x2b,0x0e,0x03,0x02,0x1a,0x05,0x00,
0x04,0x14
};
/** The RSA signature padding as a byte array */
public static byte[] SIGNATURE_PADDING;
static {
SIGNATURE_PADDING = new byte[SIGNATURE_PADDING_AS_INT.length];
for (int i = 0; i < SIGNATURE_PADDING.length; i++)
SIGNATURE_PADDING[i] = (byte)SIGNATURE_PADDING_AS_INT[i];
}
/**
* Converts a standard RSAPublicKey object to the special ADB format
* @param pubkey RSAPublicKey object to convert
* @return Byte array containing the converted RSAPublicKey object
*/
private static byte[] convertRsaPublicKeyToAdbFormat(RSAPublicKey pubkey)
{
/*
* ADB literally just saves the RSAPublicKey struct to a file.
*
* typedef struct RSAPublicKey {
* int len; // Length of n[] in number of uint32_t
* uint32_t n0inv; // -1 / n[0] mod 2^32
* uint32_t n[RSANUMWORDS]; // modulus as little endian array
* uint32_t rr[RSANUMWORDS]; // R^2 as little endian array
* int exponent; // 3 or 65537
* } RSAPublicKey;
*/
/* ------ This part is a Java-ified version of RSA_to_RSAPublicKey from adb_host_auth.c ------ */
BigInteger r32, r, rr, rem, n, n0inv;
r32 = BigInteger.ZERO.setBit(32);
n = pubkey.getModulus();
r = BigInteger.ZERO.setBit(KEY_LENGTH_WORDS * 32);
rr = r.modPow(BigInteger.valueOf(2), n);
rem = n.remainder(r32);
n0inv = rem.modInverse(r32);
int myN[] = new int[KEY_LENGTH_WORDS];
int myRr[] = new int[KEY_LENGTH_WORDS];
BigInteger res[];
for (int i = 0; i < KEY_LENGTH_WORDS; i++)
{
res = rr.divideAndRemainder(r32);
rr = res[0];
rem = res[1];
myRr[i] = rem.intValue();
res = n.divideAndRemainder(r32);
n = res[0];
rem = res[1];
myN[i] = rem.intValue();
}
/* ------------------------------------------------------------------------------------------- */
ByteBuffer bbuf = ByteBuffer.allocate(524).order(ByteOrder.LITTLE_ENDIAN);
bbuf.putInt(KEY_LENGTH_WORDS);
bbuf.putInt(n0inv.negate().intValue());
for (int i : myN)
bbuf.putInt(i);
for (int i : myRr)
bbuf.putInt(i);
bbuf.putInt(pubkey.getPublicExponent().intValue());
return bbuf.array();
}
/**
* Creates a new AdbCrypto object from a key pair loaded from files.
* @param base64 Implementation of base 64 conversion interface required by ADB
* @param privateKey File containing the RSA private key
* @param publicKey File containing the RSA public key
* @return New AdbCrypto object
* @throws IOException If the files cannot be read
* @throws NoSuchAlgorithmException If an RSA key factory cannot be found
* @throws InvalidKeySpecException If a PKCS8 or X509 key spec cannot be found
*/
public static AdbCrypto loadAdbKeyPair(AdbBase64 base64, File privateKey, File publicKey) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException
{
AdbCrypto crypto = new AdbCrypto();
int privKeyLength = (int)privateKey.length();
int pubKeyLength = (int)publicKey.length();
byte[] privKeyBytes = new byte[privKeyLength];
byte[] pubKeyBytes = new byte[pubKeyLength];
FileInputStream privIn = new FileInputStream(privateKey);
FileInputStream pubIn = new FileInputStream(publicKey);
privIn.read(privKeyBytes);
pubIn.read(pubKeyBytes);
privIn.close();
pubIn.close();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privKeyBytes);
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pubKeyBytes);
crypto.keyPair = new KeyPair(keyFactory.generatePublic(publicKeySpec),
keyFactory.generatePrivate(privateKeySpec));
crypto.base64 = base64;
return crypto;
}
/**
* Creates a new AdbCrypto object by generating a new key pair.
* @param base64 Implementation of base 64 conversion interface required by ADB
* @return A new AdbCrypto object
* @throws NoSuchAlgorithmException If an RSA key factory cannot be found
*/
public static AdbCrypto generateAdbKeyPair(AdbBase64 base64) throws NoSuchAlgorithmException
{
AdbCrypto crypto = new AdbCrypto();
KeyPairGenerator rsaKeyPg = KeyPairGenerator.getInstance("RSA");
rsaKeyPg.initialize(KEY_LENGTH_BITS);
crypto.keyPair = rsaKeyPg.genKeyPair();
crypto.base64 = base64;
return crypto;
}
/**
* Signs the ADB SHA1 payload with the private key of this object.
* @param payload SHA1 payload to sign
* @return Signed SHA1 payload
* @throws GeneralSecurityException If signing fails
*/
public byte[] signAdbTokenPayload(byte[] payload) throws GeneralSecurityException
{
Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
c.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate());
c.update(SIGNATURE_PADDING);
return c.doFinal(payload);
}
/**
* Gets the RSA public key in ADB format.
* @return Byte array containing the RSA public key in ADB format.
* @throws IOException If the key cannot be retrived
*/
public byte[] getAdbPublicKeyPayload() throws IOException
{
byte[] convertedKey = convertRsaPublicKeyToAdbFormat((RSAPublicKey)keyPair.getPublic());
StringBuilder keyString = new StringBuilder(720);
/* The key is base64 encoded with a user@host suffix and terminated with a NUL */
keyString.append(base64.encodeToString(convertedKey));
keyString.append(" unknown@unknown");
keyString.append('\0');
return keyString.toString().getBytes("UTF-8");
}
/**
* Saves the AdbCrypto's key pair to the specified files.
* @param privateKey The file to store the encoded private key
* @param publicKey The file to store the encoded public key
* @throws IOException If the files cannot be written
*/
public void saveAdbKeyPair(File privateKey, File publicKey) throws IOException
{
FileOutputStream privOut = new FileOutputStream(privateKey);
FileOutputStream pubOut = new FileOutputStream(publicKey);
privOut.write(keyPair.getPrivate().getEncoded());
pubOut.write(keyPair.getPublic().getEncoded());
privOut.close();
pubOut.close();
}
}

View File

@@ -1,310 +0,0 @@
package com.cgutman.adblib;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* This class provides useful functions and fields for ADB protocol details.
* @author Cameron Gutman
*/
public class AdbProtocol {
/** The length of the ADB message header */
public static final int ADB_HEADER_LENGTH = 24;
public static final int CMD_SYNC = 0x434e5953;
/** CNXN is the connect message. No messages (except AUTH)
* are valid before this message is received. */
public static final int CMD_CNXN = 0x4e584e43;
/** The current version of the ADB protocol */
public static final int CONNECT_VERSION = 0x01000000;
/** The maximum data payload supported by the ADB implementation */
public static final int CONNECT_MAXDATA = 4096;
/** The payload sent with the connect message */
public static byte[] CONNECT_PAYLOAD;
static {
try {
CONNECT_PAYLOAD = "host::\0".getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {}
}
/** AUTH is the authentication message. It is part of the
* RSA public key authentication added in Android 4.2.2. */
public static final int CMD_AUTH = 0x48545541;
/** This authentication type represents a SHA1 hash to sign */
public static final int AUTH_TYPE_TOKEN = 1;
/** This authentication type represents the signed SHA1 hash */
public static final int AUTH_TYPE_SIGNATURE = 2;
/** This authentication type represents a RSA public key */
public static final int AUTH_TYPE_RSA_PUBLIC = 3;
/** OPEN is the open stream message. It is sent to open
* a new stream on the target device. */
public static final int CMD_OPEN = 0x4e45504f;
/** OKAY is a success message. It is sent when a write is
* processed successfully. */
public static final int CMD_OKAY = 0x59414b4f;
/** CLSE is the close stream message. It it sent to close an
* existing stream on the target device. */
public static final int CMD_CLSE = 0x45534c43;
/** WRTE is the write stream message. It is sent with a payload
* that is the data to write to the stream. */
public static final int CMD_WRTE = 0x45545257;
/**
* This function performs a checksum on the ADB payload data.
* @param payload Payload to checksum
* @return The checksum of the payload
*/
private static int getPayloadChecksum(byte[] payload)
{
int checksum = 0;
for (byte b : payload)
{
/* We have to manually "unsign" these bytes because Java sucks */
if (b >= 0)
checksum += b;
else
checksum += b+256;
}
return checksum;
}
/**
* This function validate the ADB message by checking
* its command, magic, and payload checksum.
* @param msg ADB message to validate
* @return True if the message was valid, false otherwise
*/
public static boolean validateMessage(AdbMessage msg)
{
/* Magic is cmd ^ 0xFFFFFFFF */
if (msg.command != (msg.magic ^ 0xFFFFFFFF))
return false;
if (msg.payloadLength != 0)
{
if (getPayloadChecksum(msg.payload) != msg.checksum)
return false;
}
return true;
}
/**
* This function generates an ADB message given the fields.
* @param cmd Command identifier
* @param arg0 First argument
* @param arg1 Second argument
* @param payload Data payload
* @return Byte array containing the message
*/
public static byte[] generateMessage(int cmd, int arg0, int arg1, byte[] payload)
{
/* struct message {
* unsigned command; // command identifier constant
* unsigned arg0; // first argument
* unsigned arg1; // second argument
* unsigned data_length; // length of payload (0 is allowed)
* unsigned data_check; // checksum of data payload
* unsigned magic; // command ^ 0xffffffff
* };
*/
ByteBuffer message;
if (payload != null)
{
message = ByteBuffer.allocate(ADB_HEADER_LENGTH + payload.length).order(ByteOrder.LITTLE_ENDIAN);
}
else
{
message = ByteBuffer.allocate(ADB_HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN);
}
message.putInt(cmd);
message.putInt(arg0);
message.putInt(arg1);
if (payload != null)
{
message.putInt(payload.length);
message.putInt(getPayloadChecksum(payload));
}
else
{
message.putInt(0);
message.putInt(0);
}
message.putInt(cmd ^ 0xFFFFFFFF);
if (payload != null)
{
message.put(payload);
}
return message.array();
}
/**
* Generates a connect message with default parameters.
* @return Byte array containing the message
*/
public static byte[] generateConnect()
{
return generateMessage(CMD_CNXN, CONNECT_VERSION, CONNECT_MAXDATA, CONNECT_PAYLOAD);
}
/**
* Generates an auth message with the specified type and payload.
* @param type Authentication type (see AUTH_TYPE_* constants)
* @param data The payload for the message
* @return Byte array containing the message
*/
public static byte[] generateAuth(int type, byte[] data)
{
return generateMessage(CMD_AUTH, type, 0, data);
}
/**
* Generates an open stream message with the specified local ID and destination.
* @param localId A unique local ID identifying the stream
* @param dest The destination of the stream on the target
* @return Byte array containing the message
* @throws UnsupportedEncodingException If the destination cannot be encoded to UTF-8
*/
public static byte[] generateOpen(int localId, String dest) throws UnsupportedEncodingException
{
ByteBuffer bbuf = ByteBuffer.allocate(dest.length() + 1);
bbuf.put(dest.getBytes("UTF-8"));
bbuf.put((byte)0);
return generateMessage(CMD_OPEN, localId, 0, bbuf.array());
}
/**
* Generates a write stream message with the specified IDs and payload.
* @param localId The unique local ID of the stream
* @param remoteId The unique remote ID of the stream
* @param data The data to provide as the write payload
* @return Byte array containing the message
*/
public static byte[] generateWrite(int localId, int remoteId, byte[] data)
{
return generateMessage(CMD_WRTE, localId, remoteId, data);
}
/**
* Generates a close stream message with the specified IDs.
* @param localId The unique local ID of the stream
* @param remoteId The unique remote ID of the stream
* @return Byte array containing the message
*/
public static byte[] generateClose(int localId, int remoteId)
{
return generateMessage(CMD_CLSE, localId, remoteId, null);
}
/**
* Generates an okay message with the specified IDs.
* @param localId The unique local ID of the stream
* @param remoteId The unique remote ID of the stream
* @return Byte array containing the message
*/
public static byte[] generateReady(int localId, int remoteId)
{
return generateMessage(CMD_OKAY, localId, remoteId, null);
}
/**
* This class provides an abstraction for the ADB message format.
* @author Cameron Gutman
*/
final static class AdbMessage {
/** The command field of the message */
public int command;
/** The arg0 field of the message */
public int arg0;
/** The arg1 field of the message */
public int arg1;
/** The payload length field of the message */
public int payloadLength;
/** The checksum field of the message */
public int checksum;
/** The magic field of the message */
public int magic;
/** The payload of the message */
public byte[] payload;
/**
* Read and parse an ADB message from the supplied input stream.
* This message is NOT validated.
* @param in InputStream object to read data from
* @return An AdbMessage object represented the message read
* @throws IOException If the stream fails while reading
*/
public static AdbMessage parseAdbMessage(InputStream in) throws IOException
{
AdbMessage msg = new AdbMessage();
ByteBuffer packet = ByteBuffer.allocate(ADB_HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN);
/* Read the header first */
int dataRead = 0;
do
{
int bytesRead = in.read(packet.array(), dataRead, 24 - dataRead);
if (bytesRead < 0)
throw new IOException("Stream closed");
else
dataRead += bytesRead;
}
while (dataRead < ADB_HEADER_LENGTH);
/* Pull out header fields */
msg.command = packet.getInt();
msg.arg0 = packet.getInt();
msg.arg1 = packet.getInt();
msg.payloadLength = packet.getInt();
msg.checksum = packet.getInt();
msg.magic = packet.getInt();
/* If there's a payload supplied, read that too */
if (msg.payloadLength != 0)
{
msg.payload = new byte[msg.payloadLength];
dataRead = 0;
do
{
int bytesRead = in.read(msg.payload, dataRead, msg.payloadLength - dataRead);
if (bytesRead < 0)
throw new IOException("Stream closed");
else
dataRead += bytesRead;
}
while (dataRead < msg.payloadLength);
}
return msg;
}
}
}

View File

@@ -1,209 +0,0 @@
package com.cgutman.adblib;
import java.io.Closeable;
import java.io.IOException;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* This class abstracts the underlying ADB streams
* @author Cameron Gutman
*/
public class AdbStream implements Closeable {
/** The AdbConnection object that the stream communicates over */
private AdbConnection adbConn;
/** The local ID of the stream */
private int localId;
/** The remote ID of the stream */
private int remoteId;
/** Indicates whether a write is currently allowed */
private AtomicBoolean writeReady;
/** A queue of data from the target's write packets */
private Queue<byte[]> readQueue;
/** Indicates whether the connection is closed already */
private boolean isClosed;
/**
* Creates a new AdbStream object on the specified AdbConnection
* with the given local ID.
* @param adbConn AdbConnection that this stream is running on
* @param localId Local ID of the stream
*/
public AdbStream(AdbConnection adbConn, int localId)
{
this.adbConn = adbConn;
this.localId = localId;
this.readQueue = new ConcurrentLinkedQueue<byte[]>();
this.writeReady = new AtomicBoolean(false);
this.isClosed = false;
}
/**
* Called by the connection thread to indicate newly received data.
* @param payload Data inside the write message
*/
void addPayload(byte[] payload)
{
synchronized (readQueue) {
readQueue.add(payload);
readQueue.notifyAll();
}
}
/**
* Called by the connection thread to send an OKAY packet, allowing the
* other side to continue transmission.
* @throws IOException If the connection fails while sending the packet
*/
void sendReady() throws IOException
{
/* Generate and send a READY packet */
byte[] packet = AdbProtocol.generateReady(localId, remoteId);
adbConn.outputStream.write(packet);
adbConn.outputStream.flush();
}
/**
* Called by the connection thread to update the remote ID for this stream
* @param remoteId New remote ID
*/
void updateRemoteId(int remoteId)
{
this.remoteId = remoteId;
}
/**
* Called by the connection thread to indicate the stream is okay to send data.
*/
void readyForWrite()
{
writeReady.set(true);
}
/**
* Called by the connection thread to notify that the stream was closed by the peer.
*/
void notifyClose()
{
/* We don't call close() because it sends another CLOSE */
isClosed = true;
/* Unwait readers and writers */
synchronized (this) {
notifyAll();
}
synchronized (readQueue) {
readQueue.notifyAll();
}
}
/**
* Reads a pending write payload from the other side.
* @return Byte array containing the payload of the write
* @throws InterruptedException If we are unable to wait for data
* @throws IOException If the stream fails while waiting
*/
public byte[] read() throws InterruptedException, IOException
{
byte[] data = null;
synchronized (readQueue) {
/* Wait for the connection to close or data to be received */
while (!isClosed && (data = readQueue.poll()) == null) {
readQueue.wait();
}
if (isClosed) {
throw new IOException("Stream closed");
}
}
return data;
}
/**
* Sends a write packet with a given String payload.
* @param payload Payload in the form of a String
* @throws IOException If the stream fails while sending data
* @throws InterruptedException If we are unable to wait to send data
*/
public void write(String payload) throws IOException, InterruptedException
{
/* ADB needs null-terminated strings */
write(payload.getBytes("UTF-8"), false);
write(new byte[]{0}, true);
}
/**
* Sends a write packet with a given byte array payload.
* @param payload Payload in the form of a byte array
* @throws IOException If the stream fails while sending data
* @throws InterruptedException If we are unable to wait to send data
*/
public void write(byte[] payload) throws IOException, InterruptedException
{
write(payload, true);
}
/**
* Queues a write packet and optionally sends it immediately.
* @param payload Payload in the form of a byte array
* @param flush Specifies whether to send the packet immediately
* @throws IOException If the stream fails while sending data
* @throws InterruptedException If we are unable to wait to send data
*/
public void write(byte[] payload, boolean flush) throws IOException, InterruptedException
{
synchronized (this) {
/* Make sure we're ready for a write */
while (!isClosed && !writeReady.compareAndSet(true, false))
wait();
if (isClosed) {
throw new IOException("Stream closed");
}
}
/* Generate a WRITE packet and send it */
byte[] packet = AdbProtocol.generateWrite(localId, remoteId, payload);
adbConn.outputStream.write(packet);
if (flush)
adbConn.outputStream.flush();
}
/**
* Closes the stream. This sends a close message to the peer.
* @throws IOException If the stream fails while sending the close message.
*/
@Override
public void close() throws IOException {
synchronized (this) {
/* This may already be closed by the remote host */
if (isClosed)
return;
/* Notify readers/writers that we've closed */
notifyClose();
}
byte[] packet = AdbProtocol.generateClose(localId, remoteId);
adbConn.outputStream.write(packet);
adbConn.outputStream.flush();
}
/**
* Retreives whether the stream is closed or not
* @return True if the stream is close, false if not
*/
public boolean isClosed() {
return isClosed;
}
}

View File

@@ -1,5 +0,0 @@
/**
* This package provides a native Java implementation of the ADB protocol.
* @author Cameron Gutman
*/
package com.cgutman.adblib;

View File

@@ -1,74 +0,0 @@
package com.cgutman.androidremotedebugger;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import com.cgutman.adblib.AdbCrypto;
import com.cgutman.androidremotedebugger.adblib.AndroidBase64;
public class AdbUtils {
public static final String PUBLIC_KEY_NAME = "public.key";
public static final String PRIVATE_KEY_NAME = "private.key";
public static AdbCrypto readCryptoConfig(File dataDir) {
File pubKey = new File(dataDir, PUBLIC_KEY_NAME);
File privKey = new File(dataDir, PRIVATE_KEY_NAME);
AdbCrypto crypto = null;
if (pubKey.exists() && privKey.exists())
{
try {
crypto = AdbCrypto.loadAdbKeyPair(new AndroidBase64(), privKey, pubKey);
} catch (Exception e) {
crypto = null;
}
}
return crypto;
}
public static AdbCrypto writeNewCryptoConfig(File dataDir) {
File pubKey = new File(dataDir, PUBLIC_KEY_NAME);
File privKey = new File(dataDir, PRIVATE_KEY_NAME);
AdbCrypto crypto = null;
try {
crypto = AdbCrypto.generateAdbKeyPair(new AndroidBase64());
crypto.saveAdbKeyPair(privKey, pubKey);
} catch (Exception e) {
crypto = null;
}
return crypto;
}
public static boolean safeClose(Closeable c) {
if (c == null)
return false;
try {
c.close();
} catch (IOException e) {
return false;
}
return true;
}
public static void safeAsyncClose(final Closeable c) {
if (c == null)
return;
new Thread(new Runnable() {
@Override
public void run() {
try {
c.close();
} catch (IOException ignored) {}
}
}).start();
}
}

View File

@@ -1,12 +0,0 @@
package com.cgutman.androidremotedebugger.adblib;
import android.util.Base64;
import com.cgutman.adblib.AdbBase64;
public class AndroidBase64 implements AdbBase64 {
@Override
public String encodeToString(byte[] data) {
return Base64.encodeToString(data, Base64.NO_WRAP);
}
}

View File

@@ -1,56 +0,0 @@
package com.cgutman.androidremotedebugger.console;
import java.util.LinkedList;
import android.content.Context;
import android.content.SharedPreferences;
import android.view.ContextMenu;
import android.view.Menu;
public class CommandHistory {
private SharedPreferences prefs;
private LinkedList<String> previousCommands;
private int historyLimit;
public static CommandHistory loadCommandHistoryFromPrefs(int limit, Context context, String pref) {
CommandHistory ch = new CommandHistory(limit);
ch.prefs = context.getSharedPreferences(pref, 0);
int size = ch.prefs.getInt("Size", 0);
for (int i = 0; i < size; i++) {
String cmd = ch.prefs.getString(""+i, null);
if (cmd != null)
ch.add(cmd);
}
return ch;
}
private CommandHistory(int historyLimit) {
this.previousCommands = new LinkedList<String>();
this.historyLimit = historyLimit;
}
public void add(String command) {
if (previousCommands.size() > historyLimit)
previousCommands.removeFirst();
previousCommands.add(command);
}
public void populateMenu(ContextMenu menu) {
/* We iterate backwards because the first item added is the latest in the command list */
for (int i = previousCommands.size()-1; i >= 0; i--)
menu.add(Menu.NONE, 0, Menu.NONE, previousCommands.get(i));
}
public void save() {
SharedPreferences.Editor edit = prefs.edit();
for (int i = 0; i < previousCommands.size(); i++)
{
edit.putString(""+i, previousCommands.get(i));
}
edit.putInt("Size", previousCommands.size());
edit.apply();
}
}

View File

@@ -1,39 +0,0 @@
package com.cgutman.androidremotedebugger.console;
import android.widget.TextView;
public class ConsoleBuffer {
private char[] buffer;
private int amountPopulated;
public ConsoleBuffer(int bufferSize)
{
buffer = new char[bufferSize];
amountPopulated = 0;
}
public synchronized void append(byte[] asciiData, int offset, int length)
{
if (amountPopulated + length > buffer.length)
{
/* Move the old data backwards */
System.arraycopy(buffer,
length,
buffer,
0,
amountPopulated - length);
amountPopulated -= length;
}
for (int i = 0; i < length; i++)
{
buffer[amountPopulated++] = (char)asciiData[offset+i];
}
}
public synchronized void updateTextView(TextView textView)
{
textView.setText(buffer, 0, amountPopulated);
}
}

View File

@@ -1,200 +0,0 @@
package com.cgutman.androidremotedebugger.devconn;
import java.io.Closeable;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingQueue;
import com.cgutman.adblib.AdbConnection;
import com.cgutman.adblib.AdbCrypto;
import com.cgutman.adblib.AdbStream;
import com.cgutman.androidremotedebugger.AdbUtils;
public class DeviceConnection implements Closeable {
private static final int CONN_TIMEOUT = 5000;
private String host;
private int port;
private DeviceConnectionListener listener;
private AdbConnection connection;
private AdbStream shellStream;
private boolean closed;
private boolean foreground;
private LinkedBlockingQueue<byte[]> commandQueue = new LinkedBlockingQueue<byte[]>();
public DeviceConnection(DeviceConnectionListener listener, String host, int port) {
this.host = host;
this.port = port;
this.listener = listener;
this.foreground = true; /* Connections start in the foreground */
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public boolean queueCommand(String command) {
try {
/* Queue it up for sending to the device */
commandQueue.add(command.getBytes("UTF-8"));
return true;
} catch (UnsupportedEncodingException e) {
return false;
}
}
public boolean queueBytes(byte[] buffer) {
/* Queue it up for sending to the device */
commandQueue.add(buffer);
return true;
}
public void startConnect() {
new Thread(new Runnable() {
@Override
public void run() {
boolean connected = false;
Socket socket = new Socket();
AdbCrypto crypto;
/* Load the crypto config */
crypto = listener.loadAdbCrypto(DeviceConnection.this);
if (crypto == null) {
return;
}
try {
/* Establish a connect to the remote host */
socket.connect(new InetSocketAddress(host, port), CONN_TIMEOUT);
} catch (IOException e) {
listener.notifyConnectionFailed(DeviceConnection.this, e);
return;
}
try {
/* Establish the application layer connection */
connection = AdbConnection.create(socket, crypto);
connection.connect();
/* Open the shell stream */
shellStream = connection.open("shell:");
connected = true;
} catch (IOException e) {
listener.notifyConnectionFailed(DeviceConnection.this, e);
} catch (InterruptedException e) {
listener.notifyConnectionFailed(DeviceConnection.this, e);
} finally {
/* Cleanup if the connection failed */
if (!connected) {
AdbUtils.safeClose(shellStream);
/* The AdbConnection object will close the underlying socket
* but we need to close it ourselves if the AdbConnection object
* wasn't successfully constructed.
*/
if (!AdbUtils.safeClose(connection)) {
try {
socket.close();
} catch (IOException e) {}
}
return;
}
}
/* Notify the listener that the connection is complete */
listener.notifyConnectionEstablished(DeviceConnection.this);
/* Start the receive thread */
startReceiveThread();
/* Enter the blocking send loop */
sendLoop();
}
}).start();
}
private void sendLoop() {
/* We become the send thread */
try {
for (;;) {
/* Get the next command */
byte[] command = commandQueue.take();
/* This may be a close indication */
if (shellStream.isClosed()) {
listener.notifyStreamClosed(DeviceConnection.this);
break;
}
/* Issue it to the device */
shellStream.write(command);
}
} catch (IOException e) {
listener.notifyStreamFailed(DeviceConnection.this, e);
} catch (InterruptedException e) {
} finally {
AdbUtils.safeClose(DeviceConnection.this);
}
}
private void startReceiveThread() {
new Thread(new Runnable() {
@Override
public void run() {
try {
while (!shellStream.isClosed()) {
byte[] data = shellStream.read();
listener.receivedData(DeviceConnection.this, data, 0, data.length);
}
listener.notifyStreamClosed(DeviceConnection.this);
} catch (IOException e) {
listener.notifyStreamFailed(DeviceConnection.this, e);
} catch (InterruptedException e) {
} finally {
AdbUtils.safeClose(DeviceConnection.this);
}
}
}).start();
}
public boolean isClosed() {
return closed;
}
@Override
public void close() throws IOException {
if (isClosed()) {
return;
}
else {
closed = true;
}
/* Close the stream first */
AdbUtils.safeClose(shellStream);
/* Now the connection (and underlying socket) */
AdbUtils.safeClose(connection);
/* Finally signal the command queue to allow the send thread to terminate */
commandQueue.add(new byte[0]);
}
public boolean isForeground() {
return foreground;
}
public void setForeground(boolean foreground) {
this.foreground = foreground;
}
}

View File

@@ -1,24 +0,0 @@
package com.cgutman.androidremotedebugger.devconn;
import com.cgutman.adblib.AdbCrypto;
import com.cgutman.androidremotedebugger.console.ConsoleBuffer;
public interface DeviceConnectionListener {
public void notifyConnectionEstablished(DeviceConnection devConn);
public void notifyConnectionFailed(DeviceConnection devConn, Exception e);
public void notifyStreamFailed(DeviceConnection devConn, Exception e);
public void notifyStreamClosed(DeviceConnection devConn);
public AdbCrypto loadAdbCrypto(DeviceConnection devConn);
public boolean canReceiveData();
public void receivedData(DeviceConnection devConn, byte[] data, int offset, int length);
public boolean isConsole();
public void consoleUpdated(DeviceConnection devConn, ConsoleBuffer console);
}

View File

@@ -1,165 +0,0 @@
package com.cgutman.androidremotedebugger.service;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentHashMap;
import android.app.Service;
import com.cgutman.adblib.AdbCrypto;
import com.cgutman.androidremotedebugger.AdbUtils;
import com.cgutman.androidremotedebugger.console.ConsoleBuffer;
import com.cgutman.androidremotedebugger.devconn.DeviceConnection;
import com.cgutman.androidremotedebugger.devconn.DeviceConnectionListener;
public class ShellListener implements DeviceConnectionListener {
private static final int TERM_LENGTH = 25000;
private final HashMap<DeviceConnection, LinkedList<DeviceConnectionListener>> listenerMap =
new HashMap<DeviceConnection, LinkedList<DeviceConnectionListener>>();
private final ConcurrentHashMap<DeviceConnection, ConsoleBuffer> consoleMap =
new ConcurrentHashMap<DeviceConnection, ConsoleBuffer>();
private Service service;
public ShellListener(Service service) {
this.service = service;
}
public void addListener(DeviceConnection conn, DeviceConnectionListener listener) {
synchronized (listenerMap) {
LinkedList<DeviceConnectionListener> listeners = listenerMap.get(conn);
if (listeners != null) {
listeners.add(listener);
}
else {
listeners = new LinkedList<DeviceConnectionListener>();
listeners.add(listener);
listenerMap.put(conn, listeners);
}
}
/* If the listener supports console input, we'll tell them about the console buffer
* by firing them an initial console updated callback */
ConsoleBuffer console = consoleMap.get(conn);
if (console != null && listener.isConsole()) {
listener.consoleUpdated(conn, console);
}
}
public void removeListener(DeviceConnection conn, DeviceConnectionListener listener) {
synchronized (listenerMap) {
LinkedList<DeviceConnectionListener> listeners = listenerMap.get(conn);
if (listeners != null) {
listeners.remove(listener);
}
}
}
@Override
public void notifyConnectionEstablished(DeviceConnection devConn) {
consoleMap.put(devConn, new ConsoleBuffer(TERM_LENGTH));
synchronized (listenerMap) {
LinkedList<DeviceConnectionListener> listeners = listenerMap.get(devConn);
if (listeners != null) {
for (DeviceConnectionListener listener : listeners) {
listener.notifyConnectionEstablished(devConn);
}
}
}
}
@Override
public void notifyConnectionFailed(DeviceConnection devConn, Exception e) {
synchronized (listenerMap) {
LinkedList<DeviceConnectionListener> listeners = listenerMap.get(devConn);
if (listeners != null) {
for (DeviceConnectionListener listener : listeners) {
listener.notifyConnectionFailed(devConn, e);
}
}
}
}
@Override
public void notifyStreamFailed(DeviceConnection devConn, Exception e) {
/* Return if this connection has already "failed" */
if (consoleMap.remove(devConn) == null) {
return;
}
synchronized (listenerMap) {
LinkedList<DeviceConnectionListener> listeners = listenerMap.get(devConn);
if (listeners != null) {
for (DeviceConnectionListener listener : listeners) {
listener.notifyStreamFailed(devConn, e);
}
}
}
}
@Override
public void notifyStreamClosed(DeviceConnection devConn) {
/* Return if this connection has already "failed" */
if (consoleMap.remove(devConn) == null) {
return;
}
synchronized (listenerMap) {
LinkedList<DeviceConnectionListener> listeners = listenerMap.get(devConn);
if (listeners != null) {
for (DeviceConnectionListener listener : listeners) {
listener.notifyStreamClosed(devConn);
}
}
}
}
@Override
public AdbCrypto loadAdbCrypto(DeviceConnection devConn) {
return AdbUtils.readCryptoConfig(service.getFilesDir());
}
@Override
public void receivedData(DeviceConnection devConn, byte[] data,
int offset, int length) {
/* Add data to the console for this connection */
ConsoleBuffer console = consoleMap.get(devConn);
if (console != null) {
/* Hack to remove the bell from the end of the prompt */
if (data[offset+length-1] == 0x07) {
length--;
}
console.append(data, offset, length);
/* Attempt to deliver a console update notification */
synchronized (listenerMap) {
LinkedList<DeviceConnectionListener> listeners = listenerMap.get(devConn);
if (listeners != null) {
for (DeviceConnectionListener listener : listeners) {
if (listener.isConsole()) {
listener.consoleUpdated(devConn, console);
}
}
}
}
}
}
@Override
public boolean canReceiveData() {
/* We can always receive data */
return true;
}
@Override
public boolean isConsole() {
return false;
}
@Override
public void consoleUpdated(DeviceConnection devConsole,
ConsoleBuffer console) {
}
}

View File

@@ -1,301 +0,0 @@
package com.cgutman.androidremotedebugger.service;
import java.util.HashMap;
import com.cgutman.adblib.AdbCrypto;
import com.cgutman.androidremotedebugger.console.ConsoleBuffer;
import com.cgutman.androidremotedebugger.devconn.DeviceConnection;
import com.cgutman.androidremotedebugger.devconn.DeviceConnectionListener;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import androidx.core.app.NotificationCompat;
public class ShellService extends Service implements DeviceConnectionListener {
private ShellServiceBinder binder = new ShellServiceBinder();
private ShellListener listener = new ShellListener(this);
private HashMap<String, DeviceConnection> currentConnectionMap =
new HashMap<String, DeviceConnection>();
private WifiLock wlanLock;
private WakeLock wakeLock;
private final static int FOREGROUND_PLACEHOLDER_ID = 1;
private final static int CONN_BASE = 12131;
private final static int FAILED_BASE = 12111;
private final static String CHANNEL_ID = "connectionInfo";
private int foregroundId;
public class ShellServiceBinder extends Binder {
public DeviceConnection createConnection(String host, int port) {
DeviceConnection conn = new DeviceConnection(listener, host, port);
listener.addListener(conn, ShellService.this);
return conn;
}
public DeviceConnection findConnection(String host, int port) {
String connStr = host+":"+port;
return currentConnectionMap.get(connStr);
}
public void notifyPausingActivity(DeviceConnection devConn) {
devConn.setForeground(false);
}
public void notifyResumingActivity(DeviceConnection devConn) {
devConn.setForeground(true);
}
public void notifyDestroyingActivity(DeviceConnection devConn) {
/* If we're pausing before destruction after the connection is closed, remove the failure
* notification */
if (devConn.isClosed()) {
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel(getFailedNotificationId(devConn));
}
}
public void addListener(DeviceConnection conn, DeviceConnectionListener listener) {
ShellService.this.listener.addListener(conn, listener);
}
public void removeListener(DeviceConnection conn, DeviceConnectionListener listener) {
ShellService.this.listener.removeListener(conn, listener);
}
}
@Override
public IBinder onBind(Intent arg0) {
return binder;
}
@Override
public boolean onUnbind(Intent intent) {
/* Stop the the service if no connections remain */
if (currentConnectionMap.isEmpty()) {
stopSelf();
}
return false;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (foregroundId == 0) {
// If we're not already running in the foreground, use a placeholder
// notification until a real connection is established. After connection
// establishment, the real notification will replace this one.
startForeground(FOREGROUND_PLACEHOLDER_ID, createForegroundPlaceholderNotification());
}
// Don't restart if we've been killed. We will have already lost our connections
// when we died, so we'll just be running doing nothing if the OS restarted us.
return Service.START_NOT_STICKY;
}
@Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Connection Info", NotificationManager.IMPORTANCE_DEFAULT);
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
WifiManager wm = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
wlanLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL, "RemoteADBShell:ShellService");
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "RemoteADBShell:ShellService");
}
@Override
public void onDestroy() {
if (wlanLock.isHeld()) {
wlanLock.release();
}
if (wakeLock.isHeld()) {
wakeLock.release();
}
super.onDestroy();
}
private int getFailedNotificationId(DeviceConnection devConn) {
return FAILED_BASE + getConnectionString(devConn).hashCode();
}
private int getConnectedNotificationId(DeviceConnection devConn) {
return CONN_BASE + getConnectionString(devConn).hashCode();
}
private Notification createForegroundPlaceholderNotification() {
return new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
.setOngoing(true)
.setSilent(true)
.setContentTitle("Remote ADB Shell")
.setContentText("Connecting...")
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.build();
}
private Notification createConnectionNotification(DeviceConnection devConn, boolean connected) {
String ticker;
String message;
if (connected) {
ticker = "Connection Established";
message = "Connected to "+getConnectionString(devConn);
}
else {
ticker = "Connection Terminated";
message = "Connection to "+getConnectionString(devConn)+" failed";
}
return new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
.setTicker("Remote ADB Shell - "+ticker)
.setOnlyAlertOnce(true)
.setOngoing(connected)
.setAutoCancel(!connected)
.setSilent(connected)
.setContentTitle("Remote ADB Shell")
.setContentText(message)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.build();
}
private void updateNotification(DeviceConnection devConn, boolean connected) {
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
removeNotification(devConn);
}
private void removeNotification(DeviceConnection devConn) {
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
/* Removing failure notifications is easy */
nm.cancel(getFailedNotificationId(devConn));
/* Connected notifications is a bit more complex */
if (getConnectedNotificationId(devConn) == foregroundId) {
/* We're the foreground notification, so we need to switch in another
* notification to take our place */
/* Search for a new device connection to promote */
DeviceConnection newConn = null;
for (DeviceConnection conn : currentConnectionMap.values()) {
if (devConn == conn) {
continue;
}
else {
newConn = conn;
break;
}
}
if (newConn == null) {
/* None found, so we're done in foreground */
stopForeground(true);
foregroundId = 0;
}
else {
/* Found one, so cancel this guy's original notification
* and start it as foreground */
foregroundId = getConnectedNotificationId(newConn);
nm.cancel(foregroundId);
startForeground(foregroundId, createConnectionNotification(newConn, true));
}
}
else {
/* This just a normal connected notification */
nm.cancel(getConnectedNotificationId(devConn));
}
}
private String getConnectionString(DeviceConnection devConn) {
return devConn.getHost()+":"+devConn.getPort();
}
private synchronized void addNewConnection(DeviceConnection devConn) {
if (currentConnectionMap.isEmpty()) {
wakeLock.acquire();
wlanLock.acquire();
}
currentConnectionMap.put(getConnectionString(devConn), devConn);
}
private synchronized void removeConnection(DeviceConnection devConn) {
currentConnectionMap.remove(getConnectionString(devConn));
/* Stop the the service if no connections remain */
if (currentConnectionMap.isEmpty()) {
stopSelf();
}
}
@Override
public void notifyConnectionEstablished(DeviceConnection devConn) {
addNewConnection(devConn);
updateNotification(devConn, true);
}
@Override
public void notifyConnectionFailed(DeviceConnection devConn, Exception e) {
/* No notification is displaying here */
}
@Override
public void notifyStreamFailed(DeviceConnection devConn, Exception e) {
updateNotification(devConn, false);
removeConnection(devConn);
}
@Override
public void notifyStreamClosed(DeviceConnection devConn) {
updateNotification(devConn, false);
removeConnection(devConn);
}
@Override
public AdbCrypto loadAdbCrypto(DeviceConnection devConn) {
return null;
}
@Override
public void receivedData(DeviceConnection devConn, byte[] data, int offset,
int length) {
}
@Override
public boolean canReceiveData() {
return false;
}
@Override
public boolean isConsole() {
return false;
}
@Override
public void consoleUpdated(DeviceConnection devConn,
ConsoleBuffer console) {
}
}

View File

@@ -1,66 +0,0 @@
package com.cgutman.androidremotedebugger.ui;
import java.util.ArrayList;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
public class Dialog implements Runnable {
private String title, message;
private Activity activity;
private boolean endAfterDismiss;
AlertDialog alert;
private static ArrayList<Dialog> rundownDialogs = new ArrayList<Dialog>();
public Dialog(Activity activity, String title, String message, boolean endAfterDismiss)
{
this.activity = activity;
this.title = title;
this.message = message;
this.endAfterDismiss = endAfterDismiss;
}
public static void closeDialogs()
{
for (Dialog d : rundownDialogs)
d.alert.dismiss();
rundownDialogs.clear();
}
public static void displayDialog(Activity activity, String title, String message, boolean endAfterDismiss)
{
activity.runOnUiThread(new Dialog(activity, title, message, endAfterDismiss));
}
@Override
public void run() {
// If we're dying, don't bother creating a dialog
if (activity.isFinishing())
return;
alert = new AlertDialog.Builder(activity).create();
alert.setTitle(title);
alert.setMessage(message);
alert.setCancelable(false);
alert.setCanceledOnTouchOutside(false);
alert.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
alert.dismiss();
rundownDialogs.remove(this);
if (endAfterDismiss)
activity.finish();
}
});
rundownDialogs.add(this);
alert.show();
}
}

View File

@@ -1,88 +0,0 @@
package com.cgutman.androidremotedebugger.ui;
import java.util.ArrayList;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
public class SpinnerDialog implements Runnable,OnCancelListener {
private String title, message;
private Activity activity;
private ProgressDialog progress;
private boolean finish;
private static ArrayList<SpinnerDialog> rundownDialogs = new ArrayList<SpinnerDialog>();
public SpinnerDialog(Activity activity, String title, String message, boolean finish)
{
this.activity = activity;
this.title = title;
this.message = message;
this.progress = null;
this.finish = finish;
}
public static SpinnerDialog displayDialog(Activity activity, String title, String message, boolean finish)
{
SpinnerDialog spinner = new SpinnerDialog(activity, title, message, finish);
activity.runOnUiThread(spinner);
return spinner;
}
public static void closeDialogs()
{
for (SpinnerDialog d : rundownDialogs)
d.progress.dismiss();
rundownDialogs.clear();
}
public void dismiss()
{
// Running again with progress != null will destroy it
activity.runOnUiThread(this);
}
@Override
public void run() {
if (progress == null)
{
// If we're dying, don't bother creating a dialog
if (activity.isFinishing())
return;
progress = new ProgressDialog(activity);
progress.setTitle(title);
progress.setMessage(message);
progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progress.setOnCancelListener(this);
// If we want to finish the activity when this is killed, make it cancellable
if (finish)
{
progress.setCancelable(true);
progress.setCanceledOnTouchOutside(false);
}
else
{
progress.setCancelable(false);
}
progress.show();
}
else
{
progress.dismiss();
}
}
@Override
public void onCancel(DialogInterface dialog) {
// This will only be called if finish was true, so we don't need to check again
activity.finish();
}
}

View File

@@ -1,12 +0,0 @@
#include "androidactivityresultreceiver.h"
#ifdef Q_OS_ANDROID
AndroidActivityResultReceiver::AndroidActivityResultReceiver() { qDebug() << "AndroidActivityResultReceiver"; }
void AndroidActivityResultReceiver::handleActivityResult(int receiverRequestCode, int resultCode,
const QAndroidJniObject &data) {
qDebug() << "AndroidActivityResultReceiver::handleActivityResult" << receiverRequestCode << resultCode;
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/MediaProjection", "startService",
"(Landroid/content/Context;ILandroid/content/Intent;)V",
QtAndroid::androidContext().object(), resultCode, data.object<jobject>());
}
#endif

View File

@@ -1,16 +0,0 @@
#ifndef ANDROIDACTIVITYRESULTRECEIVER_H
#define ANDROIDACTIVITYRESULTRECEIVER_H
#include <QDebug>
#ifdef Q_OS_ANDROID
#include <QtAndroidExtras/QAndroidActivityResultReceiver>
#include <QtAndroidExtras/QAndroidJniEnvironment>
#include <QtAndroidExtras/QAndroidJniObject>
#include <QtAndroidExtras/QtAndroid>
class AndroidActivityResultReceiver : public QAndroidActivityResultReceiver {
public:
AndroidActivityResultReceiver();
virtual void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data);
};
#endif
#endif // ANDROIDACTIVITYRESULTRECEIVER_H

View File

@@ -1,27 +0,0 @@
#include "androidadblog.h"
androidadblog::androidadblog() { }
void androidadblog::run() {
while (1) {
runAdbTailCommand("logcat");
}
}
void androidadblog::runAdbTailCommand(QString command) {
#ifdef Q_OS_ANDROID
auto process = new QProcess;
QObject::connect(process, &QProcess::readyReadStandardOutput, [process, this]() {
QString output = process->readAllStandardOutput();
qDebug() << "adbLogCat STDOUT << " << output;
});
QObject::connect(process, &QProcess::readyReadStandardError, [process, this]() {
auto output = process->readAllStandardError();
qDebug() << "adbLogCat ERROR << " << output;
});
QStringList arguments;
arguments.append("*:e");
process->start("logcat", arguments);
process->waitForFinished(-1);
#endif
}

View File

@@ -1,21 +0,0 @@
#ifndef ANDROIDADBLOG_H
#define ANDROIDADBLOG_H
#include <QDebug>
#include <QProcess>
#include <QThread>
class androidadblog : public QThread
{
Q_OBJECT
public:
explicit androidadblog();
void run();
private:
void runAdbTailCommand(QString command);
};
#endif // ANDROIDADBLOG_H

View File

@@ -1,408 +0,0 @@
#include "apexbike.h"
#include "ios/lockscreen.h"
#include "keepawakehelper.h"
#include "virtualbike.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <chrono>
#include <math.h>
using namespace std::chrono_literals;
apexbike::apexbike(bool noWriteResistance, bool noHeartService, uint8_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, &apexbike::update);
refresh->start(200ms);
}
void apexbike::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;
}
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic,
QByteArray((const char *)data, data_len));
if (!disable_log) {
qDebug() << QStringLiteral(" >> ") + QByteArray((const char *)data, data_len).toHex(' ') +
QStringLiteral(" // ") + info;
}
loop.exec();
}
void apexbike::forceResistance(resistance_t requestResistance) {}
void apexbike::sendPoll() {
uint8_t noOpData[] = {0xaa, 0x55, 0xe0, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xc3, 0x3c};
writeCharacteristic(noOpData, sizeof(noOpData), QStringLiteral("noOp"), false, false);
}
void apexbike::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 5 seconds
if (sec1Update++ >= (5000 / 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 apexbike::serviceDiscovered(const QBluetoothUuid &gatt) {
qDebug() << QStringLiteral("serviceDiscovered ") + gatt.toString();
}
void apexbike::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() != 10 && newValue.at(2) != 0x31) {
return;
}
Resistance = newValue.at(5);
emit resistanceRead(Resistance.value());
m_pelotonResistance = Resistance.value();
qDebug() << QStringLiteral("Current resistance: ") + QString::number(Resistance.value());
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled"))) {
Cadence = ((uint8_t)newValue.at(4));
}
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());
}
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"))) {
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
lockscreen h;
long appleWatchHeartRate = h.heartRate();
h.setKcal(KCal.value());
h.setDistance(Distance.value());
Heart = appleWatchHeartRate;
qDebug() << "Current Heart from Apple Watch: " + QString::number(appleWatchHeartRate);
#endif
#endif
}
}
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence = settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->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 = Resistance.value();
m_pelotonResistance = m_pelotonResistance.value();
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 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();
}
}
void apexbike::btinit() {
uint8_t initData1[] = {0xeb, 0x50, 0x51, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa2};
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
initDone = true;
if (lastResistanceBeforeDisconnection != -1) {
qDebug() << QStringLiteral("forcing resistance to ") + QString::number(lastResistanceBeforeDisconnection) +
QStringLiteral(". It was the last value before the disconnection.");
forceResistance(lastResistanceBeforeDisconnection);
lastResistanceBeforeDisconnection = -1;
}
}
void apexbike::stateChanged(QLowEnergyService::ServiceState state) {
QBluetoothUuid _gattWriteCharacteristicId((quint16)0xfff2);
QBluetoothUuid _gattNotify1CharacteristicId((quint16)0xfff1);
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,
&apexbike::characteristicChanged);
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this,
&apexbike::characteristicWritten);
connect(gattCommunicationChannelService,
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
this, &apexbike::errorService);
connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this,
&apexbike::descriptorWritten);
// ******************************************* virtual bike init *************************************
if (!firstStateChanged && !virtualBike
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
&& !h
#endif
#endif
) {
QSettings settings;
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence =
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence) {
qDebug() << "ios_peloton_workaround activated!";
h = new lockscreen();
h->virtualbike_ios();
} else
#endif
#endif
if (virtual_device_enabled) {
qDebug() << QStringLiteral("creating virtual bike interface...");
virtualBike =
new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
// connect(virtualBike,&virtualbike::debug ,this,&apexbike::debug);
connect(virtualBike, &virtualbike::changeInclination, this, &apexbike::changeInclination);
}
}
firstStateChanged = 1;
// ********************************************************************************************************
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
gattCommunicationChannelService->writeDescriptor(
gattNotify1Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
}
}
void apexbike::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
qDebug() << QStringLiteral("descriptorWritten ") + descriptor.name() + QStringLiteral(" ") + newValue.toHex(' ');
initRequest = true;
emit connectedAndDiscovered();
}
void apexbike::characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
Q_UNUSED(characteristic);
qDebug() << QStringLiteral("characteristicWritten ") + newValue.toHex(' ');
}
void apexbike::serviceScanDone(void) {
qDebug() << QStringLiteral("serviceScanDone");
QBluetoothUuid _gattCommunicationChannelServiceId((quint16)0xfff0);
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &apexbike::stateChanged);
gattCommunicationChannelService->discoverDetails();
}
void apexbike::errorService(QLowEnergyService::ServiceError err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
qDebug() << QStringLiteral("apexbike::errorService") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString();
}
void apexbike::error(QLowEnergyController::Error err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
qDebug() << QStringLiteral("apexbike::error") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString();
}
void apexbike::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, &apexbike::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &apexbike::serviceScanDone);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, &apexbike::error);
connect(m_control, &QLowEnergyController::stateChanged, this, &apexbike::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 apexbike::connected() {
if (!m_control) {
return false;
}
return m_control->state() == QLowEnergyController::DiscoveredState;
}
void *apexbike::VirtualBike() { return virtualBike; }
void *apexbike::VirtualDevice() { return VirtualBike(); }
uint16_t apexbike::watts() {
return wattFromHR(true);
}
void apexbike::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();
}
}

View File

@@ -1,102 +0,0 @@
#ifndef APEXBIKE_H
#define APEXBIKE_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 "bike.h"
#include "virtualbike.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
#endif
class apexbike : public bike {
Q_OBJECT
public:
apexbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain);
bool connected();
void *VirtualBike();
void *VirtualDevice();
private:
const resistance_t max_resistance = 32;
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();
QTimer *refresh;
virtualbike *virtualBike = nullptr;
QLowEnergyService *gattCommunicationChannelService = nullptr;
QLowEnergyCharacteristic gattWriteCharacteristic;
QLowEnergyCharacteristic gattNotify1Characteristic;
uint8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
uint8_t counterPoll = 1;
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 // APEXBIKE_H

View File

@@ -39,19 +39,12 @@ void bike::changePower(int32_t power) {
RequestedPower = power;
requestPower = power; // used by some bikes that have ERG mode builtin
QSettings settings;
bool force_resistance =
settings.value(QZSettings::virtualbike_forceresistance, QZSettings::default_virtualbike_forceresistance)
.toBool();
// bool erg_mode = settings.value(QZSettings::zwift_erg, QZSettings::default_zwift_erg).toBool(); //Not used
// anywhere in code
double erg_filter_upper =
settings.value(QZSettings::zwift_erg_filter, QZSettings::default_zwift_erg_filter).toDouble();
double erg_filter_lower =
settings.value(QZSettings::zwift_erg_filter_down, QZSettings::default_zwift_erg_filter_down).toDouble();
double zwift_erg_resistance_up =
settings.value(QZSettings::zwift_erg_resistance_up, QZSettings::default_zwift_erg_resistance_up).toDouble();
double zwift_erg_resistance_down =
settings.value(QZSettings::zwift_erg_resistance_down, QZSettings::default_zwift_erg_resistance_down).toDouble();
bool force_resistance = settings.value(QZSettings::virtualbike_forceresistance, QZSettings::default_virtualbike_forceresistance).toBool();
// bool erg_mode = settings.value(QZSettings::zwift_erg, QZSettings::default_zwift_erg).toBool(); //Not used anywhere in code
double erg_filter_upper = settings.value(QZSettings::zwift_erg_filter, QZSettings::default_zwift_erg_filter).toDouble();
double erg_filter_lower = settings.value(QZSettings::zwift_erg_filter_down, QZSettings::default_zwift_erg_filter_down).toDouble();
double zwift_erg_resistance_up = settings.value(QZSettings::zwift_erg_resistance_up, QZSettings::default_zwift_erg_resistance_up).toDouble();
double zwift_erg_resistance_down = settings.value(QZSettings::zwift_erg_resistance_down, QZSettings::default_zwift_erg_resistance_down).toDouble();
double deltaDown = wattsMetric().value() - ((double)power);
double deltaUp = ((double)power) - wattsMetric().value();
@@ -73,10 +66,8 @@ void bike::changePower(int32_t power) {
int8_t bike::gears() { return m_gears; }
void bike::setGears(int8_t gears) {
QSettings settings;
qDebug() << "setGears" << gears;
m_gears = gears;
settings.setValue(QZSettings::gears_current_value, m_gears);
if (lastRawRequestedResistanceValue != -1) {
changeResistance(lastRawRequestedResistanceValue);
}
@@ -250,33 +241,3 @@ uint8_t bike::metrics_override_heartrate() {
}
bool bike::inclinationAvailableByHardware() { return false; }
uint16_t bike::wattFromHR(bool useSpeedAndCadence) {
QSettings settings;
double watt = 0;
if (currentCadence().value() == 0 && useSpeedAndCadence == true) {
return 0;
}
if (Heart.value() > 0) {
int avgP = ((settings.value(QZSettings::power_hr_pwr1, QZSettings::default_power_hr_pwr1).toDouble() *
settings.value(QZSettings::power_hr_hr2, QZSettings::default_power_hr_hr2).toDouble()) -
(settings.value(QZSettings::power_hr_pwr2, QZSettings::default_power_hr_pwr2).toDouble() *
settings.value(QZSettings::power_hr_hr1, QZSettings::default_power_hr_hr1).toDouble())) /
(settings.value(QZSettings::power_hr_hr2, QZSettings::default_power_hr_hr2).toDouble() -
settings.value(QZSettings::power_hr_hr1, QZSettings::default_power_hr_hr1).toDouble()) +
(Heart.value() *
((settings.value(QZSettings::power_hr_pwr1, QZSettings::default_power_hr_pwr1).toDouble() -
settings.value(QZSettings::power_hr_pwr2, QZSettings::default_power_hr_pwr2).toDouble()) /
(settings.value(QZSettings::power_hr_hr1, QZSettings::default_power_hr_hr1).toDouble() -
settings.value(QZSettings::power_hr_hr2, QZSettings::default_power_hr_hr2).toDouble())));
if (Speed.value() > 0 || useSpeedAndCadence == false) {
if (avgP < 50) {
avgP = 50;
}
watt = avgP;
} else {
watt = 0;
}
}
return watt;
}

View File

@@ -84,8 +84,6 @@ class bike : public bluetoothdevice {
metric m_steeringAngle;
double m_speedLimit = 0;
uint16_t wattFromHR(bool useSpeedAndCadence);
};
#endif // BIKE_H

File diff suppressed because it is too large Load Diff

View File

@@ -17,19 +17,14 @@
#include <QtCore/qbytearray.h>
#include <QtCore/qloggingcategory.h>
#include "discoveryoptions.h"
#include "qzsettings.h"
#include "activiotreadmill.h"
#include "apexbike.h"
#include "bhfitnesselliptical.h"
#include "bluetoothdevice.h"
#include "bowflext216treadmill.h"
#include "bowflextreadmill.h"
#include "chronobike.h"
#ifndef Q_OS_IOS
#include "computrainerbike.h"
#endif
#include "concept2skierg.h"
#include "cscbike.h"
#include "domyosbike.h"
@@ -75,7 +70,6 @@
#include "octanetreadmill.h"
#include "pafersbike.h"
#include "paferstreadmill.h"
#include "pelotonbike.h"
#include "proformbike.h"
#include "proformelliptical.h"
#include "proformellipticaltrainer.h"
@@ -115,20 +109,16 @@
#include "trxappgateusbbike.h"
#include "trxappgateusbtreadmill.h"
#include "ultrasportbike.h"
#include "wahookickrheadwind.h"
#include "wahookickrsnapbike.h"
#include "yesoulbike.h"
#include "ziprotreadmill.h"
class bluetooth : public QObject, public SignalHandler {
Q_OBJECT
public:
bluetooth(const discoveryoptions &options);
explicit bluetooth(bool logs, const QString &deviceName = QLatin1String(""), bool noWriteResistance = false,
bool noHeartService = false, uint32_t pollDeviceTime = 200, bool noConsole = false,
bool testResistance = false, uint8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0,
bool createTemplateManagers = true, bool startDiscovery = true);
bool testResistance = false, uint8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0);
~bluetooth();
bluetoothdevice *device();
bluetoothdevice *externalInclination() { return eliteRizer; }
@@ -139,20 +129,14 @@ class bluetooth : public QObject, public SignalHandler {
TemplateInfoSenderBuilder *getInnerTemplateManager() const { return innerTemplateManager; }
private:
bool useDiscovery = false;
bool createTemplateManagers = false;
TemplateInfoSenderBuilder *userTemplateManager = nullptr;
TemplateInfoSenderBuilder *innerTemplateManager = nullptr;
QFile *debugCommsLog = nullptr;
QBluetoothDeviceDiscoveryAgent *discoveryAgent = nullptr;
apexbike *apexBike = nullptr;
QBluetoothDeviceDiscoveryAgent *discoveryAgent;
bhfitnesselliptical *bhFitnessElliptical = nullptr;
bowflextreadmill *bowflexTreadmill = nullptr;
bowflext216treadmill *bowflexT216Treadmill = nullptr;
fitshowtreadmill *fitshowTreadmill = nullptr;
#ifndef Q_OS_IOS
computrainerbike *computrainerBike = nullptr;
#endif
concept2skierg *concept2Skierg = nullptr;
domyostreadmill *domyos = nullptr;
domyosbike *domyosBike = nullptr;
@@ -175,7 +159,6 @@ class bluetooth : public QObject, public SignalHandler {
nordictrackifitadbbike *nordictrackifitadbBike = nullptr;
octaneelliptical *octaneElliptical = nullptr;
octanetreadmill *octaneTreadmill = nullptr;
pelotonbike *pelotonBike = nullptr;
proformrower *proformRower = nullptr;
proformbike *proformBike = nullptr;
proformwifibike *proformWifiBike = nullptr;
@@ -230,7 +213,6 @@ class bluetooth : public QObject, public SignalHandler {
stagesbike *powerBike = nullptr;
ultrasportbike *ultraSportBike = nullptr;
wahookickrsnapbike *wahooKickrSnapBike = nullptr;
ziprotreadmill *ziproTreadmill = nullptr;
strydrunpowersensor *powerTreadmill = nullptr;
eliterizer *eliteRizer = nullptr;
elitesterzosmart *eliteSterzoSmart = nullptr;
@@ -238,7 +220,6 @@ class bluetooth : public QObject, public SignalHandler {
fakeelliptical *fakeElliptical = nullptr;
faketreadmill *fakeTreadmill = nullptr;
QList<fitmetria_fanfit *> fitmetriaFanfit;
QList<wahookickrheadwind *> wahookickrHeadWind;
QString filterDevice = QLatin1String("");
bool testResistance = false;
@@ -282,8 +263,6 @@ class bluetooth : public QObject, public SignalHandler {
* @param b The bluetooth device info.
*/
void setLastBluetoothDevice(const QBluetoothDeviceInfo &b);
void startTemplateManagers(bluetoothdevice *b);
void stopTemplateManagers();
signals:
void deviceConnected(QBluetoothDeviceInfo b);
void deviceFound(QString name);
@@ -294,8 +273,9 @@ class bluetooth : public QObject, public SignalHandler {
void restart();
void debug(const QString &string);
void heartRate(uint8_t heart);
void deviceDiscovered(const QBluetoothDeviceInfo &device);
private slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
void deviceUpdated(const QBluetoothDeviceInfo &device, QBluetoothDeviceInfo::Fields updateFields);
#endif

View File

@@ -124,12 +124,6 @@ void bluetoothdevice::disconnectBluetooth() {
metric bluetoothdevice::wattsMetric() { return m_watt; }
void bluetoothdevice::setDifficult(double d) { m_difficult = d; }
double bluetoothdevice::difficult() { return m_difficult; }
void bluetoothdevice::setInclinationDifficult(double d) { m_inclination_difficult = d; }
double bluetoothdevice::inclinationDifficult() { return m_inclination_difficult; }
void bluetoothdevice::setDifficultOffset(double d) { m_difficult_offset = d; }
double bluetoothdevice::difficultOffset() { return m_difficult_offset; }
void bluetoothdevice::setInclinationDifficultOffset(double d) { m_inclination_difficult_offset = d; }
double bluetoothdevice::inclinationDifficultOffset() { return m_inclination_difficult_offset; }
void bluetoothdevice::cadenceSensor(uint8_t cadence) { Q_UNUSED(cadence) }
void bluetoothdevice::powerSensor(uint16_t power) { Q_UNUSED(power) }
void bluetoothdevice::speedSensor(double speed) { Q_UNUSED(speed) }

View File

@@ -259,53 +259,17 @@ class bluetoothdevice : public QObject {
bool autoResistance() { return autoResistanceEnable; }
/**
* @brief setDifficult Sets the difficulty gain level.
* @brief setDifficult Sets the difficulty level.
* @param d The difficulty level. Units: depends on implementation.
*/
void setDifficult(double d);
/**
* @brief difficult Gets the difficulty gain level. Units: depends on implementation.
* @brief difficult Gets the difficulty level. Units: depends on implementation.
* @return
*/
double difficult();
/**
* @brief setDifficult Sets the difficulty gain level.
* @param d The difficulty level. Units: depends on implementation.
*/
void setInclinationDifficult(double d);
/**
* @brief difficult Gets the difficulty gain level. Units: depends on implementation.
* @return
*/
double inclinationDifficult();
/**
* @brief setDifficult Sets the difficulty offset level.
* @param d The difficulty level. Units: depends on implementation.
*/
void setDifficultOffset(double d);
/**
* @brief difficult Gets the difficulty offset level. Units: depends on implementation.
* @return
*/
double difficultOffset();
/**
* @brief setDifficult Sets the difficulty offset level.
* @param d The difficulty level. Units: depends on implementation.
*/
void setInclinationDifficultOffset(double d);
/**
* @brief difficult Gets the difficulty offset level. Units: depends on implementation.
* @return
*/
double inclinationDifficultOffset();
/**
* @brief weightLoss Gets the value of the weight loss metric. Units: kg
* @return
@@ -337,13 +301,6 @@ class bluetoothdevice : public QObject {
*/
metric currentPowerZone() { return PowerZone; }
/**
* @brief currentPowerZone Gets a metric object to get or set the current power zome. Units: depends on
* implementation.
* @return
*/
metric targetPowerZone() { return TargetPowerZone; }
/**
* @brief setGPXFile Sets the file for GPS data exchange.
* @param filename The file path.
@@ -371,13 +328,6 @@ class bluetoothdevice : public QObject {
*/
void setPowerZone(double pz) { PowerZone = pz; }
/**
* @brief setTargetPowerZone Set the target power zone.
* This is equivalent to targetPowerZone().setvalue(pz)
* @param pz The power zone. Unit: depends on implementation.
*/
void setTargetPowerZone(double pz) { TargetPowerZone = pz; }
enum BLUETOOTH_TYPE { UNKNOWN = 0, TREADMILL, BIKE, ROWING, ELLIPTICAL };
enum WORKOUT_EVENT_STATE { STARTED = 0, PAUSED = 1, RESUMED = 2, STOPPED = 3 };
@@ -482,32 +432,17 @@ class bluetoothdevice : public QObject {
double requestFanSpeed = -1;
/**
* @brief m_difficult The current difficulty gain. Units: device dependent
* @brief m_difficult The current difficulty. Units: device dependent
*/
double m_difficult = 1.0;
/**
* @brief m_difficult The current difficulty gain. Units: device dependent
*/
double m_inclination_difficult = 1.0;
/**
* @brief m_difficult The current difficulty offset. Units: device dependent
*/
double m_difficult_offset = 0.0;
/**
* @brief m_difficult The current difficulty offset. Units: device dependent
*/
double m_inclination_difficult_offset = 0.0;
/**
* @brief m_jouls The number of joules expended in the current session. Unit: joules
*/
metric m_jouls;
/**
* @brief elevationAcc The elevation gain. Units: meters
* @brief elevationAcc The elevation gain. Units: ?
*/
metric elevationAcc;
@@ -599,11 +534,6 @@ class bluetoothdevice : public QObject {
*/
metric PowerZone;
/**
* @brief TargetPowerZone A metric to get and set the target power zone. Unit: depends on implementation
*/
metric TargetPowerZone;
bluetoothdevice::WORKOUT_EVENT_STATE lastState;
/**

View File

@@ -243,8 +243,6 @@ void bowflext216treadmill::characteristicChanged(const QLowEnergyCharacteristic
(1000.0 / (lastTimeCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
}
cadenceFromAppleWatch();
emit debug(QStringLiteral("Current Distance Calculated: ") + QString::number(Distance.value()));
if (m_control->error() != QLowEnergyController::NoError) {
@@ -261,9 +259,9 @@ double bowflext216treadmill::GetSpeedFromPacket(const QByteArray &packet) {
double data = (double)convertedData / 100.0f;
return data * 1.60934;
} else {
uint16_t convertedData = (uint16_t)((uint8_t)packet.at(9)) + ((uint16_t)((uint8_t)packet.at(10)) << 8);
uint16_t convertedData = (uint16_t)((uint8_t)packet.at(12)) + ((uint16_t)((uint8_t)packet.at(13)) << 8);
double data = (double)convertedData / 100.0f;
return data * 1.60934;
return data / 1.60934;
}
}
@@ -279,14 +277,10 @@ double bowflext216treadmill::GetDistanceFromPacket(const QByteArray &packet) {
}
double bowflext216treadmill::GetInclinationFromPacket(const QByteArray &packet) {
if (bowflex_t6 == false) {
uint16_t convertedData = packet.at(4);
double data = convertedData;
uint16_t convertedData = packet.at(4);
double data = convertedData;
return data;
} else {
return 0;
}
return data;
}
void bowflext216treadmill::btinit(bool startTape) {

View File

@@ -95,8 +95,7 @@ void bowflextreadmill::update() {
QSettings settings;
// ******************************************* virtual treadmill init *************************************
if (!firstInit && !virtualTreadMill) {
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
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..."));
virtualTreadMill = new virtualtreadmill(this, noHeartService);
@@ -127,7 +126,7 @@ void bowflextreadmill::update() {
requestSpeed = -1;
}
if (requestInclination != -100) {
if (requestInclination < 0)
if(requestInclination < 0)
requestInclination = 0;
if (requestInclination != currentInclination().value() && requestInclination >= 0 &&
requestInclination <= 15) {
@@ -217,8 +216,7 @@ void bowflextreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
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) *
((((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(
@@ -229,8 +227,6 @@ void bowflextreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
(1000.0 / (lastTimeCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
}
cadenceFromAppleWatch();
emit debug(QStringLiteral("Current Distance Calculated: ") + QString::number(Distance.value()));
if (m_control->error() != QLowEnergyController::NoError) {

View File

@@ -30,11 +30,6 @@ void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr,
qDebug() << QStringLiteral("calculated erg grade ") + QString::number(resistance);
double grade = ((iresistance / 100.0) * gain) + offset;
double percentage = ((qTan(qDegreesToRadians(iresistance / 100.0)) * 100.0) * gain) + offset;
if (zwift_negative_inclination_x2 && iresistance < 0) {
grade = (((iresistance / 100.0) * 2.0) * gain) + offset;
percentage = (((qTan(qDegreesToRadians(iresistance / 100.0)) * 100.0) * 2.0) * gain) + offset;
}
/*
Surface Road Crr MTB Crr Gravel Crr (Namebrand) Zwift Gravel Crr
@@ -62,7 +57,11 @@ void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr,
if (!((bike *)Bike)->inclinationAvailableByHardware())
Bike->setInclination(grade + CRR_offset + CW_offset);
emit changeInclination(grade, percentage);
if (iresistance >= 0 || !zwift_negative_inclination_x2)
emit changeInclination(grade, ((qTan(qDegreesToRadians(iresistance / 100.0)) * 100.0) * gain) + offset);
else
emit changeInclination((((iresistance / 100.0) * 2.0) * gain) + offset,
(((qTan(qDegreesToRadians(iresistance / 100.0)) * 100.0) * 2.0) * gain) + offset);
if (force_resistance && !erg_mode) {
// same on the training program
@@ -70,17 +69,16 @@ void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr,
CRR_offset + CW_offset); // resistance start from 1
}
} else if (dt == bluetoothdevice::TREADMILL) {
emit changeInclination(grade, percentage);
emit changeInclination(((iresistance / 100.0) * gain) + offset,
((qTan(qDegreesToRadians(iresistance / 100.0)) * 100.0) * gain) + offset);
} else if (dt == bluetoothdevice::ELLIPTICAL) {
bool inclinationAvailableByHardware = ((elliptical *)Bike)->inclinationAvailableByHardware();
qDebug() << "inclinationAvailableByHardware" << inclinationAvailableByHardware << "erg_mode" << erg_mode;
emit changeInclination(grade, percentage);
emit changeInclination(((iresistance / 100.0) * gain) + offset,
((qTan(qDegreesToRadians(iresistance / 100.0)) * 100.0) * gain) + offset);
if (!inclinationAvailableByHardware) {
if (!((elliptical *)Bike)->inclinationAvailableByHardware()) {
if (force_resistance && !erg_mode) {
// same on the training program
((elliptical *)Bike)
->changeResistance((resistance_t)(round(resistance * bikeResistanceGain)) + bikeResistanceOffset +
Bike->changeResistance((resistance_t)(round(resistance * bikeResistanceGain)) + bikeResistanceOffset +
1 + CRR_offset + CW_offset); // resistance start from 1
}
}

View File

@@ -102,12 +102,8 @@ int CharacteristicWriteProcessor2AD9::writeProcess(quint16 uuid, const QByteArra
if (dt == bluetoothdevice::TREADMILL)
((treadmill *)Bike)->changeInclination(requestIncline, requestIncline);
// Resistance as incline on Sole E95s Elliptical #419
else if (dt == bluetoothdevice::ELLIPTICAL) {
if(((elliptical *)Bike)->inclinationAvailableByHardware())
((elliptical *)Bike)->changeInclination(requestIncline, requestIncline);
else
changeSlope(requestIncline * 100.0, 33, 34);
}
else if (dt == bluetoothdevice::ELLIPTICAL)
((elliptical *)Bike)->changeInclination(requestIncline, requestIncline);
qDebug() << "new requested incline " + QString::number(requestIncline);
} else if ((char)data.at(0) == 0x07) // Start request
{

View File

@@ -1,418 +0,0 @@
#include "computrainerbike.h"
#include "ios/lockscreen.h"
#include "keepawakehelper.h"
#include "virtualbike.h"
#include <QDateTime>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <QThread>
#include <QtXml>
#include <chrono>
#include <math.h>
using namespace std::chrono_literals;
computrainerbike::computrainerbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
double bikeResistanceGain) {
QSettings settings;
m_watt.setType(metric::METRIC_WATT);
target_watts.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
this->noHeartService = noHeartService;
this->bikeResistanceGain = bikeResistanceGain;
this->bikeResistanceOffset = bikeResistanceOffset;
initDone = false;
connect(refresh, &QTimer::timeout, this, &computrainerbike::update);
refresh->start(50ms);
QString computrainerSerialPort =
settings.value(QZSettings::computrainer_serialport, QZSettings::default_computrainer_serialport).toString();
myComputrainer = new Computrainer(this, computrainerSerialPort);
myComputrainer->start();
ergModeSupported = true; // IMPORTANT, only for this bike
initRequest = true;
// ******************************************* virtual bike init *************************************
if (!firstStateChanged && !virtualBike
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
&& !h
#endif
#endif
) {
QSettings settings;
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence =
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence) {
qDebug() << "ios_peloton_workaround activated!";
h = new lockscreen();
h->virtualbike_ios();
} else
#endif
#endif
if (virtual_device_enabled) {
emit debug(QStringLiteral("creating virtual bike interface..."));
virtualBike =
new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
// connect(virtualBike,&virtualbike::debug ,this,& computrainerbike::debug);
connect(virtualBike, &virtualbike::changeInclination, this, &computrainerbike::changeInclination);
}
}
firstStateChanged = 1;
// ********************************************************************************************************
}
resistance_t computrainerbike::resistanceFromPowerRequest(uint16_t power) {
qDebug() << QStringLiteral("resistanceFromPowerRequest") << Cadence.value();
QSettings settings;
double watt_gain = settings.value(QZSettings::watt_gain, QZSettings::default_watt_gain).toDouble();
double watt_offset = settings.value(QZSettings::watt_offset, QZSettings::default_watt_offset).toDouble();
for (resistance_t i = 1; i < max_resistance; i++) {
if (((wattsFromResistance(i) * watt_gain) + watt_offset) <= power &&
((wattsFromResistance(i + 1) * watt_gain) + watt_offset) >= power) {
qDebug() << QStringLiteral("resistanceFromPowerRequest")
<< ((wattsFromResistance(i) * watt_gain) + watt_offset)
<< ((wattsFromResistance(i + 1) * watt_gain) + watt_offset) << power;
return i;
}
}
if (power < ((wattsFromResistance(1) * watt_gain) + watt_offset))
return 1;
else
return max_resistance;
}
uint16_t computrainerbike::wattsFromResistance(resistance_t resistance) {
if (currentCadence().value() == 0)
return 0;
switch (resistance) {
case 0:
case 1:
// -13.5 + 0.999x + 0.00993x²
return (-13.5 + (0.999 * currentCadence().value()) + (0.00993 * pow(currentCadence().value(), 2)));
case 2:
// -17.7 + 1.2x + 0.0116x²
return (-17.7 + (1.2 * currentCadence().value()) + (0.0116 * pow(currentCadence().value(), 2)));
case 3:
// -17.5 + 1.24x + 0.014x²
return (-17.5 + (1.24 * currentCadence().value()) + (0.014 * pow(currentCadence().value(), 2)));
case 4:
// -20.9 + 1.43x + 0.016x²
return (-20.9 + (1.43 * currentCadence().value()) + (0.016 * pow(currentCadence().value(), 2)));
case 5:
// -27.9 + 1.75x+0.0172x²
return (-27.9 + (1.75 * currentCadence().value()) + (0.0172 * pow(currentCadence().value(), 2)));
case 6:
// -26.7 + 1.9x + 0.0201x²
return (-26.7 + (1.9 * currentCadence().value()) + (0.0201 * pow(currentCadence().value(), 2)));
case 7:
// -33.5 + 2.23x + 0.0225x²
return (-33.5 + (2.23 * currentCadence().value()) + (0.0225 * pow(currentCadence().value(), 2)));
case 8:
// -36.5+2.5x+0.0262x²
return (-36.5 + (2.5 * currentCadence().value()) + (0.0262 * pow(currentCadence().value(), 2)));
case 9:
// -38+2.62x+0.0305x²
return (-38.0 + (2.62 * currentCadence().value()) + (0.0305 * pow(currentCadence().value(), 2)));
case 10:
// -41.2+2.85x+0.0327x²
return (-41.2 + (2.85 * currentCadence().value()) + (0.0327 * pow(currentCadence().value(), 2)));
case 11:
// -43.4+3.01x+0.0359x²
return (-43.4 + (3.01 * currentCadence().value()) + (0.0359 * pow(currentCadence().value(), 2)));
case 12:
// -46.8+3.23x+0.0364x²
return (-46.8 + (3.23 * currentCadence().value()) + (0.0364 * pow(currentCadence().value(), 2)));
case 13:
// -49+3.39x+0.0371x²
return (-49.0 + (3.39 * currentCadence().value()) + (0.0371 * pow(currentCadence().value(), 2)));
case 14:
// -53.4+3.55x+0.0383x²
return (-53.4 + (3.55 * currentCadence().value()) + (0.0383 * pow(currentCadence().value(), 2)));
case 15:
// -49.9+3.37x+0.0429x²
return (-49.9 + (3.37 * currentCadence().value()) + (0.0429 * pow(currentCadence().value(), 2)));
case 16:
default:
// -47.1+3.25x+0.0464x²
return (-47.1 + (3.25 * currentCadence().value()) + (0.0464 * pow(currentCadence().value(), 2)));
}
}
// must be double because it's an inclination
void computrainerbike::forceResistance(double requestResistance) {
if(myComputrainer->getMode() != CT_SSMODE)
myComputrainer->setMode(CT_SSMODE);
myComputrainer->setGradient(requestResistance);
qDebug() << "forceResistance" << requestResistance;
}
void computrainerbike::innerWriteResistance() {
QSettings settings;
bool erg_mode = settings.value(QZSettings::zwift_erg, QZSettings::default_zwift_erg).toBool();
if (requestResistance != -1) {
if (requestResistance > max_resistance) {
requestResistance = max_resistance;
} else if (requestResistance < min_resistance) {
requestResistance = min_resistance;
} else if (requestResistance == 0) {
requestResistance = 1;
}
if (requestResistance != currentResistance().value()) {
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
if (((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike) &&
(requestPower == 0 || requestPower == -1)) {
forceResistance(requestResistance);
}
}
requestResistance = -1;
}
if (requestPower > 0) {
if(myComputrainer->getMode() != CT_ERGOMODE)
myComputrainer->setMode(CT_ERGOMODE);
myComputrainer->setLoad(requestPower);
qDebug() << "change inclination due to request power = " << requestPower;
}
if (requestInclination != -100) {
emit debug(QStringLiteral("writing inclination ") + QString::number(requestInclination));
forceResistance(requestInclination + gears()); // since this bike doesn't have the concept of resistance,
// i'm using the gears in the inclination
requestInclination = -100;
}
}
void computrainerbike::update() {
if (initRequest) {
initRequest = false;
btinit();
emit connectedAndDiscovered();
} else {
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
bool disable_hr_frommachinery =
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
int Buttons, Status;
bool calibration;
double Power, HeartRate, cadence, speed, RRC, Load, Gradient;
uint8_t ss[24];
// get latest telemetry
myComputrainer->getTelemetry(Power, HeartRate, cadence, speed, RRC, calibration, Buttons, ss, Status);
Speed = speed;
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
Distance += ((Speed.value() / 3600000.0) *
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
emit debug("Current Distance: " + QString::number(Distance.value()));
Cadence = cadence;
emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
m_watt = Power;
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
Inclination = Gradient;
emit debug(QStringLiteral("Current Inclination: ") + QString::number(Gradient));
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
/*
Resistance = resistance;
m_pelotonResistance = (100 / 32) * Resistance.value();
emit resistanceRead(Resistance.value()); */
if (!disable_hr_frommachinery) {
Heart = HeartRate;
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
}
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 (disable_hr_frommachinery && heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
lockscreen h;
long appleWatchHeartRate = h.heartRate();
h.setKcal(KCal.value());
h.setDistance(Distance.value());
Heart = appleWatchHeartRate;
debug("Current Heart from Apple Watch: " + QString::number(appleWatchHeartRate));
#endif
#endif
}
}
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence =
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
}
#endif
#endif
/*
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
emit debug(QStringLiteral("Current Calculate Distance: ") + QString::number(Distance.value()));
emit debug(QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs));
emit debug(QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime)); */
update_metrics(false, watts());
// updating the treadmill console every second
if (sec1Update++ == (500 / refresh->interval())) {
sec1Update = 0;
// updateDisplay(elapsed);
}
innerWriteResistance();
if (requestStart != -1) {
emit debug(QStringLiteral("starting..."));
// btinit();
requestStart = -1;
emit bikeStarted();
}
if (requestStop != -1) {
emit debug(QStringLiteral("stopping..."));
// writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
requestStop = -1;
}
}
}
bool computrainerbike::inclinationAvailableByHardware() {
QSettings settings;
bool proform_studio = settings.value(QZSettings::proform_studio, QZSettings::default_proform_studio).toBool();
bool proform_tdf_10 = settings.value(QZSettings::proform_tdf_10, QZSettings::default_proform_tdf_10).toBool();
if (proform_studio || proform_tdf_10)
return true;
else
return false;
}
resistance_t computrainerbike::pelotonToBikeResistance(int pelotonResistance) {
if (pelotonResistance <= 10) {
return 1;
}
if (pelotonResistance <= 20) {
return 2;
}
if (pelotonResistance <= 25) {
return 3;
}
if (pelotonResistance <= 30) {
return 4;
}
if (pelotonResistance <= 35) {
return 5;
}
if (pelotonResistance <= 40) {
return 6;
}
if (pelotonResistance <= 45) {
return 7;
}
if (pelotonResistance <= 50) {
return 8;
}
if (pelotonResistance <= 55) {
return 9;
}
if (pelotonResistance <= 60) {
return 10;
}
if (pelotonResistance <= 65) {
return 11;
}
if (pelotonResistance <= 70) {
return 12;
}
if (pelotonResistance <= 75) {
return 13;
}
if (pelotonResistance <= 80) {
return 14;
}
if (pelotonResistance <= 85) {
return 15;
}
if (pelotonResistance <= 100) {
return 16;
}
return Resistance.value();
}
void computrainerbike::btinit() { initDone = true; }
void computrainerbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit debug(QStringLiteral("Found new device: ") + device.name() + " (" + device.address().toString() + ')');
}
bool computrainerbike::connected() { return true; }
void *computrainerbike::VirtualBike() { return virtualBike; }
void *computrainerbike::VirtualDevice() { return VirtualBike(); }
uint16_t computrainerbike::watts() { return m_watt.value(); }

View File

@@ -1,95 +0,0 @@
#ifndef COMPUTRAINERBIKE_H
#define COMPUTRAINERBIKE_H
#include <QAbstractOAuth2>
#include <QObject>
#include <QSettings>
#include <QTimer>
#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 <QString>
#include "Computrainer.h"
#include "bike.h"
#include "virtualbike.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
#endif
class computrainerbike : public bike {
Q_OBJECT
public:
computrainerbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
double bikeResistanceGain);
resistance_t pelotonToBikeResistance(int pelotonResistance);
resistance_t resistanceFromPowerRequest(uint16_t power);
resistance_t maxResistance() { return max_resistance; }
bool inclinationAvailableByHardware();
bool connected();
void *VirtualBike();
void *VirtualDevice();
private:
resistance_t max_resistance = 100;
resistance_t min_resistance = -20;
uint16_t wattsFromResistance(resistance_t resistance);
double GetDistanceFromPacket(QByteArray packet);
QTime GetElapsedFromPacket(QByteArray packet);
void btinit();
void startDiscover();
void sendPoll();
uint16_t watts();
void forceResistance(double requestResistance);
void innerWriteResistance();
QTimer *refresh;
virtualbike *virtualBike = nullptr;
uint8_t counterPoll = 0;
uint8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
uint8_t sec1Update = 0;
QString lastPacket;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
uint8_t firstStateChanged = 0;
metric target_watts;
bool initDone = false;
bool initRequest = false;
bool noWriteResistance = false;
bool noHeartService = false;
Computrainer *myComputrainer = nullptr;
#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 update();
};
#endif // COMPUTRAINERBIKE_H

View File

@@ -81,8 +81,23 @@ void cscbike::update() {
}
}
m_watt = wattFromHR(false);
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
if (Heart.value() > 0) {
int avgP = ((settings.value(QZSettings::power_hr_pwr1, QZSettings::default_power_hr_pwr1).toDouble() *
settings.value(QZSettings::power_hr_hr2, QZSettings::default_power_hr_hr2).toDouble()) -
(settings.value(QZSettings::power_hr_pwr2, QZSettings::default_power_hr_pwr2).toDouble() *
settings.value(QZSettings::power_hr_hr1, QZSettings::default_power_hr_hr1).toDouble())) /
(settings.value(QZSettings::power_hr_hr2, QZSettings::default_power_hr_hr2).toDouble() -
settings.value(QZSettings::power_hr_hr1, QZSettings::default_power_hr_hr1).toDouble()) +
(Heart.value() * ((settings.value(QZSettings::power_hr_pwr1, QZSettings::default_power_hr_pwr1).toDouble() -
settings.value(QZSettings::power_hr_pwr2, QZSettings::default_power_hr_pwr2).toDouble()) /
(settings.value(QZSettings::power_hr_hr1, QZSettings::default_power_hr_hr1).toDouble() -
settings.value(QZSettings::power_hr_hr2, QZSettings::default_power_hr_hr2).toDouble())));
if (avgP < 50) {
avgP = 50;
}
m_watt = avgP;
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
}
if (m_control->state() == QLowEnergyController::UnconnectedState) {
emit disconnected();
@@ -221,13 +236,9 @@ void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characterist
oldCrankRevs = CrankRevs;
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
Speed = Cadence.value() *
settings.value(QZSettings::cadence_sensor_speed_ratio, QZSettings::default_cadence_sensor_speed_ratio)
.toDouble();
Speed = Cadence.value() * settings.value(QZSettings::cadence_sensor_speed_ratio, QZSettings::default_cadence_sensor_speed_ratio).toDouble();
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
Speed = metric::calculateSpeedFromPower(watts(), Inclination.value(), Speed.value(),fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
@@ -261,8 +272,7 @@ void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characterist
if (watts())
KCal +=
((((0.048 * ((double)watts()) + 1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
((((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
@@ -279,10 +289,8 @@ void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characterist
if (!noVirtualDevice) {
#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();
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());
@@ -305,17 +313,9 @@ void cscbike::stateChanged(QLowEnergyService::ServiceState state) {
for (QLowEnergyService *s : qAsConst(gattCommunicationChannelService)) {
qDebug() << QStringLiteral("stateChanged") << s->serviceUuid() << s->state();
#ifdef Q_OS_WINDOWS
QBluetoothUuid CyclingSpeedAndCadence(QBluetoothUuid::CyclingSpeedAndCadence);
qDebug() << "windows workaround, check only CyclingSpeedAndCadence ftms service"
<< (s->serviceUuid() == CyclingSpeedAndCadence);
if (s->serviceUuid() == CyclingSpeedAndCadence)
#endif
{
if (s->state() != QLowEnergyService::ServiceDiscovered && s->state() != QLowEnergyService::InvalidService) {
qDebug() << QStringLiteral("not all services discovered");
return;
}
if (s->state() != QLowEnergyService::ServiceDiscovered && s->state() != QLowEnergyService::InvalidService) {
qDebug() << QStringLiteral("not all services discovered");
return;
}
}
@@ -389,14 +389,11 @@ void cscbike::stateChanged(QLowEnergyService::ServiceState state) {
#endif
) {
QSettings settings;
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
bool virtual_device_enabled = settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence =
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
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();
@@ -447,18 +444,10 @@ void cscbike::serviceScanDone(void) {
#endif
auto services_list = m_control->services();
for (const QBluetoothUuid &s : qAsConst(services_list)) {
#ifdef Q_OS_WINDOWS
QBluetoothUuid CyclingSpeedAndCadence(QBluetoothUuid::CyclingSpeedAndCadence);
qDebug() << "windows workaround, check only the CyclingSpeedAndCadence service" << s << CyclingSpeedAndCadence
<< (s == CyclingSpeedAndCadence);
if (s == CyclingSpeedAndCadence)
#endif
{
gattCommunicationChannelService.append(m_control->createServiceObject(s));
connect(gattCommunicationChannelService.constLast(), &QLowEnergyService::stateChanged, this,
&cscbike::stateChanged);
gattCommunicationChannelService.constLast()->discoverDetails();
}
gattCommunicationChannelService.append(m_control->createServiceObject(s));
connect(gattCommunicationChannelService.constLast(), &QLowEnergyService::stateChanged, this,
&cscbike::stateChanged);
gattCommunicationChannelService.constLast()->discoverDetails();
}
}

View File

@@ -1,60 +0,0 @@
#ifndef DISCOVERYOPTIONS_H
#define DISCOVERYOPTIONS_H
#include <QString>
class discoveryoptions {
public:
/**
* @brief Specifies if logs should be written.
*/
bool logs = false;
/**
* @brief Specify a specific device by name.
*/
QString deviceName = QString();
/**
* @brief Used to suppress resistance.
*/
bool noWriteResistance = false;
/**
* @brief Used to suppress the device's heart service.
*/
bool noHeartService = false;
/**
* @brief The device polling interval in milliseconds.
*/
uint32_t pollDeviceTime = 200;
bool noConsole = false;
bool testResistance = false;
/**
* @brief Specifies a value that will be added to the resistance requests going to the bike, after the gain has been applied.
*/
uint8_t bikeResistanceOffset = 4;
/**
* @brief The resistance requests going to the bike should be multiplied by this, before adding the resistance offset.
*/
double bikeResistanceGain = 1.0;
/**
* @brief Use to suppress starting discovery in the constructor, e.g. for testing.
*/
bool startDiscovery = true;
/**
* @brief Used to suppress the creation of the tempalte managers to decouple from the UI.
*/
bool createTemplateManagers = true;
};
#endif // DISCOVERYOPTIONS_H

View File

@@ -171,11 +171,8 @@ void domyoselliptical::update() {
// ******************************************* virtual bike init *************************************
QSettings settings;
if (!firstVirtual && searchStopped && !virtualTreadmill && !virtualBike) {
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();
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...");
@@ -185,8 +182,7 @@ void domyoselliptical::update() {
&domyoselliptical::changeInclinationRequested);
} else {
debug("creating virtual bike interface...");
virtualBike = new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset,
bikeResistanceGain);
virtualBike = new virtualbike(this);
connect(virtualBike, &virtualbike::changeInclination, this,
&domyoselliptical::changeInclinationRequested);
connect(virtualBike, &virtualbike::changeInclination, this, &domyoselliptical::changeInclination);
@@ -218,7 +214,7 @@ void domyoselliptical::update() {
forceResistance(requestResistance);
}
requestResistance = -1;
} else if (requestInclination != -100 && inclinationAvailableByHardware()) {
} else if (requestInclination != -100) {
if (requestInclination > 15) {
requestInclination = 15;
} else if (requestInclination == 0) {
@@ -291,16 +287,10 @@ void domyoselliptical::characteristicChanged(const QLowEnergyCharacteristic &cha
inclination and speed status return;*/
double speed =
GetSpeedFromPacket(newValue) *
settings.value(QZSettings::domyos_elliptical_speed_ratio, QZSettings::default_domyos_elliptical_speed_ratio)
.toDouble();
GetSpeedFromPacket(newValue) * settings.value(QZSettings::domyos_elliptical_speed_ratio, QZSettings::default_domyos_elliptical_speed_ratio).toDouble();
double kcal = GetKcalFromPacket(newValue);
double distance =
GetDistanceFromPacket(newValue) *
settings.value(QZSettings::domyos_elliptical_speed_ratio, QZSettings::default_domyos_elliptical_speed_ratio)
.toDouble();
bool disable_hr_frommachinery =
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
double distance = GetDistanceFromPacket(newValue) *
settings.value(QZSettings::domyos_elliptical_speed_ratio, QZSettings::default_domyos_elliptical_speed_ratio).toDouble();
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
.toString()
@@ -326,21 +316,9 @@ void domyoselliptical::characteristicChanged(const QLowEnergyCharacteristic &cha
else
#endif
{
if (heartRateBeltName.startsWith(QStringLiteral("Disabled")) && !disable_hr_frommachinery) {
if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
Heart = ((uint8_t)newValue.at(18));
}
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
else {
lockscreen h;
long appleWatchHeartRate = h.heartRate();
h.setKcal(KCal.value());
h.setDistance(Distance.value());
Heart = appleWatchHeartRate;
qDebug() << "Current Heart from Apple Watch: " + QString::number(appleWatchHeartRate);
}
#endif
#endif
}
CrankRevs++;
@@ -612,48 +590,19 @@ uint16_t domyoselliptical::watts() {
double VO2A = (VO2R * weight) / 1000.0;
double hwatts = 75 * VO2A;
double inc_res_ratio;
if (settings.value(QZSettings::domyos_elliptical_inclination, QZSettings::default_domyos_elliptical_inclination)
.toBool()) {
if(settings.value(QZSettings::domyos_elliptical_inclination, QZSettings::default_domyos_elliptical_inclination).toBool())
inc_res_ratio = currentInclination().value() / 100.0;
double vwatts = ((9.8 * weight) * (inc_res_ratio));
watts = hwatts + vwatts;
} else {
if (!settings.value(QZSettings::domyos_bike_500_profile_v1, QZSettings::default_domyos_bike_500_profile_v1)
.toBool() ||
currentResistance().value() < 8)
return ((10.39 + 1.45 * (currentResistance().value() - 1.0)) *
(exp(0.028 * (currentCadence().value()))));
else {
switch ((int)currentResistance().value()) {
case 8:
return (13.6 * Cadence.value()) / 9.5488;
case 9:
return (15.3 * Cadence.value()) / 9.5488;
case 10:
return (17.3 * Cadence.value()) / 9.5488;
case 11:
return (19.8 * Cadence.value()) / 9.5488;
case 12:
return (22.5 * Cadence.value()) / 9.5488;
case 13:
return (25.6 * Cadence.value()) / 9.5488;
case 14:
return (28.4 * Cadence.value()) / 9.5488;
case 15:
return (35.9 * Cadence.value()) / 9.5488;
}
return ((10.39 + 1.45 * (currentResistance().value() - 1.0)) *
(exp(0.028 * (currentCadence().value()))));
}
}
else
inc_res_ratio = currentResistance().value() / 100.0;
double vwatts = ((9.8 * weight) * (inc_res_ratio));
watts = hwatts + vwatts;
}
return watts;
}
bool domyoselliptical::inclinationAvailableByHardware() {
QSettings settings;
return settings.value(QZSettings::domyos_elliptical_inclination, QZSettings::default_domyos_elliptical_inclination)
.toBool();
return settings.value(QZSettings::domyos_elliptical_inclination, QZSettings::default_domyos_elliptical_inclination).toBool();
}
void domyoselliptical::controllerStateChanged(QLowEnergyController::ControllerState state) {

View File

@@ -168,18 +168,13 @@ void domyosrower::update() {
update_metrics(true, watts());
// ******************************************* virtual treadmill init *************************************
// ******************************************* virtual bike init *************************************
QSettings settings;
bool virtual_device_rower = settings.value(QZSettings::virtual_device_rower, QZSettings::default_virtual_device_rower).toBool();
if (!firstVirtual && !virtualTreadmill && !virtualBike && !virtualRower) {
if (!firstVirtual && searchStopped && !virtualTreadmill && !virtualBike) {
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_rower) {
qDebug() << QStringLiteral("creating virtual rower interface...");
virtualRower = new virtualrower(this, noWriteResistance, noHeartService);
// connect(virtualRower,&virtualrower::debug ,this,&echelonrower::debug);
} else if (!virtual_device_force_bike) {
if (!virtual_device_force_bike) {
debug("creating virtual treadmill interface...");
virtualTreadmill = new virtualtreadmill(this, noHeartService);
connect(virtualTreadmill, &virtualtreadmill::debug, this, &domyosrower::debug);

View File

@@ -28,7 +28,6 @@
#include "rower.h"
#include "virtualbike.h"
#include "virtualrower.h"
#include "virtualtreadmill.h"
class domyosrower : public rower {
@@ -60,7 +59,6 @@ class domyosrower : public rower {
QTimer *refresh;
virtualtreadmill *virtualTreadmill = nullptr;
virtualbike *virtualBike = nullptr;
virtualrower *virtualRower = nullptr;
uint8_t firstVirtual = 0;
QLowEnergyService *gattCommunicationChannelService = nullptr;

View File

@@ -94,7 +94,7 @@ void domyostreadmill::writeCharacteristic(uint8_t *data, uint8_t data_len, const
return;
}
if (!gattWriteCharacteristic.isValid()) {
qDebug() << QStringLiteral("gattWriteCharacteristic is invalid");
return;
@@ -104,8 +104,8 @@ void domyostreadmill::writeCharacteristic(uint8_t *data, uint8_t data_len, const
QByteArray((const char *)data, data_len));
if (!disable_log) {
qDebug() << QStringLiteral(" >> ") + QByteArray((const char *)data, data_len).toHex(' ')
<< QStringLiteral(" // ") + info;
qDebug() << QStringLiteral(" >> ") + QByteArray((const char *)data, data_len).toHex(' ') <<
QStringLiteral(" // ") + info;
}
loop.exec();
@@ -120,13 +120,8 @@ void domyostreadmill::updateDisplay(uint16_t elapsed) {
0x01, 0x00, 0x05, 0x01, 0x01, 0x00, 0x0c, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00};
QSettings settings;
bool distance =
settings
.value(QZSettings::domyos_treadmill_distance_display, QZSettings::default_domyos_treadmill_distance_display)
.toBool();
bool domyos_treadmill_display_invert =
settings.value(QZSettings::domyos_treadmill_display_invert, QZSettings::default_domyos_treadmill_display_invert)
.toBool();
bool distance = settings.value(QZSettings::domyos_treadmill_distance_display, QZSettings::default_domyos_treadmill_distance_display).toBool();
bool domyos_treadmill_display_invert = settings.value(QZSettings::domyos_treadmill_display_invert, QZSettings::default_domyos_treadmill_display_invert).toBool();
if (elapsed > 5999) // 99:59
{
@@ -139,7 +134,7 @@ void domyostreadmill::updateDisplay(uint16_t elapsed) {
}
if (distance) {
if (!domyos_treadmill_display_invert) {
if(!domyos_treadmill_display_invert) {
if (odometer() < 10.0) {
display[7] = ((uint8_t)((uint16_t)(odometer() * 100) >> 8)) & 0xFF;
@@ -174,7 +169,7 @@ void domyostreadmill::updateDisplay(uint16_t elapsed) {
display[20] = (uint8_t)(currentSpeed().value() * 10.0);
if (!domyos_treadmill_display_invert) {
if(!domyos_treadmill_display_invert) {
display[23] = ((uint8_t)(calories().value()) >> 8) & 0xFF;
display[24] = (uint8_t)(calories().value()) & 0xFF;
} else {
@@ -272,11 +267,8 @@ void domyostreadmill::update() {
QSettings settings;
// ******************************************* virtual treadmill init *************************************
if (!firstInit && searchStopped && !virtualTreadMill && !virtualBike) {
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();
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...");
@@ -332,7 +324,7 @@ void domyostreadmill::update() {
requestSpeed = -1;
}
if (requestInclination != -100) {
if (requestInclination < 0)
if(requestInclination < 0)
requestInclination = 0;
// only 0.5 steps ara available
requestInclination = qRound(requestInclination * 2.0) / 2.0;
@@ -397,8 +389,7 @@ void domyostreadmill::characteristicChanged(const QLowEnergyCharacteristic &char
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
bool domyos_treadmill_buttons =
settings.value(QZSettings::domyos_treadmill_buttons, QZSettings::default_domyos_treadmill_buttons).toBool();
bool domyos_treadmill_buttons = settings.value(QZSettings::domyos_treadmill_buttons, QZSettings::default_domyos_treadmill_buttons).toBool();
Q_UNUSED(characteristic);
QByteArray value = newValue;
@@ -547,9 +538,8 @@ void domyostreadmill::characteristicChanged(const QLowEnergyCharacteristic &char
double incline = GetInclinationFromPacket(value);
double kcal = GetKcalFromPacket(value);
double distance = GetDistanceFromPacket(value);
bool disable_hr_frommachinery =
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
bool disable_hr_frommachinery = settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
Heart = (uint8_t)KeepAwakeHelper::heart();
@@ -576,16 +566,26 @@ void domyostreadmill::characteristicChanged(const QLowEnergyCharacteristic &char
Heart = heart;
}
}
cadenceFromAppleWatch();
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled")))
{
lockscreen h;
long appleWatchCadence = h.stepCadence();
Cadence = appleWatchCadence;
}
#endif
#endif
FanSpeed = value.at(23);
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) *
((((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(

View File

@@ -207,28 +207,7 @@ void echelonconnectsport::characteristicChanged(const QLowEnergyCharacteristic &
// resistance value is in another frame
if (newValue.length() == 5 && ((unsigned char)newValue.at(0)) == 0xf0 && ((unsigned char)newValue.at(1)) == 0xd2) {
resistance_t res = newValue.at(3);
if (settings.value(QZSettings::gears_from_bike, QZSettings::default_gears_from_bike).toBool()) {
qDebug() << QStringLiteral("gears_from_bike") << res << Resistance.value() << gears()
<< lastRawRequestedResistanceValue << lastRequestedResistance().value();
if (
// if the resistance is different from the previous one
res != qRound(Resistance.value()) &&
// and the last target resistance is different from the current one or there is no any pending last
// requested resistance
((lastRequestedResistance().value() != res && lastRequestedResistance().value() != 0) ||
lastRawRequestedResistanceValue == -1) &&
// and the difference between the 2 resistances are less than 6
qRound(Resistance.value()) > 1 && qAbs(res - qRound(Resistance.value())) < 6) {
int8_t g = gears();
g += (res - qRound(Resistance.value()));
qDebug() << QStringLiteral("gears_from_bike APPLIED") << gears() << g;
lastRawRequestedResistanceValue = -1; // in order to avoid to change resistance with the setGears
setGears(g);
}
}
Resistance = res;
Resistance = newValue.at(3);
emit resistanceRead(Resistance.value());
m_pelotonResistance = bikeResistanceToPeloton(Resistance.value());

View File

@@ -63,10 +63,8 @@ void elliptical::changeResistance(resistance_t resistance) {
}
int8_t elliptical::gears() { return m_gears; }
void elliptical::setGears(int8_t gears) {
QSettings settings;
qDebug() << "setGears" << gears;
m_gears = gears;
settings.setValue(QZSettings::gears_current_value, m_gears);
if (lastRawRequestedResistanceValue != -1) {
changeResistance(lastRawRequestedResistanceValue);
}

View File

@@ -30,7 +30,6 @@ class elliptical : public bluetoothdevice {
uint16_t watts();
void setGears(int8_t d);
int8_t gears();
virtual double minStepInclination() { return 0.5; }
public Q_SLOTS:
virtual void changeSpeed(double speed);

View File

@@ -87,28 +87,6 @@ void eslinkertreadmill::forceSpeed(double requestSpeed) {
uint8_t display[] = {0x01, 0x01, 0x00};
display[2] = requestSpeed * 10;
writeCharacteristic(display, sizeof(display),
QStringLiteral("forceSpeed speed=") + QString::number(requestSpeed), false, true);
} else if (treadmill_type == YPOO_MINI_CHANGE) {
// 0x0d: a901010da4
// 0x0e: a901010ea7
// CheckSum 8 Xor
uint8_t display[] = {0xa9, 0x01, 0x01, 0x0e, 0x00};
display[3] = requestSpeed * 10;
for (int i = 0; i < 4; i++) {
display[4] = display[4] ^ display[i];
}
writeCharacteristic(display, sizeof(display),
QStringLiteral("forceSpeed speed=") + QString::number(requestSpeed), false, true);
} else if(treadmill_type == COSTAWAY) {
// CheckSum 8 Xor
uint8_t display[] = {0xa9, 0xa0, 0x03, 0x02, 0x00, 0x00, 0x00};
display[4] = requestSpeed * 10;
for (int i = 0; i < 6; i++) {
display[6] = display[6] ^ display[i];
}
writeCharacteristic(display, sizeof(display),
QStringLiteral("forceSpeed speed=") + QString::number(requestSpeed), false, true);
}
@@ -133,8 +111,7 @@ void eslinkertreadmill::update() {
QSettings settings;
// ******************************************* virtual treadmill init *************************************
if (!firstInit && !virtualTreadMill) {
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
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..."));
virtualTreadMill = new virtualtreadmill(this, noHeartService);
@@ -152,7 +129,7 @@ void eslinkertreadmill::update() {
updateDisplay(elapsed.value());
}
if (treadmill_type == TYPE::RHYTHM_FUN || treadmill_type == TYPE::YPOO_MINI_CHANGE || treadmill_type == TYPE::COSTAWAY) { //
if (treadmill_type == TYPE::RHYTHM_FUN || treadmill_type == TYPE::YPOO_MINI_CHANGE) { //
if (requestSpeed != -1) {
if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) {
emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed));
@@ -166,7 +143,7 @@ void eslinkertreadmill::update() {
requestSpeed = -1;
}
if (requestInclination != -100) {
if (requestInclination < 0)
if(requestInclination < 0)
requestInclination = 0;
if (requestInclination != currentInclination().value() && requestInclination >= 0 &&
requestInclination <= 15) {
@@ -180,9 +157,6 @@ void eslinkertreadmill::update() {
}
requestInclination = -100;
}
} else if (treadmill_type == COSTAWAY) {
uint8_t initData11[] = {0xa9, 0xa0, 0x03, 0x02, 0x06, 0x00, 0x0e};
writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("noop"), false, true);
} else {
if (requestVar2) {
uint8_t display[] = {0x08, 0x04, 0x01, 0x00, 0x00, 0x01};
@@ -228,10 +202,8 @@ void eslinkertreadmill::update() {
if (lastSpeed == 0.0) {
lastSpeed = 0.5;
}
if (treadmill_type == TYPE::RHYTHM_FUN || treadmill_type == TYPE::YPOO_MINI_CHANGE) {
uint8_t startTape[] = {0xa9, 0xa3, 0x01, 0x01, 0x0a};
writeCharacteristic(startTape, sizeof(startTape), QStringLiteral("startTape"), false, true);
}
if (treadmill_type == TYPE::RHYTHM_FUN || treadmill_type == TYPE::YPOO_MINI_CHANGE)
btinit(true);
requestSpeed = 1.0;
requestStart = -1;
emit tapeStarted();
@@ -341,10 +313,8 @@ void eslinkertreadmill::characteristicChanged(const QLowEnergyCharacteristic &ch
if ((newValue.length() != 17 && (treadmill_type == RHYTHM_FUN || treadmill_type == YPOO_MINI_CHANGE)))
return;
else if(newValue.length() != 5 && treadmill_type == COSTAWAY)
return;
if (treadmill_type == RHYTHM_FUN || treadmill_type == YPOO_MINI_CHANGE) {
if (treadmill_type == RHYTHM_FUN || treadmill_type == YPOO_MINI_CHANGE) {
double speed = GetSpeedFromPacket(value);
double incline = GetInclinationFromPacket(value);
double kcal = GetKcalFromPacket(value);
@@ -380,18 +350,12 @@ void eslinkertreadmill::characteristicChanged(const QLowEnergyCharacteristic &ch
lastSpeed = speed;
lastInclination = incline;
}
} else if(treadmill_type == COSTAWAY) {
const double miles = 1.60934;
Speed = newValue.at(3) * miles;
Inclination = 0; // this treadmill doesn't have inclination
emit debug(QStringLiteral("Current speed: ") + QString::number(Speed.value()));
}
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) *
((((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(
@@ -402,8 +366,6 @@ void eslinkertreadmill::characteristicChanged(const QLowEnergyCharacteristic &ch
(1000.0 / (lastTimeCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
}
cadenceFromAppleWatch();
emit debug(QStringLiteral("Current Distance Calculated: ") + QString::number(Distance.value()));
if (m_control->error() != QLowEnergyController::NoError) {
@@ -426,10 +388,10 @@ double eslinkertreadmill::GetSpeedFromPacket(const QByteArray &packet) {
double eslinkertreadmill::GetKcalFromPacket(const QByteArray &packet) {
double data;
if (treadmill_type == YPOO_MINI_CHANGE) {
uint16_t convertedData = (((uint8_t)packet.at(5)) << 8) | (uint8_t)packet.at(6);
;
uint16_t convertedData = (((uint8_t)packet.at(5)) << 8) | (uint8_t)packet.at(6);;
data = (double)convertedData / 100.0f;
} else {
}
else {
uint16_t convertedData = (packet.at(7) << 8) | packet.at(8);
data = (double)convertedData;
}
@@ -447,52 +409,29 @@ double eslinkertreadmill::GetInclinationFromPacket(const QByteArray &packet) {
if (treadmill_type != YPOO_MINI_CHANGE) {
uint16_t convertedData = packet.at(11);
data = convertedData;
}
}
return data;
}
void eslinkertreadmill::btinit(bool startTape) {
Q_UNUSED(startTape)
// set speed and incline to 0
uint8_t initData1[] = {0x08, 0x01, 0x86};
uint8_t initData2[] = {0xa9, 0x08, 0x01, 0x86, 0x26};
uint8_t initData3[] = {0xa9, 0x80, 0x05, 0x05, 0xb0, 0x04, 0x52, 0xa9, 0x66};
uint8_t initData4[] = {0xa9, 0x08, 0x04, 0xb2, 0x51, 0x03, 0x52, 0x17};
uint8_t initData5[] = {0xa9, 0x1e, 0x01, 0xfe, 0x48};
uint8_t initData6[] = {0xa9, 0x0a, 0x01, 0x01, 0xa3};
uint8_t initData7[] = {0xa9, 0xf0, 0x01, 0x01, 0x59};
uint8_t initData8[] = {0xa9, 0xa0, 0x03, 0xff, 0x00, 0x00, 0xf5};
uint8_t initData9[] = {0xa9, 0xa0, 0x03, 0x00, 0x00, 0x00, 0x0a};
uint8_t initData10[] = {0xa9, 0xa0, 0x03, 0x01, 0x00, 0x00, 0x0b};
uint8_t initData11[] = {0xa9, 0x01, 0x01, 0x08, 0xa1};
uint8_t initData12[] = {0xa9, 0xa0, 0x03, 0x02, 0x08, 0x00, 0x00};
if (treadmill_type == COSTAWAY) {
uint8_t initData1[] = {0xa9, 0xf2, 0x01, 0x2f, 0x75};
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
uint8_t initData2[] = {0xa9, 0x08, 0x01, 0x79, 0xd9};
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, true);
uint8_t initData3[] = {0xa9, 0x08, 0x04, 0x05, 0x04, 0x04, 0x01, 0xa1};
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, true);
uint8_t initData4[] = {0xa9, 0x1e, 0x01, 0xfe, 0x48};
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, true);
uint8_t initData5[] = {0xa9, 0xa0, 0x03, 0x00, 0x00, 0x00, 0x0a};
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
uint8_t initData6[] = {0xa9, 0x0a, 0x01, 0x48, 0xea};
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, true);
uint8_t initData7[] = {0xa9, 0xae, 0x01, 0xfe, 0xf8};
writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, true);
uint8_t initData8[] = {0xa9, 0xa0, 0x03, 0x00, 0x00, 0x00, 0x0a};
writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, true);
} else if (treadmill_type == RHYTHM_FUN || treadmill_type == YPOO_MINI_CHANGE) {
// set speed and incline to 0
uint8_t initData1[] = {0x08, 0x01, 0x86};
uint8_t initData2[] = {0xa9, 0x08, 0x01, 0x86, 0x26};
uint8_t initData3[] = {0xa9, 0x80, 0x05, 0x05, 0xb0, 0x04, 0x52, 0xa9, 0x66};
uint8_t initData4[] = {0xa9, 0x08, 0x04, 0xb2, 0x51, 0x03, 0x52, 0x17};
uint8_t initData5[] = {0xa9, 0x1e, 0x01, 0xfe, 0x48};
uint8_t initData6[] = {0xa9, 0x0a, 0x01, 0x01, 0xa3};
uint8_t initData7[] = {0xa9, 0xf0, 0x01, 0x01, 0x59};
uint8_t initData8[] = {0xa9, 0xa0, 0x03, 0xff, 0x00, 0x00, 0xf5};
uint8_t initData9[] = {0xa9, 0xa0, 0x03, 0x00, 0x00, 0x00, 0x0a};
uint8_t initData10[] = {0xa9, 0xa0, 0x03, 0x01, 0x00, 0x00, 0x0b};
uint8_t initData11[] = {0xa9, 0x01, 0x01, 0x08, 0xa1};
uint8_t initData12[] = {0xa9, 0xa0, 0x03, 0x02, 0x08, 0x00, 0x00};
uint8_t initData2_CADENZA[] = {0x08, 0x01, 0x01};
if (treadmill_type == RHYTHM_FUN || treadmill_type == YPOO_MINI_CHANGE) {
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, true);
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, true);
@@ -510,7 +449,6 @@ void eslinkertreadmill::btinit(bool startTape) {
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, true);
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, true);
} else {
uint8_t initData2_CADENZA[] = {0x08, 0x01, 0x01};
writeCharacteristic(initData2_CADENZA, sizeof(initData2_CADENZA), QStringLiteral("init"), false, true);
}
@@ -617,17 +555,12 @@ void eslinkertreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
});
QSettings settings;
bool eslinker_cadenza =
settings.value(QZSettings::eslinker_cadenza, QZSettings::default_eslinker_cadenza).toBool();
bool eslinker_cadenza = settings.value(QZSettings::eslinker_cadenza, QZSettings::default_eslinker_cadenza).toBool();
bool eslinker_ypoo = settings.value(QZSettings::eslinker_ypoo, QZSettings::default_eslinker_ypoo).toBool();
bool eslinker_costaway =
settings.value(QZSettings::eslinker_costaway, QZSettings::default_eslinker_costaway).toBool();
if (eslinker_cadenza) {
treadmill_type = CADENZA_FITNESS_T45;
} else if (eslinker_ypoo) {
treadmill_type = YPOO_MINI_CHANGE;
} else if (eslinker_costaway) {
treadmill_type = COSTAWAY;
} else
treadmill_type = RHYTHM_FUN;

View File

@@ -68,8 +68,7 @@ class eslinkertreadmill : public treadmill {
typedef enum TYPE {
RHYTHM_FUN = 0,
CADENZA_FITNESS_T45 = 1, // it has the same protocol of RHYTHM_FUN but without the header and the footer
YPOO_MINI_CHANGE = 2, // Similar to RHYTHM_FUN but has no ascension
COSTAWAY = 3,
YPOO_MINI_CHANGE = 2, // Similar to RHYTHM_FUN but has no ascension
} TYPE;
volatile TYPE treadmill_type = RHYTHM_FUN;

View File

@@ -47,8 +47,6 @@ void faketreadmill::update() {
requestInclination = -100;
}
cadenceFromAppleWatch();
Distance += ((Speed.value() / (double)3600.0) /
((double)1000.0 / (double)(lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();

View File

@@ -19,13 +19,7 @@ extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
fitplusbike::fitplusbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
double bikeResistanceGain) {
#ifdef Q_OS_IOS
QSettings settings;
bool sportstech_sx600 = settings.value(QZSettings::sportstech_sx600, QZSettings::default_sportstech_sx600).toBool();
if (sportstech_sx600) {
QZ_EnableDiscoveryCharsAndDescripttors = false;
} else {
QZ_EnableDiscoveryCharsAndDescripttors = true;
}
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif
m_watt.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
@@ -78,7 +72,6 @@ void fitplusbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QSt
void fitplusbike::forceResistance(resistance_t requestResistance) {
QSettings settings;
bool virtufit_etappe = settings.value(QZSettings::virtufit_etappe, QZSettings::default_virtufit_etappe).toBool();
bool sportstech_sx600 = settings.value(QZSettings::sportstech_sx600, QZSettings::default_sportstech_sx600).toBool();
if (virtufit_etappe || merach_MRK) {
if (requestResistance == 1) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x01, 0xf9, 0xb9, 0x03};
@@ -153,104 +146,6 @@ void fitplusbike::forceResistance(resistance_t requestResistance) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x18, 0x0e, 0x57, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
}
} else if (sportstech_sx600) {
if (requestResistance == 1) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x00, 0x00, 0x41, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 2) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x02, 0x00, 0x43, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 3) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x03, 0xfa, 0xb8, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 4) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x04, 0xfb, 0xbe, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 5) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x05, 0x00, 0x44, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 6) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x06, 0xfd, 0xba, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 7) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x07, 0xfe, 0xb8, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 8) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x08, 0x00, 0x49, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 9) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x09, 0x00, 0x48, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 10) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x0a, 0x00, 0x4b, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 11) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x0b, 0x00, 0x4a, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 12) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x0c, 0x03, 0x4e, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 13) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x0d, 0x03, 0x4f, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 14) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x0e, 0x00, 0x4f, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 15) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x0f, 0x05, 0x4b, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 16) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x10, 0x06, 0x57, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 17) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x11, 0x00, 0x50, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 18) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x12, 0x08, 0x5b, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 19) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x13, 0x00, 0x52, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 20) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x14, 0x00, 0x55, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 21) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x15, 0x00, 0x54, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 22) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x16, 0x00, 0x57, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 23) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x17, 0x00, 0x56, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 24) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x18, 0x00, 0x59, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 25) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x19, 0x00, 0x58, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 27) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x1a, 0x00, 0x59, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 27) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x1b, 0x00, 0x5a, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 28) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x1c, 0x00, 0x5b, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 29) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x1d, 0x00, 0x5c, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 30) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x1e, 0x00, 0x5f, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 31) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x1f, 0x00, 0x5e, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
} else if (requestResistance == 32) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x20, 0x00, 0x61, 0x03};
writeCharacteristic(res, sizeof(res), "force resistance", false, true);
}
}
}
@@ -270,14 +165,29 @@ void fitplusbike::update() {
update_metrics(true, watts());
bool virtufit_etappe =
settings.value(QZSettings::virtufit_etappe, QZSettings::default_virtufit_etappe).toBool();
bool sportstech_sx600 =
settings.value(QZSettings::sportstech_sx600, QZSettings::default_sportstech_sx600).toBool();
if (virtufit_etappe || merach_MRK || sportstech_sx600) {
if (virtufit_etappe || merach_MRK) {
} else {
m_watt = wattFromHR(false);
qDebug() << QStringLiteral("Current Watt: ") + QString::number(m_watt.value());
if (Heart.value() > 0) {
int avgP = ((settings.value(QZSettings::power_hr_pwr1, QZSettings::default_power_hr_pwr1).toDouble() *
settings.value(QZSettings::power_hr_hr2, QZSettings::default_power_hr_hr2).toDouble()) -
(settings.value(QZSettings::power_hr_pwr2, QZSettings::default_power_hr_pwr2).toDouble() *
settings.value(QZSettings::power_hr_hr1, QZSettings::default_power_hr_hr1).toDouble())) /
(settings.value(QZSettings::power_hr_hr2, QZSettings::default_power_hr_hr2).toDouble() -
settings.value(QZSettings::power_hr_hr1, QZSettings::default_power_hr_hr1).toDouble()) +
(Heart.value() *
((settings.value(QZSettings::power_hr_pwr1, QZSettings::default_power_hr_pwr1).toDouble() -
settings.value(QZSettings::power_hr_pwr2, QZSettings::default_power_hr_pwr2).toDouble()) /
(settings.value(QZSettings::power_hr_hr1, QZSettings::default_power_hr_hr1).toDouble() -
settings.value(QZSettings::power_hr_hr2, QZSettings::default_power_hr_hr2).toDouble())));
if (avgP < 50) {
avgP = 50;
}
m_watt = avgP;
qDebug() << QStringLiteral("Current Watt: ") + QString::number(m_watt.value());
}
}
// sending poll every 2 seconds
@@ -286,50 +196,20 @@ void fitplusbike::update() {
// updateDisplay(elapsed);
}
if (sportstech_sx600) {
uint8_t noOpData[] = {0x02, 0x42, 0x42, 0x03};
uint8_t noOpData1[] = {0x02, 0x02, 0x43, 0x01, 0x42, 0x03, 0x01, 0x03};
uint8_t noOpData2[] = {0x02, 0x53, 0x9f, 0x02, 0x00, 0x00, 0xce, 0x03};
uint8_t noOpData3[] = {0x02, 0x43, 0x01, 0x42, 0x03};
uint8_t noOpData4[] = {0x02, 0x53, 0x9f, 0x02, 0x00, 0x00, 0xce, 0x03};
switch (counterPoll) {
case 0:
writeCharacteristic(noOpData, sizeof(noOpData), QStringLiteral("noOp"), false, true);
break;
case 1:
writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp"), false, true);
break;
case 2:
writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("noOp"), false, true);
break;
case 3:
writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("noOp"), false, true);
break;
default:
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"), false, true);
break;
}
uint8_t noOpData[] = {0x02, 0x42, 0x42, 0x03};
uint8_t noOpData1[] = {0x02, 0x43, 0x01, 0x42, 0x03};
switch (counterPoll) {
case 0:
writeCharacteristic(noOpData, sizeof(noOpData), QStringLiteral("noOp"), false, true);
break;
default:
writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp"), false, true);
break;
}
counterPoll++;
if (counterPoll > 4) {
counterPoll = 0;
}
} else {
uint8_t noOpData[] = {0x02, 0x42, 0x42, 0x03};
uint8_t noOpData1[] = {0x02, 0x43, 0x01, 0x42, 0x03};
switch (counterPoll) {
case 0:
writeCharacteristic(noOpData, sizeof(noOpData), QStringLiteral("noOp"), false, true);
break;
default:
writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp"), false, true);
break;
}
counterPoll++;
if (counterPoll > 1) {
counterPoll = 0;
}
counterPoll++;
if (counterPoll > 1) {
counterPoll = 0;
}
if (requestResistance != -1) {
@@ -377,185 +257,15 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
lastPacket = newValue;
bool virtufit_etappe = settings.value(QZSettings::virtufit_etappe, QZSettings::default_virtufit_etappe).toBool();
bool sportstech_sx600 = settings.value(QZSettings::sportstech_sx600, QZSettings::default_sportstech_sx600).toBool();
if (sportstech_sx600 && characteristic.uuid() == QBluetoothUuid((quint16)0x2AD2)) {
bool disable_hr_frommachinery =
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
union flags {
struct {
uint16_t moreData : 1;
uint16_t avgSpeed : 1;
uint16_t instantCadence : 1;
uint16_t avgCadence : 1;
uint16_t totDistance : 1;
uint16_t resistanceLvl : 1;
uint16_t instantPower : 1;
uint16_t avgPower : 1;
uint16_t expEnergy : 1;
uint16_t heartRate : 1;
uint16_t metabolic : 1;
uint16_t elapsedTime : 1;
uint16_t remainingTime : 1;
uint16_t spare : 3;
};
uint16_t word_flags;
};
flags Flags;
int index = 0;
Flags.word_flags = (newValue.at(1) << 8) | newValue.at(0);
index += 2;
if (!Flags.moreData) {
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
Speed = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index)))) /
100.0;
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
index += 2;
qDebug() << QStringLiteral("Current Speed: ") + QString::number(Speed.value());
}
if (Flags.avgSpeed) {
double avgSpeed;
avgSpeed = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index)))) /
100.0;
index += 2;
qDebug() << QStringLiteral("Current Average Speed: ") + QString::number(avgSpeed);
}
if (Flags.instantCadence) {
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled"))) {
// this bike sent a cadence 1/10 of the real one
Cadence = (((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index)))) /
2.0) *
settings
.value(QZSettings::horizon_gr7_cadence_multiplier,
QZSettings::default_horizon_gr7_cadence_multiplier)
.toDouble();
}
index += 2;
qDebug() << QStringLiteral("Current Cadence: ") + QString::number(Cadence.value());
}
if (Flags.avgCadence) {
double avgCadence;
avgCadence = (((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index)))) /
2.0) *
settings
.value(QZSettings::horizon_gr7_cadence_multiplier,
QZSettings::default_horizon_gr7_cadence_multiplier)
.toDouble();
index += 2;
qDebug() << QStringLiteral("Current Average Cadence: ") + QString::number(avgCadence);
}
if (Flags.totDistance) {
index += 3;
} else {
}
qDebug() << 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());
m_pelotonResistance = Resistance.value();
index += 2;
qDebug() << QStringLiteral("Current Resistance: ") + QString::number(Resistance.value());
}
if (Flags.instantPower) {
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled")))
m_watt = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));
index += 2;
qDebug() << (QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
}
if (Flags.avgPower) {
double avgPower;
avgPower = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));
index += 2;
qDebug() << (QStringLiteral("Current Average Watt: ") + QString::number(avgPower));
}
if (Flags.expEnergy && newValue.length() > index + 1) {
KCal = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));
index += 2;
// energy per hour
index += 2;
// energy per minute
index += 1;
} else {
if (watts())
KCal += ((((0.048 * ((double)watts()) + 1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
200.0) /
(60000.0 /
((double)lastRefreshCharacteristicChanged.msecsTo(
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
}
qDebug() << (QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
Heart = (uint8_t)KeepAwakeHelper::heart();
else
#endif
{
if (Flags.heartRate && !disable_hr_frommachinery && newValue.length() > index) {
Heart = ((double)((newValue.at(index))));
// index += 1; // NOTE: clang-analyzer-deadcode.DeadStores
qDebug() << (QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
} else {
Flags.heartRate = false;
}
}
if (Flags.metabolic) {
// todo
}
if (Flags.elapsedTime) {
// todo
}
if (Flags.remainingTime) {
// todo
}
} else if (virtufit_etappe || merach_MRK || (sportstech_sx600 && !gattCommunicationChannelServiceFTMS)) {
if (virtufit_etappe || merach_MRK) {
if (newValue.length() != 15 && newValue.length() != 13)
return;
if (newValue.length() == 15) {
Resistance = newValue.at(5);
if (merach_MRK || sportstech_sx600) {
// if we change this, also change the wattsFromResistance function. We can create a standard function in
// order to have all the costants in one place (I WANT MORE TIME!!!)
if(merach_MRK) {
// if we change this, also change the wattsFromResistance function. We can create a standard function in order to
// have all the costants in one place (I WANT MORE TIME!!!)
double ac = 0.01243107769;
double bc = 1.145964912;
double cc = -23.50977444;
@@ -565,9 +275,9 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
double cr = 97.62165482;
m_pelotonResistance =
(((sqrt(pow(br, 2.0) - 4.0 * ar *
(cr - (m_watt.value() * 132.0 /
(ac * pow(Cadence.value(), 2.0) + bc * Cadence.value() + cc)))) -
(((sqrt(pow(br, 2.0) -
4.0 * ar *
(cr - (m_watt.value() * 132.0 / (ac * pow(Cadence.value(), 2.0) + bc * Cadence.value() + cc)))) -
br) /
(2.0 * ar)) *
settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
@@ -585,9 +295,7 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
/*if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool())
Speed = (double)((((uint8_t)newValue.at(4)) << 10) | ((uint8_t)newValue.at(9))) / 100.0;
else*/
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
Speed = metric::calculateSpeedFromPower(watts(), Inclination.value(), Speed.value(),fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
} else if (newValue.length() == 13) {
@@ -613,9 +321,7 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool())
Speed = (double)((((uint8_t)newValue.at(7)) << 8) | ((uint8_t)newValue.at(6))) / 10.0;
else
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
Speed = metric::calculateSpeedFromPower(watts(), Inclination.value(), Speed.value(),fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
if (watts())
@@ -684,7 +390,6 @@ void fitplusbike::btinit() {
QSettings settings;
bool virtufit_etappe = settings.value(QZSettings::virtufit_etappe, QZSettings::default_virtufit_etappe).toBool();
bool sportstech_sx600 = settings.value(QZSettings::sportstech_sx600, QZSettings::default_sportstech_sx600).toBool();
if (merach_MRK) {
uint8_t initData1[] = {0xaa, 0x01, 0x00, 0x01, 0x55};
@@ -710,28 +415,6 @@ void fitplusbike::btinit() {
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, true);
writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, true);
} else if (sportstech_sx600) {
uint8_t initData1[] = {0x02, 0x42, 0x42, 0x03};
uint8_t initData2[] = {0x02, 0x53, 0x9f, 0x02, 0x00, 0x00, 0xce, 0x03};
uint8_t initData3[] = {0x02, 0x44, 0x01, 0x45, 0x03};
uint8_t initData4[] = {0x02, 0x44, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x3c, 0xaa, 0x18, 0x00, 0xc0, 0x03};
uint8_t initData5[] = {0x02, 0x44, 0x0b, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x03};
uint8_t initData6[] = {0x02, 0x44, 0x02, 0x46, 0x03};
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, true);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, true);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, true);
writeCharacteristic(initData1, sizeof(initData2), QStringLiteral("init"), false, false);
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, true);
writeCharacteristic(initData1, sizeof(initData2), QStringLiteral("init"), false, false);
max_resistance = 32;
} else {
@@ -759,24 +442,12 @@ void fitplusbike::btinit() {
}
void fitplusbike::stateChanged(QLowEnergyService::ServiceState state) {
QSettings settings;
bool sportstech_sx600 = settings.value(QZSettings::sportstech_sx600, QZSettings::default_sportstech_sx600).toBool();
QBluetoothUuid _gattWriteCharacteristicId((quint16)0xfff2);
QBluetoothUuid _gattNotify1CharacteristicId((quint16)0xfff1);
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
qDebug() << QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state));
if (sportstech_sx600 && gattCommunicationChannelServiceFTMS) {
if (gattCommunicationChannelService->state() != QLowEnergyService::ServiceDiscovered ||
gattCommunicationChannelServiceFTMS->state() != QLowEnergyService::ServiceDiscovered) {
qDebug() << "sportstech_sx600 not all services discovered" << gattCommunicationChannelService->state()
<< gattCommunicationChannelServiceFTMS->state();
return;
}
}
if (state == QLowEnergyService::ServiceDiscovered) {
// qDebug() << gattCommunicationChannelService->characteristics();
@@ -836,30 +507,6 @@ void fitplusbike::stateChanged(QLowEnergyService::ServiceState state) {
descriptor.append((char)0x00);
gattCommunicationChannelService->writeDescriptor(
gattNotify1Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
if (sportstech_sx600 && gattCommunicationChannelServiceFTMS) {
QBluetoothUuid _gattNotifyFTMSCharacteristicId((quint16)0x2AD2);
gattNotifyFTMSCharacteristic =
gattCommunicationChannelServiceFTMS->characteristic(_gattNotifyFTMSCharacteristicId);
Q_ASSERT(gattNotifyFTMSCharacteristic.isValid());
connect(gattCommunicationChannelServiceFTMS, &QLowEnergyService::characteristicChanged, this,
&fitplusbike::characteristicChanged);
connect(gattCommunicationChannelServiceFTMS, &QLowEnergyService::characteristicWritten, this,
&fitplusbike::characteristicWritten);
connect(
gattCommunicationChannelServiceFTMS,
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
this, &fitplusbike::errorService);
connect(gattCommunicationChannelServiceFTMS, &QLowEnergyService::descriptorWritten, this,
&fitplusbike::descriptorWritten);
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
gattCommunicationChannelServiceFTMS->writeDescriptor(
gattNotifyFTMSCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
}
}
}
@@ -876,8 +523,6 @@ void fitplusbike::characteristicWritten(const QLowEnergyCharacteristic &characte
}
void fitplusbike::serviceScanDone(void) {
QSettings settings;
bool sportstech_sx600 = settings.value(QZSettings::sportstech_sx600, QZSettings::default_sportstech_sx600).toBool();
qDebug() << QStringLiteral("serviceScanDone");
QBluetoothUuid _gattCommunicationChannelServiceId((quint16)0xfff0);
@@ -885,16 +530,6 @@ void fitplusbike::serviceScanDone(void) {
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &fitplusbike::stateChanged);
gattCommunicationChannelService->discoverDetails();
if (sportstech_sx600) {
gattCommunicationChannelServiceFTMS = m_control->createServiceObject(QBluetoothUuid((quint16)0x1826));
if (gattCommunicationChannelServiceFTMS) {
qDebug() << "FTMS found!";
connect(gattCommunicationChannelServiceFTMS, &QLowEnergyService::stateChanged, this,
&fitplusbike::stateChanged);
gattCommunicationChannelServiceFTMS->discoverDetails();
}
}
}
void fitplusbike::errorService(QLowEnergyService::ServiceError err) {

View File

@@ -44,7 +44,7 @@ class fitplusbike : public bike {
void *VirtualDevice();
private:
resistance_t max_resistance = 24;
const resistance_t max_resistance = 24;
void btinit();
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
bool wait_for_response = false);
@@ -57,10 +57,8 @@ class fitplusbike : public bike {
virtualbike *virtualBike = nullptr;
QLowEnergyService *gattCommunicationChannelService = nullptr;
QLowEnergyService *gattCommunicationChannelServiceFTMS = nullptr;
QLowEnergyCharacteristic gattWriteCharacteristic;
QLowEnergyCharacteristic gattNotify1Characteristic;
QLowEnergyCharacteristic gattNotifyFTMSCharacteristic;
uint8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;

View File

@@ -121,12 +121,6 @@ bool fitshowtreadmill::writePayload(const uint8_t *array, uint8_t size, const QS
void fitshowtreadmill::forceSpeedOrIncline(double requestSpeed, double requestIncline) {
if (MAX_SPEED > 0) {
QSettings settings;
// the treadmill send the speed in miles always
double miles = 1;
if (settings.value(QZSettings::fitshow_treadmill_miles, QZSettings::default_fitshow_treadmill_miles).toBool())
miles = 1.60934;
requestSpeed /= miles;
requestSpeed *= 10.0;
if (requestSpeed >= MAX_SPEED) {
requestSpeed = MAX_SPEED;
@@ -140,7 +134,7 @@ void fitshowtreadmill::forceSpeedOrIncline(double requestSpeed, double requestIn
}
uint8_t writeIncline[] = {FITSHOW_SYS_CONTROL, FITSHOW_CONTROL_TARGET_OR_RUN, (uint8_t)(requestSpeed + 0.5),
(uint8_t)(requestIncline * (noblepro_connected ? 2.0 : 1.0))};
(uint8_t)requestIncline};
scheduleWrite(writeIncline, sizeof(writeIncline),
QStringLiteral("forceSpeedOrIncline speed=") + QString::number(requestSpeed) +
QStringLiteral(" incline=") + QString::number(requestIncline));
@@ -183,11 +177,18 @@ void fitshowtreadmill::update() {
if (requestSpeed != -1) {
if (requestSpeed != currentSpeed().value()) {
emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed));
double inc = rawInclination.value();
double inc = currentInclination().value();
if (requestInclination != -100) {
// only 0.5 or 1 changes otherwise it beeps forever
double a = 1.0 / minStepInclination();
inc = qRound(treadmillInclinationOverrideReverse(requestInclination) * a) / a;
int diffInc = (int)(requestInclination - inc);
if (!diffInc) {
if (requestInclination > inc) {
inc += 1.0;
} else if (requestInclination < inc) {
inc -= 1.0;
}
} else {
inc = (int)requestInclination;
}
requestInclination = -100;
}
forceSpeedOrIncline(requestSpeed, inc);
@@ -196,13 +197,19 @@ void fitshowtreadmill::update() {
}
if (requestInclination != -100) {
double inc = rawInclination.value();
// only 0.5 or 1 changes otherwise it beeps forever
double a = 1.0 / minStepInclination();
requestInclination = qRound(treadmillInclinationOverrideReverse(requestInclination) * a) / a;
double inc = currentInclination().value();
if (requestInclination != inc) {
emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
inc = requestInclination;
int diffInc = (int)(requestInclination - inc);
if (!diffInc) {
if (requestInclination > inc) {
inc += 1.0;
} else if (requestInclination < inc) {
inc -= 1.0;
}
} else {
inc = (int)requestInclination;
}
double speed = currentSpeed().value();
if (requestSpeed != -1) {
speed = requestSpeed;
@@ -270,10 +277,9 @@ void fitshowtreadmill::removeFromBuffer() {
void fitshowtreadmill::serviceDiscovered(const QBluetoothUuid &gatt) {
uint32_t servRepr = gatt.toUInt32();
QBluetoothUuid nobleproconnect(QStringLiteral("0000ae00-0000-1000-8000-00805f9b34fb"));
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString() + QStringLiteral(" ") +
QString::number(servRepr));
if (gatt == nobleproconnect || servRepr == 0xfff0 || (servRepr == 0xffe0 && serviceId.isNull())) {
if (servRepr == 0xfff0 || (servRepr == 0xffe0 && serviceId.isNull())) {
serviceId = gatt; // NOTE: clazy-rule-of-tow
}
}
@@ -291,8 +297,6 @@ void fitshowtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
bool disable_hr_frommachinery =
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
Q_UNUSED(characteristic);
QByteArray value = newValue;
@@ -439,10 +443,9 @@ void fitshowtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
lastTimeCharacteristicChanged = QDateTime::currentDateTime();
}
StepCount = step_count;
emit debug(QStringLiteral("Current elapsed from treadmill: ") + QString::number(seconds_elapsed));
emit debug(QStringLiteral("Current speed: ") + QString::number(speed));
emit debug(QStringLiteral("Current speed: ") + QString::number(speed));
emit debug(QStringLiteral("Current incline: ") + QString::number(incline));
emit debug(QStringLiteral("Current heart: ") + QString::number(heart));
emit debug(QStringLiteral("Current Distance: ") + QString::number(distance));
emit debug(QStringLiteral("Current Distance Calculated: ") + QString::number(DistanceCalculated));
@@ -461,26 +464,14 @@ void fitshowtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
lastStop = 0;
}
// the treadmill send the speed in miles always
double miles = 1;
if (settings.value(QZSettings::fitshow_treadmill_miles, QZSettings::default_fitshow_treadmill_miles)
.toBool())
miles = 1.60934;
Speed = speed * miles;
Speed = speed;
if (Speed.value() != speed) {
emit speedChanged(speed);
}
if (noblepro_connected)
incline /= 2;
rawInclination = incline;
Inclination = treadmillInclinationOverride(incline);
Inclination = incline;
if (Inclination.value() != incline) {
emit inclinationChanged(0, incline);
}
emit debug(QStringLiteral("Current incline: ") + QString::number(Inclination.value()));
if (truetimer)
elapsed = seconds_elapsed;
@@ -491,7 +482,7 @@ void fitshowtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
else
#endif
{
if (heartRateBeltName.startsWith(QStringLiteral("Disabled")) && !disable_hr_frommachinery) {
if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
#if defined(Q_OS_IOS) && !defined(IO_UNDER_QT)
long appleWatchHeartRate = h->heartRate();
h->setKcal(KCal.value());
@@ -582,8 +573,6 @@ void fitshowtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
double distance = array[4] | array[5] << 8;
uint16_t step_count = array[8] | array[9] << 8;
StepCount = step_count;
emit debug(QStringLiteral("Current elapsed from treadmill: ") + QString::number(seconds_elapsed));
emit debug(QStringLiteral("Current step countl: ") + QString::number(step_count));
emit debug(QStringLiteral("Current KCal from the machine: ") + QString::number(kcal));
@@ -671,9 +660,9 @@ void fitshowtreadmill::stateChanged(QLowEnergyService::ServiceState state) {
for (const QLowEnergyDescriptor &d : qAsConst(descriptors_list)) {
qDebug() << QStringLiteral("d -> ") << d.uuid();
}
if (id32 == 0xffe1 || id32 == 0xfff2 || id32 == 0xae01) {
if (id32 == 0xffe1 || id32 == 0xfff2) {
gattWriteCharacteristic = c;
} else if (id32 == 0xffe4 || id32 == 0xfff1 || id32 == 0xae02) {
} else if (id32 == 0xffe4 || id32 == 0xfff1) {
gattNotifyCharacteristic = c;
}
}
@@ -723,10 +712,6 @@ void fitshowtreadmill::serviceScanDone(void) {
emit debug(QStringLiteral("serviceScanDone"));
gattCommunicationChannelService = m_control->createServiceObject(serviceId);
if (!gattCommunicationChannelService) {
qDebug() << "service not valid";
return;
}
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &fitshowtreadmill::stateChanged);
#ifdef _MSC_VER
// QTBluetooth bug on Win10 (https://bugreports.qt.io/browse/QTBUG-78488)
@@ -753,13 +738,6 @@ void fitshowtreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
device.address().toString() + ')');
/*if (device.name().startsWith(QStringLiteral("FS-")) ||
(device.name().startsWith(QStringLiteral("SW")) && device.name().length() == 14))*/
if (device.name().toUpper().startsWith(QStringLiteral("NOBLEPRO CONNECT"))) {
qDebug() << "NOBLEPRO FIX!";
minStepInclinationValue = 0.5;
noblepro_connected = true;
}
{
bluetoothDevice = device;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
@@ -833,7 +811,7 @@ bool fitshowtreadmill::autoStartWhenSpeedIsGreaterThenZero() {
return false;
}
double fitshowtreadmill::minStepInclination() { return minStepInclinationValue; }
double fitshowtreadmill::minStepInclination() { return 1.0; }
void fitshowtreadmill::changeInclinationRequested(double grade, double percentage) {
if (percentage < 0)

View File

@@ -152,12 +152,6 @@ class fitshowtreadmill : public treadmill {
bool initDone = false;
bool initRequest = false;
double minStepInclinationValue = 1.0;
bool noblepro_connected = false;
metric rawInclination;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif

View File

@@ -80,24 +80,12 @@ void ftmsbike::forcePower(int16_t requestPower) {
void ftmsbike::forceResistance(resistance_t requestResistance) {
QSettings settings;
if (!settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool()) {
uint8_t write[] = {FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t write[] = {FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
double fr = (((double)requestResistance) * bikeResistanceGain) + ((double)bikeResistanceOffset);
requestResistance = fr;
write[3] = ((uint16_t)requestResistance * 100) & 0xFF;
write[4] = ((uint16_t)requestResistance * 100) >> 8;
write[3] = ((uint16_t)requestResistance * 10) & 0xFF;
write[4] = ((uint16_t)requestResistance * 10) >> 8;
writeCharacteristic(write, sizeof(write),
QStringLiteral("forceResistance ") + QString::number(requestResistance));
} else {
uint8_t write[] = {FTMS_SET_TARGET_RESISTANCE_LEVEL, 0x00};
write[1] = ((uint8_t)(requestResistance));
writeCharacteristic(write, sizeof(write),
QStringLiteral("forceResistance ") + QString::number(requestResistance));
}
writeCharacteristic(write, sizeof(write), QStringLiteral("forceResistance ") + QString::number(requestResistance));
}
void ftmsbike::update() {
@@ -738,13 +726,6 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
}
void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
if (!autoResistance()) {
qDebug() << "ignoring routing FTMS packet to the bike from virtualbike because of auto resistance OFF"
<< characteristic.uuid() << newValue.toHex(' ');
return;
}
QByteArray b = newValue;
if (gattWriteCharControlPointId.isValid()) {
qDebug() << "routing FTMS packet to the bike from virtualbike" << characteristic.uuid() << newValue.toHex(' ');

View File

@@ -137,8 +137,7 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
bool disable_hr_frommachinery =
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
bool disable_hr_frommachinery = settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
@@ -175,7 +174,7 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
flags Flags;
int index = 0;
double cadence_divider = 2.0;
if (WHIPR || KINGSMITH)
if (WHIPR)
cadence_divider = 1.0;
Flags.word_flags = (newValue.at(1) << 8) | newValue.at(0);
index += 2;
@@ -280,8 +279,8 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
} else {
if (watts())
KCal +=
((((0.048 * ((double)watts()) + 1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
((((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
@@ -346,8 +345,7 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
#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();
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());
@@ -459,14 +457,11 @@ void ftmsrower::stateChanged(QLowEnergyService::ServiceState state) {
) {
QSettings settings;
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
bool virtual_device_enabled = settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence =
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
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!";
@@ -552,9 +547,6 @@ void ftmsrower::deviceDiscovered(const QBluetoothDeviceInfo &device) {
filterWattNull = true;
WHIPR = true;
qDebug() << "WHIPR found! filtering null wattage";
} else if (device.name().toUpper().startsWith(QStringLiteral("KS-WLT"))) { // KS-WLT-W1
KINGSMITH = true;
qDebug() << "KINGSMITH found! cadence multiplier 1x";
}
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);

View File

@@ -69,7 +69,6 @@ class ftmsrower : public rower {
bool filterWattNull = false;
bool WHIPR = false;
bool KINGSMITH = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;

View File

@@ -109,13 +109,11 @@ void homefitnessbuddy::login_onfinish(QNetworkReply *reply) {
emit loginState(true);
// REMOVE IT
// searchWorkout(QDate(2021,5,19) ,"Christine D'Ercole", 3600, "");
// searchWorkout(QDate(2020, 1, 18), "Denis & Matt", 3600, ""); // Multiple Instructors
// searchWorkout(QDate(2021, 4, 23), "Ben Alldis", 2700, "a5f95a660f5b4a84ac6a86aa4468ea1d");
// searchWorkout(QDate(2021,5,19) ,"Christine D'Ercole", 3600);
// searchWorkout(QDate(2020,1,18) ,"Denis & Matt", 3600); // Multiple Instructors
}
void homefitnessbuddy::searchWorkout(QDate date, const QString &coach, int pedaling_duration, QString class_id) {
pelotonClassID = class_id;
void homefitnessbuddy::searchWorkout(QDate date, const QString &coach, int pedaling_duration) {
int found = 0;
for (const QJsonValue &r : qAsConst(lessons)) {
QDate d = QDate::fromString(r.toObject().value(QStringLiteral("Date")).toString(), QStringLiteral("MM/dd/yy"));
@@ -142,13 +140,22 @@ void homefitnessbuddy::searchWorkout(QDate date, const QString &coach, int pedal
coach.contains('&') &&
r.toObject().value(QStringLiteral("Coach")).toString().contains(QStringLiteral("Multiple Instructors"));
if (d == date && c) {
getClassID(r.toObject().value(QStringLiteral("Class ID")).toString());
connect(mgr, &QNetworkAccessManager::finished, this, &homefitnessbuddy::search_workout_onfinish);
QUrl url(QStringLiteral("https://app.homefitnessbuddy.com/peloton/powerzone/zwift_export.php"));
QUrlQuery query;
query.addQueryItem(QStringLiteral("class_id"),
r.toObject().value(QStringLiteral("Class ID")).toString());
url.setQuery(query.query());
QNetworkRequest request(url);
// request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("qdomyos-zwift"));
mgr->get(request);
return;
}
}
} else if (found > 1) {
found = 0;
QStringList hfbID;
qDebug() << QStringLiteral("HomeFitnessBuddy found more than one workout with the same date and same "
"Instructor, try to filter this out using the length...");
for (const QJsonValue &r : qAsConst(lessons)) {
@@ -159,43 +166,24 @@ void homefitnessbuddy::searchWorkout(QDate date, const QString &coach, int pedal
int len = r.toObject().value(QStringLiteral("Length")).toString().toInt();
bool duration = (len == (pedaling_duration / 60));
if (d == date && c && duration) {
found++;
hfbID.append(r.toObject().value(QStringLiteral("Class ID")).toString());
}
}
if (found == 1) {
getClassID(hfbID.first());
} else {
for (QString h : hfbID) {
connect(mgr, &QNetworkAccessManager::finished, this, &homefitnessbuddy::search_detail_onfinish);
QUrl url(QStringLiteral("https://app.homefitnessbuddy.com/peloton/powerzone/details/") + h);
qDebug() << "test" << h << url;
connect(mgr, &QNetworkAccessManager::finished, this, &homefitnessbuddy::search_workout_onfinish);
QUrl url(QStringLiteral("https://app.homefitnessbuddy.com/peloton/powerzone/zwift_export.php"));
QUrlQuery query;
query.addQueryItem(QStringLiteral("class_id"),
r.toObject().value(QStringLiteral("Class ID")).toString());
url.setQuery(query.query());
QNetworkRequest request(url);
// request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("qdomyos-zwift"));
mgr->get(request);
return;
}
}
}
}
void homefitnessbuddy::getClassID(QString id) {
connect(mgr, &QNetworkAccessManager::finished, this, &homefitnessbuddy::search_workout_onfinish);
QUrl url(QStringLiteral("https://app.homefitnessbuddy.com/peloton/powerzone/zwift_export.php"));
QUrlQuery query;
query.addQueryItem(QStringLiteral("class_id"), id);
url.setQuery(query.query());
QNetworkRequest request(url);
// request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("qdomyos-zwift"));
mgr->get(request);
}
void homefitnessbuddy::search_workout_onfinish(QNetworkReply *reply) {
disconnect(mgr, &QNetworkAccessManager::finished, this, &homefitnessbuddy::search_workout_onfinish);
QByteArray payload = reply->readAll(); // JSON
@@ -204,8 +192,7 @@ void homefitnessbuddy::search_workout_onfinish(QNetworkReply *reply) {
QSettings settings;
// NOTE: clazy-unused-non-trivial-variable
// QString difficulty = settings.value(QZSettings::peloton_difficulty,
// QZSettings::default_peloton_difficulty).toString();
// QString difficulty = settings.value(QZSettings::peloton_difficulty, QZSettings::default_peloton_difficulty).toString();
trainrows.clear();
trainrows = zwiftworkout::load(payload);
@@ -214,14 +201,3 @@ void homefitnessbuddy::search_workout_onfinish(QNetworkReply *reply) {
emit workoutStarted(&trainrows);
}
}
void homefitnessbuddy::search_detail_onfinish(QNetworkReply *reply) {
QString payload = reply->readAll(); // JSON
qDebug() << QStringLiteral("search_detail_onfinish") << payload;
if (payload.contains(pelotonClassID)) {
disconnect(mgr, &QNetworkAccessManager::finished, this, &homefitnessbuddy::search_detail_onfinish);
getClassID(reply->url().toString().split('/').last());
}
}

View File

@@ -23,12 +23,10 @@ class homefitnessbuddy : public QObject {
public:
homefitnessbuddy(bluetooth *bl, QObject *parent);
void searchWorkout(QDate date, const QString &coach, int pedaling_duration, QString class_id);
void searchWorkout(QDate date, const QString &coach, int pedaling_duration);
QList<trainrow> trainrows;
private:
QString pelotonClassID;
void getClassID(QString id);
const int peloton_workout_second_resolution = 10;
QNetworkAccessManager *mgr = nullptr;
@@ -41,7 +39,6 @@ class homefitnessbuddy : public QObject {
private slots:
void login_onfinish(QNetworkReply *reply);
void search_workout_onfinish(QNetworkReply *reply);
void search_detail_onfinish(QNetworkReply *reply);
void error(QNetworkReply::NetworkError code);
void startEngine();

File diff suppressed because it is too large Load Diff

View File

@@ -151,8 +151,6 @@ class homeform : public QObject {
Q_PROPERTY(QList<double> workout_resistance_points READ workout_resistance_points)
Q_PROPERTY(double wattMaxChart READ wattMaxChart)
Q_PROPERTY(bool autoResistance READ autoResistance NOTIFY autoResistanceChanged WRITE setAutoResistance)
Q_PROPERTY(bool stopRequested READ stopRequested NOTIFY stopRequestedChanged WRITE setStopRequestedChanged)
Q_PROPERTY(bool startRequested READ startRequested NOTIFY startRequestedChanged WRITE setStartRequestedChanged)
// workout preview
Q_PROPERTY(int preview_workout_points READ preview_workout_points NOTIFY previewWorkoutPointsChanged)
@@ -162,12 +160,7 @@ class homeform : public QObject {
Q_PROPERTY(bool currentCoordinateValid READ currentCoordinateValid)
Q_PROPERTY(QString getStravaAuthUrl READ getStravaAuthUrl NOTIFY stravaAuthUrlChanged)
Q_PROPERTY(bool stravaWebVisible READ stravaWebVisible NOTIFY stravaWebVisibleChanged)
public:
static homeform *singleton() { return m_singleton; }
Q_INVOKABLE void save_screenshot() {
QString path = getWritableAppDir();
@@ -331,8 +324,6 @@ class homeform : public QObject {
homeform(QQmlApplicationEngine *engine, bluetooth *bl);
~homeform();
int topBarHeight() { return m_topBarHeight; }
bool stopRequested() { return m_stopRequested; }
bool startRequested() { return m_startRequested; }
QString info() { return m_info; }
QString signal();
QString startText();
@@ -367,6 +358,7 @@ class homeform : public QObject {
QString instructorName() { return stravaPelotonInstructorName; }
int pelotonLogin() { return m_pelotonLoginState; }
int pzpLogin() { return m_pzpLoginState; }
bool pelotonAskStart() { return m_pelotonAskStart; }
void setPelotonAskStart(bool value) { m_pelotonAskStart = value; }
QString pelotonProvider() { return m_pelotonProvider; }
void setPelotonProvider(const QString &value) { m_pelotonProvider = value; }
@@ -396,14 +388,6 @@ class homeform : public QObject {
bluetoothManager->device()->setAutoResistance(value);
}
}
void setStopRequestedChanged(bool value) {
m_stopRequested = value;
emit stopRequestedChanged(value);
}
void setStartRequestedChanged(bool value) {
m_startRequested = value;
emit startRequestedChanged(value);
}
void setLicensePopupVisible(bool value);
void setVideoIconVisible(bool value);
void setVideoVisible(bool value) {
@@ -518,12 +502,7 @@ class homeform : public QObject {
return false;
}
QString getStravaAuthUrl() { return stravaAuthUrl; }
bool stravaWebVisible() { return stravaAuthWebVisible; }
trainprogram *trainingProgram() { return trainProgram; }
private:
static homeform *m_singleton;
QList<QObject *> dataList;
QList<SessionLine> Session;
bluetooth *bluetoothManager;
@@ -574,8 +553,6 @@ class homeform : public QObject {
QList<QString> chartImagesFilenames;
bool m_autoresistance = true;
bool m_stopRequested = false;
bool m_startRequested = false;
DataObject *speed;
DataObject *inclination;
@@ -636,7 +613,6 @@ class homeform : public QObject {
DataObject *preset_inclination_3;
DataObject *preset_inclination_4;
DataObject *preset_inclination_5;
DataObject *pace_last500m;
QTimer *timer;
QTimer *backupTimer;
@@ -648,10 +624,8 @@ class homeform : public QObject {
QAbstractOAuth::ModifyParametersFunction buildModifyParametersFunction(const QUrl &clientIdentifier,
const QUrl &clientIdentifierSharedKey);
bool strava_upload_file(const QByteArray &data, const QString &remotename);
QString stravaAuthUrl;
bool stravaAuthWebVisible;
static quint64 cryptoKeySettingsProfiles();
quint64 cryptoKeySettingsProfiles();
int16_t fanOverride = 0;
@@ -665,7 +639,7 @@ class homeform : public QObject {
QTextToSpeech m_speech;
int tts_summary_count = 0;
#if defined(Q_OS_WIN) || (defined(Q_OS_MAC) && !defined(Q_OS_IOS)) || (defined(Q_OS_ANDROID) && defined(LICENSE))
#if defined(Q_OS_WIN) || (defined(Q_OS_MAC) && !defined(Q_OS_IOS))
QTimer tLicense;
QNetworkAccessManager *mgr = nullptr;
void licenseRequest();
@@ -675,24 +649,17 @@ class homeform : public QObject {
PathController pathController;
bool videoMustBeReset = true;
#ifdef Q_OS_ANDROID
bool floating_open = false;
#endif
public slots:
void aboutToQuit();
void saveSettings(const QUrl &filename);
static void loadSettings(const QUrl &filename);
void loadSettings(const QUrl &filename);
void deleteSettings(const QUrl &filename);
void saveProfile(QString profilename);
void restart();
bool pelotonAskStart() { return m_pelotonAskStart; }
private slots:
void Start();
void Stop();
void StartRequested();
void StopRequested();
void Lap();
void Minus(const QString &);
void Plus(const QString &);
@@ -736,17 +703,12 @@ class homeform : public QObject {
void gearUp();
void gearDown();
void changeTimestamp(QTime source, QTime actual);
void pelotonOffset_Plus();
void pelotonOffset_Minus();
int pelotonOffset() { return (trainProgram ? trainProgram->offsetElapsedTime() : 0); }
#if defined(Q_OS_WIN) || (defined(Q_OS_MAC) && !defined(Q_OS_IOS)) || (defined(Q_OS_ANDROID) && defined(LICENSE))
#if defined(Q_OS_WIN) || (defined(Q_OS_MAC) && !defined(Q_OS_IOS))
void licenseReply(QNetworkReply *reply);
void licenseTimeout();
#endif
void toggleAutoResistance() { setAutoResistance(!autoResistance()); }
signals:
void changeOfdevice();
@@ -780,14 +742,10 @@ class homeform : public QObject {
void workoutNameChanged(QString name);
void workoutStartDateChanged(QString name);
void instructorNameChanged(QString name);
void startRequestedChanged(bool value);
void stopRequestedChanged(bool value);
void previewWorkoutPointsChanged(int value);
void previewWorkoutDescriptionChanged(QString value);
void previewWorkoutTagsChanged(QString value);
void stravaAuthUrlChanged(QString value);
void stravaWebVisibleChanged(bool value);
void workoutEventStateChanged(bluetoothdevice::WORKOUT_EVENT_STATE state);
};

View File

@@ -181,8 +181,23 @@ void horizongr7bike::characteristicChanged(const QLowEnergyCharacteristic &chara
Resistance = newValue.at(12);
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
m_watt = wattFromHR(false);
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
if (Heart.value() > 0) {
int avgP = ((settings.value(QZSettings::power_hr_pwr1, QZSettings::default_power_hr_pwr1).toDouble() *
settings.value(QZSettings::power_hr_hr2, QZSettings::default_power_hr_hr2).toDouble()) -
(settings.value(QZSettings::power_hr_pwr2, QZSettings::default_power_hr_pwr2).toDouble() *
settings.value(QZSettings::power_hr_hr1, QZSettings::default_power_hr_hr1).toDouble())) /
(settings.value(QZSettings::power_hr_hr2, QZSettings::default_power_hr_hr2).toDouble() -
settings.value(QZSettings::power_hr_hr1, QZSettings::default_power_hr_hr1).toDouble()) +
(Heart.value() * ((settings.value(QZSettings::power_hr_pwr1, QZSettings::default_power_hr_pwr1).toDouble() -
settings.value(QZSettings::power_hr_pwr2, QZSettings::default_power_hr_pwr2).toDouble()) /
(settings.value(QZSettings::power_hr_hr1, QZSettings::default_power_hr_hr1).toDouble() -
settings.value(QZSettings::power_hr_hr2, QZSettings::default_power_hr_hr2).toDouble())));
if (avgP < 50) {
avgP = 50;
}
m_watt = avgP;
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
}
if (watts())
KCal +=

View File

@@ -54,7 +54,7 @@ void horizontreadmill::writeCharacteristic(QLowEnergyService *service, QLowEnerg
if (wait_for_response) {
connect(this, &horizontreadmill::packetReceived, &loop, &QEventLoop::quit);
timeout.singleShot(8000, &loop, SLOT(quit())); // 6 seconds are important
timeout.singleShot(3000, &loop, SLOT(quit()));
} else {
connect(service, SIGNAL(characteristicWritten(QLowEnergyCharacteristic, QByteArray)), &loop, SLOT(quit()));
timeout.singleShot(3000, &loop, SLOT(quit()));
@@ -171,9 +171,6 @@ void horizontreadmill::btinit() {
QStringLiteral("init"), false, true);
waitForAPacket();
init1:
initPacketRecv = false;
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData7, sizeof(initData7),
QStringLiteral("init"), false, false);
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData8, sizeof(initData8),
@@ -251,15 +248,6 @@ void horizontreadmill::btinit() {
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData14, sizeof(initData14),
QStringLiteral("init"), false, true);
if (!initPacketRecv) {
qDebug() << "init 1 not received";
waitForAPacket();
goto init1;
}
init2:
initPacketRecv = false;
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData7_1, sizeof(initData7_1),
QStringLiteral("init"), false, false);
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData8, sizeof(initData8),
@@ -337,15 +325,6 @@ void horizontreadmill::btinit() {
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData14, sizeof(initData14),
QStringLiteral("init"), false, true);
if (!initPacketRecv) {
qDebug() << "init 2 not received";
waitForAPacket();
goto init2;
}
init3:
initPacketRecv = false;
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData7_2, sizeof(initData7_2),
QStringLiteral("init"), false, false);
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData8, sizeof(initData8),
@@ -423,15 +402,6 @@ void horizontreadmill::btinit() {
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData14, sizeof(initData14),
QStringLiteral("init"), false, true);
if (!initPacketRecv) {
qDebug() << "init 3 not received";
waitForAPacket();
goto init3;
}
init4:
initPacketRecv = false;
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData7_3, sizeof(initData7_3),
QStringLiteral("init"), false, false);
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData8, sizeof(initData8),
@@ -509,15 +479,6 @@ void horizontreadmill::btinit() {
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData14, sizeof(initData14),
QStringLiteral("init"), false, true);
if (!initPacketRecv) {
qDebug() << "init 4 not received";
waitForAPacket();
goto init4;
}
init5:
initPacketRecv = false;
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData7_4, sizeof(initData7_4),
QStringLiteral("init"), false, false);
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData8, sizeof(initData8),
@@ -595,15 +556,6 @@ void horizontreadmill::btinit() {
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData14, sizeof(initData14),
QStringLiteral("init"), false, true);
if (!initPacketRecv) {
qDebug() << "init 5 not received";
waitForAPacket();
goto init5;
}
init6:
initPacketRecv = false;
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData7_5, sizeof(initData7_5),
QStringLiteral("init"), false, false);
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData8, sizeof(initData8),
@@ -681,15 +633,6 @@ void horizontreadmill::btinit() {
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData14, sizeof(initData14),
QStringLiteral("init"), false, true);
if (!initPacketRecv) {
qDebug() << "init 6 not received";
waitForAPacket();
goto init6;
}
init7:
initPacketRecv = false;
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData7_6, sizeof(initData7_6),
QStringLiteral("init"), false, false);
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData8, sizeof(initData8),
@@ -767,15 +710,6 @@ void horizontreadmill::btinit() {
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData14, sizeof(initData14),
QStringLiteral("init"), false, true);
if (!initPacketRecv) {
qDebug() << "init 7 not received";
waitForAPacket();
goto init7;
}
init8:
initPacketRecv = false;
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData02, sizeof(initData02),
QStringLiteral("init"), false, true);
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData03, sizeof(initData03),
@@ -792,12 +726,6 @@ void horizontreadmill::btinit() {
QStringLiteral("init"), false, true);
writeCharacteristic(gattCustomService, gattWriteCharCustomService, initData4, sizeof(initData4),
QStringLiteral("init"), false, true);
if (!initPacketRecv) {
qDebug() << "init 8 not received";
waitForAPacket();
goto init8;
}
}
messageID = 0x10;
}
@@ -838,11 +766,10 @@ void horizontreadmill::update() {
}
if (requestSpeed != -1) {
bool minSpeed = fabs(requestSpeed - currentSpeed().value()) >= minStepSpeed();
bool forceSpeedNeed = checkIfForceSpeedNeeding(requestSpeed);
qDebug() << "requestSpeed=" << requestSpeed << minSpeed << forceSpeedNeed;
if (requestSpeed != currentSpeed().value() && minSpeed && requestSpeed >= 0 && requestSpeed <= 22 &&
forceSpeedNeed) {
qDebug() << "requestSpeed=" << requestSpeed;
if (requestSpeed != currentSpeed().value() &&
fabs(requestSpeed - currentSpeed().value()) > minStepSpeed() && requestSpeed >= 0 &&
requestSpeed <= 22 && checkIfForceSpeedNeeding(requestSpeed)) {
emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed));
forceSpeed(requestSpeed);
}
@@ -852,7 +779,7 @@ void horizontreadmill::update() {
qDebug() << "requestInclination=" << requestInclination;
if (requestInclination < 0)
requestInclination = 0;
else if (((int)requestInclination) != requestInclination) { // it has decimal
else if(((int)requestInclination) != requestInclination) { // it has decimal
// the treadmill accepts only .5 steps
requestInclination = floor(requestInclination) + 0.5;
}
@@ -940,33 +867,27 @@ void horizontreadmill::update() {
// stop
if (requestPause == -1) {
Speed = 0; // forcing the speed to be sure, maybe I could remove this
if (!settings
.value(QZSettings::horizon_treadmill_disable_pause,
QZSettings::default_horizon_treadmill_disable_pause)
.toBool()) {
if(!settings.value(QZSettings::horizon_treadmill_disable_pause, QZSettings::default_horizon_treadmill_disable_pause).toBool()) {
messageID++;
uint8_t write1[] = {0x55, 0xaa, 0x13, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x00};
write1[2] = messageID & 0xff;
write1[3] = messageID >> 8;
writeCharacteristic(gattCustomService, gattWriteCharCustomService, write1,
sizeof(write1), QStringLiteral("requestStop"), false, true);
writeCharacteristic(gattCustomService, gattWriteCharCustomService, write1, sizeof(write1),
QStringLiteral("requestStop"), false, true);
}
// pause
} else {
requestPause = -1;
Speed = 0; // forcing the speed to be sure, maybe I could remove this
if (!settings
.value(QZSettings::horizon_treadmill_disable_pause,
QZSettings::default_horizon_treadmill_disable_pause)
.toBool()) {
if(!settings.value(QZSettings::horizon_treadmill_disable_pause, QZSettings::default_horizon_treadmill_disable_pause).toBool()) {
messageID++;
uint8_t write1[] = {0x55, 0xaa, 0x12, 0x00, 0x03, 0x03, 0x01, 0x00, 0xf0, 0xe1, 0x00};
write1[2] = messageID & 0xff;
write1[3] = messageID >> 8;
writeCharacteristic(gattCustomService, gattWriteCharCustomService, write1,
sizeof(write1), QStringLiteral("requestPause"), false, false);
writeCharacteristic(gattCustomService, gattWriteCharCustomService, write1, sizeof(write1),
QStringLiteral("requestPause"), false, false);
horizonPaused = true;
}
}
@@ -1024,10 +945,9 @@ void horizontreadmill::forceSpeed(double requestSpeed) {
if (!horizon_paragon_x) {
messageID++;
uint8_t datas[4];
double s = qRound(requestSpeed * 0.621371 * 10);
datas[0] = 0;
datas[1] = (uint8_t)(s)&0xff;
datas[2] = (uint16_t)(s) >> 8;
datas[1] = (uint8_t)(requestSpeed * 0.621371 * 10) & 0xff;
datas[2] = (uint16_t)(requestSpeed * 0.621371 * 10) >> 8;
datas[3] = 0;
int confirm = GenerateCRC_CCITT(datas, 4);
uint8_t write[] = {0x55, 0xaa, 0x00, 0x00, 0x03, 0x05, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
@@ -1048,9 +968,8 @@ void horizontreadmill::forceSpeed(double requestSpeed) {
double miles_conversion = 1.0;
if (miles)
miles_conversion = 0.621371;
double s = qRound(requestSpeed * miles_conversion * 10);
datas[0] = (uint8_t)(s)&0xff;
datas[1] = (uint16_t)(s) >> 8;
datas[0] = (uint8_t)(requestSpeed * miles_conversion * 10) & 0xff;
datas[1] = (uint16_t)(requestSpeed * miles_conversion * 10) >> 8;
datas[2] = 0x01;
uint8_t initData02_paragon[] = {0x55, 0xaa, 0x00, 0x00, 0x03, 0x05, 0x03, 0x00,
0x00, 0x00, 0x6f, 0x00, 0x01, 0x0d, 0x0a};
@@ -1071,17 +990,17 @@ void horizontreadmill::forceSpeed(double requestSpeed) {
// for the Tecnogym Myrun
uint8_t write[] = {FTMS_REQUEST_CONTROL};
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
false);
true);
write[0] = {FTMS_START_RESUME};
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "start simulation",
false, false);
false, true);
uint8_t writeS[] = {FTMS_SET_TARGET_SPEED, 0x00, 0x00};
writeS[1] = ((uint16_t)(requestSpeed * 100)) & 0xFF;
writeS[2] = ((uint16_t)(requestSpeed * 100)) >> 8;
writeS[1] = ((uint16_t)requestSpeed * 100) & 0xFF;
writeS[2] = ((uint16_t)requestSpeed * 100) >> 8;
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, writeS, sizeof(writeS),
QStringLiteral("forceSpeed"), false, false);
QStringLiteral("forceSpeed"), false, true);
}
}
@@ -1132,17 +1051,17 @@ void horizontreadmill::forceIncline(double requestIncline) {
// for the Tecnogym Myrun
uint8_t write[] = {FTMS_REQUEST_CONTROL};
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
false);
true);
write[0] = {FTMS_START_RESUME};
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "start simulation",
false, false);
false, true);
uint8_t writeS[] = {FTMS_SET_TARGET_INCLINATION, 0x00, 0x00};
writeS[1] = ((int16_t)(requestIncline * 10.0)) & 0xFF;
writeS[2] = ((int16_t)(requestIncline * 10.0)) >> 8;
writeS[1] = ((int16_t)requestIncline * 10) & 0xFF;
writeS[2] = ((int16_t)requestIncline * 10) >> 8;
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, writeS, sizeof(writeS),
QStringLiteral("forceIncline"), false, false);
QStringLiteral("forceIncline"), false, true);
}
}
@@ -1180,20 +1099,11 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
if (customRecv <= 0) {
emit debug(QStringLiteral(" << FULL ") + " " + lastPacketComplete.toHex(' '));
qDebug() << "full custom packet received";
initPacketRecv = true;
customRecv = 0;
emit packetReceived();
}
}
if (isPaused() && settings
.value(QZSettings::horizon_treadmill_suspend_stats_pause,
QZSettings::default_horizon_treadmill_suspend_stats_pause)
.toBool()) {
qDebug() << "treadmill paused so I'm ignoring the new metrics";
return;
}
if (characteristic.uuid() == QBluetoothUuid((quint16)0xFFF4) && lastPacketComplete.length() > 70 &&
lastPacketComplete.at(0) == 0x55 && lastPacketComplete.at(5) == 0x17) {
Speed = (((double)(((uint16_t)((uint8_t)lastPacketComplete.at(25)) << 8) |
@@ -1619,8 +1529,6 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
}
}
cadenceFromAppleWatch();
if (Speed.value() > 0)
lastStart = 0;
else
@@ -1637,7 +1545,6 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
}
void horizontreadmill::stateChanged(QLowEnergyService::ServiceState state) {
QSettings settings;
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
QBluetoothUuid _gattWriteCharCustomService((quint16)0xFFF3);
QBluetoothUuid _gattWriteCharControlPointId((quint16)0x2AD9);
@@ -1688,11 +1595,7 @@ void horizontreadmill::stateChanged(QLowEnergyService::ServiceState state) {
gattFTMSService = s;
}
if (c.properties() & QLowEnergyCharacteristic::Write && c.uuid() == _gattWriteCharCustomService &&
!settings
.value(QZSettings::horizon_treadmill_force_ftms,
QZSettings::default_horizon_treadmill_force_ftms)
.toBool()) {
if (c.properties() & QLowEnergyCharacteristic::Write && c.uuid() == _gattWriteCharCustomService) {
qDebug() << QStringLiteral("Custom service and Control Point found");
gattWriteCharCustomService = c;
gattCustomService = s;
@@ -1838,11 +1741,6 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
{
bluetoothDevice = device;
if (device.name().toUpper().startsWith(QStringLiteral("MOBVOI TM"))) {
mobvoi_treadmill = true;
qDebug() << QStringLiteral("MOBVOI TM workaround ON!");
}
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &horizontreadmill::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &horizontreadmill::serviceScanDone);

View File

@@ -85,7 +85,6 @@ class horizontreadmill : public treadmill {
bool initDone = false;
bool initRequest = false;
bool initPacketRecv = false;
bool noWriteResistance = false;
bool noHeartService = false;
@@ -93,8 +92,6 @@ class horizontreadmill : public treadmill {
int32_t customRecv = 0;
int32_t messageID = 0;
bool mobvoi_treadmill = false;
void testProfileCRC();
void updateProfileCRC();
int GenerateCRC_CCITT(uint8_t *PUPtr8, int PU16_Count, int crcStart = 65535);

View File

@@ -1,5 +1,4 @@
#include "iconceptbike.h"
#include "keepawakehelper.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QMetaEnum>
@@ -28,28 +27,14 @@ void iconceptbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
discoveryAgent = new QBluetoothServiceDiscoveryAgent(this);
connect(discoveryAgent, &QBluetoothServiceDiscoveryAgent::serviceDiscovered, this,
&iconceptbike::serviceDiscovered);
connect(discoveryAgent, &QBluetoothServiceDiscoveryAgent::finished, this, &iconceptbike::serviceFinished);
// Start a discovery
qDebug() << QStringLiteral("iconceptbike::deviceDiscovered");
discoveryAgent->start();
discoveryAgent->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
return;
}
}
void iconceptbike::serviceFinished() {
qDebug() << QStringLiteral("iconceptbike::serviceFinished") << socket;
if (socket) {
#ifdef Q_OS_ANDROID
socket->setPreferredSecurityFlags(QBluetooth::NoSecurity);
#endif
emit debug(QStringLiteral("Create socket"));
socket->connectToService(serialPortService);
emit debug(QStringLiteral("ConnectToService done"));
}
}
// In your local slot, read information about the found devices
void iconceptbike::serviceDiscovered(const QBluetoothServiceInfo &service) {
// this treadmill has more serial port, just the first one is the right one.
@@ -76,6 +61,14 @@ void iconceptbike::serviceDiscovered(const QBluetoothServiceInfo &service) {
connect(socket, &QBluetoothSocket::disconnected, this, &iconceptbike::disconnected);
connect(socket, QOverload<QBluetoothSocket::SocketError>::of(&QBluetoothSocket::error), this,
&iconceptbike::onSocketErrorOccurred);
#ifdef Q_OS_ANDROID
socket->setPreferredSecurityFlags(QBluetooth::NoSecurity);
#endif
emit debug(QStringLiteral("Create socket"));
socket->connectToService(serialPortService);
emit debug(QStringLiteral("ConnectToService done"));
}
}
}
@@ -87,8 +80,7 @@ void iconceptbike::update() {
// ******************************************* virtual treadmill init *************************************
if (!firstStateChanged && !virtualBike) {
QSettings settings;
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
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 bike interface..."));
virtualBike = new virtualbike(this, true);
@@ -100,26 +92,22 @@ void iconceptbike::update() {
// ********************************************************************************************************
if (requestResistance != -1) {
if (requestResistance > 12) {
requestResistance = 12;
if (requestResistance > 32) {
requestResistance = 32;
} else if (requestResistance < 1) {
requestResistance = 1;
}
char resValues[] = {0x08, 0x0a, 0x0b, 0x0d, 0x0e, 0x10, 0x11, 0x13, 0x14, 0x16, 0x17, 0x18};
char res[] = {0x55, 0x11, 0x01, 0x12};
res[3] = resValues[requestResistance - 1];
qDebug() << QStringLiteral(">>") << QByteArray(res, sizeof(res)).toHex(' ');
res[3] = requestResistance;
socket->write(res, sizeof(res));
Resistance = requestResistance;
requestResistance = -1;
} else {
const char poll[] = {0x55, 0x17, 0x01, 0x01};
qDebug() << QStringLiteral(">>") << QByteArray(poll, sizeof(poll)).toHex(' ');
socket->write(poll, sizeof(poll));
emit debug(QStringLiteral("write poll"));
}
update_metrics(false, watts());
const char poll[] = {0x55, 0x17, 0x01, 0x01};
socket->write(poll, sizeof(poll));
emit debug(QStringLiteral("write poll"));
update_metrics(true, watts());
}
}
@@ -140,7 +128,6 @@ void iconceptbike::rfCommConnected() {
const uint8_t init6[] = {0x55, 0x11, 0x01, 0x01};
const uint8_t init7[] = {0x55, 0x0a, 0x01, 0x01};
const uint8_t init8[] = {0x55, 0x07, 0x01, 0xff};
const uint8_t init9[] = {0x55, 0x11, 0x01, 0x08};
socket->write((char *)init1, sizeof(init1));
qDebug() << QStringLiteral(" init1 write");
@@ -167,9 +154,6 @@ void iconceptbike::rfCommConnected() {
QThread::msleep(600);
socket->write((char *)init8, sizeof(init8));
qDebug() << QStringLiteral(" init8 write");
QThread::msleep(600);
socket->write((char *)init9, sizeof(init9));
qDebug() << QStringLiteral(" init9 write");
initDone = true;
// requestStart = 1;
@@ -185,95 +169,24 @@ void iconceptbike::readSocket() {
qDebug() << QStringLiteral(" << ") + line.toHex(' ');
if (line.length() == 16) {
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
bool bh_spada_2_watt =
settings.value(QZSettings::bh_spada_2_watt, QZSettings::default_bh_spada_2_watt).toBool();
elapsed = GetElapsedTimeFromPacket(line);
Distance = GetDistanceFromPacket(line);
KCal = GetCaloriesFromPacket(line);
if (bh_spada_2_watt) {
m_watt = GetSpeedFromPacket(line) * 0.24 * (1.0 + (Resistance.value() / 20.0));
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
Speed = GetSpeedFromPacket(line);
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
if (watts())
KCal +=
((((0.048 * ((double)watts()) + 1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
200.0) /
(60000.0 /
((double)lastRefreshCharacteristicChanged.msecsTo(
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
} else {
Speed = GetSpeedFromPacket(line);
}
Speed = GetSpeedFromPacket(line);
Cadence = (uint8_t)line.at(13);
// Heart = GetHeartRateFromPacket(line);
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"))) {
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
lockscreen h;
long appleWatchHeartRate = h.heartRate();
h.setKcal(KCal.value());
h.setDistance(Distance.value());
Heart = appleWatchHeartRate;
qDebug() << "Current Heart from Apple Watch: " + QString::number(appleWatchHeartRate);
#endif
#endif
}
}
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence =
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->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 = Resistance.value();
m_pelotonResistance = m_pelotonResistance.value();
emit debug(QStringLiteral("Current speed: ") + QString::number(Speed.value()));
emit debug(QStringLiteral("Current cadence: ") + QString::number(Cadence.value()));
// emit debug(QStringLiteral("Current heart: ") + QString::number(Heart.value()));
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
qDebug() << QStringLiteral("Current Watt: ") + QString::number(watts());
}
}
}
double iconceptbike::GetSpeedFromPacket(const QByteArray &packet) {
double convertedData = ((double)(((double)((uint8_t)packet.at(9))) * 256) + ((double)packet.at(10))) / 100.0;
double convertedData = ((double)((double)((uint8_t)packet.at(9))) + ((double)packet.at(10))) / 100.0;
return convertedData;
}
@@ -299,10 +212,3 @@ void iconceptbike::onSocketErrorOccurred(QBluetoothSocket::SocketError error) {
void *iconceptbike::VirtualBike() { return virtualBike; }
void *iconceptbike::VirtualDevice() { return VirtualBike(); }
uint16_t iconceptbike::watts() {
if (currentCadence().value() == 0) {
return 0;
}
return m_watt.value();
}

View File

@@ -42,7 +42,6 @@ class iconceptbike : public bike {
private slots:
void serviceDiscovered(const QBluetoothServiceInfo &service);
void serviceFinished();
void readSocket();
void rfCommConnected();
void onSocketErrorOccurred(QBluetoothSocket::SocketError);
@@ -64,10 +63,6 @@ class iconceptbike : public bike {
uint16_t GetCaloriesFromPacket(const QByteArray &packet);
double GetSpeedFromPacket(const QByteArray &packet);
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
uint16_t watts();
signals:
void disconnected();
void debug(QString string);

View File

@@ -1,5 +1,4 @@
<!doctype html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -8,771 +7,114 @@
<script src="globals.js"></script>
<script src="main_ws_manager.js"></script>
<style>
td {
text-align: center;
vertical-align: middle;
border-right: 5px solid #000000;
}
canvas {
td {
text-align: center;
vertical-align: middle;
}
canvas{
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
@font-face {
font-family: "Radikal";
src: url("radikalmedium.otf") format("opentype");
}
table.customfont {
font-family: "Radikal", Verdana, Tahoma;
}
.icon {
font-size: 24px
}
.values {
min-width: 80px;
max-width: 80px;
font-size: 24px;
}
.overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0, 0, 0, 1);
z-index: 2;
}
.confirm-box {
position: absolute;
width: 100%;
height: 100%;
top: 0%;
left: 0%;
text-align: center;
}
}
</style>
</head>
<body class="transparency" style="background-color: #000000; opacity: 0.8">
<div class="overlay" id="overlay" hidden>
<div class="confirm-box">
<h2 style="color: #FFFFFF; font-family: sans-serif">Peloton Workout in progress!</h2>
<p style="color: #FFFFFF; font-family: sans-serif">Do you want to follow the resistance?</p>
<button onclick="isConfirm(true)">Yes</button>
<button onclick="isConfirm(false)">No</button>
</div>
</div>
<table id="maintable" class="metrics customfont" style="color: #FFFFFF; font-family: sans-serif; border: 0">
<tr class="speed">
<td class="icon">🏃</td>
<td style="text-align: left">SPEED</td>
<td><small>AVG</small></td>
<td class="speed-avg">0.0</td>
<td class="speed-value values"><b>0.0</b></td>
<td><small>MAX</small></td>
<td class="speed-max">0.0</td>
</tr>
<tr class="inclination">
<td class="icon">📐</td>
<td style="text-align: left">INCLINE</td>
<td><small>AVG</small></td>
<td class="inclination-avg">0.0</td>
<td class="inclination-value values"><b>0.0</b></td>
<td><small>MAX</small></td>
<td class="inclination-max">0.0</td>
</tr>
<tr class="pace">
<td class="icon">🏃</td>
<td style="text-align: left">PACE</td>
<td><small>AVG</small></td>
<td class="pace-avg">00:00</td>
<td class="pace-value values"><b>00:00</b></td>
<td><small>MAX</small></td>
<td class="pace-max">00:00</td>
</tr>
<tr class="elevation">
<td class="icon">🚵</td>
<td style="text-align: left">ELEV.</td>
<td class="elevation-value values" colspan="5"><b>0.0</b></td>
</tr>
<tr class="cadence">
<td class="icon">🚴</td>
<td style="text-align: left">CADENCE</td>
<td><small>AVG</small></td>
<td class="cadence-avg">0</td>
<td class="cadence-value values"><b>0</b></td>
<td><small>MAX</small></td>
<td class="cadence-max">0</td>
</tr>
<tr class="heart">
<td class="icon">💓</td>
<td style="text-align: left">PULSE</td>
<td><small>AVG</small></td>
<td class="heart-avg">0</td>
<td class="heart-value values"><b>0</b></td>
<td><small>MAX</small></td>
<td class="heart-max">0</td>
</tr>
<tr class="watt">
<td class="icon"></td>
<td style="text-align: left">POWER</td>
<td><small>AVG</small></td>
<td class="watt-avg">0</td>
<td class="watt-value values"><b>0</b></td>
<td><small>MAX</small></td>
<td class="watt-max">0</td>
</tr>
<tr class="powerzone">
<td class="icon">🚥</td>
<td style="text-align: left">P.ZONE</td>
<td><small>AVG</small></td>
<td class="powerzone-avg">0.0</td>
<td class="powerzone-value values"><b>0.0</b></td>
<td><small>MAX</small></td>
<td class="powerzone-max">0.0</td>
</tr>
<tr class="resistance">
<td class="icon">🆁</td>
<td style="text-align: left">RESISTANCE</td>
<td><small>AVG</small></td>
<td class="resistance-avg">1</td>
<td class="resistance-value values"><b>1</b></td>
<td><small>MAX</small></td>
<td class="resistance-max">1</td>
</tr>
<tr class="pelotonresistance">
<td class="icon">🅿</td>
<td style="text-align: left">P.RESISTANCE</td>
<td><small>AVG</small></td>
<td class="pelotonresistance-avg">1</td>
<td class="pelotonresistance-value values"><b>1</b></td>
<td><small>MAX</small></td>
<td class="pelotonresistance-max">1</td>
</tr>
<tr class="calories">
<td class="icon">🔥</td>
<td style="text-align: left">CALORIES</td>
<td class="calories-value values" colspan="5"><b>0</b></td>
</tr>
<tr class="jouls">
<td class="icon">🔥</td>
<td style="text-align: left">TOT.OUTPUT</td>
<td class="jouls-value values" colspan="5"><b>0</b></td>
</tr>
<tr class="distance">
<td class="icon">📏</td>
<td style="text-align: left">DISTANCE</td>
<td class="distance-value values" colspan="5"><b>0.00</b></td>
</tr>
<tr class="elapsed">
<td class="icon">⏲️</td>
<td style="text-align: left">ELAPSED</td>
<td class="elapsed-value values" colspan="5"><b>0:00:00</b></td>
</tr>
<tr class="rowremainingtime">
<td class="icon">⏲️</td>
<td style="text-align: left">REM.TIME</td>
<td class="rowremainingtime-value values" colspan="5"><b>0:00:00</b></td>
</tr>
<tr class="pelotonoffset">
<td class="icon"></td>
<td style="text-align: left">P.OFFSET</td>
<td colspan="2"><button style="width: 40px; font-size: 24px; color: white; background-color:#4C70BF"
onclick="PelotonOffsetMinus()">-</button></td>
<td class="pelotonoffset-value values"><b>0</b></td>
<td colspan="2"><button style="width: 40px; font-size: 24px; color: white; background-color:#4C70BF"
onclick="PelotonOffsetPlus()">+</button></td>
</tr>
<tr class="gears">
<td class="icon"></td>
<td style="text-align: left">GEARS</td>
<td colspan="2"><button style="width: 40px; font-size: 24px; color: white; background-color:#4C70BF"
onclick="GearsMinus()">-</button></td>
<td class="gears-value values"><b>0</b></td>
<td colspan="2"><button style="width: 40px; font-size: 24px; color: white; background-color:#4C70BF"
onclick="GearsPlus()">+</button></td>
</tr>
<tr>
<td colspan=2 style="text-align:left"><button style="font-size: 16px; color: white; background-color:#4C70BF"
onclick="Lap()">CLEAR</button></td>
<td colspan="2"><button style="font-size: 16px; color: white; background-color:#4C70BF"
onclick="Start()">▶/⏸</button></td>
<td><button style="font-size: 16px; color: white; background-color:#4C70BF" onclick="Stop()"></button></td>
<td colspan=3 style="text-align:right">
<button class="autoresistance" style="font-size: 16px; color: white; background-color:#4C70BF"
onclick="AutoResistance()">🧲</button>
<button style="font-size: 16px; color: red; background-color:#4C70BF" onclick="Close()">🗙</button>
</td>
</tr>
</table>
<script>
function closeConfirmBox() {
document.getElementById("overlay").hidden = true;
document.getElementById("maintable").hidden = false;
}
function isConfirm(answer) {
if (answer) {
peloton_ask_already_running = false;
peloton_start_workout();
} else {
peloton_ask_already_running = false;
peloton_abort_workout();
}
closeConfirmBox();
}
function AutoResistance() {
let el = new MainWSQueueElement({
msg: 'autoresistance',
}, function (msg) {
if (msg.msg === 'R_autoresistance') {
return msg.content;
}
return null;
}, 15000, 1);
el.enqueue().catch(function (err) {
console.error('Error is ' + err);
});
}
function PelotonOffsetPlus() {
let el = new MainWSQueueElement({
msg: 'pelotonoffset_plus',
}, function (msg) {
if (msg.msg === 'R_pelotonoffset_plus') {
return msg.content;
}
return null;
}, 15000, 1);
el.enqueue().catch(function (err) {
console.error('Error is ' + err);
});
}
function PelotonOffsetMinus() {
let el = new MainWSQueueElement({
msg: 'pelotonoffset_minus',
}, function (msg) {
if (msg.msg === 'R_pelotonoffset_minus') {
return msg.content;
}
return null;
}, 15000, 1);
el.enqueue().catch(function (err) {
console.error('Error is ' + err);
});
}
function GearsPlus() {
let el = new MainWSQueueElement({
msg: 'gears_plus',
}, function (msg) {
if (msg.msg === 'R_gears_plus') {
return msg.content;
}
return null;
}, 15000, 1);
el.enqueue().catch(function (err) {
console.error('Error is ' + err);
});
}
function GearsMinus() {
let el = new MainWSQueueElement({
msg: 'gears_minus',
}, function (msg) {
if (msg.msg === 'R_gears_minus') {
return msg.content;
}
return null;
}, 15000, 1);
el.enqueue().catch(function (err) {
console.error('Error is ' + err);
});
}
function Lap() {
let el = new MainWSQueueElement({
msg: 'lap',
}, function (msg) {
if (msg.msg === 'R_lap') {
return msg.content;
}
return null;
}, 15000, 1);
el.enqueue().catch(function (err) {
console.error('Error is ' + err);
});
}
function Close() {
let el = new MainWSQueueElement({
msg: 'floating_close',
}, function (msg) {
if (msg.msg === 'R_floating_close') {
return msg.content;
}
return null;
}, 15000, 1);
el.enqueue().catch(function (err) {
console.error('Error is ' + err);
});
}
function peloton_start_workout() {
let el = new MainWSQueueElement({
msg: 'peloton_start_workout',
}, function (msg) {
if (msg.msg === 'R_peloton_start_workout') {
return msg.content;
}
return null;
}, 15000, 1);
el.enqueue().catch(function (err) {
console.error('Error is ' + err);
});
}
function peloton_abort_workout() {
let el = new MainWSQueueElement({
msg: 'peloton_abort_workout',
}, function (msg) {
if (msg.msg === 'R_peloton_abort_workout') {
return msg.content;
}
return null;
}, 15000, 1);
el.enqueue().catch(function (err) {
console.error('Error is ' + err);
});
}
function Start() {
let el = new MainWSQueueElement({
msg: 'start',
}, function (msg) {
if (msg.msg === 'R_start') {
return msg.content;
}
return null;
}, 15000, 1);
el.enqueue().catch(function (err) {
console.error('Error is ' + err);
});
}
function Stop() {
let el = new MainWSQueueElement({
msg: 'stop',
}, function (msg) {
if (msg.msg === 'R_stop') {
return msg.content;
}
return null;
}, 15000, 1);
el.enqueue().catch(function (err) {
console.error('Error is ' + err);
});
}
</script>
<script type="text/javascript">
<body style="background-color: #000000; opacity: 0.8">
<table class="metrics" style="color: #FFFFFF; font-family: sans-serif border: 0">
<tr class="speed"><td>🏃</td><td><small>km/h</small></td><td class="speed-value"><b>0.0</b></td><td><small>AVG</small></td><td class="speed-avg">0.0</td></tr>
<tr class="cadence"><td>🚴</td><td><small>rpm</small></td><td class="cadence-value"><b>0</b></td><td><small>AVG</small></td><td class="cadence-avg">0</td></tr>
<tr class="heart"><td>💓</td><td><small>bpm</small></td><td class="heart-value"><b>0</b></td><td><small>AVG</small></td><td class="heart-avg">0</td></tr>
<tr class="watt"><td></td><td><small>watt</small></td><td class="watt-value"><b>0</b></td><td><small>AVG</small></td><td class="watt-avg">0</td></tr>
<tr class="resistance"><td>🧲</td><td><small>level</small></td><td class="resistance-value"><b>1</b></td><td><small>AVG</small></td><td class="resistance-avg">1</td></tr>
<tr class="pelotonresistance"><td>🅿</td><td><small>level</small></td><td class="pelotonresistance-value"><b>1</b></td><td><small>AVG</small></td><td class="pelotonresistance-avg">1</td></tr>
<tr class="calories"><td>🔥</td><td><small>kcal</small></td><td class="calories-value" colspan="3"><b>0</b></td></tr>
<tr class="distance"><td>📏</td><td><small>km</small></td><td class="distance-value" colspan="3"><b>0.00</b></td></tr>
<tr class="elapsed"><td>⏲️</td><td><small>time</small></td><td class="elapsed-value" colspan="3"><b>0:00:00</b></td></tr>
</table>
<script type="text/javascript">
function a() {
onSettingsOK = false;
onWorkout = false;
tile_target_resistance_enabled = false;
tile_target_peloton_resistance_enabled = false;
tile_target_cadence_enabled = false;
tile_target_speed_enabled = false;
tile_target_zone_enabled = false;
tile_target_inclination_enabled = false;
tile_target_power_enabled = false;
miles_unit = false;
miles_conversion = 1.0;
meter_conversion = 1.0;
settings_arr = ['tile_speed_enabled', 'tile_cadence_enabled', 'tile_calories_enabled', 'tile_odometer_enabled', 'tile_resistance_enabled', 'tile_watt_enabled',
'tile_heart_enabled', 'tile_elapsed_enabled', 'tile_peloton_resistance_enabled', 'floating_transparency', 'tile_target_resistance_enabled', 'tile_target_peloton_resistance_enabled',
'tile_target_cadence_enabled', 'tile_target_power_enabled', 'tile_peloton_offset_enabled', 'miles_unit', 'tile_target_speed_enabled', 'tile_inclination_enabled',
'tile_target_incline_enabled', 'tile_target_zone_enabled', 'tile_ftp_enabled', 'tile_jouls_enabled', 'tile_remainingtimetrainprogramrow_enabled', 'tile_gears_enabled',
'tile_elevation_enabled', 'tile_pace_enabled']
let el = new MainWSQueueElement({
msg: 'getsettings',
content: {
keys: settings_arr
}
}, function (msg) {
if (msg.msg === 'R_getsettings') {
for (let key of settings_arr) {
if (msg.content[key] === undefined)
return null;
if (key === 'tile_speed_enabled') {
if (msg.content[key] === false || msg.content[key] === 'false') {
$(".speed").hide();
}
} else if (key === 'tile_ftp_enabled') {
if (msg.content[key] === false || msg.content[key] === 'false') {
$(".powerzone").hide();
}
} else if (key === 'tile_pace_enabled') {
if (msg.content[key] === false || msg.content[key] === 'false') {
$(".pace").hide();
}
} else if (key === 'tile_inclination_enabled') {
if (msg.content[key] === false || msg.content[key] === 'false') {
$(".inclination").hide();
}
} else if (key === 'tile_elevation_enabled') {
if (msg.content[key] === false || msg.content[key] === 'false') {
$(".elevation").hide();
}
} else if (key === 'tile_cadence_enabled') {
if (msg.content[key] === false || msg.content[key] === 'false') {
$(".cadence").hide();
}
} else if (key === 'tile_calories_enabled') {
if (msg.content[key] === false || msg.content[key] === 'false') {
$(".calories").hide();
}
} else if (key === 'tile_jouls_enabled') {
if (msg.content[key] === false || msg.content[key] === 'false') {
$(".jouls").hide();
}
} else if (key === 'tile_odometer_enabled') {
if (msg.content[key] === false || msg.content[key] === 'false') {
$(".distance").hide();
}
} else if (key === 'tile_resistance_enabled') {
if (msg.content[key] === false || msg.content[key] === 'false') {
$(".resistance").hide();
}
} else if (key === 'tile_watt_enabled') {
if (msg.content[key] === false || msg.content[key] === 'false') {
$(".watt").hide();
}
} else if (key === 'tile_heart_enabled') {
if (msg.content[key] === false || msg.content[key] === 'false') {
$(".heart").hide();
}
} else if (key === 'tile_elapsed_enabled') {
if (msg.content[key] === false || msg.content[key] === 'false') {
$(".elapsed").hide();
}
} else if (key === 'tile_remainingtimetrainprogramrow_enabled') {
if (msg.content[key] === false || msg.content[key] === 'false') {
$(".rowremainingtime").hide();
}
} else if (key === 'tile_peloton_resistance_enabled') {
if (msg.content[key] === false || msg.content[key] === 'false') {
$(".pelotonresistance").hide();
}
} else if (key === 'miles_unit') {
miles_unit = (msg.content[key] === true || msg.content[key] === 'true');
if (miles_unit) {
miles_conversion = 0.621371;
meter_conversion = 3.28084;
}
} else if (key === 'floating_transparency') {
$(".transparency").css("opacity", msg.content[key] / 100.0);
} else if (key === 'tile_target_resistance_enabled') {
tile_target_resistance_enabled = (msg.content[key] === true || msg.content[key] === 'true');
} else if (key === 'tile_target_peloton_resistance_enabled') {
tile_target_peloton_resistance_enabled = (msg.content[key] === true || msg.content[key] === 'true');
} else if (key === 'tile_target_cadence_enabled') {
tile_target_cadence_enabled = (msg.content[key] === true || msg.content[key] === 'true');
} else if (key === 'tile_target_speed_enabled') {
tile_target_speed_enabled = (msg.content[key] === true || msg.content[key] === 'true');
} else if (key === 'tile_target_zone_enabled') {
tile_target_zone_enabled = (msg.content[key] === true || msg.content[key] === 'true');
} else if (key === 'tile_target_incline_enabled') {
tile_target_inclination_enabled = (msg.content[key] === true || msg.content[key] === 'true');
} else if (key === 'tile_target_power_enabled') {
tile_target_power_enabled = (msg.content[key] === true || msg.content[key] === 'true');
} else if (key === 'tile_peloton_offset_enabled') {
if (msg.content[key] === false || msg.content[key] === 'false') {
$(".pelotonoffset").hide();
}
} else if (key === 'tile_gears_enabled') {
if (msg.content[key] === false || msg.content[key] === 'false') {
$(".gears").hide();
}
}
}
return msg.content;
}
return null;
}, 5000, 3);
el.enqueue().then(onSettingsOK).catch(function (err) {
console.error('Error is ' + err);
})
keys_arr = ['speed', 'speed_lapavg', 'cadence', 'cadence_lapavg', 'heart', 'heart_lapavg', 'calories', 'distance', 'watts', 'watts_lapavg',
'elapsed_h', 'elapsed_m', 'elapsed_s', 'resistance', 'resistance_lapavg', 'peloton_resistance', 'peloton_resistance_lapavg',
'speed_lapmax', 'cadence_lapmax', 'heart_lapmax', 'watts_lapmax', 'resistance_lapmax', 'peloton_resistance_lapmax',
'speed_color', 'cadence_color', 'heart_color', 'watts_color', 'peloton_resistance_color', 'target_resistance', 'target_peloton_resistance',
'target_cadence', 'target_power', 'peloton_offset', 'peloton_ask_start', 'target_speed', 'inclination', 'inclination_lapavg',
'inclination_lapmax', 'target_inclination', 'power_zone', 'power_zone_lapavg', 'power_zone_lapmax', 'target_power_zone', 'jouls',
'row_remaining_time_s', 'row_remaining_time_m', 'row_remaining_time_h' , 'autoresistance', 'gears', 'elevation', 'pace_s' , 'pace_m',
'avgpace_s', 'avgpace_m', 'maxpace_s' , 'maxpace_m',]
let ell = new MainWSQueueElement(null, function (msg) {
if (msg.msg === 'workout') {
var speed = 0;
var speed_lapavg = 0;
var speed_lapmax = 0;
var powerzone = 0;
var powerzone_lapavg = 0;
var powerzone_lapmax = 0;
var elevation = 0;
var inclination = 0;
var inclination_lapavg = 0;
var inclination_lapmax = 0;
var cadence = 0;
var cadence_lapavg = 0;
var hr = 0;
var hr_lapavg = 0;
var calories = 0;
var jouls = 0;
var odometer = 0;
var watt = 0;
var watt_lapavg = 0;
var elapsed_h = 0;
var elapsed_m = 0;
var elapsed_s = 0;
var pace_m = 0;
var pace_s = 0;
var avgpace_m = 0;
var avgpace_s = 0;
var maxpace_m = 0;
var maxpace_s = 0;
var row_remaining_time_s = 0;
var row_remaining_time_m = 0;
row_remaining_time_h = 0;
var resistance = 0;
var resistance_lapavg = 0;
var peloton_resistance = 0;
var peloton_resistance_lapavg = 0;
var cadence_lapmax = 0;
var hr_lapmax = 0;
var watt_lapmax = 0;
var resistance_lapmax = 0;
var peloton_resistance_lapmax = 0;
var target_resistance = 0;
var target_peloton_resistance = 0;
var target_cadence = 0;
var target_speed = 0;
var target_power_zone = 0;
var target_inclination = 0;
var target_power = 0;
var peloton_offset = 0;
var gears = 0;
var peloton_ask_already_running = false;
for (let key of keys_arr) {
if (msg.content[key] === undefined)
continue;
if (key === 'speed') {
speed = msg.content[key] * miles_conversion;
} else if (key === 'speed_lapavg') {
speed_lapavg = msg.content[key] * miles_conversion;
} else if (key === 'speed_lapmax') {
speed_lapmax = msg.content[key] * miles_conversion;
} else if (key === 'elevation') {
elevation = msg.content[key] * meter_conversion;
} else if (key === 'inclination') {
inclination = msg.content[key];
} else if (key === 'inclination_lapavg') {
inclination_lapavg = msg.content[key];
} else if (key === 'inclination_lapmax') {
inclination_lapmax = msg.content[key];
} else if (key === 'power_zone') {
powerzone = msg.content[key];
} else if (key === 'power_zone_lapavg') {
powerzone_lapavg = msg.content[key];
} else if (key === 'power_zone_lapmax') {
powerzone_lapmax = msg.content[key];
} else if (key === 'cadence') {
cadence = msg.content[key];
} else if (key === 'cadence_lapavg') {
cadence_lapavg = msg.content[key];
} else if (key === 'cadence_lapmax') {
cadence_lapmax = msg.content[key];
} else if (key === 'heart') {
hr = msg.content[key];
} else if (key === 'heart_lapavg') {
hr_lapavg = msg.content[key];
} else if (key === 'heart_lapmax') {
hr_lapmax = msg.content[key];
} else if (key === 'calories') {
calories = msg.content[key];
} else if (key === 'jouls') {
jouls = msg.content[key] / 1000.0;
} else if (key === 'distance') {
odometer = msg.content[key] * miles_conversion;
} else if (key === 'watts') {
watt = msg.content[key];
} else if (key === 'watts_lapavg') {
watt_lapavg = msg.content[key];
} else if (key === 'watts_lapmax') {
watt_lapmax = msg.content[key];
} else if (key === 'elapsed_h') {
elapsed_h = msg.content[key];
} else if (key === 'elapsed_m') {
elapsed_m = msg.content[key];
} else if (key === 'elapsed_s') {
elapsed_s = msg.content[key];
} else if (key === 'pace_m') {
pace_m = msg.content[key];
} else if (key === 'pace_s') {
pace_s = msg.content[key];
} else if (key === 'avgpace_m') {
avgpace_m = msg.content[key];
} else if (key === 'avgpace_s') {
avgpace_s = msg.content[key];
} else if (key === 'maxpace_m') {
maxpace_m = msg.content[key];
} else if (key === 'maxpace_s') {
maxpace_s = msg.content[key];
} else if (key === 'row_remaining_time_h') {
row_remaining_time_h = msg.content[key];
} else if (key === 'row_remaining_time_m') {
row_remaining_time_m = msg.content[key];
} else if (key === 'row_remaining_time_s') {
row_remaining_time_s = msg.content[key];
} else if (key === 'resistance') {
resistance = msg.content[key];
} else if (key === 'resistance_lapavg') {
resistance_lapavg = msg.content[key];
} else if (key === 'resistance_lapmax') {
resistance_lapmax = msg.content[key];
} else if (key === 'peloton_resistance') {
peloton_resistance = msg.content[key];
} else if (key === 'peloton_resistance_lapavg') {
peloton_resistance_lapavg = msg.content[key];
} else if (key === 'peloton_resistance_lapmax') {
peloton_resistance_lapmax = msg.content[key];
} else if (key === 'target_resistance') {
target_resistance = msg.content[key];
} else if (key === 'target_peloton_resistance') {
target_peloton_resistance = msg.content[key];
} else if (key === 'target_cadence') {
target_cadence = msg.content[key];
} else if (key === 'target_speed') {
target_speed = msg.content[key] * miles_conversion;
} else if (key === 'target_power_zone') {
target_power_zone = msg.content[key];
} else if (key === 'target_inclination') {
target_inclination = msg.content[key];
} else if (key === 'target_power') {
target_power = msg.content[key];
} else if (key === 'peloton_offset') {
peloton_offset = msg.content[key];
} else if (key === 'gears') {
gears = msg.content[key];
} else if (key === 'peloton_resistance_color') {
$('.peloton_resistance-value').css('color', msg.content[key]);
} else if (key === 'heart_color') {
$('.heart-value').css('color', msg.content[key]);
} else if (key === 'cadence_color') {
$('.cadence-value').css('color', msg.content[key]);
} else if (key === 'watts_color') {
$('.watt-value').css('color', msg.content[key]);
} else if (key === 'speed_color') {
$('.speed-value').css('color', msg.content[key]);
} else if (key === 'peloton_ask_start' && !peloton_ask_already_running && (msg.content[key] === true || msg.content[key] === 'true')) {
peloton_ask_already_running = true;
document.getElementById("overlay").hidden = false;
document.getElementById("maintable").hidden = true;
} else if (key === 'autoresistance') {
if((msg.content[key] === true || msg.content[key] === 'true'))
$(".autoresistance").html('🧲');
else
$(".autoresistance").html('⊘')
}
}
if (tile_target_speed_enabled && target_speed > 0)
$('.speed-value').html("<b>" + speed.toFixed(1) + "/" + target_speed.toFixed(1) + "</b>");
else
$('.speed-value').html("<b>" + speed.toFixed(1) + "</b>");
$('.speed-avg').html(speed_lapavg.toFixed(1));
$('.speed-max').html(speed_lapmax.toFixed(1));
if (tile_target_inclination_enabled && target_inclination > 0)
$('.inclination-value').html("<b>" + inclination.toFixed(1) + "/" + target_inclination.toFixed(1) + "</b>");
else
$('.inclination-value').html("<b>" + inclination.toFixed(1) + "</b>");
$('.inclination-avg').html(inclination_lapavg.toFixed(1));
$('.inclination-max').html(inclination_lapmax.toFixed(1));
$('.elevation-value').html("<b>" + elevation.toFixed(1) + "</b>");
if (tile_target_cadence_enabled && target_cadence > 0)
$('.cadence-value').html("<b>" + cadence.toFixed(0) + "/" + target_cadence.toFixed(0) + "</b>");
else
$('.cadence-value').html("<b>" + cadence.toFixed(0) + "</b>");
$('.cadence-avg').html(cadence_lapavg.toFixed(0));
$('.cadence-max').html(cadence_lapmax.toFixed(0));
$('.heart-value').html("<b>" + hr.toFixed(0) + "</b>");
$('.heart-avg').html(hr_lapavg.toFixed(0));
$('.heart-max').html(hr_lapmax.toFixed(0));
if (tile_target_power_enabled && target_power > 0)
$('.watt-value').html("<b>" + watt.toFixed(0) + "/" + target_power.toFixed(0) + "</b>");
else
$('.watt-value').html("<b>" + watt.toFixed(0) + "</b>");
$('.watt-avg').html(watt_lapavg.toFixed(0));
$('.watt-max').html(watt_lapmax.toFixed(0));
if (tile_target_resistance_enabled && target_resistance > 0)
$('.resistance-value').html("<b>" + resistance.toFixed(0) + "/" + target_resistance.toFixed(0) + "</b>");
else
$('.resistance-value').html("<b>" + resistance.toFixed(0) + "</b>");
$('.resistance-avg').html(resistance_lapavg.toFixed(0));
$('.resistance-max').html(resistance_lapmax.toFixed(0));
if (tile_target_peloton_resistance_enabled && target_peloton_resistance > 0)
$('.pelotonresistance-value').html("<b>" + peloton_resistance.toFixed(0) + "/" + target_peloton_resistance.toFixed(0) + "</b>");
else
$('.pelotonresistance-value').html("<b>" + peloton_resistance.toFixed(0) + "</b>");
$('.pelotonresistance-avg').html(peloton_resistance_lapavg.toFixed(0));
$('.pelotonresistance-max').html(peloton_resistance_lapmax.toFixed(0));
$('.distance-value').html("<b>" + odometer.toFixed(2) + "</b>");
$('.rowremainingtime-value').html("<b>" + row_remaining_time_h.toString().padStart(2, "0") + ":" + row_remaining_time_m.toString().padStart(2, "0") + ":" + row_remaining_time_s.toString().padStart(2, "0") + "</b>");
$('.elapsed-value').html("<b>" + elapsed_h.toString().padStart(2, "0") + ":" + elapsed_m.toString().padStart(2, "0") + ":" + elapsed_s.toString().padStart(2, "0") + "</b>");
if(pace_s.toString() === "-1" || (pace_s.toString() === "0" && pace_m.toString() === "0"))
$('.pace-value').html("<b>N/A</b>");
else
$('.pace-value').html("<b>" + pace_m.toString().padStart(2, "0") + ":" + pace_s.toString().padStart(2, "0") + "</b>");
if(avgpace_s.toString() === "-1" || (avgpace_s.toString() === "0" && avgpace_m.toString() === "0"))
$('.pace-avg').html("<b>N/A</b>");
else
$('.pace-avg').html("<b>" + avgpace_m.toString().padStart(2, "0") + ":" + avgpace_s.toString().padStart(2, "0") + "</b>");
if(maxpace_s.toString() === "-1" || (maxpace_s.toString() === "0" && maxpace_m.toString() === "0"))
$('.pace-max').html("<b>N/A</b>");
else
$('.pace-max').html("<b>" + maxpace_m.toString().padStart(2, "0") + ":" + maxpace_s.toString().padStart(2, "0") + "</b>");
$('.calories-value').html("<b>" + calories.toFixed(0) + "</b>");
$('.jouls-value').html("<b>" + jouls.toFixed(1) + "</b>");
$('.pelotonoffset-value').html(peloton_offset.toFixed(0));
if (tile_target_zone_enabled && target_power_zone > 0)
$('.powerzone-value').html("<b>" + powerzone.toFixed(1) + "/" + target_power_zone.toFixed(1) + "</b>");
else
$('.powerzone-value').html("<b>" + powerzone.toFixed(1) + "</b>");
$('.powerzone-avg').html(powerzone_lapavg.toFixed(1));
$('.spepowerzoneed-max').html(powerzone_lapmax.toFixed(1));
}
return null;
}, 15000, 3);
ell.enqueue().then(onWorkout).catch(function (err) {
console.error('Error is ' + err);
});
onWorkout = false;
keys_arr = ['speed', 'speed_avg','cadence','cadence_avg', 'heart','heart_avg', 'calories', 'distance', 'watts','watts_avg', 'elapsed_h', 'elapsed_m', 'elapsed_s', 'resistance','resistance_avg', 'peloton_resistance','peloton_resistance_avg']
let ell = new MainWSQueueElement(null, function(msg) {
if (msg.msg === 'workout') {
var speed = 0;
var speed_avg = 0;
var cadence = 0;
var cadence_avg = 0;
var hr = 0;
var hr_avg = 0;
var calories = 0;
var odometer = 0;
var watt = 0;
var watt_avg = 0;
var elapsed_h = 0;
var elapsed_m = 0;
var elapsed_s = 0;
var resistance = 0;
var resistance_avg = 0;
var peloton_resistance = 0;
var peloton_resistance_avg = 0;
for (let key of keys_arr) {
if (msg.content[key] === undefined)
continue;
if (key === 'speed') {
speed = msg.content[key];
} else if (key === 'speed_avg') {
speed_avg = msg.content[key];
} else if (key === 'cadence') {
cadence = msg.content[key];
} else if (key === 'cadence_avg') {
cadence_avg = msg.content[key];
} else if (key === 'heart') {
hr = msg.content[key];
} else if (key === 'heart_avg') {
hr_avg = msg.content[key];
} else if (key === 'calories') {
calories = msg.content[key];
} else if (key === 'distance') {
odometer = msg.content[key];
} else if (key === 'watts') {
watt = msg.content[key];
} else if (key === 'watts_avg') {
watt_avg = msg.content[key];
} else if (key === 'elapsed_h') {
elapsed_h = msg.content[key];
} else if (key === 'elapsed_m') {
elapsed_m = msg.content[key];
} else if (key === 'elapsed_s') {
elapsed_s = msg.content[key];
} else if (key === 'resistance') {
resistance = msg.content[key];
} else if (key === 'resistance_avg') {
resistance_avg = msg.content[key];
} else if (key === 'peloton_resistance') {
peloton_resistance = msg.content[key];
} else if (key === 'peloton_resistance_avg') {
peloton_resistance_avg = msg.content[key];
}
}
$('.speed-value').html("<b>" + speed.toFixed(1) + "</b>");
$('.speed-avg').html(speed_avg.toFixed(1));
$('.cadence-value').html("<b>" + cadence.toFixed(0) + "</b>");
$('.cadence-avg').html(cadence_avg.toFixed(0));
$('.heart-value').html("<b>" + hr.toFixed(0) + "</b>");
$('.heart-avg').html(hr_avg.toFixed(0));
$('.watt-value').html("<b>" + watt.toFixed(0) + "</b>");
$('.watt-avg').html(watt_avg.toFixed(0));
$('.resistance-value').html("<b>" + resistance.toFixed(0) + "</b>");
$('.resistance-avg').html(resistance_avg.toFixed(0));
$('.peloton_resistance-value').html("<b>" + peloton_resistance.toFixed(0) + "</b>");
$('.peloton_resistance-avg').html(peloton_resistance_avg.toFixed(0));
$('.distance-value').html("<b>" + odometer.toFixed(2) + "</b>");
$('.elapsed-value').html("<b>" + elapsed_h.toString().padStart(2, "0") + ":" + elapsed_m.toString().padStart(2, "0") + ":"+ elapsed_s.toString().padStart(2, "0") + "</b>");
$('.calories-value').html("<b>" + calories.toFixed(0) + "</b>");
}
return null;
}, 15000, 3);
ell.enqueue().then(onWorkout).catch(function(err) {
console.error('Error is ' + err);
});
}
setTimeout(a, 0);
</script>
</body>
</html>
setTimeout(a,0);
</script>
</body>
</html>

View File

@@ -56,7 +56,7 @@ const bike = viewer.entities.add({
uri: bikeUri,
runAnimations: false,
minimumPixelSize: 32,
scale: 0.5,
scale: 1,
color: Cesium.Color.ORANGE,
silhouetteColor: Cesium.Color.ORANGE,
silhouetteSize: 1,

View File

@@ -15,7 +15,7 @@ class lockscreen {
void virtualbike_setHeartRate(unsigned char heartRate);
void virtualbike_setCadence(unsigned short crankRevolutions, unsigned short lastCrankEventTime);
void virtualbike_zwift_ios(bool disable_hr);
void virtualbike_zwift_ios();
double virtualbike_getCurrentSlope();
double virtualbike_getCurrentCRR();
double virtualbike_getCurrentCW();
@@ -42,8 +42,7 @@ class lockscreen {
uint64_t virtualtreadmill_lastChangeCurrentSlope();
double virtualtreadmill_getPowerRequested();
bool virtualtreadmill_updateFTMS(unsigned short normalizeSpeed, unsigned char currentResistance,
unsigned short currentCadence, unsigned short currentWatt,
unsigned short currentInclination);
unsigned short currentCadence, unsigned short currentWatt);
// volume
double getVolume();

View File

@@ -68,9 +68,9 @@ void lockscreen::virtualbike_setCadence(unsigned short crankRevolutions, unsigne
[_virtualbike updateCadenceWithCrankRevolutions:crankRevolutions LastCrankEventTime:lastCrankEventTime];
}
void lockscreen::virtualbike_zwift_ios(bool disable_hr)
void lockscreen::virtualbike_zwift_ios()
{
_virtualbike_zwift = [[virtualbike_zwift alloc] initWithDisable_hr: disable_hr];
_virtualbike_zwift = [[virtualbike_zwift alloc] init];
}
void lockscreen::virtualrower_ios()
@@ -174,10 +174,10 @@ double lockscreen::virtualtreadmill_getPowerRequested()
return 0;
}
bool lockscreen::virtualtreadmill_updateFTMS(UInt16 normalizeSpeed, UInt8 currentResistance, UInt16 currentCadence, UInt16 currentWatt, UInt16 currentInclination)
bool lockscreen::virtualtreadmill_updateFTMS(UInt16 normalizeSpeed, UInt8 currentResistance, UInt16 currentCadence, UInt16 currentWatt)
{
if(_virtualtreadmill_zwift != nil)
return [_virtualtreadmill_zwift updateFTMSWithNormalizeSpeed:normalizeSpeed currentCadence:currentCadence currentResistance:currentResistance currentWatt:currentWatt currentInclination:currentInclination];
return [_virtualtreadmill_zwift updateFTMSWithNormalizeSpeed:normalizeSpeed currentCadence:currentCadence currentResistance:currentResistance currentWatt:currentWatt];
return 0;
}

View File

@@ -11,9 +11,9 @@ let TrainingStatusUuid = CBUUID(string: "0x2AD3");
@objc public class virtualbike_zwift: NSObject {
private var peripheralManager: BLEPeripheralManagerZwift!
@objc public init(disable_hr: Bool) {
@objc public override init() {
super.init()
peripheralManager = BLEPeripheralManagerZwift(disable_hr: disable_hr)
peripheralManager = BLEPeripheralManagerZwift()
}
@objc public func updateHeartRate(HeartRate: UInt8)
@@ -61,7 +61,6 @@ let TrainingStatusUuid = CBUUID(string: "0x2AD3");
}
class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
private var disable_hr: Bool = false
private var peripheralManager: CBPeripheralManager!
private var heartRateService: CBMutableService!
@@ -108,9 +107,8 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
private var notificationTimer: Timer! = nil
//var delegate: BLEPeripheralManagerDelegate?
init(disable_hr: Bool) {
override init() {
super.init()
self.disable_hr = disable_hr
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
}
@@ -226,14 +224,14 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
let PowerFeaturePermissions: CBAttributePermissions = [.readable]
self.PowerFeatureCharacteristic = CBMutableCharacteristic(type: PowerFeatureCharacteristicUUID,
properties: PowerFeatureProperties,
value: Data (bytes: [0x00, 0x00, 0x00, 0x08]),
value: Data (bytes: [0x08, 0x00, 0x00, 0x00]),
permissions: PowerFeaturePermissions)
let PowerSensorLocationProperties: CBCharacteristicProperties = [.read]
let PowerSensorLocationPermissions: CBAttributePermissions = [.readable]
self.PowerSensorLocationCharacteristic = CBMutableCharacteristic(type: PowerSensorLocationCharacteristicUUID,
properties: PowerSensorLocationProperties,
value: Data (bytes: [0x0D]),
value: Data (bytes: [0x13]),
permissions: PowerSensorLocationPermissions)
let PowerMeasurementProperties: CBCharacteristicProperties = [.notify, .read]
@@ -260,19 +258,11 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
print("Failed to add service with error: \(uwError.localizedDescription)")
return
}
if(disable_hr) {
// useful in order to hide HR from Garmin devices
let advertisementData = [CBAdvertisementDataLocalNameKey: "QZ",
CBAdvertisementDataServiceUUIDsKey: [FitnessMachineServiceUuid, CSCServiceUUID, PowerServiceUUID]] as [String : Any]
peripheralManager.startAdvertising(advertisementData)
} else {
let advertisementData = [CBAdvertisementDataLocalNameKey: "QZ",
CBAdvertisementDataServiceUUIDsKey: [heartRateServiceUUID, FitnessMachineServiceUuid, CSCServiceUUID, PowerServiceUUID]] as [String : Any]
peripheralManager.startAdvertising(advertisementData)
}
let advertisementData = [CBAdvertisementDataLocalNameKey: "QZ",
CBAdvertisementDataServiceUUIDsKey: [heartRateServiceUUID, FitnessMachineServiceUuid, CSCServiceUUID, PowerServiceUUID]] as [String : Any]
peripheralManager.startAdvertising(advertisementData)
print("Successfully added service")
}
@@ -331,10 +321,6 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
request.value = self.calculateIndoorBike()
self.peripheralManager.respond(to: request, withResult: .success)
print("Responded successfully to a read request")
} else if request.characteristic == self.PowerMeasurementCharacteristic {
request.value = self.calculatePower()
self.peripheralManager.respond(to: request, withResult: .success)
print("Responded successfully to a read request")
}
}

Some files were not shown because too many files have changed in this diff Show More