mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 23:41:50 +01:00
Compare commits
4 Commits
crossQFile
...
kingsmith-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53a41b35e2 | ||
|
|
1f178ff3fa | ||
|
|
fd3cc25732 | ||
|
|
64e9052f25 |
274
.github/workflows/main.yml
vendored
274
.github/workflows/main.yml
vendored
@@ -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
3
.gitignore
vendored
@@ -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
4
.gitmodules
vendored
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
14
defaults.pri
14
defaults.pri
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
1058
src/Computrainer.cpp
1058
src/Computrainer.cpp
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
16
src/Home.qml
16
src/Home.qml
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
BIN
src/adb/adb.exe
BIN
src/adb/adb.exe
Binary file not shown.
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -116,9 +116,6 @@ public class ChannelService extends Service {
|
||||
if (null != powerChannelController) {
|
||||
powerChannelController.cadence = cadence;
|
||||
}
|
||||
if (null != speedChannelController) {
|
||||
speedChannelController.cadence = cadence;
|
||||
}
|
||||
}
|
||||
|
||||
int getHeart() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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) { }
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* This package provides a native Java implementation of the ADB protocol.
|
||||
* @author Cameron Gutman
|
||||
*/
|
||||
package com.cgutman.adblib;
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
408
src/apexbike.cpp
408
src/apexbike.cpp
@@ -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();
|
||||
}
|
||||
}
|
||||
102
src/apexbike.h
102
src/apexbike.h
@@ -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
|
||||
51
src/bike.cpp
51
src/bike.cpp
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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(); }
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(' ');
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -69,7 +69,6 @@ class ftmsrower : public rower {
|
||||
|
||||
bool filterWattNull = false;
|
||||
bool WHIPR = false;
|
||||
bool KINGSMITH = false;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
lockscreen *h = 0;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
658
src/homeform.cpp
658
src/homeform.cpp
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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 +=
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
Binary file not shown.
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user