Compare commits

..

8 Commits

Author SHA1 Message Date
Roberto Viola
f64c389df9 Update main.yml 2022-06-03 16:21:48 +02:00
Roberto Viola
5fa895e561 Update main.yml 2022-06-03 16:20:15 +02:00
Roberto Viola
9f2eff21e2 Update main.yml 2022-06-03 16:03:52 +02:00
Roberto Viola
4c0533c43f Update main.yml 2022-06-03 16:02:24 +02:00
Roberto Viola
853cb4108d Update main.yml 2022-06-03 15:57:07 +02:00
Roberto Viola
249b0bdbb4 Update main.yml 2022-06-03 15:55:58 +02:00
Roberto Viola
fd363c2558 Update main.yml 2022-06-03 15:51:03 +02:00
Roberto Viola
4535ac0cda Update main.yml 2022-06-03 15:49:10 +02:00
698 changed files with 73893 additions and 159608 deletions

2
.github/FUNDING.yml vendored
View File

@@ -7,6 +7,6 @@ ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: cagnulein
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://www.buymeacoffee.com/cagnulein'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -10,431 +10,29 @@ env:
on:
workflow_dispatch:
push:
branches: [ master, github-workflow-playground ]
branches: [ master, github-workflow-playground, local-runner ]
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:
window-build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: bluetiger9/SmtpClient-for-Qt
path: "src/smtpclient/"
ref: 3fa4a0fe5797070339422cf18b5e9ed8dcb91f9c
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: cagnulein/qmdnsengine
path: "src/qmdnsengine/"
ref: "zwift"
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: google/googletest
path: "tst/googletest/"
ref: "release-1.12.1"
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: microsoft/MSIX-Toolkit
path: "src/MSIX-Toolkit/"
ref: b82af826d29e93e4c85d34fad8a405b6c49905e7
- 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
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" .
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
# Steps represent a sequence of tasks that will be executed as part of the job
runs-on: self-hosted
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: 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
- 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
# - 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.12.9'
# host: 'linux'
# target: 'android'
# arch: 'android_armv7'
# 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
# - name: Install Qt MacOS
# uses: jurplel/install-qt-action@v2
# with:
# version: '5.12.9'
# host: 'mac'
# 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
cd ../../; rm src/secret.h; git reset --hard; git pull
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > src/secret.h
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> src/secret.h
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> src/secret.h
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> src/secret.h
"/Users/cagnulein/Qt/5.15.0/android/bin/qmake" src/qdomyos-zwift.pro -spec android-clang CONFIG-=qtquickcompiler 'ANDROID_ABIS=armeabi-v7a arm64-v8a x86 x86_64'
"/Users/cagnulein/Library/Android/sdk/ndk/21.1.6352462/prebuilt/darwin-x86_64/bin/make" -f build-qdomyos-zwift-Android_Qt_5_15_0_android_Clang_Multi_Abi-Release/Makefile qmake_all
"/Users/cagnulein/Library/Android/sdk/ndk/21.1.6352462/prebuilt/darwin-x86_64/bin/make" -j8
"/Users/cagnulein/Library/Android/sdk/ndk/21.1.6352462/prebuilt/darwin-x86_64/bin/make" INSTALL_ROOT=build-qdomyos-zwift-Android_Qt_5_15_0_android_Clang_Multi_Abi-Release/android-build install
"/Users/cagnulein/Qt/5.15.0/android/bin/androiddeployqt" --input build-qdomyos-zwift-Android_Qt_5_15_0_android_Clang_Multi_Abi-Release/android-qdomyos-zwift-deployment-settings.json --output build-qdomyos-zwift-Android_Qt_5_15_0_android_Clang_Multi_Abi-Release/android-build --android-platform android-31 --jdk /Library/Java/JavaVirtualMachines/temurin-8.jdk/Contents/Home --gradle --sign '${{secrets.android_certificate_password}}' --storepass '${{secrets.android_certificate_password}}'

5
.gitignore vendored
View File

@@ -40,12 +40,7 @@ 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

7
.gitmodules vendored
View File

@@ -3,13 +3,8 @@
url = https://github.com/KDAB/android_openssl.git
[submodule "src/smtpclient"]
path = src/smtpclient
url = https://github.com/cagnulein/SmtpClient-for-Qt.git
branch = cagnulein-patch-2
url = https://github.com/bluetiger9/SmtpClient-for-Qt.git
[submodule "src/qmdnsengine"]
path = src/qmdnsengine
url = https://github.com/cagnulein/qmdnsengine.git
branch = zwift
[submodule "tst/googletest"]
path = tst/googletest
url = https://github.com/google/googletest.git
branch = tags/release-1.12.1

View File

@@ -116,24 +116,6 @@
868B65D0AB5114A4A0D5479E /* qmldbg_messages in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 008F20821C7E4D5F7DB55754 /* qmldbg_messages */; };
8703BAEB273C67A90058E206 /* pafersbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8703BAE9273C67A90058E206 /* pafersbike.cpp */; };
8703BAED273C67B60058E206 /* moc_pafersbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8703BAEC273C67B50058E206 /* moc_pafersbike.cpp */; };
87061390286D8B4F00D2446E /* libQt5PositioningQuick.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 8706138F286D8B4F00D2446E /* libQt5PositioningQuick.a */; };
87061394286D8C9900D2446E /* libdeclarative_positioning.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 87061393286D8C9900D2446E /* libdeclarative_positioning.a */; };
87061397286D8CFE00D2446E /* PathController.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87061395286D8CFE00D2446E /* PathController.cpp */; };
87061399286D8D6500D2446E /* moc_wobjectdefs.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87061398286D8D6500D2446E /* moc_wobjectdefs.cpp */; };
8706139B286D8DA300D2446E /* libdeclarative_location.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 8706139A286D8DA200D2446E /* libdeclarative_location.a */; };
8706139D286D8E7300D2446E /* libqtgeoservices_osm.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 8706139C286D8E7300D2446E /* libqtgeoservices_osm.a */; };
870613A0286D8F1200D2446E /* libqtposition_positionpoll.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 8706139E286D8F1100D2446E /* libqtposition_positionpoll.a */; };
870613A1286D8F1200D2446E /* libqtposition_cl.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 8706139F286D8F1200D2446E /* libqtposition_cl.a */; };
870613A4286D917700D2446E /* libQt5Location.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 870613A2286D917600D2446E /* libQt5Location.a */; };
870613A5286D917700D2446E /* libQt5Positioning.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 870613A3286D917700D2446E /* libQt5Positioning.a */; };
870613B1286D969500D2446E /* libqtgeoservices_itemsoverlay.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 870613AC286D969300D2446E /* libqtgeoservices_itemsoverlay.a */; };
870613B3286D969500D2446E /* libqtgeoservices_esri.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 870613AE286D969400D2446E /* libqtgeoservices_esri.a */; };
870613B4286D969500D2446E /* libqtgeoservices_nokia.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 870613AF286D969400D2446E /* libqtgeoservices_nokia.a */; };
870613B5286D969500D2446E /* libqtgeoservices_mapbox.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 870613B0286D969500D2446E /* libqtgeoservices_mapbox.a */; };
870613B6286D96DF00D2446E /* libqtgeoservices_mapboxgl.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 870613AD286D969300D2446E /* libqtgeoservices_mapboxgl.a */; };
870613B8286D973C00D2446E /* libqsqlite.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 870613B7286D973B00D2446E /* libqsqlite.a */; };
870613BA286D979100D2446E /* libqmapboxgl.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 870613B9286D979000D2446E /* libqmapboxgl.a */; };
870613BC286D97D200D2446E /* libQt5Sql.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 870613BB286D97D100D2446E /* libQt5Sql.a */; };
87062643259480A200D06586 /* AppDelegate.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876E4E52259479EE00BD5714 /* AppDelegate.swift */; };
87062644259480A600D06586 /* APIFetcher.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876E4E4E259479EE00BD5714 /* APIFetcher.swift */; };
87062645259480AB00D06586 /* LocalNotificationHelper.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876E4E4D259479ED00BD5714 /* LocalNotificationHelper.swift */; };
@@ -143,10 +125,6 @@
87083D9626678EFA0072410D /* zwiftworkout.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87083D9526678EFA0072410D /* zwiftworkout.cpp */; };
87097D2F275EA9A30020EE6F /* sportsplusbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87097D2D275EA9A20020EE6F /* sportsplusbike.cpp */; };
87097D31275EA9AF0020EE6F /* moc_sportsplusbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87097D30275EA9AE0020EE6F /* moc_sportsplusbike.cpp */; };
871189132893C930006A04D1 /* libQt5Multimedia.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 871189122893C92F006A04D1 /* libQt5Multimedia.a */; };
871189152893CB52006A04D1 /* libdeclarative_multimedia.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 871189142893CB51006A04D1 /* libdeclarative_multimedia.a */; };
871189172893CC45006A04D1 /* libQt5MultimediaQuick.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 871189162893CC44006A04D1 /* libQt5MultimediaQuick.a */; };
871189192893CECF006A04D1 /* libqavfmediaplayer.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 871189182893CECC006A04D1 /* libqavfmediaplayer.a */; };
871235BF26B297670012D0F2 /* kingsmithr1protreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871235BE26B297660012D0F2 /* kingsmithr1protreadmill.cpp */; };
871235C126B297720012D0F2 /* moc_kingsmithr1protreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871235C026B297720012D0F2 /* moc_kingsmithr1protreadmill.cpp */; };
87182A09276BBAF600141463 /* virtualrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87182A08276BBAF600141463 /* virtualrower.cpp */; };
@@ -163,12 +141,8 @@
871B9FD2265E6A8800DB41F4 /* powerzonepack.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871B9FD1265E6A8800DB41F4 /* powerzonepack.cpp */; };
871B9FD4265E6A9A00DB41F4 /* moc_powerzonepack.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871B9FD3265E6A9A00DB41F4 /* moc_powerzonepack.cpp */; };
871E4CD125A6FB5A00E18D6D /* BLEPeripheralManager.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871E4CD025A6FB5A00E18D6D /* BLEPeripheralManager.swift */; };
872261EE289EA873006A6F75 /* nordictrackelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872261EC289EA873006A6F75 /* nordictrackelliptical.cpp */; };
872261F0289EA887006A6F75 /* moc_nordictrackelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872261EF289EA887006A6F75 /* moc_nordictrackelliptical.cpp */; };
8727A47727849EA600019B5D /* paferstreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727A47627849EA600019B5D /* paferstreadmill.cpp */; };
8727A47927849EB200019B5D /* moc_paferstreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727A47827849EB200019B5D /* moc_paferstreadmill.cpp */; };
872A20DA28C5EC380037774D /* faketreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872A20D928C5EC380037774D /* faketreadmill.cpp */; };
872A20DC28C5F5CE0037774D /* moc_faketreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872A20DB28C5F5CE0037774D /* moc_faketreadmill.cpp */; };
872BAB4E261750EE006A59AB /* libQt5Charts.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 872BAB4D261750EE006A59AB /* libQt5Charts.a */; };
872BAB50261751FB006A59AB /* libqtchartsqml2.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 872BAB4F261751FB006A59AB /* libqtchartsqml2.a */; };
873063BE259DF20000DA0F44 /* heartratebelt.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 873063BC259DF20000DA0F44 /* heartratebelt.cpp */; };
@@ -249,8 +223,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 */; };
@@ -263,9 +235,6 @@
8762D5132601F89500F6F049 /* scanrecordresult.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8762D5112601F89500F6F049 /* scanrecordresult.cpp */; };
87646C2027B5064600F82131 /* bhfitnesselliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87646C1E27B5064500F82131 /* bhfitnesselliptical.cpp */; };
87646C2227B5065100F82131 /* moc_bhfitnesselliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87646C2127B5065100F82131 /* moc_bhfitnesselliptical.cpp */; };
8767EF1E29448D6700810C0F /* characteristicwriteprocessor.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8767EF1D29448D6700810C0F /* characteristicwriteprocessor.cpp */; };
8768D1FB285081FE00F58E3A /* nordictrackifitadbtreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8768D1F9285081FE00F58E3A /* nordictrackifitadbtreadmill.cpp */; };
8768D1FD2850820B00F58E3A /* moc_nordictrackifitadbtreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8768D1FC2850820B00F58E3A /* moc_nordictrackifitadbtreadmill.cpp */; };
876BFC9C27BE35C5001D7645 /* proformelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876BFC9827BE35C4001D7645 /* proformelliptical.cpp */; };
876BFC9D27BE35C5001D7645 /* bowflext216treadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876BFC9927BE35C4001D7645 /* bowflext216treadmill.cpp */; };
876BFCA027BE35D8001D7645 /* moc_proformelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876BFC9E27BE35D8001D7645 /* moc_proformelliptical.cpp */; };
@@ -295,7 +264,6 @@
876F9B61275385D8006AE6FA /* moc_fitmetria_fanfit.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876F9B60275385D8006AE6FA /* moc_fitmetria_fanfit.cpp */; };
8772A0E625E43ADB0080718C /* trxappgateusbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772A0E525E43ADA0080718C /* trxappgateusbbike.cpp */; };
8772A0E825E43AE70080718C /* moc_trxappgateusbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772A0E725E43AE70080718C /* moc_trxappgateusbbike.cpp */; };
877A080D2893DC4300C0F0AB /* CoreVideo.framework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 879F74122893D705009A64C8 /* CoreVideo.framework */; };
877A7609269D8E9F0024DD2C /* WebKit.framework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 877A7608269D8E9F0024DD2C /* WebKit.framework */; };
877FBA29276E684500F6C0C9 /* bowflextreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 877FBA27276E684400F6C0C9 /* bowflextreadmill.cpp */; };
877FBA2B276E684E00F6C0C9 /* moc_bowflextreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 877FBA2A276E684E00F6C0C9 /* moc_bowflextreadmill.cpp */; };
@@ -313,16 +281,10 @@
878531692711A3EC004B153D /* moc_fakebike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878531672711A3EB004B153D /* moc_fakebike.cpp */; };
878A331A25AB4FF800BD13E1 /* yesoulbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878A331725AB4FF800BD13E1 /* yesoulbike.cpp */; };
878A331D25AB50C300BD13E1 /* moc_yesoulbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878A331B25AB50C200BD13E1 /* moc_yesoulbike.cpp */; };
878C9E6928B77E7C00669129 /* nordictrackifitadbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878C9E6828B77E7B00669129 /* nordictrackifitadbbike.cpp */; };
878C9E6B28B77E9800669129 /* moc_nordictrackifitadbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878C9E6A28B77E9800669129 /* moc_nordictrackifitadbbike.cpp */; };
87900DC6268B672E000CB351 /* renphobike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87900DC5268B672E000CB351 /* renphobike.cpp */; };
87900DC8268B673C000CB351 /* moc_renphobike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87900DC7268B673C000CB351 /* moc_renphobike.cpp */; };
8790FDDF277B0ABA00247550 /* nautilustreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8790FDDD277B0ABA00247550 /* nautilustreadmill.cpp */; };
8790FDE1277B0AC600247550 /* moc_nautilustreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8790FDE0277B0AC600247550 /* moc_nautilustreadmill.cpp */; };
87917A7328E768D200F8D9AC /* Browser.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87917A6E28E768D200F8D9AC /* Browser.swift */; };
87917A7528E768D200F8D9AC /* Connection.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87917A7028E768D200F8D9AC /* Connection.swift */; };
87917A7628E768D200F8D9AC /* Server.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87917A7128E768D200F8D9AC /* Server.swift */; };
87917A7728E768D200F8D9AC /* Client.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87917A7228E768D200F8D9AC /* Client.swift */; };
8791A8AA25C8603F003B50B2 /* moc_inspirebike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8791A8A925C8603F003B50B2 /* moc_inspirebike.cpp */; };
8791A8AB25C861BD003B50B2 /* inspirebike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8791A8A825C8602A003B50B2 /* inspirebike.cpp */; };
87958F1927628D4500124B24 /* elitesterzosmart.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87958F1827628D4500124B24 /* elitesterzosmart.cpp */; };
@@ -330,18 +292,8 @@
8798C8872733E103003148B3 /* strydrunpowersensor.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8798C8862733E103003148B3 /* strydrunpowersensor.cpp */; };
8798C8892733E10E003148B3 /* moc_strydrunpowersensor.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8798C8882733E10E003148B3 /* moc_strydrunpowersensor.cpp */; };
879A38C8281BD83300F78B2A /* characteristicnotifier2ad9.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 879A38C7281BD83300F78B2A /* characteristicnotifier2ad9.cpp */; };
879E5AA8289C057E00FEA38A /* proformwifitreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 879E5AA6289C057E00FEA38A /* proformwifitreadmill.cpp */; };
879E5AAA289C05A500FEA38A /* moc_proformwifitreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 879E5AA9289C05A500FEA38A /* moc_proformwifitreadmill.cpp */; };
879F16462847E55C00CE4945 /* proformellipticaltrainer.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 879F16442847E55C00CE4945 /* proformellipticaltrainer.cpp */; };
879F16482847E57400CE4945 /* moc_proformellipticaltrainer.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 879F16472847E57400CE4945 /* moc_proformellipticaltrainer.cpp */; };
879F74092893D4B6009A64C8 /* libQt5MultimediaWidgets.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 879F74082893D4B5009A64C8 /* libQt5MultimediaWidgets.a */; };
879F740C2893D4FA009A64C8 /* libqtaudio_coreaudio.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 879F740A2893D4F9009A64C8 /* libqtaudio_coreaudio.a */; };
879F740D2893D4FA009A64C8 /* libqtmultimedia_m3u.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 879F740B2893D4F9009A64C8 /* libqtmultimedia_m3u.a */; };
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 +308,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 +318,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 */; };
@@ -404,10 +352,6 @@
87CC3B9E25A08812001EC5A8 /* moc_elliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87CC3B9C25A08812001EC5A8 /* moc_elliptical.cpp */; };
87CC3BA325A0885F001EC5A8 /* domyoselliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87CC3B9F25A0885D001EC5A8 /* domyoselliptical.cpp */; };
87CC3BA425A0885F001EC5A8 /* elliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87CC3BA025A0885D001EC5A8 /* elliptical.cpp */; };
87CF5169293C879800A7CABC /* characteristicwriteprocessore005.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87CF5167293C879700A7CABC /* characteristicwriteprocessore005.cpp */; };
87CF516B293C87B000A7CABC /* moc_characteristicwriteprocessore005.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87CF516A293C87AF00A7CABC /* moc_characteristicwriteprocessore005.cpp */; };
87D10552290996EA00B3935B /* mepanelbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87D10550290996EA00B3935B /* mepanelbike.cpp */; };
87D105542909971100B3935B /* moc_mepanelbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87D105532909971100B3935B /* moc_mepanelbike.cpp */; };
87D2699F25F535200076AA48 /* m3ibike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87D2699A25F535160076AA48 /* m3ibike.cpp */; };
87D269A025F535200076AA48 /* skandikawiribike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87D2699D25F535180076AA48 /* skandikawiribike.cpp */; };
87D269A325F535340076AA48 /* moc_skandikawiribike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87D269A125F535300076AA48 /* moc_skandikawiribike.cpp */; };
@@ -417,9 +361,9 @@
87D5DC4228230496008CCDE7 /* moc_truetreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87D5DC4128230496008CCDE7 /* moc_truetreadmill.cpp */; };
87D91F9A2800B9970026D43C /* proformwifibike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87D91F992800B9970026D43C /* proformwifibike.cpp */; };
87D91F9C2800B9B90026D43C /* moc_proformwifibike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87D91F9B2800B9B90026D43C /* moc_proformwifibike.cpp */; };
87DA76502848F98200A71B64 /* libQt5TextToSpeech.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 87DA764F2848F8F200A71B64 /* libQt5TextToSpeech.a */; };
87DA8465284933D200B550E9 /* fakeelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DA8464284933D200B550E9 /* fakeelliptical.cpp */; };
87DA8467284933DE00B550E9 /* moc_fakeelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DA8466284933DE00B550E9 /* moc_fakeelliptical.cpp */; };
87DA76502848F98200A71B64 /* libQt5TextToSpeech.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 87DA764F2848F8F200A71B64 /* libQt5TextToSpeech.a */; };
87DAE16326E9FF3A00B0527E /* shuaa5treadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DAE15D26E9FF3900B0527E /* shuaa5treadmill.cpp */; };
87DAE16426E9FF3A00B0527E /* kingsmithr2treadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DAE15F26E9FF3A00B0527E /* kingsmithr2treadmill.cpp */; };
87DAE16526E9FF3A00B0527E /* solef80treadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DAE16026E9FF3A00B0527E /* solef80treadmill.cpp */; };
@@ -433,10 +377,6 @@
87DF68BF25E2675100FCDA46 /* moc_schwinnic4bike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DF68BC25E2675100FCDA46 /* moc_schwinnic4bike.cpp */; };
87E0761D277A081A00FDA0F9 /* technogymmyruntreadmillrfcomm.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87E0761B277A081900FDA0F9 /* technogymmyruntreadmillrfcomm.cpp */; };
87E0761F277A082300FDA0F9 /* moc_technogymmyruntreadmillrfcomm.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87E0761E277A082300FDA0F9 /* moc_technogymmyruntreadmillrfcomm.cpp */; };
87E2F85D291ED308002BDC65 /* lifefitnesstreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87E2F85C291ED308002BDC65 /* lifefitnesstreadmill.cpp */; };
87E2F85F291ED315002BDC65 /* moc_lifefitnesstreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87E2F85E291ED315002BDC65 /* moc_lifefitnesstreadmill.cpp */; };
87E34C2B2886F95400CEDE4B /* octanetreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87E34C2A2886F95400CEDE4B /* octanetreadmill.cpp */; };
87E34C2D2886F99A00CEDE4B /* moc_octanetreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87E34C2C2886F99900CEDE4B /* moc_octanetreadmill.cpp */; };
87E5D2C625E69F3100BDBE6C /* horizontreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87E5D2C525E69F3100BDBE6C /* horizontreadmill.cpp */; };
87E5D2C825E69F4700BDBE6C /* moc_horizontreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87E5D2C725E69F4700BDBE6C /* moc_horizontreadmill.cpp */; };
87E6A85825B5C88E00371D28 /* moc_flywheelbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87E6A85725B5C88E00371D28 /* moc_flywheelbike.cpp */; };
@@ -454,10 +394,7 @@
87EFB57025BD704A0039DD5A /* moc_proformtreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EFB56F25BD704A0039DD5A /* moc_proformtreadmill.cpp */; };
87EFE45927A518F5006EA1C3 /* nautiluselliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EFE45827A518F5006EA1C3 /* nautiluselliptical.cpp */; };
87EFE45B27A51901006EA1C3 /* moc_nautiluselliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EFE45A27A51900006EA1C3 /* moc_nautiluselliptical.cpp */; };
87F02E4029178524000DB52C /* octaneelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87F02E3E29178523000DB52C /* octaneelliptical.cpp */; };
87F02E4229178545000DB52C /* moc_octaneelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87F02E4129178545000DB52C /* moc_octaneelliptical.cpp */; };
87F1179E26A5FBDE00541B3A /* libqtwebview_darwin.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 87420DF7269D7CE1000C5EC6 /* libqtwebview_darwin.a */; };
87F527BE28EEB5AA00A9F8D5 /* qzsettings.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87F527BC28EEB5AA00A9F8D5 /* qzsettings.cpp */; };
87F93427278E0EC00088B596 /* domyosrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87F93426278E0EC00088B596 /* domyosrower.cpp */; };
87F93429278E0ECF0088B596 /* moc_domyosrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87F93428278E0ECF0088B596 /* moc_domyosrower.cpp */; };
87FA11AB27C5ECD1008AC5D1 /* ultrasportbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FA11AA27C5ECD1008AC5D1 /* ultrasportbike.cpp */; };
@@ -797,35 +734,11 @@
8703BAE9273C67A90058E206 /* pafersbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pafersbike.cpp; path = ../src/pafersbike.cpp; sourceTree = "<group>"; };
8703BAEA273C67A90058E206 /* pafersbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = pafersbike.h; path = ../src/pafersbike.h; sourceTree = "<group>"; };
8703BAEC273C67B50058E206 /* moc_pafersbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_pafersbike.cpp; sourceTree = "<group>"; };
8706138F286D8B4F00D2446E /* libQt5PositioningQuick.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libQt5PositioningQuick.a; path = ../../Qt/5.15.2/ios/lib/libQt5PositioningQuick.a; sourceTree = "<group>"; };
87061391286D8C4200D2446E /* Qt */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Qt; path = ../../Qt; sourceTree = "<group>"; };
87061393286D8C9900D2446E /* libdeclarative_positioning.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libdeclarative_positioning.a; path = ../../Qt/5.15.2/ios/qml/QtPositioning/libdeclarative_positioning.a; sourceTree = "<group>"; };
87061395286D8CFE00D2446E /* PathController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PathController.cpp; path = ../src/PathController.cpp; sourceTree = "<group>"; };
87061396286D8CFE00D2446E /* PathController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PathController.h; path = ../src/PathController.h; sourceTree = "<group>"; };
87061398286D8D6500D2446E /* moc_wobjectdefs.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_wobjectdefs.cpp; sourceTree = "<group>"; };
8706139A286D8DA200D2446E /* libdeclarative_location.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libdeclarative_location.a; path = ../../Qt/5.15.2/ios/qml/QtLocation/libdeclarative_location.a; sourceTree = "<group>"; };
8706139C286D8E7300D2446E /* libqtgeoservices_osm.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtgeoservices_osm.a; path = ../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_osm.a; sourceTree = "<group>"; };
8706139E286D8F1100D2446E /* libqtposition_positionpoll.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtposition_positionpoll.a; path = ../../Qt/5.15.2/ios/plugins/position/libqtposition_positionpoll.a; sourceTree = "<group>"; };
8706139F286D8F1200D2446E /* libqtposition_cl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtposition_cl.a; path = ../../Qt/5.15.2/ios/plugins/position/libqtposition_cl.a; sourceTree = "<group>"; };
870613A2286D917600D2446E /* libQt5Location.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libQt5Location.a; path = ../../Qt/5.15.2/ios/lib/libQt5Location.a; sourceTree = "<group>"; };
870613A3286D917700D2446E /* libQt5Positioning.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libQt5Positioning.a; path = ../../Qt/5.15.2/ios/lib/libQt5Positioning.a; sourceTree = "<group>"; };
870613AC286D969300D2446E /* libqtgeoservices_itemsoverlay.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtgeoservices_itemsoverlay.a; path = ../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_itemsoverlay.a; sourceTree = "<group>"; };
870613AD286D969300D2446E /* libqtgeoservices_mapboxgl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtgeoservices_mapboxgl.a; path = ../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_mapboxgl.a; sourceTree = "<group>"; };
870613AE286D969400D2446E /* libqtgeoservices_esri.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtgeoservices_esri.a; path = ../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_esri.a; sourceTree = "<group>"; };
870613AF286D969400D2446E /* libqtgeoservices_nokia.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtgeoservices_nokia.a; path = ../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_nokia.a; sourceTree = "<group>"; };
870613B0286D969500D2446E /* libqtgeoservices_mapbox.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtgeoservices_mapbox.a; path = ../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_mapbox.a; sourceTree = "<group>"; };
870613B7286D973B00D2446E /* libqsqlite.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqsqlite.a; path = ../../Qt/5.15.2/ios/plugins/sqldrivers/libqsqlite.a; sourceTree = "<group>"; };
870613B9286D979000D2446E /* libqmapboxgl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqmapboxgl.a; path = ../../Qt/5.15.2/ios/lib/libqmapboxgl.a; sourceTree = "<group>"; };
870613BB286D97D100D2446E /* libQt5Sql.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libQt5Sql.a; path = ../../Qt/5.15.2/ios/lib/libQt5Sql.a; sourceTree = "<group>"; };
87083D9426678EFA0072410D /* zwiftworkout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zwiftworkout.h; path = ../src/zwiftworkout.h; sourceTree = "<group>"; };
87083D9526678EFA0072410D /* zwiftworkout.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = zwiftworkout.cpp; path = ../src/zwiftworkout.cpp; sourceTree = "<group>"; };
87097D2D275EA9A20020EE6F /* sportsplusbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sportsplusbike.cpp; path = ../src/sportsplusbike.cpp; sourceTree = "<group>"; };
87097D2E275EA9A20020EE6F /* sportsplusbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sportsplusbike.h; path = ../src/sportsplusbike.h; sourceTree = "<group>"; };
87097D30275EA9AE0020EE6F /* moc_sportsplusbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_sportsplusbike.cpp; sourceTree = "<group>"; };
871189122893C92F006A04D1 /* libQt5Multimedia.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libQt5Multimedia.a; path = ../../Qt/5.15.2/ios/lib/libQt5Multimedia.a; sourceTree = "<group>"; };
871189142893CB51006A04D1 /* libdeclarative_multimedia.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libdeclarative_multimedia.a; path = ../../Qt/5.15.2/ios/qml/QtMultimedia/libdeclarative_multimedia.a; sourceTree = "<group>"; };
871189162893CC44006A04D1 /* libQt5MultimediaQuick.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libQt5MultimediaQuick.a; path = ../../Qt/5.15.2/ios/lib/libQt5MultimediaQuick.a; sourceTree = "<group>"; };
871189182893CECC006A04D1 /* libqavfmediaplayer.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqavfmediaplayer.a; path = ../../Qt/5.15.2/ios/plugins/mediaservice/libqavfmediaplayer.a; sourceTree = "<group>"; };
871235BD26B297660012D0F2 /* kingsmithr1protreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = kingsmithr1protreadmill.h; path = ../src/kingsmithr1protreadmill.h; sourceTree = "<group>"; };
871235BE26B297660012D0F2 /* kingsmithr1protreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = kingsmithr1protreadmill.cpp; path = ../src/kingsmithr1protreadmill.cpp; sourceTree = "<group>"; };
871235C026B297720012D0F2 /* moc_kingsmithr1protreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_kingsmithr1protreadmill.cpp; sourceTree = "<group>"; };
@@ -851,15 +764,9 @@
871B9FD1265E6A8800DB41F4 /* powerzonepack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = powerzonepack.cpp; path = ../src/powerzonepack.cpp; sourceTree = "<group>"; };
871B9FD3265E6A9A00DB41F4 /* moc_powerzonepack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_powerzonepack.cpp; sourceTree = "<group>"; };
871E4CD025A6FB5A00E18D6D /* BLEPeripheralManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BLEPeripheralManager.swift; path = ../src/ios/BLEPeripheralManager.swift; sourceTree = "<group>"; };
872261EC289EA873006A6F75 /* nordictrackelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = nordictrackelliptical.cpp; path = ../src/nordictrackelliptical.cpp; sourceTree = "<group>"; };
872261ED289EA873006A6F75 /* nordictrackelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nordictrackelliptical.h; path = ../src/nordictrackelliptical.h; sourceTree = "<group>"; };
872261EF289EA887006A6F75 /* moc_nordictrackelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_nordictrackelliptical.cpp; sourceTree = "<group>"; };
8727A47527849EA600019B5D /* paferstreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = paferstreadmill.h; path = ../src/paferstreadmill.h; sourceTree = "<group>"; };
8727A47627849EA600019B5D /* paferstreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = paferstreadmill.cpp; path = ../src/paferstreadmill.cpp; sourceTree = "<group>"; };
8727A47827849EB200019B5D /* moc_paferstreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_paferstreadmill.cpp; sourceTree = "<group>"; };
872A20D828C5EC380037774D /* faketreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = faketreadmill.h; path = ../src/faketreadmill.h; sourceTree = "<group>"; };
872A20D928C5EC380037774D /* faketreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = faketreadmill.cpp; path = ../src/faketreadmill.cpp; sourceTree = "<group>"; };
872A20DB28C5F5CE0037774D /* moc_faketreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_faketreadmill.cpp; sourceTree = "<group>"; };
872BAB4D261750EE006A59AB /* libQt5Charts.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libQt5Charts.a; path = ../../Qt/5.15.2/ios/lib/libQt5Charts.a; sourceTree = "<group>"; };
872BAB4F261751FB006A59AB /* libqtchartsqml2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtchartsqml2.a; path = ../../Qt/5.15.2/ios/qml/QtCharts/libqtchartsqml2.a; sourceTree = "<group>"; };
873063BC259DF20000DA0F44 /* heartratebelt.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = heartratebelt.cpp; path = ../src/heartratebelt.cpp; sourceTree = "<group>"; };
@@ -981,9 +888,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>"; };
@@ -1002,10 +906,6 @@
87646C1E27B5064500F82131 /* bhfitnesselliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bhfitnesselliptical.cpp; path = ../src/bhfitnesselliptical.cpp; sourceTree = "<group>"; };
87646C1F27B5064500F82131 /* bhfitnesselliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = bhfitnesselliptical.h; path = ../src/bhfitnesselliptical.h; sourceTree = "<group>"; };
87646C2127B5065100F82131 /* moc_bhfitnesselliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_bhfitnesselliptical.cpp; sourceTree = "<group>"; };
8767EF1D29448D6700810C0F /* characteristicwriteprocessor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = characteristicwriteprocessor.cpp; path = ../src/characteristicwriteprocessor.cpp; sourceTree = "<group>"; };
8768D1F9285081FE00F58E3A /* nordictrackifitadbtreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = nordictrackifitadbtreadmill.cpp; path = ../src/nordictrackifitadbtreadmill.cpp; sourceTree = "<group>"; };
8768D1FA285081FE00F58E3A /* nordictrackifitadbtreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nordictrackifitadbtreadmill.h; path = ../src/nordictrackifitadbtreadmill.h; sourceTree = "<group>"; };
8768D1FC2850820B00F58E3A /* moc_nordictrackifitadbtreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_nordictrackifitadbtreadmill.cpp; sourceTree = "<group>"; };
876BFC9827BE35C4001D7645 /* proformelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = proformelliptical.cpp; path = ../src/proformelliptical.cpp; sourceTree = "<group>"; };
876BFC9927BE35C4001D7645 /* bowflext216treadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bowflext216treadmill.cpp; path = ../src/bowflext216treadmill.cpp; sourceTree = "<group>"; };
876BFC9A27BE35C5001D7645 /* bowflext216treadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = bowflext216treadmill.h; path = ../src/bowflext216treadmill.h; sourceTree = "<group>"; };
@@ -1081,19 +981,12 @@
878A331725AB4FF800BD13E1 /* yesoulbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = yesoulbike.cpp; path = ../src/yesoulbike.cpp; sourceTree = "<group>"; };
878A331825AB4FF800BD13E1 /* yesoulbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = yesoulbike.h; path = ../src/yesoulbike.h; sourceTree = "<group>"; };
878A331B25AB50C200BD13E1 /* moc_yesoulbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_yesoulbike.cpp; sourceTree = "<group>"; };
878C9E6728B77E7B00669129 /* nordictrackifitadbbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nordictrackifitadbbike.h; path = ../src/nordictrackifitadbbike.h; sourceTree = "<group>"; };
878C9E6828B77E7B00669129 /* nordictrackifitadbbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = nordictrackifitadbbike.cpp; path = ../src/nordictrackifitadbbike.cpp; sourceTree = "<group>"; };
878C9E6A28B77E9800669129 /* moc_nordictrackifitadbbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_nordictrackifitadbbike.cpp; sourceTree = "<group>"; };
87900DC4268B672E000CB351 /* renphobike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = renphobike.h; path = ../src/renphobike.h; sourceTree = "<group>"; };
87900DC5268B672E000CB351 /* renphobike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = renphobike.cpp; path = ../src/renphobike.cpp; sourceTree = "<group>"; };
87900DC7268B673C000CB351 /* moc_renphobike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_renphobike.cpp; sourceTree = "<group>"; };
8790FDDD277B0ABA00247550 /* nautilustreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = nautilustreadmill.cpp; path = ../src/nautilustreadmill.cpp; sourceTree = "<group>"; };
8790FDDE277B0ABA00247550 /* nautilustreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nautilustreadmill.h; path = ../src/nautilustreadmill.h; sourceTree = "<group>"; };
8790FDE0277B0AC600247550 /* moc_nautilustreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_nautilustreadmill.cpp; sourceTree = "<group>"; };
87917A6E28E768D200F8D9AC /* Browser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Browser.swift; path = ../src/ios/AppleWatchToIpad/Browser.swift; sourceTree = "<group>"; };
87917A7028E768D200F8D9AC /* Connection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Connection.swift; path = ../src/ios/AppleWatchToIpad/Connection.swift; sourceTree = "<group>"; };
87917A7128E768D200F8D9AC /* Server.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Server.swift; path = ../src/ios/AppleWatchToIpad/Server.swift; sourceTree = "<group>"; };
87917A7228E768D200F8D9AC /* Client.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Client.swift; path = ../src/ios/AppleWatchToIpad/Client.swift; sourceTree = "<group>"; };
8791A8A725C8602A003B50B2 /* inspirebike.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = inspirebike.h; path = ../src/inspirebike.h; sourceTree = "<group>"; };
8791A8A825C8602A003B50B2 /* inspirebike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = inspirebike.cpp; path = ../src/inspirebike.cpp; sourceTree = "<group>"; };
8791A8A925C8603F003B50B2 /* moc_inspirebike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_inspirebike.cpp; sourceTree = "<group>"; };
@@ -1104,22 +997,9 @@
8798C8862733E103003148B3 /* strydrunpowersensor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = strydrunpowersensor.cpp; path = ../src/strydrunpowersensor.cpp; sourceTree = "<group>"; };
8798C8882733E10E003148B3 /* moc_strydrunpowersensor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_strydrunpowersensor.cpp; sourceTree = "<group>"; };
879A38C7281BD83300F78B2A /* characteristicnotifier2ad9.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = characteristicnotifier2ad9.cpp; path = ../src/characteristicnotifier2ad9.cpp; sourceTree = "<group>"; };
879E5AA6289C057E00FEA38A /* proformwifitreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = proformwifitreadmill.cpp; path = ../src/proformwifitreadmill.cpp; sourceTree = "<group>"; };
879E5AA7289C057E00FEA38A /* proformwifitreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformwifitreadmill.h; path = ../src/proformwifitreadmill.h; sourceTree = "<group>"; };
879E5AA9289C05A500FEA38A /* moc_proformwifitreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_proformwifitreadmill.cpp; sourceTree = "<group>"; };
879F16442847E55C00CE4945 /* proformellipticaltrainer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = proformellipticaltrainer.cpp; path = ../src/proformellipticaltrainer.cpp; sourceTree = "<group>"; };
879F16452847E55C00CE4945 /* proformellipticaltrainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformellipticaltrainer.h; path = ../src/proformellipticaltrainer.h; sourceTree = "<group>"; };
879F16472847E57400CE4945 /* moc_proformellipticaltrainer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_proformellipticaltrainer.cpp; sourceTree = "<group>"; };
879F74082893D4B5009A64C8 /* libQt5MultimediaWidgets.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libQt5MultimediaWidgets.a; path = ../../Qt/5.15.2/ios/lib/libQt5MultimediaWidgets.a; sourceTree = "<group>"; };
879F740A2893D4F9009A64C8 /* libqtaudio_coreaudio.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtaudio_coreaudio.a; path = ../../Qt/5.15.2/ios/plugins/audio/libqtaudio_coreaudio.a; sourceTree = "<group>"; };
879F740B2893D4F9009A64C8 /* libqtmultimedia_m3u.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtmultimedia_m3u.a; path = ../../Qt/5.15.2/ios/plugins/playlistformats/libqtmultimedia_m3u.a; sourceTree = "<group>"; };
879F740E2893D591009A64C8 /* libqtmedia_audioengine.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtmedia_audioengine.a; path = ../../Qt/5.15.2/ios/plugins/mediaservice/libqtmedia_audioengine.a; sourceTree = "<group>"; };
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 +1024,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 +1038,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>"; };
@@ -1217,12 +1091,6 @@
87CC3BA025A0885D001EC5A8 /* elliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = elliptical.cpp; path = ../src/elliptical.cpp; sourceTree = "<group>"; };
87CC3BA125A0885E001EC5A8 /* elliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = elliptical.h; path = ../src/elliptical.h; sourceTree = "<group>"; };
87CC3BA225A0885E001EC5A8 /* domyoselliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = domyoselliptical.h; path = ../src/domyoselliptical.h; sourceTree = "<group>"; };
87CF5167293C879700A7CABC /* characteristicwriteprocessore005.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = characteristicwriteprocessore005.cpp; path = ../src/characteristicwriteprocessore005.cpp; sourceTree = "<group>"; };
87CF5168293C879700A7CABC /* characteristicwriteprocessore005.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = characteristicwriteprocessore005.h; path = ../src/characteristicwriteprocessore005.h; sourceTree = "<group>"; };
87CF516A293C87AF00A7CABC /* moc_characteristicwriteprocessore005.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_characteristicwriteprocessore005.cpp; sourceTree = "<group>"; };
87D10550290996EA00B3935B /* mepanelbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = mepanelbike.cpp; path = ../src/mepanelbike.cpp; sourceTree = "<group>"; };
87D10551290996EA00B3935B /* mepanelbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mepanelbike.h; path = ../src/mepanelbike.h; sourceTree = "<group>"; };
87D105532909971100B3935B /* moc_mepanelbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_mepanelbike.cpp; sourceTree = "<group>"; };
87D2699925F535160076AA48 /* m3ibike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = m3ibike.h; path = ../src/m3ibike.h; sourceTree = "<group>"; };
87D2699A25F535160076AA48 /* m3ibike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = m3ibike.cpp; path = ../src/m3ibike.cpp; sourceTree = "<group>"; };
87D2699C25F535170076AA48 /* skandikawiribike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = skandikawiribike.h; path = ../src/skandikawiribike.h; sourceTree = "<group>"; };
@@ -1236,10 +1104,10 @@
87D91F982800B9970026D43C /* proformwifibike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformwifibike.h; path = ../src/proformwifibike.h; sourceTree = "<group>"; };
87D91F992800B9970026D43C /* proformwifibike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = proformwifibike.cpp; path = ../src/proformwifibike.cpp; sourceTree = "<group>"; };
87D91F9B2800B9B90026D43C /* moc_proformwifibike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_proformwifibike.cpp; sourceTree = "<group>"; };
87DA764F2848F8F200A71B64 /* libQt5TextToSpeech.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libQt5TextToSpeech.a; path = ../../Qt/5.15.2/ios/lib/libQt5TextToSpeech.a; sourceTree = "<group>"; };
87DA8463284933D200B550E9 /* fakeelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fakeelliptical.h; path = ../src/fakeelliptical.h; sourceTree = "<group>"; };
87DA8464284933D200B550E9 /* fakeelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = fakeelliptical.cpp; path = ../src/fakeelliptical.cpp; sourceTree = "<group>"; };
87DA8466284933DE00B550E9 /* moc_fakeelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_fakeelliptical.cpp; sourceTree = "<group>"; };
87DA764F2848F8F200A71B64 /* libQt5TextToSpeech.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libQt5TextToSpeech.a; path = ../../Qt/5.15.2/ios/lib/libQt5TextToSpeech.a; sourceTree = "<group>"; };
87DAE15D26E9FF3900B0527E /* shuaa5treadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = shuaa5treadmill.cpp; path = ../src/shuaa5treadmill.cpp; sourceTree = "<group>"; };
87DAE15E26E9FF3900B0527E /* kingsmithr2treadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = kingsmithr2treadmill.h; path = ../src/kingsmithr2treadmill.h; sourceTree = "<group>"; };
87DAE15F26E9FF3A00B0527E /* kingsmithr2treadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = kingsmithr2treadmill.cpp; path = ../src/kingsmithr2treadmill.cpp; sourceTree = "<group>"; };
@@ -1259,12 +1127,6 @@
87E0761B277A081900FDA0F9 /* technogymmyruntreadmillrfcomm.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = technogymmyruntreadmillrfcomm.cpp; path = ../src/technogymmyruntreadmillrfcomm.cpp; sourceTree = "<group>"; };
87E0761C277A081900FDA0F9 /* technogymmyruntreadmillrfcomm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = technogymmyruntreadmillrfcomm.h; path = ../src/technogymmyruntreadmillrfcomm.h; sourceTree = "<group>"; };
87E0761E277A082300FDA0F9 /* moc_technogymmyruntreadmillrfcomm.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_technogymmyruntreadmillrfcomm.cpp; sourceTree = "<group>"; };
87E2F85B291ED308002BDC65 /* lifefitnesstreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lifefitnesstreadmill.h; path = ../src/lifefitnesstreadmill.h; sourceTree = "<group>"; };
87E2F85C291ED308002BDC65 /* lifefitnesstreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lifefitnesstreadmill.cpp; path = ../src/lifefitnesstreadmill.cpp; sourceTree = "<group>"; };
87E2F85E291ED315002BDC65 /* moc_lifefitnesstreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_lifefitnesstreadmill.cpp; sourceTree = "<group>"; };
87E34C292886F95300CEDE4B /* octanetreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = octanetreadmill.h; path = ../src/octanetreadmill.h; sourceTree = "<group>"; };
87E34C2A2886F95400CEDE4B /* octanetreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = octanetreadmill.cpp; path = ../src/octanetreadmill.cpp; sourceTree = "<group>"; };
87E34C2C2886F99900CEDE4B /* moc_octanetreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_octanetreadmill.cpp; sourceTree = "<group>"; };
87E5D2C425E69F3100BDBE6C /* horizontreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = horizontreadmill.h; path = ../src/horizontreadmill.h; sourceTree = "<group>"; };
87E5D2C525E69F3100BDBE6C /* horizontreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = horizontreadmill.cpp; path = ../src/horizontreadmill.cpp; sourceTree = "<group>"; };
87E5D2C725E69F4700BDBE6C /* moc_horizontreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_horizontreadmill.cpp; sourceTree = "<group>"; };
@@ -1290,11 +1152,6 @@
87EFE45727A518F5006EA1C3 /* nautiluselliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nautiluselliptical.h; path = ../src/nautiluselliptical.h; sourceTree = "<group>"; };
87EFE45827A518F5006EA1C3 /* nautiluselliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = nautiluselliptical.cpp; path = ../src/nautiluselliptical.cpp; sourceTree = "<group>"; };
87EFE45A27A51900006EA1C3 /* moc_nautiluselliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_nautiluselliptical.cpp; sourceTree = "<group>"; };
87F02E3E29178523000DB52C /* octaneelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = octaneelliptical.cpp; path = ../src/octaneelliptical.cpp; sourceTree = "<group>"; };
87F02E3F29178524000DB52C /* octaneelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = octaneelliptical.h; path = ../src/octaneelliptical.h; sourceTree = "<group>"; };
87F02E4129178545000DB52C /* moc_octaneelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_octaneelliptical.cpp; sourceTree = "<group>"; };
87F527BC28EEB5AA00A9F8D5 /* qzsettings.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = qzsettings.cpp; path = ../src/qzsettings.cpp; sourceTree = "<group>"; };
87F527BD28EEB5AA00A9F8D5 /* qzsettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = qzsettings.h; path = ../src/qzsettings.h; sourceTree = "<group>"; };
87F93425278E0EC00088B596 /* domyosrower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = domyosrower.h; path = ../src/domyosrower.h; sourceTree = "<group>"; };
87F93426278E0EC00088B596 /* domyosrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = domyosrower.cpp; path = ../src/domyosrower.cpp; sourceTree = "<group>"; };
87F93428278E0ECF0088B596 /* moc_domyosrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_domyosrower.cpp; sourceTree = "<group>"; };
@@ -1518,31 +1375,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
879F74112893D5B8009A64C8 /* libqavfcamera.a in Link Binary With Libraries */,
879F740F2893D592009A64C8 /* libqtmedia_audioengine.a in Link Binary With Libraries */,
879F740C2893D4FA009A64C8 /* libqtaudio_coreaudio.a in Link Binary With Libraries */,
879F740D2893D4FA009A64C8 /* libqtmultimedia_m3u.a in Link Binary With Libraries */,
879F74092893D4B6009A64C8 /* libQt5MultimediaWidgets.a in Link Binary With Libraries */,
871189192893CECF006A04D1 /* libqavfmediaplayer.a in Link Binary With Libraries */,
871189172893CC45006A04D1 /* libQt5MultimediaQuick.a in Link Binary With Libraries */,
871189152893CB52006A04D1 /* libdeclarative_multimedia.a in Link Binary With Libraries */,
871189132893C930006A04D1 /* libQt5Multimedia.a in Link Binary With Libraries */,
870613BC286D97D200D2446E /* libQt5Sql.a in Link Binary With Libraries */,
870613BA286D979100D2446E /* libqmapboxgl.a in Link Binary With Libraries */,
870613B8286D973C00D2446E /* libqsqlite.a in Link Binary With Libraries */,
870613B6286D96DF00D2446E /* libqtgeoservices_mapboxgl.a in Link Binary With Libraries */,
870613B1286D969500D2446E /* libqtgeoservices_itemsoverlay.a in Link Binary With Libraries */,
870613B3286D969500D2446E /* libqtgeoservices_esri.a in Link Binary With Libraries */,
870613B4286D969500D2446E /* libqtgeoservices_nokia.a in Link Binary With Libraries */,
870613B5286D969500D2446E /* libqtgeoservices_mapbox.a in Link Binary With Libraries */,
870613A4286D917700D2446E /* libQt5Location.a in Link Binary With Libraries */,
870613A5286D917700D2446E /* libQt5Positioning.a in Link Binary With Libraries */,
87061390286D8B4F00D2446E /* libQt5PositioningQuick.a in Link Binary With Libraries */,
870613A0286D8F1200D2446E /* libqtposition_positionpoll.a in Link Binary With Libraries */,
870613A1286D8F1200D2446E /* libqtposition_cl.a in Link Binary With Libraries */,
8706139D286D8E7300D2446E /* libqtgeoservices_osm.a in Link Binary With Libraries */,
8706139B286D8DA300D2446E /* libdeclarative_location.a in Link Binary With Libraries */,
87061394286D8C9900D2446E /* libdeclarative_positioning.a in Link Binary With Libraries */,
87540FAD2848FD70005E0D44 /* libqtexttospeech_speechios.a in Link Binary With Libraries */,
87DA76502848F98200A71B64 /* libQt5TextToSpeech.a in Link Binary With Libraries */,
877FE4EC27368F7D006FAB7B /* AVFoundation.framework in Link Binary With Libraries */,
@@ -1596,7 +1428,6 @@
7BEF7EDD71FE6186DE2A2CA8 /* qt_poly2tri in Link Binary With Libraries */,
B4D9325E1B419854C84A6E86 /* qt_clipper in Link Binary With Libraries */,
4FA524144B680D741D33EACB /* qtgraphicaleffectsprivate in Link Binary With Libraries */,
879F74152893D732009A64C8 /* CoreMedia.framework in Link Binary With Libraries */,
1C823E40F377B93A664EAC1B /* modelsplugin in Link Binary With Libraries */,
B9DED9CC16B0F3339F363FBF /* workerscriptplugin in Link Binary With Libraries */,
023642106C14651D2E1F4D5D /* dialogplugin in Link Binary With Libraries */,
@@ -1607,7 +1438,6 @@
7C8D236C48F2964061C3457C /* widgetsplugin in Link Binary With Libraries */,
401B341C04019FFA2146E79D /* Qt5Widgets in Link Binary With Libraries */,
61EC5BE7EEC8D905C63FF628 /* qmlplugin in Link Binary With Libraries */,
877A080D2893DC4300C0F0AB /* CoreVideo.framework in Link Binary With Libraries */,
876EE3CDDF69DA139329ADD8 /* qquicklayoutsplugin in Link Binary With Libraries */,
7167B22320D1DE063C3DF45E /* windowplugin in Link Binary With Libraries */,
F55B541DFCF9C73160D34905 /* qtquick2plugin in Link Binary With Libraries */,
@@ -1651,7 +1481,6 @@
0FF051564C679F373AD93E32 /* ios */ = {
isa = PBXGroup;
children = (
87917A6D28E768B000F8D9AC /* AppleWatchToIpad */,
8754D24B27F786F0003D7054 /* virtualrower.swift */,
2F0E70F726316F3600E11F3A /* virtualbike_zwift.swift */,
871E4CD025A6FB5A00E18D6D /* BLEPeripheralManager.swift */,
@@ -1674,21 +1503,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 */,
87D105532909971100B3935B /* moc_mepanelbike.cpp */,
872A20DB28C5F5CE0037774D /* moc_faketreadmill.cpp */,
878C9E6A28B77E9800669129 /* moc_nordictrackifitadbbike.cpp */,
872261EF289EA887006A6F75 /* moc_nordictrackelliptical.cpp */,
879E5AA9289C05A500FEA38A /* moc_proformwifitreadmill.cpp */,
87E34C2C2886F99900CEDE4B /* moc_octanetreadmill.cpp */,
87061398286D8D6500D2446E /* moc_wobjectdefs.cpp */,
8768D1FC2850820B00F58E3A /* moc_nordictrackifitadbtreadmill.cpp */,
87DA8466284933DE00B550E9 /* moc_fakeelliptical.cpp */,
879F16472847E57400CE4945 /* moc_proformellipticaltrainer.cpp */,
87D5DC4128230496008CCDE7 /* moc_truetreadmill.cpp */,
@@ -1835,39 +1649,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 */,
87E2F85C291ED308002BDC65 /* lifefitnesstreadmill.cpp */,
87E2F85B291ED308002BDC65 /* lifefitnesstreadmill.h */,
87F02E3E29178523000DB52C /* octaneelliptical.cpp */,
87F02E3F29178524000DB52C /* octaneelliptical.h */,
87D10550290996EA00B3935B /* mepanelbike.cpp */,
87D10551290996EA00B3935B /* mepanelbike.h */,
87F527BC28EEB5AA00A9F8D5 /* qzsettings.cpp */,
87F527BD28EEB5AA00A9F8D5 /* qzsettings.h */,
872A20D928C5EC380037774D /* faketreadmill.cpp */,
872A20D828C5EC380037774D /* faketreadmill.h */,
878C9E6828B77E7B00669129 /* nordictrackifitadbbike.cpp */,
878C9E6728B77E7B00669129 /* nordictrackifitadbbike.h */,
872261EC289EA873006A6F75 /* nordictrackelliptical.cpp */,
872261ED289EA873006A6F75 /* nordictrackelliptical.h */,
879E5AA6289C057E00FEA38A /* proformwifitreadmill.cpp */,
879E5AA7289C057E00FEA38A /* proformwifitreadmill.h */,
87E34C2A2886F95400CEDE4B /* octanetreadmill.cpp */,
87E34C292886F95300CEDE4B /* octanetreadmill.h */,
87061395286D8CFE00D2446E /* PathController.cpp */,
87061396286D8CFE00D2446E /* PathController.h */,
8768D1F9285081FE00F58E3A /* nordictrackifitadbtreadmill.cpp */,
8768D1FA285081FE00F58E3A /* nordictrackifitadbtreadmill.h */,
87DA8464284933D200B550E9 /* fakeelliptical.cpp */,
87DA8463284933D200B550E9 /* fakeelliptical.h */,
879F16442847E55C00CE4945 /* proformellipticaltrainer.cpp */,
@@ -2243,17 +2024,6 @@
path = "Preview Content";
sourceTree = "<group>";
};
87917A6D28E768B000F8D9AC /* AppleWatchToIpad */ = {
isa = PBXGroup;
children = (
87917A6E28E768D200F8D9AC /* Browser.swift */,
87917A7228E768D200F8D9AC /* Client.swift */,
87917A7028E768D200F8D9AC /* Connection.swift */,
87917A7128E768D200F8D9AC /* Server.swift */,
);
name = AppleWatchToIpad;
sourceTree = "<group>";
};
87DF60DE337FB58864343E39 /* Resources */ = {
isa = PBXGroup;
children = (
@@ -2266,34 +2036,6 @@
AF39DD055C3EF8226FBE929D /* Frameworks */ = {
isa = PBXGroup;
children = (
879F74142893D732009A64C8 /* CoreMedia.framework */,
879F74122893D705009A64C8 /* CoreVideo.framework */,
879F74102893D5B7009A64C8 /* libqavfcamera.a */,
879F740E2893D591009A64C8 /* libqtmedia_audioengine.a */,
879F740A2893D4F9009A64C8 /* libqtaudio_coreaudio.a */,
879F740B2893D4F9009A64C8 /* libqtmultimedia_m3u.a */,
879F74082893D4B5009A64C8 /* libQt5MultimediaWidgets.a */,
871189182893CECC006A04D1 /* libqavfmediaplayer.a */,
871189162893CC44006A04D1 /* libQt5MultimediaQuick.a */,
871189142893CB51006A04D1 /* libdeclarative_multimedia.a */,
871189122893C92F006A04D1 /* libQt5Multimedia.a */,
870613BB286D97D100D2446E /* libQt5Sql.a */,
870613B9286D979000D2446E /* libqmapboxgl.a */,
870613B7286D973B00D2446E /* libqsqlite.a */,
870613AE286D969400D2446E /* libqtgeoservices_esri.a */,
870613AC286D969300D2446E /* libqtgeoservices_itemsoverlay.a */,
870613B0286D969500D2446E /* libqtgeoservices_mapbox.a */,
870613AD286D969300D2446E /* libqtgeoservices_mapboxgl.a */,
870613AF286D969400D2446E /* libqtgeoservices_nokia.a */,
870613A2286D917600D2446E /* libQt5Location.a */,
870613A3286D917700D2446E /* libQt5Positioning.a */,
8706139F286D8F1200D2446E /* libqtposition_cl.a */,
8706139E286D8F1100D2446E /* libqtposition_positionpoll.a */,
8706139C286D8E7300D2446E /* libqtgeoservices_osm.a */,
8706139A286D8DA200D2446E /* libdeclarative_location.a */,
87061393286D8C9900D2446E /* libdeclarative_positioning.a */,
87061391286D8C4200D2446E /* Qt */,
8706138F286D8B4F00D2446E /* libQt5PositioningQuick.a */,
87540FAC2848FD70005E0D44 /* libqtexttospeech_speechios.a */,
87DA764F2848F8F200A71B64 /* libQt5TextToSpeech.a */,
873CD22E27EF8EC1000131BC /* StoreKit.framework */,
@@ -2868,7 +2610,6 @@
8738249127E646E3004F1B46 /* dirconpacket.cpp in Compile Sources */,
87C5F0BB26285E5F0067A1B5 /* mimetext.cpp in Compile Sources */,
8732C17F27353464006DF424 /* iconceptbike.cpp in Compile Sources */,
87CF5169293C879800A7CABC /* characteristicwriteprocessore005.cpp in Compile Sources */,
873824C027E64707004F1B46 /* moc_dirconmanager.cpp in Compile Sources */,
2F0E70F826316F3600E11F3A /* virtualbike_zwift.swift in Compile Sources */,
8772A0E825E43AE70080718C /* moc_trxappgateusbbike.cpp in Compile Sources */,
@@ -2897,7 +2638,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 */,
@@ -2916,9 +2656,6 @@
C6B3CD471768392E18F85819 /* fit_accumulated_field.cpp in Compile Sources */,
3D7395B0A17915A06361C7F3 /* fit_accumulator.cpp in Compile Sources */,
2A61806454201575EDB3F94F /* fit_buffer_encode.cpp in Compile Sources */,
87F02E4229178545000DB52C /* moc_octaneelliptical.cpp in Compile Sources */,
87E2F85D291ED308002BDC65 /* lifefitnesstreadmill.cpp in Compile Sources */,
87917A7328E768D200F8D9AC /* Browser.swift in Compile Sources */,
873CD20B27EF8D8A000131BC /* inapptransaction.cpp in Compile Sources */,
873824EF27E647A9004F1B46 /* query.cpp in Compile Sources */,
876F45FF279350D9003CDA5A /* moc_concept2skierg.cpp in Compile Sources */,
@@ -2932,7 +2669,6 @@
87368825259C602800C71C7E /* watchAppStart.swift in Compile Sources */,
876ED21925C3E9000065F3DC /* moc_ftmsbike.cpp in Compile Sources */,
87C5F0BD26285E5F0067A1B5 /* chronobike.cpp in Compile Sources */,
872A20DA28C5EC380037774D /* faketreadmill.cpp in Compile Sources */,
87900DC8268B673C000CB351 /* moc_renphobike.cpp in Compile Sources */,
87CC3B9E25A08812001EC5A8 /* moc_elliptical.cpp in Compile Sources */,
876BFC9C27BE35C5001D7645 /* proformelliptical.cpp in Compile Sources */,
@@ -2945,11 +2681,9 @@
87CC3B9D25A08812001EC5A8 /* moc_domyoselliptical.cpp in Compile Sources */,
87900DC6268B672E000CB351 /* renphobike.cpp in Compile Sources */,
879F16462847E55C00CE4945 /* proformellipticaltrainer.cpp in Compile Sources */,
87917A7728E768D200F8D9AC /* Client.swift in Compile Sources */,
873824B927E64707004F1B46 /* moc_provider.cpp in Compile Sources */,
8727A47727849EA600019B5D /* paferstreadmill.cpp in Compile Sources */,
DF1FD9718B75FA591A7E3D80 /* fit_decode.cpp in Compile Sources */,
878C9E6B28B77E9800669129 /* moc_nordictrackifitadbbike.cpp in Compile Sources */,
878A331D25AB50C300BD13E1 /* moc_yesoulbike.cpp in Compile Sources */,
952DBD14DF6369E885020EF4 /* fit_developer_field.cpp in Compile Sources */,
879F16482847E57400CE4945 /* moc_proformellipticaltrainer.cpp in Compile Sources */,
@@ -2964,9 +2698,6 @@
873824B127E64706004F1B46 /* moc_browser_p.cpp in Compile Sources */,
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 */,
@@ -2980,7 +2711,6 @@
87CC3BA425A0885F001EC5A8 /* elliptical.cpp in Compile Sources */,
4AD2C93A2B8FD5855E521630 /* fit_encode.cpp in Compile Sources */,
87EB918C27EE5FE7002535E1 /* moc_inappproduct.cpp in Compile Sources */,
87E34C2D2886F99A00CEDE4B /* moc_octanetreadmill.cpp in Compile Sources */,
87D91F9A2800B9970026D43C /* proformwifibike.cpp in Compile Sources */,
873CD22327EF8E18000131BC /* inappstoreqmltype.cpp in Compile Sources */,
87C481FA26DFA7C3006211AD /* eliterizer.cpp in Compile Sources */,
@@ -2992,7 +2722,6 @@
87C7074227E4CF5300E79C46 /* moc_keepbike.cpp in Compile Sources */,
BBBE7689F5792CB3FD1997EC /* fit_factory.cpp in Compile Sources */,
876ED21625C3E8DE0065F3DC /* schwinnic4bike.cpp in Compile Sources */,
879E5AAA289C05A500FEA38A /* moc_proformwifitreadmill.cpp in Compile Sources */,
87D269A325F535340076AA48 /* moc_skandikawiribike.cpp in Compile Sources */,
87C5F0C226285E5F0067A1B5 /* emailaddress.cpp in Compile Sources */,
873824AF27E64706004F1B46 /* moc_characteristicwriteprocessor2ad9.cpp in Compile Sources */,
@@ -3021,7 +2750,6 @@
875F69B926342E8D0009FD78 /* spirittreadmill.cpp in Compile Sources */,
87DF68B825E2673B00FCDA46 /* eslinkertreadmill.cpp in Compile Sources */,
87C7074327E4CF5900E79C46 /* keepbike.cpp in Compile Sources */,
878C9E6928B77E7C00669129 /* nordictrackifitadbbike.cpp in Compile Sources */,
8798C8892733E10E003148B3 /* moc_strydrunpowersensor.cpp in Compile Sources */,
8781907E2615089D0085E656 /* peloton.cpp in Compile Sources */,
2B800DC34C91D8B080DEFBE8 /* fit_mesg_with_event_broadcaster.cpp in Compile Sources */,
@@ -3029,9 +2757,7 @@
87C5F0B726285E5F0067A1B5 /* mimecontentformatter.cpp in Compile Sources */,
23191C28CB29474279752FD3 /* fit_protocol_validator.cpp in Compile Sources */,
275D55B5D956B2E5F1B7E46E /* fit_unicode.cpp in Compile Sources */,
87F527BE28EEB5AA00A9F8D5 /* qzsettings.cpp in Compile Sources */,
8703BAEB273C67A90058E206 /* pafersbike.cpp in Compile Sources */,
87061399286D8D6500D2446E /* moc_wobjectdefs.cpp in Compile Sources */,
873824BA27E64707004F1B46 /* moc_server_p.cpp in Compile Sources */,
87958F1927628D4500124B24 /* elitesterzosmart.cpp in Compile Sources */,
87EB917627EE5FB3002535E1 /* nautilusbike.cpp in Compile Sources */,
@@ -3049,7 +2775,6 @@
87A3BC222656429600D302E3 /* rower.cpp in Compile Sources */,
C719682D8D421AF6B2DAAEA9 /* main.cpp in Compile Sources */,
87BB1774269E983200F46A1C /* moc_webserverinfosender.cpp in Compile Sources */,
87E2F85F291ED315002BDC65 /* moc_lifefitnesstreadmill.cpp in Compile Sources */,
876BFCA127BE35D8001D7645 /* moc_bowflext216treadmill.cpp in Compile Sources */,
25FCD41CCCAF49293B9369E8 /* qfit.cpp in Compile Sources */,
87ADD2BD27634C2100B7A0AB /* moc_technogymmyruntreadmill.cpp in Compile Sources */,
@@ -3072,9 +2797,7 @@
873824BB27E64707004F1B46 /* moc_prober_p.cpp in Compile Sources */,
8742C2B227C92C30007D3FA0 /* wahookickrsnapbike.cpp in Compile Sources */,
87EB918327EE5FE7002535E1 /* moc_inappstore.cpp in Compile Sources */,
87CF516B293C87B000A7CABC /* moc_characteristicwriteprocessore005.cpp in Compile Sources */,
8780D949264FB8B800192D41 /* moc_smartspin2k.cpp in Compile Sources */,
8768D1FD2850820B00F58E3A /* moc_nordictrackifitadbtreadmill.cpp in Compile Sources */,
87D5DC402823047D008CCDE7 /* truetreadmill.cpp in Compile Sources */,
873CD23027EF8EF5000131BC /* iosinapppurchasetransaction.mm in Compile Sources */,
6943DA124B60175E1F9EBD1B /* virtualbike.cpp in Compile Sources */,
@@ -3108,16 +2831,13 @@
87AE0CB227760DCB00E547E9 /* virtualtreadmill_zwift.swift in Compile Sources */,
87062643259480A200D06586 /* AppDelegate.swift in Compile Sources */,
873824F027E647A9004F1B46 /* server.cpp in Compile Sources */,
872A20DC28C5F5CE0037774D /* moc_faketreadmill.cpp in Compile Sources */,
873CD20927EF8D8A000131BC /* inapppurchasebackend.cpp in Compile Sources */,
87E0761F277A082300FDA0F9 /* moc_technogymmyruntreadmillrfcomm.cpp in Compile Sources */,
87433F2127D8B722003D1672 /* simplecrypt.cpp in Compile Sources */,
87917A7628E768D200F8D9AC /* Server.swift in Compile Sources */,
2B42755BF45173E11E2110CB /* FitFieldDefinition.mm in Compile Sources */,
873824AE27E64706004F1B46 /* moc_browser.cpp in Compile Sources */,
8738249727E646E3004F1B46 /* characteristicnotifier2a53.cpp in Compile Sources */,
DF373364C5474D877506CB26 /* FitMesg.mm in Compile Sources */,
872261F0289EA887006A6F75 /* moc_nordictrackelliptical.cpp in Compile Sources */,
873824E327E647A8004F1B46 /* bitmap.cpp in Compile Sources */,
87FE5BB12692F31E0056EFC8 /* moc_tacxneo2.cpp in Compile Sources */,
873824E827E647A8004F1B46 /* provider.cpp in Compile Sources */,
@@ -3129,12 +2849,10 @@
873824E527E647A8004F1B46 /* message.cpp in Compile Sources */,
210F6A0A7E2FA7CDD3CA0084 /* qdomyoszwift_qml_plugin_import.cpp in Compile Sources */,
87062644259480A600D06586 /* APIFetcher.swift in Compile Sources */,
87F02E4029178524000DB52C /* octaneelliptical.cpp in Compile Sources */,
878531682711A3EC004B153D /* moc_activiotreadmill.cpp in Compile Sources */,
87C5F0D626285E7E0067A1B5 /* moc_emailaddress.cpp in Compile Sources */,
39FAA19B9285AB16AE3A39BA /* qrc_icons.cpp in Compile Sources */,
87D2699F25F535200076AA48 /* m3ibike.cpp in Compile Sources */,
87917A7528E768D200F8D9AC /* Connection.swift in Compile Sources */,
87FFA13727BBE3FF00924E4E /* solebike.cpp in Compile Sources */,
7352E0F0EE5366AC809B9D64 /* qrc_qml.cpp in Compile Sources */,
873824E727E647A8004F1B46 /* record.cpp in Compile Sources */,
@@ -3148,7 +2866,6 @@
873824BD27E64707004F1B46 /* moc_resolver_p.cpp in Compile Sources */,
876ED21A25C3E9010065F3DC /* moc_material.cpp in Compile Sources */,
87A4B76125AF27CB0027EF3C /* metric.cpp in Compile Sources */,
87D10552290996EA00B3935B /* mepanelbike.cpp in Compile Sources */,
9D9484EED654597C394345DE /* moc_echelonconnectsport.cpp in Compile Sources */,
87DED80627D1273900BE4FBB /* filedownloader.cpp in Compile Sources */,
7DEEAF0C3D671FBFD84ACFCE /* moc_homeform.cpp in Compile Sources */,
@@ -3160,13 +2877,11 @@
E62DA5FF2436135448C94671 /* moc_toorxtreadmill.cpp in Compile Sources */,
87586A4325B8341B00A243C4 /* moc_proformbike.cpp in Compile Sources */,
87CC3BA325A0885F001EC5A8 /* domyoselliptical.cpp in Compile Sources */,
87D105542909971100B3935B /* moc_mepanelbike.cpp in Compile Sources */,
87B617F325F260150094A1CB /* moc_snodebike.cpp in Compile Sources */,
87DA8467284933DE00B550E9 /* moc_fakeelliptical.cpp in Compile Sources */,
87C5F0D726285E7E0067A1B5 /* moc_mimefile.cpp in Compile Sources */,
877FBA29276E684500F6C0C9 /* bowflextreadmill.cpp in Compile Sources */,
8762D5102601F7EA00F6F049 /* M3iNSQT.cpp in Compile Sources */,
872261EE289EA873006A6F75 /* nordictrackelliptical.cpp in Compile Sources */,
8718CBA3263063BD004BF4EE /* templateinfosender.cpp in Compile Sources */,
E8B499F921FB0AB55C7A8A8B /* moc_gpx.cpp in Compile Sources */,
87E6A85825B5C88E00371D28 /* moc_flywheelbike.cpp in Compile Sources */,
@@ -3176,26 +2891,19 @@
140BAAA8823E05940EF35A38 /* moc_treadmill.cpp in Compile Sources */,
873824BF27E64707004F1B46 /* moc_abstractserver.cpp in Compile Sources */,
87C5F0BA26285E5F0067A1B5 /* mimefile.cpp in Compile Sources */,
879E5AA8289C057E00FEA38A /* proformwifitreadmill.cpp in Compile Sources */,
873824B227E64706004F1B46 /* moc_hostname.cpp in Compile Sources */,
873824EB27E647A8004F1B46 /* prober.cpp in Compile Sources */,
8767EF1E29448D6700810C0F /* characteristicwriteprocessor.cpp in Compile Sources */,
87B617F425F260150094A1CB /* moc_screencapture.cpp in Compile Sources */,
87B617ED25F25FED0094A1CB /* fitshowtreadmill.cpp in Compile Sources */,
87A0C4BC262329A600121A76 /* cscbike.cpp in Compile Sources */,
692540CF811B06A8710A0A52 /* moc_mainwindow.cpp in Compile Sources */,
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 */,
87061397286D8CFE00D2446E /* PathController.cpp in Compile Sources */,
87D44181269DE979003263D5 /* webserverinfosender.cpp in Compile Sources */,
87062647259480B400D06586 /* WatchKitConnection.swift in Compile Sources */,
876F45FD279350CC003CDA5A /* concept2skierg.cpp in Compile Sources */,
@@ -3206,7 +2914,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 +2926,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 +3282,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.10.94;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
HEADER_SEARCH_PATHS = (
@@ -3605,7 +3311,6 @@
../src/purchasing/qmltypes,
../src/purchasing/ios,
../../Qt/5.15.2/ios/include/QtTextToSpeech,
../../Qt/5.15.2/ios/include/QtMultimedia,
);
LIBRARY_SEARCH_PATHS = (
/Users/cagnulein/Qt/5.15.2/ios/plugins/platforms,
@@ -3641,17 +3346,8 @@
/Users/cagnulein/Qt/5.15.2/ios/plugins/webview,
/Users/cagnulein/Qt/5.15.2/ios/qml/QtWebView,
/Users/cagnulein/Qt/5.15.2/ios/plugins/texttospeech,
/Users/cagnulein/Qt/5.15.2/ios/qml/QtPositioning,
/Users/cagnulein/Qt/5.15.2/ios/qml/QtLocation,
/Users/cagnulein/Qt/5.15.2/ios/plugins/geoservices,
/Users/cagnulein/Qt/5.15.2/ios/plugins/position,
/Users/cagnulein/Qt/5.15.2/ios/plugins/sqldrivers,
/Users/cagnulein/Qt/5.15.2/ios/qml/QtMultimedia,
/Users/cagnulein/Qt/5.15.2/ios/plugins/mediaservice,
/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.10;
OTHER_CFLAGS = (
"-pipe",
"-g",
@@ -3678,9 +3374,6 @@
"-DNS_FORMAT_ARGUMENT(A)=",
"-D_Nullable_result=_Nullable",
"-DQT_TEXTTOSPEECH_LIB",
"-DQT_POSITIONINGQUICK_LIB",
"-DQT_LOCATION_LIB",
"-DQT_MULTIMEDIA_LIB",
);
OTHER_CPLUSPLUSFLAGS = (
"-pipe",
@@ -3712,9 +3405,6 @@
"-D_Nullable_result=_Nullable",
"-DNS_FORMAT_ARGUMENT(A)=",
"-DQT_TEXTTOSPEECH_LIB",
"-DQT_LOCATION_LIB",
"-DQT_POSITIONINGQUICK_LIB",
"-DQT_MULTIMEDIA_LIB",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.cagnulein.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = qdomyoszwift;
@@ -3744,7 +3434,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.10.94;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -3775,7 +3465,6 @@
../src/purchasing/qmltypes,
../src/purchasing/ios,
../../Qt/5.15.2/ios/include/QtTextToSpeech,
../../Qt/5.15.2/ios/include/QtMultimedia,
);
LIBRARY_SEARCH_PATHS = (
/Users/cagnulein/Qt/5.15.2/ios/plugins/platforms,
@@ -3811,17 +3500,8 @@
/Users/cagnulein/Qt/5.15.2/ios/plugins/webview,
/Users/cagnulein/Qt/5.15.2/ios/qml/QtWebView,
/Users/cagnulein/Qt/5.15.2/ios/plugins/texttospeech,
/Users/cagnulein/Qt/5.15.2/ios/qml/QtPositioning,
/Users/cagnulein/Qt/5.15.2/ios/qml/QtLocation,
/Users/cagnulein/Qt/5.15.2/ios/plugins/geoservices,
/Users/cagnulein/Qt/5.15.2/ios/plugins/position,
/Users/cagnulein/Qt/5.15.2/ios/plugins/sqldrivers,
/Users/cagnulein/Qt/5.15.2/ios/qml/QtMultimedia,
/Users/cagnulein/Qt/5.15.2/ios/plugins/mediaservice,
/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.10;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = (
"-pipe",
@@ -3849,9 +3529,6 @@
"-DNS_FORMAT_ARGUMENT(A)=",
"-D_Nullable_result=_Nullable",
"-DQT_TEXTTOSPEECH_LIB",
"-DQT_POSITIONINGQUICK_LIB",
"-DQT_LOCATION_LIB",
"-DQT_MULTIMEDIA_LIB",
);
OTHER_CPLUSPLUSFLAGS = (
"-pipe",
@@ -3883,9 +3560,6 @@
"-D_Nullable_result=_Nullable",
"-DNS_FORMAT_ARGUMENT(A)=",
"-DQT_TEXTTOSPEECH_LIB",
"-DQT_LOCATION_LIB",
"-DQT_POSITIONINGQUICK_LIB",
"-DQT_MULTIMEDIA_LIB",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.cagnulein.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = qdomyoszwift;
@@ -3948,7 +3622,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.10.94;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -3969,7 +3643,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.10;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
@@ -4040,7 +3714,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.10.94;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -4057,7 +3731,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.10;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
@@ -4127,7 +3801,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.10.94;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -4146,29 +3820,9 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
../src,
.,
../../Qt/5.15.2/ios/mkspecs/common/uikit,
"../src/fit-sdk",
../../Qt/5.15.2/ios/include,
../../Qt/5.15.2/ios/include/QtWidgets,
../../Qt/5.15.2/ios/include/QtQuick,
../../Qt/5.15.2/ios/include/QtGui,
../../Qt/5.15.2/ios/include/QtBluetooth,
../../Qt/5.15.2/ios/include/QtXml,
../../Qt/5.15.2/ios/include/QtPositioning,
../../Qt/5.15.2/ios/include/QtQmlModels,
../../Qt/5.15.2/ios/include/QtQml,
../../Qt/5.15.2/ios/include/QtNetwork,
../../Qt/5.15.2/ios/include/QtCore,
.,
"../../Qt/5.15.2/ios/mkspecs/macx-ios-clang",
../../Qt/5.15.2/ios/include/QtMultimedia,
);
INFOPLIST_FILE = "watchkit Extension/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 2.13;
MARKETING_VERSION = 2.10;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
@@ -4237,7 +3891,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.10.94;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
@@ -4252,29 +3906,9 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
../src,
.,
../../Qt/5.15.2/ios/mkspecs/common/uikit,
"../src/fit-sdk",
../../Qt/5.15.2/ios/include,
../../Qt/5.15.2/ios/include/QtWidgets,
../../Qt/5.15.2/ios/include/QtQuick,
../../Qt/5.15.2/ios/include/QtGui,
../../Qt/5.15.2/ios/include/QtBluetooth,
../../Qt/5.15.2/ios/include/QtXml,
../../Qt/5.15.2/ios/include/QtPositioning,
../../Qt/5.15.2/ios/include/QtQmlModels,
../../Qt/5.15.2/ios/include/QtQml,
../../Qt/5.15.2/ios/include/QtNetwork,
../../Qt/5.15.2/ios/include/QtCore,
.,
"../../Qt/5.15.2/ios/mkspecs/macx-ios-clang",
../../Qt/5.15.2/ios/include/QtMultimedia,
);
INFOPLIST_FILE = "watchkit Extension/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 2.13;
MARKETING_VERSION = 2.10;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";

View File

@@ -54,10 +54,8 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.Carousel"
RemotePath = "/qdomyoszwift">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "876E4E102594747F00BD5714"
@@ -65,7 +63,7 @@
BlueprintName = "watchkit"
ReferencedContainer = "container:qdomyoszwift.xcodeproj">
</BuildableReference>
</RemoteRunnable>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -73,10 +71,8 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.Carousel"
RemotePath = "/qdomyoszwift">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "876E4E102594747F00BD5714"
@@ -84,16 +80,7 @@
BlueprintName = "watchkit"
ReferencedContainer = "container:qdomyoszwift.xcodeproj">
</BuildableReference>
</RemoteRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "876E4E102594747F00BD5714"
BuildableName = "watchkit.app"
BlueprintName = "watchkit"
ReferencedContainer = "container:qdomyoszwift.xcodeproj">
</BuildableReference>
</MacroExpansion>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View File

@@ -17,33 +17,16 @@ class MainController: WKInterfaceController {
@IBOutlet weak var distanceLabel: WKInterfaceLabel!
@IBOutlet weak var heartRateLabel: WKInterfaceLabel!
@IBOutlet weak var startButton: WKInterfaceButton!
@IBOutlet weak var cmbSports: WKInterfacePicker!
static var start: Bool! = false
let pedometer = CMPedometer()
var sport: Int = 0
override func awake(withContext context: Any?) {
super.awake(withContext: context)
let sports: [WKPickerItem] = [WKPickerItem(),WKPickerItem(),WKPickerItem(),WKPickerItem(),WKPickerItem()]
sports[0].title = "Bike"
sports[1].title = "Run"
sports[2].title = "Walk"
sports[3].title = "Elliptical"
sports[4].title = "Rowing"
cmbSports.setItems(sports)
sport = UserDefaults.standard.value(forKey: "sport") as? Int ?? 0
cmbSports.setSelectedItemIndex(sport)
// Configure interface objects here.
print("AWAKE")
}
@IBAction func changeSport(_ value: Int) {
self.sport = value
UserDefaults.standard.set(value, forKey: "sport")
UserDefaults.standard.synchronize()
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
@@ -74,7 +57,6 @@ extension MainController {
MainController.start = true
startButton.setTitle("Stop")
WorkoutTracking.authorizeHealthKit()
WorkoutTracking.shared.setSport(sport)
WorkoutTracking.shared.startWorkOut()
WorkoutTracking.shared.delegate = self
@@ -104,12 +86,8 @@ extension MainController: WorkoutTrackingDelegate {
"\(heartRate)" as AnyObject])
WorkoutTracking.distance = WatchKitConnection.distance
WorkoutTracking.kcal = WatchKitConnection.kcal
if Locale.current.measurementSystem != "Metric" {
self.distanceLabel.setText("Distance \(String(format:"%.2f", WorkoutTracking.distance))")
} else {
self.distanceLabel.setText("Distance \(String(format:"%.2f", WorkoutTracking.distance * 1.60934))")
}
self.distanceLabel.setText("Distance \(Double(WorkoutTracking.distance))")
self.caloriesLabel.setText("KCal \(Int(WorkoutTracking.kcal))")
//WorkoutTracking.cadenceSteps = pedometer.
}
@@ -127,11 +105,3 @@ extension MainController: WatchKitConnectionDelegate {
userNameLabel.setText(userName)
}
}
extension Locale
{
var measurementSystem : String?
{
return (self as NSLocale).object(forKey: NSLocale.Key.measurementSystem) as? String
}
}

View File

@@ -31,7 +31,6 @@ class WorkoutTracking: NSObject {
public static var cadenceTimeStamp = NSDate().timeIntervalSince1970
public static var cadenceLastSteps = Double()
public static var cadenceSteps = 0
var sport: Int = 0
let healthStore = HKHealthStore()
let configuration = HKWorkoutConfiguration()
var workoutSession: HKWorkoutSession!
@@ -102,23 +101,8 @@ extension WorkoutTracking {
}
}
func setSport(_ sport: Int) {
self.sport = sport
}
private func configWorkout() {
var activityType = HKWorkoutActivityType.cycling
if self.sport == 1 {
activityType = HKWorkoutActivityType.running
} else if self.sport == 2 {
activityType = HKWorkoutActivityType.walking
} else if self.sport == 3 {
activityType = HKWorkoutActivityType.elliptical
} else if self.sport == 4 {
activityType = HKWorkoutActivityType.rowing
}
configuration.activityType = activityType
configuration.activityType = .cycling
configuration.locationType = .indoor
do {
@@ -150,7 +134,6 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
HKSampleType.quantityType(forIdentifier: .stepCount)!,
HKSampleType.quantityType(forIdentifier: .heartRate)!,
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
HKSampleType.quantityType(forIdentifier: .distanceWalkingRunning)!,
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
HKSampleType.workoutType()
])
@@ -176,10 +159,6 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
if let error = error {
print(error)
}
if self.sport > 0 {
self.workoutBuilder.dataSource?.enableCollection(for: HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning)!, predicate: nil)
}
}
}
@@ -204,72 +183,29 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
end: Date())
workoutBuilder.add([sample]) {(success, error) in}
guard let quantityTypeDistance = HKQuantityType.quantityType(
forIdentifier: .distanceCycling) else {
return
}
let unitDistance = HKUnit.mile()
let miles = WorkoutTracking.distance
let quantityMiles = HKQuantity(unit: unitDistance,
doubleValue: miles)
if(sport == 0) {
guard let quantityTypeDistance = HKQuantityType.quantityType(
forIdentifier: .distanceCycling) else {
return
}
let sampleDistance = HKCumulativeQuantitySeriesSample(type: quantityTypeDistance,
quantity: quantityMiles,
start: workoutSession.startDate!,
end: Date())
workoutBuilder.add([sampleDistance]) {(success, error) in
if let error = error {
print(error)
}
self.workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
if let error = error {
print(error)
}
self.workoutBuilder.finishWorkout{ (workout, error) in
if let error = error {
print(error)
}
workout?.setValue(quantityMiles, forKey: "totalDistance")
}
}
}
} else {
guard let quantityTypeDistance = HKQuantityType.quantityType(
forIdentifier: .distanceWalkingRunning) else {
return
}
let sampleDistance = HKCumulativeQuantitySeriesSample(type: quantityTypeDistance,
quantity: quantityMiles,
start: workoutSession.startDate!,
end: Date())
workoutBuilder.add([sampleDistance]) {(success, error) in
if let error = error {
print(error)
}
self.workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
if let error = error {
print(error)
}
self.workoutBuilder.finishWorkout{ (workout, error) in
if let error = error {
print(error)
}
workout?.setValue(quantityMiles, forKey: "totalDistance")
}
}
}
}
let sampleDistance = HKCumulativeQuantitySeriesSample(type: quantityTypeDistance,
quantity: quantityMiles,
start: workoutSession.startDate!,
end: Date())
workoutBuilder.add([sample]) {(success, error) in}
workoutBuilder.add([sampleDistance]) {(success, error) in}
workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
}
workoutBuilder.finishWorkout{ (success, error) in }
}
func fetchStepCounts() {

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder.WatchKit.Storyboard" version="3.0" toolsVersion="20037" targetRuntime="watchKit" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Tpn-rd-UUX">
<document type="com.apple.InterfaceBuilder.WatchKit.Storyboard" version="3.0" toolsVersion="19529" targetRuntime="watchKit" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Tpn-rd-UUX">
<device id="watch38"/>
<dependencies>
<deployment identifier="watchOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="20006"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="19514"/>
</dependencies>
<scenes>
<!--Main-->
@@ -22,15 +22,9 @@
<label width="136" alignment="left" text="Step Counts" id="HpA-e9-6YV"/>
<label width="136" alignment="left" text="Calories" id="Szi-Jp-J3S"/>
<label width="136" alignment="left" text="Distance" id="eRf-NJ-6If"/>
<picker height="100" alignment="left" id="OTR-HF-vYb">
<connections>
<action selector="changeSport:" destination="Tpn-rd-UUX" id="3vY-lq-IhZ"/>
</connections>
</picker>
</items>
<connections>
<outlet property="caloriesLabel" destination="Szi-Jp-J3S" id="trd-YS-bJy"/>
<outlet property="cmbSports" destination="OTR-HF-vYb" id="Ws5-w9-ZT8"/>
<outlet property="distanceLabel" destination="eRf-NJ-6If" id="ZE2-OB-jqN"/>
<outlet property="heartRateLabel" destination="Nda-m1-XRw" id="1la-8R-3jG"/>
<outlet property="startButton" destination="vZg-X8-uY5" id="pJc-09-kfV"/>

View File

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

View File

@@ -4,20 +4,17 @@ QDomyos-Zwift can be installed from source on MacOs, Linux, Android and IOS.
Once you've installed QDomyos-Zwift, you can access the [operation guide](30_usage.md) for more information.
These instructions build the app itself, not the test project.
## On a Linux System (from source)
```buildoutcfg
$ sudo apt update && sudo apt upgrade # this is very important on raspberry pi: you need the bluetooth firmware updated!
$ sudo apt install git qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qml-module* libqt5texttospeech5-dev libqt5texttospeech5 libqt5location5-plugins qtlocation5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 g++ make
$ sudo sudo apt install git qt5quickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qml-module*
$ 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
```
@@ -28,13 +25,13 @@ $ sudo ./qdomyos-zwift
You will need to (at a minimum) to install the xcode Command Line Tools (CLI) thanks to @richardwait
https://developer.apple.com/download/more/?=xcode
Download and install http://download.qt.io/official_releases/qt/5.12/5.12.9/qt-opensource-mac-x64-5.12.9.dmg and simply run the qdomyos-zwift release for MacOs
Download and install http://download.qt.io/official_releases/qt/5.12/5.12.9/qt-opensource-mac-x64-5.12.9.dmg and simply run the qdomyos-zwift relase for MacOs
## On Raspberry Pi Zero W
![raspi](../docs/img/raspi-bike.jpg)
This guide will walk you through steps to setup an autonomous, headless raspberry bridge.
This guide will walk you through steps to setup an autonomous, headless raspberry brigde.
### Initial System Preparation
@@ -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`
`git clone https://github.com/cagnulein/qdomyos-zwift.git`
`cd qdomyos-zwift`
`git submodule update --init src/smtpclient/`
`git submodule update --init src/qmdnsengine/`
`cd src`
`qmake`
`make`
Please note :
- Don't build the application with `-j4` option (this will fail)

View File

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

View File

@@ -26,5 +26,5 @@ You can have ae true 4k video stream while you ride ("extreme quality" setting)
The application do not read the FTMS value. It is required to start the application with `-heart-service` or `bike_heartrate_service: true` in settings.
### Resistance management
You can adjust resistance using arrows [up and down](img/21_zwift-resistance-buttons.jpg) rom the riding screen.
You can adjust resistence using arrows [up and down](img/21_zwift-resistance-buttons.jpg) rom the riding screen.

View File

@@ -24,9 +24,8 @@ This is the list of settings available in the application. These settings needs
| **Option** | **Type** | **Default** | **Function** |
|:------------------------------|:---------|:------------|:-----------------------------------------------------------------------------|
| -no-gui | Boolean | False | Disable GUI |
| -qml | Boolean | True | Enables the QML interface |
| -noqml | Boolean | False | Enables the NativeQT interface |
| -miles | Boolean | False | Switches to Imperial Units System |
| -qml | Boolean | False | Enables the QML interface |
| -miles | Boolean | False | Swithes to Imperial Units System |
| -no-console | Boolean | False | Not in use |
| -test-resistance | Boolean | False | |
| -no-log | Boolean | False | Disable Logging |
@@ -43,10 +42,10 @@ This is the list of settings available in the application. These settings needs
| -service-changed | Boolean | False | |
| -bike-wheel-revs | Boolean | False | |
| -run-cadence-sensor | Boolean | False | |
| -nordictrack-10-treadmill | Boolean | False | Enable NordicTrack compatibility mode |
| -nordictrack-10-treadmill | Boolean | False | Enable NordicTrack compatibility mode |
| -train | String | | Force training program |
| -name | String | | Force bluetooth device name (if QZ struggles finding your fitness equipment) |
| -poll-device-time | Int | 200 (ms) | Frequency to refresh information from QZ to Fitness equipment |
| -poll-device-time | Int | 200 (ms) | Frequency to refresh informations from QZ to Fitness equipment |
| -bike-resistance-gain | Int | | Adjust resistance from the fitness application |
| -bike-resistance-offset | Int | | Set another resistance point than default |

View File

@@ -1,272 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--Copyright 2017 Bluetooth SIG, Inc. All rights reserved.-->
<Characteristic xsi:noNamespaceSchemaLocation="http://schemas.bluetooth.org/Documents/characteristic.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
name="Cross Trainer Data"
type="org.bluetooth.characteristic.cross_trainer_data" uuid="2ACE"
last-modified="2017-02-14" approved="Yes">
<InformativeText>
<Summary>The Cross Trainer Data characteristic is used to send
training-related data to the Client from a cross trainer
(Server).</Summary>
</InformativeText>
<Value>
<Field name="Flags">
<Requirement>Mandatory</Requirement>
<Format>24bit</Format>
<BitField>
<Bit index="0" size="1" name="More Data">
<Enumerations>
<Enumeration key="0" value="False" requires="C1" />
<Enumeration key="1" value="True" />
</Enumerations>
</Bit>
<Bit index="1" size="1" name="Average Speed present">
<Enumerations>
<Enumeration key="0" value="False" />
<Enumeration key="1" value="True" requires="C2" />
</Enumerations>
</Bit>
<Bit index="2" size="1" name="Total Distance Present">
<Enumerations>
<Enumeration key="0" value="False" />
<Enumeration key="1" value="True" requires="C3" />
</Enumerations>
</Bit>
<Bit index="3" size="1" name="Step Count present">
<Enumerations>
<Enumeration key="0" value="False" />
<Enumeration key="1" value="True" requires="C4" />
</Enumerations>
</Bit>
<Bit index="4" size="1" name="Stride Count present">
<Enumerations>
<Enumeration key="0" value="False" />
<Enumeration key="1" value="True" requires="C5" />
</Enumerations>
</Bit>
<Bit index="5" size="1" name="Elevation Gain present">
<Enumerations>
<Enumeration key="0" value="False" />
<Enumeration key="1" value="True" requires="C6" />
</Enumerations>
</Bit>
<Bit index="6" size="1"
name="Inclination and Ramp Angle Setting present">
<Enumerations>
<Enumeration key="0" value="False" />
<Enumeration key="1" value="True" requires="C7" />
</Enumerations>
</Bit>
<Bit index="7" size="1" name="Resistance Level Present">
<Enumerations>
<Enumeration key="0" value="False" />
<Enumeration key="1" value="True" requires="C8" />
</Enumerations>
</Bit>
<Bit index="8" size="1" name="Instantaneous Power present">
<Enumerations>
<Enumeration key="0" value="False" />
<Enumeration key="1" value="True" requires="C9" />
</Enumerations>
</Bit>
<Bit index="9" size="1" name="Average Power present">
<Enumerations>
<Enumeration key="0" value="False" />
<Enumeration key="1" value="True" requires="C10" />
</Enumerations>
</Bit>
<Bit index="10" size="1" name="Expended Energy present">
<Enumerations>
<Enumeration key="0" value="False" />
<Enumeration key="1" value="True" requires="C11" />
</Enumerations>
</Bit>
<Bit index="11" size="1" name="Heart Rate present">
<Enumerations>
<Enumeration key="0" value="False" />
<Enumeration key="1" value="True" requires="C12" />
</Enumerations>
</Bit>
<Bit index="12" size="1"
name="Metabolic Equivalent present">
<Enumerations>
<Enumeration key="0" value="False" />
<Enumeration key="1" value="True" requires="C13" />
</Enumerations>
</Bit>
<Bit index="13" size="1" name="Elapsed Time present">
<Enumerations>
<Enumeration key="0" value="False" />
<Enumeration key="1" value="True" requires="C14" />
</Enumerations>
</Bit>
<Bit index="14" size="1" name="Remaining Time present">
<Enumerations>
<Enumeration key="0" value="False" />
<Enumeration key="1" value="True" requires="C15" />
</Enumerations>
</Bit>
<Bit index="15" size="1" name="Movement Direction">
<Enumerations>
<Enumeration key="0" value="Forward" />
<Enumeration key="1" value="Backward" />
</Enumerations>
</Bit>
<ReservedForFutureUse index="16" size="8" />
</BitField>
</Field>
<Field name="Instantaneous Speed">
<InformativeText>Kilometer per hour with a resolution of
0.01</InformativeText>
<Requirement>C1</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.velocity.kilometre_per_hour</Unit>
<DecimalExponent>-2</DecimalExponent>
</Field>
<Field name="Average Speed">
<InformativeText>Kilometer per hour with a resolution of
0.01</InformativeText>
<Requirement>C2</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.velocity.kilometre_per_hour</Unit>
<DecimalExponent>-2</DecimalExponent>
</Field>
<Field name="Total Distance">
<InformativeText>Meters with a resolution of
1</InformativeText>
<Requirement>C3</Requirement>
<Format>uint24</Format>
<Unit>org.bluetooth.unit.length.metre</Unit>
</Field>
<Field name="Step Per Minute">
<InformativeText>Step/minute with a resolution of
1</InformativeText>
<Requirement>C4</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.step_per_minute</Unit>
</Field>
<Field name="Average Step Rate">
<InformativeText>Step/minute with a resolution of
1</InformativeText>
<Requirement>C4</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.step_per_minute</Unit>
</Field>
<Field name="Stride Count">
<InformativeText>Unitless with a resolution of
0.1</InformativeText>
<Requirement>C5</Requirement>
<Format>uint16</Format>
<DecimalExponent>-1</DecimalExponent>
<Unit>org.bluetooth.unit.unitless</Unit>
</Field>
<Field name="Positive Elevation Gain">
<InformativeText>Meters with a resolution of
1</InformativeText>
<Requirement>C6</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.length.metre</Unit>
</Field>
<Field name="Negative Elevation Gain">
<InformativeText>Meters with a resolution of
1</InformativeText>
<Requirement>C6</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.length.metre</Unit>
</Field>
<Field name="Inclination">
<InformativeText>Percent with a resolution of
0.1</InformativeText>
<Requirement>C7</Requirement>
<Format>sint16</Format>
<Unit>org.bluetooth.unit.percentage</Unit>
<DecimalExponent>-1</DecimalExponent>
</Field>
<Field name="Ramp Angle Setting">
<InformativeText>Degree with a resolution of
0.1</InformativeText>
<Requirement>C7</Requirement>
<Format>sint16</Format>
<Unit>org.bluetooth.unit.plane_angle.degree</Unit>
<DecimalExponent>-1</DecimalExponent>
</Field>
<Field name="Resistance Level">
<InformativeText>Unitless with a resolution of
0.1</InformativeText>
<Requirement>C8</Requirement>
<Format>sint16</Format>
<DecimalExponent>-1</DecimalExponent>
<Unit>org.bluetooth.unit.unitless</Unit>
</Field>
<Field name="Instantaneous Power">
<InformativeText>Watts with a resolution of
1</InformativeText>
<Requirement>C9</Requirement>
<Format>sint16</Format>
<Unit>org.bluetooth.unit.power.watt</Unit>
</Field>
<Field name="Average Power">
<InformativeText>Watts with a resolution of
1</InformativeText>
<Requirement>C10</Requirement>
<Format>sint16</Format>
<Unit>org.bluetooth.unit.power.watt</Unit>
</Field>
<Field name="Total Energy">
<InformativeText>Kilo Calorie with a resolution of
1</InformativeText>
<Requirement>C11</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.energy.kilogram_calorie</Unit>
</Field>
<Field name="Energy Per Hour">
<InformativeText>Kilo Calorie with a resolution of
1</InformativeText>
<Requirement>C11</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.energy.kilogram_calorie</Unit>
</Field>
<Field name="Energy Per Minute">
<InformativeText>Kilo Calorie with a resolution of
1</InformativeText>
<Requirement>C11</Requirement>
<Format>uint8</Format>
<Unit>org.bluetooth.unit.energy.kilogram_calorie</Unit>
</Field>
<Field name="Heart Rate">
<InformativeText>Beats per minute with a resolution of
1</InformativeText>
<Requirement>C12</Requirement>
<Format>uint8</Format>
<Unit>org.bluetooth.unit.period.beats_per_minute</Unit>
</Field>
<Field name="Metabolic Equivalent">
<InformativeText>Metabolic Equivalent with a resolution of
0.1</InformativeText>
<Requirement>C13</Requirement>
<Format>uint8</Format>
<DecimalExponent>-1</DecimalExponent>
<Unit>org.bluetooth.unit.metabolic_equivalent</Unit>
</Field>
<Field name="Elapsed Time">
<InformativeText>Second with a resolution of
1</InformativeText>
<Requirement>C14</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.time.second</Unit>
</Field>
<Field name="Remaining Time">
<InformativeText>Second with a resolution of
1</InformativeText>
<Requirement>C15</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.time.second</Unit>
</Field>
</Value>
<Note>The fields in the above table, reading from top to bottom,
are shown in the order of LSO to MSO, where LSO = Least
Significant Octet and MSO = Most Significant Octet. The Least
Significant Octet represents the eight bits numbered 0 to
7.</Note>
</Characteristic>

View File

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

View File

@@ -1018,7 +1018,7 @@ public class QtBluetoothLE {
/*
* Already executed in GattCallback so executed by the HandlerThread. No need to
* post it to the Handler.
* post it to the Hander.
*/
private void scheduleMtuExchange() {
ReadWriteJob newJob = new ReadWriteJob();

View File

@@ -385,11 +385,11 @@ QT_USE_NAMESPACE
}
[uuids addObject:nsUuid];
// With the latest CoreBluetooth, we can synchronously retrieve peripherals:
// With the latest CoreBluetooth, we can synchronously retrive peripherals:
QT_BT_MAC_AUTORELEASEPOOL;
NSArray *const peripherals = [manager retrievePeripheralsWithIdentifiers:uuids];
if (!peripherals || peripherals.count != 1) {
qCWarning(QT_BT_OSX) << "failed to retrieve a peripheral";
qCWarning(QT_BT_OSX) << "failed to retrive a peripheral";
if (notifier)
emit notifier->CBManagerError(QLowEnergyController::UnknownRemoteDeviceError);
return;
@@ -648,7 +648,7 @@ QT_USE_NAMESPACE
servicesToDiscoverDetails.removeAll(serviceUuid);
const NSUInteger nHandles = qt_countGATTEntries(service);
Q_ASSERT_X(nHandles, Q_FUNC_INFO, "unexpected number of GATT entries");
Q_ASSERT_X(nHandles, Q_FUNC_INFO, "unexpected number of GATT entires");
const QLowEnergyHandle maxHandle = std::numeric_limits<QLowEnergyHandle>::max();
if (nHandles >= maxHandle || lastValidHandle > maxHandle - nHandles) {
@@ -1058,7 +1058,7 @@ QT_USE_NAMESPACE
// TODO: test that we NEVER have the same characteristic twice in array!
// At the moment I just protect against this by iterating in a reverse
// order (at least avoiding a potential infinite loop with '-indexOfObject:').
// order (at least avoiding a potential inifite loop with '-indexOfObject:').
NSArray *const cs = service.characteristics;
if (cs.count == 1)
return nil;
@@ -1091,7 +1091,7 @@ QT_USE_NAMESPACE
// TODO: test that we NEVER have the same characteristic twice in array!
// At the moment I just protect against this by iterating in a reverse
// order (at least avoiding a potential infinite loop with '-indexOfObject:').
// order (at least avoiding a potential inifite loop with '-indexOfObject:').
NSArray *const cs = service.characteristics;
if (cs.count == 1)
return nil;
@@ -1597,8 +1597,8 @@ QT_USE_NAMESPACE
} else {
// This is (probably) the result of update notification.
// It's very possible we can have an invalid handle here (0) -
// if something else is wrong (we subscribed for a notification),
// disconnected (but other application is connected) and still receiving
// if something esle is wrong (we subscribed for a notification),
// disconnected (but other application is connected) and still receiveing
// updated values ...
// TODO: this must be properly tested.
if (!chHandle) {

View File

@@ -19,19 +19,11 @@ ColumnLayout {
url: "http://localhost:" + settings.value("template_inner_QZWS_port") + "/chartjs/chart.htm"
visible: true
onLoadingChanged: {
if (loadRequest.errorString) {
if (loadRequest.errorString)
console.error(loadRequest.errorString);
console.error("port " + settings.value("template_inner_QZWS_port"));
}
}
}
Timer {
id: chartJscheckStartFromWeb
interval: 200; running: true; repeat: true
onTriggered: {if(rootItem.startRequested) {rootItem.startRequested = false; rootItem.stopRequested = false; stackView.pop(); }}
}
Button {
id: closeButton
height: 50

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -1,267 +0,0 @@
import QtQuick 2.7
import Qt.labs.folderlistmodel 2.15
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.15
import QtQuick.Controls.Material 2.0
import QtQuick.Dialogs 1.0
import QtCharts 2.2
import Qt.labs.settings 1.0
import QtPositioning 5.5
import QtLocation 5.6
ColumnLayout {
signal trainprogram_open_clicked(url name)
signal trainprogram_preview(url name)
FileDialog {
id: fileDialogTrainProgram
title: "Please choose a file"
folder: shortcuts.home
onAccepted: {
console.log("You chose: " + fileDialogTrainProgram.fileUrl)
trainprogram_open_clicked(fileDialogTrainProgram.fileUrl)
fileDialogTrainProgram.close()
}
onRejected: {
console.log("Canceled")
fileDialogTrainProgram.close()
}
}
RowLayout{
spacing: 2
anchors.top: parent.top
anchors.fill: parent
ColumnLayout {
spacing: 0
anchors.top: parent.top
anchors.fill: parent
Row
{
spacing: 5
Text
{
text:"Filter"
color: "white"
verticalAlignment: Text.AlignVCenter
}
TextField
{
function updateFilter()
{
var text = filterField.text
var filter = "*"
for(var i = 0; i<text.length; i++)
filter+= "[%1%2]".arg(text[i].toUpperCase()).arg(text[i].toLowerCase())
filter+="*"
print(filter)
folderModel.nameFilters = [filter + ".gpx"]
}
id: filterField
onTextChanged: updateFilter()
}
Button {
anchors.left: mainRect.right
anchors.leftMargin: 5
text: "←"
onClicked: folderModel.folder = folderModel.parentFolder
}
}
ListView {
Layout.fillWidth: true
Layout.minimumWidth: 50
Layout.preferredWidth: 100
Layout.maximumWidth: row.left
Layout.minimumHeight: 150
Layout.preferredHeight: parent.height
ScrollBar.vertical: ScrollBar {}
id: list
FolderListModel {
id: folderModel
nameFilters: ["*.gpx"]
folder: "file://" + rootItem.getWritableAppDir() + 'gpx'
showDotAndDotDot: false
showDirs: true
sortField: "Name"
showDirsFirst: true
}
model: folderModel
delegate: Component {
Rectangle {
property alias textColor: fileTextBox.color
width: parent.width
height: 40
color: Material.backgroundColor
z: 1
Item {
id: root
property alias text: fileTextBox.text
property int spacing: 30
width: fileTextBox.width + spacing
height: fileTextBox.height
clip: true
Text {
id: fileTextBox
color: (!folderModel.isFolder(index)?Material.color(Material.Grey):Material.color(Material.Orange))
font.pixelSize: Qt.application.font.pixelSize * 1.6
text: fileName.substring(0, fileName.length-4)
NumberAnimation on x {
Component.onCompleted: {
if(fileName.length > 30) {
running: true;
} else {
stop();
}
}
from: 0; to: -root.width; duration: 20000; loops: Animation.Infinite
}
Text {
x: root.width
text: fileTextBox.text
color: Material.color(Material.Grey)
font.pixelSize: Qt.application.font.pixelSize * 1.6
}
}
}
MouseArea {
anchors.fill: parent
z: 100
onClicked: {
console.log('onclicked ' + index+ " count "+list.count);
if (index == list.currentIndex) {
let fileUrl = folderModel.get(list.currentIndex, 'fileUrl') || folderModel.get(list.currentIndex, 'fileURL');
if (fileUrl && !folderModel.isFolder(list.currentIndex)) {
trainprogram_open_clicked(fileUrl);
popup.open()
} else {
folderModel.folder = fileURL
}
}
else {
if (list.currentItem)
list.currentItem.textColor = Material.color(Material.Grey)
list.currentIndex = index
}
}
}
}
}
highlight: Rectangle {
color: Material.color(Material.Green)
z:3
radius: 5
opacity: 0.4
focus: true
}
focus: true
onCurrentItemChanged: {
let fileUrl = folderModel.get(list.currentIndex, 'fileUrl') || folderModel.get(list.currentIndex, 'fileURL');
if (fileUrl) {
list.currentItem.textColor = Material.color(Material.Yellow)
console.log(fileUrl + ' selected');
trainprogram_preview(fileUrl)
}
}
Component.onCompleted: {
}
}
}
ScrollView {
anchors.top: parent.top
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
//contentHeight: map.height
Layout.preferredHeight: parent.height
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumWidth: 100
Layout.preferredWidth: 200
Row {
id: row
anchors.fill: parent
Text {
id: distance
width: parent.width
text: rootItem.previewWorkoutDescription
font.pixelSize: 16
color: "white"
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.horizontalCenter: parent.horizontalCenter
}
Plugin {
id: osmMapPlugin
name: "osm"
PluginParameter { name: "osm.useragent"; value: "QZ Fitness" }
}
Map {
height: parent.height - distance.height
width: parent.width
id: map
anchors.top: distance.bottom
plugin: osmMapPlugin
zoomLevel: 14
center: pathController.center
visible: true
MapPolyline {
id: pl
line.width: 3
line.color: 'red'
}
Component.onCompleted: {
console.log("Dimensions: ", width, height)
}
}
function loadPath(){
var lines = []
var elevationGain = 0
var offsetElevation = 0
for(var i = 0; i < pathController.geopath.size(); i++){
if(i > 0 && pathController.geopath.coordinateAt(i).altitude > pathController.geopath.coordinateAt(i-1).altitude)
elevationGain = elevationGain + (pathController.geopath.coordinateAt(i).altitude - pathController.geopath.coordinateAt(i-1).altitude)
lines[i] = pathController.geopath.coordinateAt(i)
}
distance.text = "Distance " + (pathController.geopath.length() / 1000.0).toFixed(1) + " km Elevation Gain: " + elevationGain.toFixed(1) + " meters"
return lines;
}
Connections{
target: pathController
onGeopathChanged: {
pl.path = row.loadPath();
}
onCenterChanged: {
map.center = pathController.center;
}
}
Component.onCompleted: pl.path = loadPath()
}
}
}
Button {
id: searchButton
height: 50
width: parent.width
text: "Other folders"
Layout.alignment: Qt.AlignCenter | Qt.AlignVCenter
onClicked: {
console.log("folder is " + rootItem.getWritableAppDir() + 'gpx')
fileDialogTrainProgram.visible = true
}
anchors {
bottom: parent.bottom
}
}
}

View File

@@ -12,12 +12,11 @@ ColumnLayout {
anchors.fill: parent
Settings {
id: settings
property string maps_type: "3D"
}
WebView {
id: webView
anchors.fill: parent
url: "http://localhost:" + settings.value("template_inner_QZWS_port") + "/" + (settings.value("maps_type") === "3D" ? "googlemaps" : "maps2d") + "/maps.htm"
url: "http://localhost:" + settings.value("template_inner_QZWS_port") + "/googlemaps/maps.htm"
visible: true
onLoadingChanged: {
if (loadRequest.errorString)

View File

@@ -5,7 +5,6 @@ import QtGraphicalEffects 1.12
import QtQuick.Window 2.12
import Qt.labs.settings 1.0
import Qt.labs.platform 1.1
import QtMultimedia 5.15
HomeForm{
objectName: "home"
@@ -16,7 +15,6 @@ HomeForm{
signal peloton_abort_workout;
signal plus_clicked(string name)
signal minus_clicked(string name)
signal largeButton_clicked(string name)
Settings {
id: settings
@@ -68,13 +66,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,254 +75,177 @@ 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"); }
GridView {
anchors.horizontalCenter: parent.horizontalCenter
anchors.fill: parent
cellWidth: 175 * settings.ui_zoom / 100
cellHeight: 130 * settings.ui_zoom / 100
focus: true
model: appModel
leftMargin: { if(OS_VERSION === "Android") (Screen.width % cellWidth) / 2; else (parent.width % cellWidth) / 2; }
anchors.topMargin: (!window.lockTiles ? rootItem.topBarHeight + 30 : 0)
id: gridView
objectName: "gridview"
onMovementEnded: { headerToolbar.visible = (contentY == 0) || window.lockTiles; }
Screen.orientationUpdateMask: Qt.LandscapeOrientation | Qt.PortraitOrientation
Screen.onPrimaryOrientationChanged:{
if(OS_VERSION === "Android")
gridView.leftMargin = (Screen.width % cellWidth) / 2;
else
gridView.leftMargin = (parent.width % cellWidth) / 2;
}
// highlight: Rectangle {
// width: 150
// height: 150
// color: "lightsteelblue"
// }
delegate: Item {
id: id1
width: 170 * settings.ui_zoom / 100
height: 125 * settings.ui_zoom / 100
visible: visibleItem
Component.onCompleted: console.log("completed " + objectName)
Behavior on x {
enabled: id1.state != "active"
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
}
Behavior on y {
enabled: id1.state != "active"
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
}
SequentialAnimation on rotation {
NumberAnimation { to: 2; duration: 60 }
NumberAnimation { to: -2; duration: 120 }
NumberAnimation { to: 0; duration: 60 }
running: loc.currentId !== -1 && id1.state !== "active" && window.lockTiles
loops: Animation.Infinite; alwaysRunToEnd: true
}
states: State {
name: "active"; when: loc.currentId === gridId && window.lockTiles
PropertyChanges { target: id1; x: loc.mouseX - gridView.x - width/2; y: loc.mouseY - gridView.y - height/2; scale: 0.5; z: 10 }
}
transitions: Transition { NumberAnimation { property: "scale"; duration: 200} }
Rectangle {
width: 168 * settings.ui_zoom / 100
height: 123 * settings.ui_zoom / 100
radius: 3
border.width: 1
border.color: "purple"
color: Material.backgroundColor
id: rect
}
DropShadow {
anchors.fill: rect
cached: true
horizontalOffset: 3
verticalOffset: 3
radius: 8.0
samples: 16
color: Material.color(Material.Purple)
source: rect
}
Timer {
id: toggleIconTimer
interval: 500; running: true; repeat: true
onTriggered: { if(identificator === "inclination" && rootItem.autoInclinationEnabled()) myIcon.visible = !myIcon.visible; else myIcon.visible = true; }
}
Image {
id: myIcon
x: 5
anchors {
bottom: id1.bottom
}
width: 48 * settings.ui_zoom / 100
height: 48 * settings.ui_zoom / 100
source: icon
visible: !largeButton
}
Text {
objectName: "value"
id: myValue
color: valueFontColor
y: 0
anchors {
horizontalCenter: parent.horizontalCenter
}
text: value
horizontalAlignment: Text.AlignHCenter
font.pointSize: valueFontSize * settings.ui_zoom / 100
font.bold: true
visible: !largeButton
}
Text {
objectName: "secondLine"
id: secondLineText
color: "white"
y: myValue.bottom
anchors {
top: myValue.bottom
horizontalCenter: parent.horizontalCenter
}
text: secondLine
horizontalAlignment: Text.AlignHCenter
font.pointSize: 12 * settings.ui_zoom / 100
font.bold: false
visible: !largeButton
}
Text {
id: myText
anchors {
top: myIcon.top
}
font.bold: true
font.pointSize: labelFontSize
color: "white"
text: name
anchors.left: parent.left
anchors.leftMargin: 55 * settings.ui_zoom / 100
anchors.topMargin: 20 * settings.ui_zoom / 100
visible: !largeButton
}
RoundButton {
objectName: minusName
autoRepeat: true
text: "-"
onClicked: minus_clicked(objectName)
visible: writable && !largeButton
anchors.top: myValue.top
anchors.left: parent.left
anchors.leftMargin: 2
width: 48 * settings.ui_zoom / 100
height: 48 * settings.ui_zoom / 100
}
RoundButton {
autoRepeat: true
objectName: plusName
text: "+"
onClicked: plus_clicked(objectName)
visible: writable && !largeButton
anchors.top: myValue.top
anchors.right: parent.right
anchors.rightMargin: 2
width: 48 * settings.ui_zoom / 100
height: 48 * settings.ui_zoom / 100
}
RoundButton {
autoRepeat: true
objectName: identificator
text: largeButtonLabel
onClicked: largeButton_clicked(objectName)
visible: largeButton
anchors.fill: rect
background: Rectangle {
color: largeButtonColor
radius: 20
}
font.pointSize: 20 * settings.ui_zoom / 100
//width: 48 * settings.ui_zoom / 100
//height: 48 * settings.ui_zoom / 100
}
/*MouseArea {
anchors.fill: parent
onClicked: parent.GridView.view.currentIndex = index
}*/
}
GridView {
anchors.horizontalCenter: parent.horizontalCenter
anchors.fill: parent
cellWidth: 175 * settings.ui_zoom / 100
cellHeight: 130 * settings.ui_zoom / 100
focus: true
model: appModel
leftMargin: { if(OS_VERSION === "Android") (Screen.width % cellWidth) / 2; else (parent.width % cellWidth) / 2; }
anchors.topMargin: (!window.lockTiles ? rootItem.topBarHeight + 30 : 0)
id: gridView
objectName: "gridview"
onMovementEnded: { headerToolbar.visible = (contentY == 0) || window.lockTiles; }
Screen.orientationUpdateMask: Qt.LandscapeOrientation | Qt.PortraitOrientation
Screen.onPrimaryOrientationChanged:{
if(OS_VERSION === "Android")
gridView.leftMargin = (Screen.width % cellWidth) / 2;
else
gridView.leftMargin = (parent.width % cellWidth) / 2;
}
footer:
Rectangle {
objectName: "footerrectangle"
visible: rootItem.videoVisible
anchors.top: gridView.bottom
width: parent.width
height: parent.height / 2
// highlight: Rectangle {
// width: 150
// height: 150
// color: "lightsteelblue"
// }
delegate: Item {
id: id1
width: 170 * settings.ui_zoom / 100
height: 125 * settings.ui_zoom / 100
Timer {
id: pauseTimer
interval: 1000; running: true; repeat: true
onTriggered: { if(visible == true) { (rootItem.currentSpeed > 0 ?
videoPlaybackHalf.play() :
videoPlaybackHalf.pause()) } }
}
visible: visibleItem
Component.onCompleted: console.log("completed " + objectName)
onVisibleChanged: {
if(visible === true) {
console.log("mediaPlayer onCompleted: " + rootItem.videoPath)
console.log("videoRate: " + rootItem.videoRate)
videoPlaybackHalf.source = rootItem.videoPath
//videoPlaybackHalf.playbackRate = rootItem.videoRate
videoPlaybackHalf.seek(rootItem.videoPosition)
videoPlaybackHalf.play()
videoPlaybackHalf.muted = true
} else {
videoPlaybackHalf.stop()
}
}
MediaPlayer {
id: videoPlaybackHalf
objectName: "videoplaybackhalf"
autoPlay: false
playbackRate: rootItem.videoRate
onError: {
if (videoPlaybackHalf.NoError !== error) {
console.log("[qmlvideo] VideoItem.onError error " + error + " errorString " + errorString)
}
}
}
VideoOutput {
id:videoPlayer
anchors.fill: parent
source: videoPlaybackHalf
}
Behavior on x {
enabled: id1.state != "active"
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
}
Behavior on y {
enabled: id1.state != "active"
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
}
SequentialAnimation on rotation {
NumberAnimation { to: 2; duration: 60 }
NumberAnimation { to: -2; duration: 120 }
NumberAnimation { to: 0; duration: 60 }
running: loc.currentId !== -1 && id1.state !== "active" && window.lockTiles
loops: Animation.Infinite; alwaysRunToEnd: true
}
states: State {
name: "active"; when: loc.currentId === gridId && window.lockTiles
PropertyChanges { target: id1; x: loc.mouseX - gridView.x - width/2; y: loc.mouseY - gridView.y - height/2; scale: 0.5; z: 10 }
}
transitions: Transition { NumberAnimation { property: "scale"; duration: 200} }
Rectangle {
width: 168 * settings.ui_zoom / 100
height: 123 * settings.ui_zoom / 100
radius: 3
border.width: 1
border.color: "purple"
color: Material.backgroundColor
id: rect
}
DropShadow {
anchors.fill: rect
cached: true
horizontalOffset: 3
verticalOffset: 3
radius: 8.0
samples: 16
color: Material.color(Material.Purple)
source: rect
}
Timer {
id: toggleIconTimer
interval: 500; running: true; repeat: true
onTriggered: { if(identificator === "inclination" && rootItem.autoInclinationEnabled()) myIcon.visible = !myIcon.visible; else myIcon.visible = true; }
}
Image {
id: myIcon
x: 5
anchors {
bottom: id1.bottom
}
width: 48 * settings.ui_zoom / 100
height: 48 * settings.ui_zoom / 100
source: icon
}
Text {
objectName: "value"
id: myValue
color: valueFontColor
y: 0
anchors {
horizontalCenter: parent.horizontalCenter
}
text: value
horizontalAlignment: Text.AlignHCenter
font.pointSize: valueFontSize * settings.ui_zoom / 100
font.bold: true
}
Text {
objectName: "secondLine"
id: secondLineText
color: "white"
y: myValue.bottom
anchors {
top: myValue.bottom
horizontalCenter: parent.horizontalCenter
}
text: secondLine
horizontalAlignment: Text.AlignHCenter
font.pointSize: 12 * settings.ui_zoom / 100
font.bold: false
}
Text {
id: myText
anchors {
top: myIcon.top
}
font.bold: true
font.pointSize: labelFontSize
color: "white"
text: name
anchors.left: parent.left
anchors.leftMargin: 55 * settings.ui_zoom / 100
anchors.topMargin: 20 * settings.ui_zoom / 100
}
RoundButton {
objectName: minusName
autoRepeat: true
text: "-"
onClicked: minus_clicked(objectName)
visible: writable
anchors.top: myValue.top
anchors.left: parent.left
anchors.leftMargin: 2
width: 48 * settings.ui_zoom / 100
height: 48 * settings.ui_zoom / 100
}
RoundButton {
autoRepeat: true
objectName: plusName
text: "+"
onClicked: plus_clicked(objectName)
visible: writable
anchors.top: myValue.top
anchors.right: parent.right
anchors.rightMargin: 2
width: 48 * settings.ui_zoom / 100
height: 48 * settings.ui_zoom / 100
}
/*MouseArea {
anchors.fill: parent
onClicked: parent.GridView.view.currentIndex = index
}*/
}
}
MouseArea {
property int currentId: -1 // Original position in model
property int newIndex // Current Position in model

View File

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

View File

@@ -1,39 +0,0 @@
#include <wobjectimpl.h>
#include "PathController.h"
#include <QtDebug>
W_OBJECT_IMPL(PathController)
PathController::PathController(QObject *parent)
: QObject(parent)
{
QGeoPositionInfoSource *source = QGeoPositionInfoSource::createDefaultSource(this);
if (source) {
auto sourceName = source->sourceName();
auto objectName = source->objectName();
qDebug() << "Found QGeoPositionInfoSource: sourceName:" << sourceName
<< ", objectName:"<<objectName;
QGeoCoordinate const coordinate = source->lastKnownPosition().coordinate();
if(coordinate.isValid()) {
this->setCenter(coordinate);
} else {
// This has been known to happen.
// The Trixter X-Dream V1 bike using a Prolific PL2303HXA Serial to USB converter
// is identified by QGeoPositionInfoSource in Qt 5.15.2 as a source, but it delivers
// invalid data.
qDebug() << "Last known coordinate was not valid. Ignoring device.";
delete source;
}
}
}

View File

@@ -1,52 +0,0 @@
#ifndef APPLICATION_PATHCONTROLLER_H
#define APPLICATION_PATHCONTROLLER_H
#include <wobjectdefs.h>
#include <QGeoPath>
#include <QGeoPositionInfoSource>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
class PathController : public QObject {
// Q_OBJECT
W_OBJECT(PathController)
public:
PathController(QObject *parent = 0);
QGeoPath geoPath() const { return mGeoPath; }
void setGeoPath(const QGeoPath &geoPath) {
if (geoPath == mGeoPath) {
return;
}
mGeoPath = geoPath;
emit geopathChanged();
}
void geopathChanged() W_SIGNAL(geopathChanged)
QGeoCoordinate center() const {
return mCenter;
}
void setCenter(const QGeoCoordinate &center) {
if (center == mCenter) {
return;
}
mCenter = center;
emit centerChanged();
}
void centerChanged() W_SIGNAL(centerChanged)
private : QGeoPath mGeoPath;
QGeoCoordinate mCenter;
W_PROPERTY(QGeoPath, geopath READ geoPath WRITE setGeoPath NOTIFY geopathChanged)
W_PROPERTY(QGeoCoordinate, center READ center WRITE setCenter NOTIFY centerChanged)
};
#endif // APPLICATION_PATHCONTROLLER_H

View File

@@ -63,35 +63,21 @@ Item {
color: "white"
font.pointSize: 22
wrapMode: TextArea.Wrap
text: qsTr("Hi! Do you know that QZ is just an Open Source Indie App?<br><br>No Big Companies are running this!<br>The \"Swag Bag\" is a way to support the ongoing development, maintenance and support of QZ Fitness!")
text: qsTr("Hi! Do you know that QZ is just an Open Souce Indie App?<br>No Big Companies are running this!<br>The \"Swag Bag\" is a way to support the ongoing development, maintenance and support of QZ Fitness!<br>Thanks to Rungap App to give me the idea of the name!")
}
Column {
//anchors.top: description.bottom + 10
anchors.top: description.bottom
//anchors.bottom: restoreButton.top
anchors.right: parent.right
anchors.left: parent.left
id: itemSwagBag
SwagBagItem {
product: productUnlockVowels
width: parent.width
}
}
Text {
anchors {
top: itemSwagBag.bottom
horizontalCenter: parent.horizontalCenter
}
padding: 5
id: appleDescription
width: parent.width
color: "white"
font.pointSize: 8
wrapMode: TextArea.Wrap
text: qsTr("<html><style type='text/css'></style>Swag bag feature:<br>• an auto-renewable subscription<br>• 1 month ($1.99)<br>• Your subscription will be charged to your iTunes account at confirmation of purchase and will automatically renew (at the duration selected) unless auto-renew is turned off at least 24 hours before the end of the current period.<br>• Current subscription may not be cancelled during the active subscription period; however, you can manage your subscription and/or turn off auto-renewal by visiting your iTunes Account Settings after purchase.<br>• Privacy policy: <a href='https://robertoviola.cloud/privacy-policy-qdomyos-zwift/'>https://robertoviola.cloud/privacy-policy-qdomyos-zwift/</a><br>• Licensed Application end user license agreement: <a href='https://www.apple.com/legal/internet-services/itunes/dev/stdeula/'>https://www.apple.com/legal/internet-services/itunes/dev/stdeula/</a><br></html>")
onLinkActivated: Qt.openUrlExternally(link)
}
/*Button {
id: restoreButton

View File

@@ -32,7 +32,7 @@ ColumnLayout {
horizontalAlignment: Text.AlignRight
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
//inputMethodHints: Qt.ImhFormattedNumbersOnly
inputMethodHints: Qt.ImhFormattedNumbersOnly
onAccepted: hostRow.doSaveHost(text)
}
Button {

View File

@@ -4,12 +4,9 @@ import QtQuick.Layouts 1.3
import QtQuick.Controls 2.15
import QtQuick.Controls.Material 2.0
import QtQuick.Dialogs 1.0
import QtCharts 2.2
import Qt.labs.settings 1.0
ColumnLayout {
signal trainprogram_open_clicked(url name)
signal trainprogram_preview(url name)
FileDialog {
id: fileDialogTrainProgram
title: "Please choose a file"
@@ -25,65 +22,21 @@ ColumnLayout {
}
}
RowLayout{
spacing: 2
anchors.top: parent.top
anchors.fill: parent
ColumnLayout {
spacing: 0
anchors.top: parent.top
anchors.fill: parent
Row
{
spacing: 5
Text
{
text:"Filter"
color: "white"
verticalAlignment: Text.AlignVCenter
}
TextField
{
function updateFilter()
{
var text = filterField.text
var filter = "*"
for(var i = 0; i<text.length; i++)
filter+= "[%1%2]".arg(text[i].toUpperCase()).arg(text[i].toLowerCase())
filter+="*"
print(filter)
folderModel.nameFilters = [filter + ".zwo", filter + ".xml"]
}
id: filterField
onTextChanged: updateFilter()
}
Button {
anchors.left: mainRect.right
anchors.leftMargin: 5
text: "←"
onClicked: folderModel.folder = folderModel.parentFolder
}
}
AccordionElement {
title: qsTr("Application Training folder")
indicatRectColor: Material.color(Material.Grey)
textColor: Material.color(Material.Grey)
color: Material.backgroundColor
accordionContent: ColumnLayout {
ListView {
Layout.fillWidth: true
Layout.minimumWidth: 50
Layout.preferredWidth: 100
Layout.maximumWidth: row.left
Layout.minimumHeight: 150
Layout.preferredHeight: parent.height
ScrollBar.vertical: ScrollBar {}
id: list
anchors.fill: parent
FolderListModel {
id: folderModel
nameFilters: ["*.xml", "*.zwo"]
folder: "file://" + rootItem.getWritableAppDir() + 'training'
showDotAndDotDot: false
showDotAndDotDot: false
showDirs: true
sortField: "Name"
showDirsFirst: true
}
model: folderModel
delegate: Component {
@@ -91,37 +44,13 @@ ColumnLayout {
property alias textColor: fileTextBox.color
width: parent.width
height: 40
color: Material.backgroundColor
color: Material.backgroundColor
z: 1
Item {
id: root
property alias text: fileTextBox.text
property int spacing: 30
width: fileTextBox.width + spacing
height: fileTextBox.height
clip: true
Text {
id: fileTextBox
color: (!folderModel.isFolder(index)?Material.color(Material.Grey):Material.color(Material.Orange))
font.pixelSize: Qt.application.font.pixelSize * 1.6
text: fileName.substring(0, fileName.length-4)
NumberAnimation on x {
Component.onCompleted: {
if(fileName.length > 30) {
running: true;
} else {
stop();
}
}
from: 0; to: -root.width; duration: 20000; loops: Animation.Infinite
}
Text {
x: root.width
text: fileTextBox.text
color: Material.color(Material.Grey)
font.pixelSize: Qt.application.font.pixelSize * 1.6
}
}
Text {
id: fileTextBox
color: Material.color(Material.Grey)
font.pixelSize: Qt.application.font.pixelSize * 1.6
text: fileName.substring(0, fileName.length-4)
}
MouseArea {
anchors.fill: parent
@@ -130,12 +59,10 @@ ColumnLayout {
console.log('onclicked ' + index+ " count "+list.count);
if (index == list.currentIndex) {
let fileUrl = folderModel.get(list.currentIndex, 'fileUrl') || folderModel.get(list.currentIndex, 'fileURL');
if (fileUrl && !folderModel.isFolder(list.currentIndex)) {
if (fileUrl) {
trainprogram_open_clicked(fileUrl);
popup.open()
} else {
folderModel.folder = fileURL
}
}
}
else {
if (list.currentItem)
@@ -164,124 +91,14 @@ ColumnLayout {
if (fileUrl) {
list.currentItem.textColor = Material.color(Material.Yellow)
console.log(fileUrl + ' selected');
trainprogram_preview(fileUrl)
powerSeries.clear();
for(var i=0;i<rootItem.preview_workout_points;i+=10)
{
powerSeries.append(i * 1000, rootItem.preview_workout_watt[i]);
}
rootItem.update_chart_power(powerChart);
//trainprogram_open_clicked(fileUrl);
//popup.open()
}
}
Component.onCompleted: {
}
}
}
ScrollView {
anchors.top: parent.top
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
contentHeight: date.height + description.height + powerChart.height
Layout.preferredHeight: parent.height
Layout.fillWidth: true
Layout.minimumWidth: 100
Layout.preferredWidth: 200
property alias powerSeries: powerSeries
property alias powerChart: powerChart
Settings {
id: settings
property real ftp: 200.0
}
Row {
id: row
anchors.fill: parent
Text {
id: date
width: parent.width
text: rootItem.previewWorkoutDescription
font.pixelSize: 14
color: "white"
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
anchors.top: date.bottom
id: description
width: parent.width
text: rootItem.previewWorkoutTags
font.pixelSize: 10
wrapMode: Text.WordWrap
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.horizontalCenter: parent.horizontalCenter
}
Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: description.bottom
anchors.bottom: parent.bottom
ChartView {
id: powerChart
objectName: "powerChart"
antialiasing: true
legend.visible: false
height: 400
width: parent.width
title: "Power"
titleFont.pixelSize: 20
DateTimeAxis {
id: valueAxisX
tickCount: 7
min: new Date(0)
max: new Date(rootItem.preview_workout_points * 1000)
format: "mm:ss"
//labelsVisible: false
gridVisible: false
//lineVisible: false
labelsFont.pixelSize: 10
}
ValueAxis {
id: valueAxisY
min: 0
max: rootItem.wattMaxChart
//tickCount: 60
tickCount: 8
labelFormat: "%.0f"
//labelsVisible: false
//gridVisible: false
//lineVisible: false
labelsFont.pixelSize: 10
}
LineSeries {
//name: "Power"
id: powerSeries
visible: true
axisX: valueAxisX
axisY: valueAxisY
color: "black"
width: 1
}
}
}
}
}
}
spacing: 10
Button {
id: searchButton

View File

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

View File

@@ -1,5 +1,6 @@
#include "activiotreadmill.h"
#include "activiotreadmill.h"
#include "ios/lockscreen.h"
#include "keepawakehelper.h"
#include "virtualtreadmill.h"
@@ -33,7 +34,7 @@ activiotreadmill::activiotreadmill(uint32_t pollDeviceTime, bool noConsole, bool
refresh->start(pollDeviceTime);
}
void activiotreadmill::writeCharacteristic(const QLowEnergyCharacteristic characteristic, uint8_t *data,
void activiotreadmill::writeCharacteristic(const QLowEnergyCharacteristic characteristc, uint8_t *data,
uint8_t data_len, const QString &info, bool disable_log,
bool wait_for_response) {
QEventLoop loop;
@@ -54,7 +55,7 @@ void activiotreadmill::writeCharacteristic(const QLowEnergyCharacteristic charac
return;
}
gattCommunicationChannelService->writeCharacteristic(characteristic, QByteArray((const char *)data, data_len));
gattCommunicationChannelService->writeCharacteristic(characteristc, QByteArray((const char *)data, data_len));
if (!disable_log) {
emit debug(QStringLiteral(" >> ") + QByteArray((const char *)data, data_len).toHex(' ') +
@@ -74,7 +75,7 @@ void activiotreadmill::forceSpeed(double requestSpeed) {
writeSpeed[1] = (requestSpeed * 10);
writeSpeed[5] += writeSpeed[1];
if(!settings.value(QZSettings::fitfiu_mc_v460, QZSettings::default_fitfiu_mc_v460).toBool())
if(!settings.value(QStringLiteral("fitfiu_mc_v460"), false).toBool())
writeSpeed[6] = writeSpeed[1] + 1;
else {
switch(writeSpeed[1] & 0x0F) {
@@ -168,8 +169,8 @@ void activiotreadmill::update() {
QSettings settings;
// ******************************************* virtual treadmill init *************************************
if (!firstInit && !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("virtual_device_enabled", true).toBool();
bool virtual_device_force_bike = settings.value("virtual_device_force_bike", false).toBool();
if (virtual_device_enabled) {
if (!virtual_device_force_bike) {
debug("creating virtual treadmill interface...");
@@ -190,7 +191,7 @@ void activiotreadmill::update() {
// debug("Domyos Treadmill RSSI " + QString::number(bluetoothDevice.rssi()));
update_metrics(true, watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()));
update_metrics(true, watts(settings.value(QStringLiteral("weight"), 75.0).toFloat()));
{
if (requestSpeed != -1) {
@@ -282,7 +283,7 @@ void activiotreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
settings.value(QStringLiteral("heart_rate_belt_name"), QStringLiteral("Disabled")).toString();
Q_UNUSED(characteristic);
QByteArray value = newValue;
@@ -297,13 +298,13 @@ void activiotreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
double speed = GetSpeedFromPacket(value);
double incline = 1.0; // "fitfiu_mc_v460" has 1.0 fixed inclination
if(!settings.value(QZSettings::fitfiu_mc_v460, QZSettings::default_fitfiu_mc_v460).toBool())
if(!settings.value(QStringLiteral("fitfiu_mc_v460"), false).toBool())
incline = GetInclinationFromPacket(value);
// double kcal = GetKcalFromPacket(value);
// double distance = GetDistanceFromPacket(value);
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
if (settings.value("ant_heart", false).toBool())
Heart = (uint8_t)KeepAwakeHelper::heart();
else
#endif
@@ -330,10 +331,10 @@ void activiotreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
}
if (!firstCharacteristicChanged) {
if (watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()))
if (watts(settings.value(QStringLiteral("weight"), 75.0).toFloat()))
KCal +=
((((0.048 * ((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) + 1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
((((0.048 * ((double)watts(settings.value(QStringLiteral("weight"), 75.0).toFloat())) + 1.19) *
settings.value(QStringLiteral("weight"), 75.0).toFloat() * 3.5) /
200.0) /
(60000.0 / ((double)lastTimeCharacteristicChanged.msecsTo(
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in

View File

@@ -53,7 +53,7 @@ class activiotreadmill : public treadmill {
void forceSpeed(double requestSpeed);
void forceIncline(double requestIncline);
void btinit(bool startTape);
void writeCharacteristic(const QLowEnergyCharacteristic characteristic, uint8_t *data, uint8_t data_len,
void writeCharacteristic(const QLowEnergyCharacteristic characteristc, uint8_t *data, uint8_t data_len,
const QString &info, bool disable_log = false, bool wait_for_response = false);
void startDiscover();
bool noConsole = false;

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,7 +1,7 @@
<?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">
<!-- 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. -->
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="2.10.96" android:versionCode="335" 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 -->
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
@@ -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: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"/>
@@ -67,48 +67,18 @@
* none - useful for apps that don't use any of the above Qt modules
-->
<meta-data android:name="android.app.extract_android_style" android:value="default"/>
<!-- extract android style -->
<!-- extract android style -->
</activity>
<service
android:name=".ForegroundService"
android:enabled="true"
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>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.ACCESS_CHECKIN_PROPERTIES"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="com.android.vending.BILLING"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.things.permission.USE_PERIPHERAL_IO" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions" />
</manifest>

View File

@@ -12,40 +12,13 @@ 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"
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation "com.android.billingclient:billing:4.0.0"
implementation 'com.android.support:appcompat-v7:28.0.0'
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"
}
android {
@@ -66,11 +39,6 @@ android {
buildToolsVersion '29.0.2'
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
@@ -96,8 +64,7 @@ android {
defaultConfig {
resConfig "en"
compileSdkVersion 33
minSdkVersion = 21
targetSdkVersion = 33
targetSdkVersion = 30
}
}

View File

@@ -1,2 +0,0 @@
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#CC000000">
<WebView android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/buttonMaximize"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -47,7 +47,7 @@ public class ChannelService extends Service {
private AntChannelProvider mAntChannelProvider = null;
private boolean mAllowAddChannel = false;
HeartChannelController heartChannelController = null;
HeartChannelController heartChannelController = null;
PowerChannelController powerChannelController = null;
SpeedChannelController speedChannelController = null;
@@ -116,17 +116,17 @@ public class ChannelService extends Service {
if (null != powerChannelController) {
powerChannelController.cadence = cadence;
}
if (null != speedChannelController) {
speedChannelController.cadence = cadence;
}
if (null != speedChannelController) {
speedChannelController.cadence = cadence;
}
}
int getHeart() {
if (null != heartChannelController) {
return heartChannelController.heart;
}
return 0;
}
int getHeart() {
if (null != heartChannelController) {
return heartChannelController.heart;
}
return 0;
}
/**
* Closes all channels currently added.
@@ -134,28 +134,28 @@ public class ChannelService extends Service {
void clearAllChannels() {
closeAllChannels();
}
}
}
public void openAllChannels() throws ChannelNotAvailableException {
if (Ant.heartRequest)
heartChannelController = new HeartChannelController(acquireChannel());
if(Ant.heartRequest)
heartChannelController = new HeartChannelController(acquireChannel());
if (Ant.speedRequest) {
powerChannelController = new PowerChannelController(acquireChannel());
speedChannelController = new SpeedChannelController(acquireChannel());
}
if(Ant.speedRequest) {
powerChannelController = new PowerChannelController(acquireChannel());
speedChannelController = new SpeedChannelController(acquireChannel());
}
}
private void closeAllChannels() {
if (heartChannelController != null)
heartChannelController.close();
if (powerChannelController != null)
powerChannelController.close();
if (speedChannelController != null)
speedChannelController.close();
heartChannelController = null;
powerChannelController = null;
speedChannelController = null;
if(heartChannelController != null)
heartChannelController.close();
if(powerChannelController != null)
powerChannelController.close();
if(speedChannelController != null)
speedChannelController.close();
heartChannelController = null;
powerChannelController = null;
speedChannelController = null;
}
AntChannel acquireChannel() throws ChannelNotAvailableException {
@@ -171,18 +171,19 @@ public class ChannelService extends Service {
* acquireChannel(context, PredefinedNetwork,
* requiredCapabilities, desiredCapabilities).
*/
if (Ant.garminKey == false)
mAntChannel = mAntChannelProvider.acquireChannel(this, PredefinedNetwork.ANT_PLUS_1);
else {
NetworkKey mNK = new NetworkKey(new byte[]{(byte) 0xb9, (byte) 0xa5, (byte) 0x21, (byte) 0xfb,
(byte) 0xbd, (byte) 0x72, (byte) 0xc3, (byte) 0x45});
Log.v(TAG, mNK.toString());
mAntChannel = mAntChannelProvider.acquireChannelOnPrivateNetwork(this, mNK);
}
} catch (RemoteException e) {
Log.v(TAG, "ACP Remote Ex");
} catch (UnsupportedFeatureException e) {
Log.v(TAG, "ACP UnsupportedFeature Ex");
if(Ant.garminKey == false)
mAntChannel = mAntChannelProvider.acquireChannel(this, PredefinedNetwork.ANT_PLUS_1);
else
{
NetworkKey mNK = new NetworkKey(new byte[] { (byte)0xb9, (byte)0xa5, (byte)0x21, (byte)0xfb,
(byte)0xbd, (byte)0x72, (byte)0xc3, (byte)0x45 });
Log.v(TAG, mNK.toString());
mAntChannel = mAntChannelProvider.acquireChannelOnPrivateNetwork(this, mNK);
}
} catch (RemoteException e) {
Log.v(TAG, "ACP Remote Ex");
} catch (UnsupportedFeatureException e) {
Log.v(TAG, "ACP UnsupportedFeature Ex");
}
}
return mAntChannel;

View File

@@ -1,80 +0,0 @@
package org.cagnulen.qdomyoszwift;
import android.app.ActivityManager;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import android.os.Looper;
import android.os.Handler;
import android.util.Log;
import androidx.appcompat.app.AlertDialog;
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) {
_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);
// The MainActivity closes here
//finish();
} else {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + _context.getPackageName()));
// This method will start the intent. It takes two parameter, one is the Intent and the other is
// an requestCode Integer. Here it is -1.
Activity a = (Activity)_context;
a.startActivityForResult(intent, -1);
}
}
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
// doesn't need 'Display over other apps' permission enabling.
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
// If 'Display over other apps' is not enabled it
// will return false or else true
if (!Settings.canDrawOverlays(_context)) {
return false;
} else {
return true;
}
} else {
return true;
}
}
}

View File

@@ -1,203 +0,0 @@
// https://www.geeksforgeeks.org/how-to-make-a-floating-window-application-in-android/
package org.cagnulen.qdomyoszwift;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.IBinder;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import android.webkit.WebView;
import android.webkit.WebSettings;
import android.webkit.WebViewClient;
import android.util.Log;
import android.content.SharedPreferences;
public class FloatingWindowGFG extends Service {
// The reference variables for the
// ViewGroup, WindowManager.LayoutParams,
// WindowManager, Button, EditText classes are created
private ViewGroup floatView;
private int LAYOUT_TYPE;
private WindowManager.LayoutParams floatWindowLayoutParam;
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
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
// 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();
int width = metrics.widthPixels;
int height = metrics.heightPixels;*/
// To obtain a WindowManager of a different Display,
// we need a Context for that display, so WINDOW_SERVICE is used
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
// A LayoutInflater instance is created to retrieve the
// LayoutInflater for the floating_layout xml
LayoutInflater inflater = (LayoutInflater) getBaseContext().getSystemService(LAYOUT_INFLATER_SERVICE);
// inflate a new view hierarchy from the floating_layout xml
floatView = (ViewGroup) inflater.inflate(R.layout.floating_layout, null);
WebView wv = (WebView)floatView.findViewById(R.id.webview);
wv.setWebViewClient(new WebViewClient(){
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
WebSettings settings = wv.getSettings();
settings.setJavaScriptEnabled(true);
wv.loadUrl("http://localhost:" + FloatingHandler._port + "/floating/floating.htm");
wv.clearView();
wv.measure(100, 100);
wv.setAlpha(Float.valueOf(FloatingHandler._alpha) / 100.0f);
settings.setBuiltInZoomControls(true);
settings.setUseWideViewPort(true);
settings.setDomStorageEnabled(true);
Log.d("QZ","loadurl");
// WindowManager.LayoutParams takes a lot of parameters to set the
// the parameters of the layout. One of them is Layout_type.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// If API Level is more than 26, we need TYPE_APPLICATION_OVERLAY
LAYOUT_TYPE = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
// If API Level is lesser than 26, then we can
// use TYPE_SYSTEM_ERROR,
// TYPE_SYSTEM_OVERLAY, TYPE_PHONE, TYPE_PRIORITY_PHONE.
// But these are all
// deprecated in API 26 and later. Here TYPE_TOAST works best.
LAYOUT_TYPE = WindowManager.LayoutParams.TYPE_TOAST;
}
// Now the Parameter of the floating-window layout is set.
// 1) The Width of the window will be 55% of the phone width.
// 2) The Height of the window will be 58% of the phone height.
// 3) Layout_Type is already set.
// 4) Next Parameter is Window_Flag. Here FLAG_NOT_FOCUSABLE is used. But
// problem with this flag is key inputs can't be given to the EditText.
// 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 ),
LAYOUT_TYPE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
);
// The Gravity of the Floating Window is set.
// The Window will appear in the center of the screen
floatWindowLayoutParam.gravity = Gravity.CENTER;
// X and Y value of the window is set
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);
// Another feature of the floating window is, the window is movable.
// The window can be moved at any position on the screen.
floatView.setOnTouchListener(new View.OnTouchListener() {
final WindowManager.LayoutParams floatWindowLayoutUpdateParam = floatWindowLayoutParam;
double x;
double y;
double px;
double py;
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("QZ","onTouch");
switch (event.getAction()) {
// When the window will be touched,
// the x and y position of that position
// will be retrieved
case MotionEvent.ACTION_DOWN:
x = floatWindowLayoutUpdateParam.x;
y = floatWindowLayoutUpdateParam.y;
// returns the original raw X
// coordinate of this event
px = event.getRawX();
// returns the original raw Y
// coordinate of this event
py = event.getRawY();
break;
// When the window will be dragged around,
// it will update the x, y of the Window Layout Parameter
case MotionEvent.ACTION_MOVE:
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;
}
return false;
}
});
}
// It is called when stopService()
// method is called in MainActivity
@Override
public void onDestroy() {
super.onDestroy();
stopSelf();
// Window is removed from the screen
windowManager.removeView(floatView);
}
}

View File

@@ -1,57 +0,0 @@
package org.cagnulen.qdomyoszwift;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import androidx.core.app.NotificationCompat;
public class ForegroundService extends Service {
public static final String CHANNEL_ID = "ForegroundServiceChannel";
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String input = intent.getStringExtra("inputExtra");
createNotificationChannel();
Intent notificationIntent = new Intent();
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("QZ is Running")
.setContentText(input)
.setSmallIcon(R.drawable.icon)
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
//do heavy work on a background thread
//stopSelf();
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel serviceChannel = new NotificationChannel(
CHANNEL_ID,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(serviceChannel);
}
}
}

View File

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

View File

@@ -1,36 +0,0 @@
package org.cagnulen.qdomyoszwift;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.app.PendingIntent;
import android.graphics.Color;
import android.graphics.BitmapFactory;
import android.app.NotificationChannel;
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);
serviceIntent.putExtra("inputExtra", "QZ is Running");
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent);
} else {
context.startService(serviceIntent);
}
}
public static void hide() {
if(serviceIntent != null)
_context.stopService(serviceIntent);
}
}

View File

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

View File

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

View File

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

View File

@@ -16,7 +16,6 @@
package org.cagnulen.qdomyoszwift;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import com.dsi.ant.channel.AntChannel;
@@ -33,25 +32,26 @@ 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;
public static final int SPEED_SENSOR_ID = 0x9e3d4b65;
private static final double MILLISECOND_TO_1_1024_CONVERSION = 0.9765625;
private static Random randGen = new Random();
private AntChannel mAntChannel;
private ChannelEventCallback mChannelEventCallback = new ChannelEventCallback();
private boolean mIsOpen;
double speed = 0.0;
int cadence = 0;
double cadence = 0.0;
public SpeedChannelController(AntChannel antChannel) {
mAntChannel = antChannel;
@@ -111,7 +111,7 @@ public class SpeedChannelController {
String logString = "Remote service communication failed.";
Log.e(TAG, logString);
}
}
void channelError(String error, AntCommandFailedException e) {
StringBuilder logString;
@@ -165,15 +165,16 @@ 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;
long unixTime = 0;
@Override
public void onChannelDeath() {
@@ -206,31 +207,56 @@ public class SpeedChannelController {
// Switching on event code to handle the different types of channel events
switch (code) {
case TX:
long realtimeMillis = SystemClock.elapsedRealtime();
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);
}
lastTime = realtimeMillis;
if(speed > 0)
{
revCounts++;
unixTime += (long)(1024.0 / (((double)(speed)) / 60.0));
}
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 +268,9 @@ public class SpeedChannelController {
}
break;
case CHANNEL_COLLISION:
ucPageChange += 0x20;
ucPageChange &= 0xF0;
ucMessageCount += 1;
break;
case RX_SEARCH_TIMEOUT:
// TODO May want to keep searching

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -181,20 +181,6 @@ public class InAppPurchase implements PurchasesUpdatedListener
purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, e.getMessage());
}
purchaseSucceeded(purchaseRequestCode, purchase.getSignature(), purchase.getOriginalJson(), purchase.getPurchaseToken(), purchase.getOrderId(), purchase.getPurchaseTime());
AcknowledgePurchaseParams acknowledgePurchaseParams =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
billingClient.acknowledgePurchase(acknowledgePurchaseParams,
new AcknowledgePurchaseResponseListener()
{
@Override
public void onAcknowledgePurchaseResponse(BillingResult billingResult)
{
Log.d(TAG, "Purchase acknowledged ");
}
}
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,408 +0,0 @@
#include "apexbike.h"
#include "ios/lockscreen.h"
#include "keepawakehelper.h"
#include "virtualbike.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <chrono>
#include <math.h>
using namespace std::chrono_literals;
apexbike::apexbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
double bikeResistanceGain) {
m_watt.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
this->noHeartService = noHeartService;
this->bikeResistanceGain = bikeResistanceGain;
this->bikeResistanceOffset = bikeResistanceOffset;
initDone = false;
connect(refresh, &QTimer::timeout, this, &apexbike::update);
refresh->start(200ms);
}
void apexbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
bool wait_for_response) {
QEventLoop loop;
QTimer timeout;
// if there are some crash here, maybe it's better to use 2 separate event for the characteristicChanged.
// one for the resistance changed event (spontaneous), and one for the other ones.
if (wait_for_response) {
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, &loop, &QEventLoop::quit);
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
} else {
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, &loop, &QEventLoop::quit);
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
}
if (gattCommunicationChannelService->state() != QLowEnergyService::ServiceState::ServiceDiscovered ||
m_control->state() == QLowEnergyController::UnconnectedState) {
qDebug() << QStringLiteral("writeCharacteristic error because the connection is closed");
return;
}
if (!gattWriteCharacteristic.isValid()) {
qDebug() << QStringLiteral("gattWriteCharacteristic is invalid");
return;
}
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic,
QByteArray((const char *)data, data_len));
if (!disable_log) {
qDebug() << QStringLiteral(" >> ") + QByteArray((const char *)data, data_len).toHex(' ') +
QStringLiteral(" // ") + info;
}
loop.exec();
}
void apexbike::forceResistance(resistance_t requestResistance) {}
void apexbike::sendPoll() {
uint8_t noOpData[] = {0xaa, 0x55, 0xe0, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xc3, 0x3c};
writeCharacteristic(noOpData, sizeof(noOpData), QStringLiteral("noOp"), false, false);
}
void apexbike::update() {
if (m_control->state() == QLowEnergyController::UnconnectedState) {
emit disconnected();
return;
}
if (initRequest) {
initRequest = false;
btinit();
} else if (bluetoothDevice.isValid() && m_control->state() == QLowEnergyController::DiscoveredState &&
gattCommunicationChannelService && gattWriteCharacteristic.isValid() &&
gattNotify1Characteristic.isValid() && initDone) {
update_metrics(true, watts());
// sending poll every 5 seconds
if (sec1Update++ >= (5000 / refresh->interval())) {
sec1Update = 0;
sendPoll();
// updateDisplay(elapsed);
}
if (requestResistance != -1) {
if (requestResistance > max_resistance)
requestResistance = max_resistance;
else if (requestResistance <= 0)
requestResistance = 1;
if (requestResistance != currentResistance().value()) {
qDebug() << QStringLiteral("writing resistance ") + QString::number(requestResistance);
forceResistance(requestResistance);
}
requestResistance = -1;
}
if (requestStart != -1) {
qDebug() << QStringLiteral("starting...");
// btinit();
requestStart = -1;
emit bikeStarted();
}
if (requestStop != -1) {
qDebug() << QStringLiteral("stopping...");
// writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
requestStop = -1;
}
}
}
void apexbike::serviceDiscovered(const QBluetoothUuid &gatt) {
qDebug() << QStringLiteral("serviceDiscovered ") + gatt.toString();
}
void apexbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
qDebug() << " << " + newValue.toHex(' ');
lastPacket = newValue;
if (newValue.length() != 10 && newValue.at(2) != 0x31) {
return;
}
Resistance = newValue.at(5);
emit resistanceRead(Resistance.value());
m_pelotonResistance = Resistance.value();
qDebug() << QStringLiteral("Current resistance: ") + QString::number(Resistance.value());
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled"))) {
Cadence = ((uint8_t)newValue.at(4));
}
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
Speed = 0.37497622 * ((double)Cadence.value());
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
if (watts())
KCal +=
((((0.048 * ((double)watts()) + 1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
200.0) /
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
Distance += ((Speed.value() / 3600000.0) *
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) {
Heart = (uint8_t)KeepAwakeHelper::heart();
} else
#endif
{
if (heartRateBeltName.startsWith(QLatin1String("Disabled"))) {
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
lockscreen h;
long appleWatchHeartRate = h.heartRate();
h.setKcal(KCal.value());
h.setDistance(Distance.value());
Heart = appleWatchHeartRate;
qDebug() << "Current Heart from Apple Watch: " + QString::number(appleWatchHeartRate);
#endif
#endif
}
}
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence = settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
}
#endif
#endif
// these useless lines are needed to calculate the AVG resistance and AVG peloton resistance since
// echelon just send the resistance values when it changes
Resistance = Resistance.value();
m_pelotonResistance = m_pelotonResistance.value();
qDebug() << QStringLiteral("Current Speed: ") + QString::number(Speed.value());
qDebug() << QStringLiteral("Current Calculate Distance: ") + QString::number(Distance.value());
qDebug() << QStringLiteral("Current Cadence: ") + QString::number(Cadence.value());
qDebug() << QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs);
qDebug() << QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime);
qDebug() << QStringLiteral("Current Watt: ") + QString::number(watts());
if (m_control->error() != QLowEnergyController::NoError) {
qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString();
}
}
void apexbike::btinit() {
uint8_t initData1[] = {0xeb, 0x50, 0x51, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa2};
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
initDone = true;
if (lastResistanceBeforeDisconnection != -1) {
qDebug() << QStringLiteral("forcing resistance to ") + QString::number(lastResistanceBeforeDisconnection) +
QStringLiteral(". It was the last value before the disconnection.");
forceResistance(lastResistanceBeforeDisconnection);
lastResistanceBeforeDisconnection = -1;
}
}
void apexbike::stateChanged(QLowEnergyService::ServiceState state) {
QBluetoothUuid _gattWriteCharacteristicId((quint16)0xfff2);
QBluetoothUuid _gattNotify1CharacteristicId((quint16)0xfff1);
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
qDebug() << QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state));
if (state == QLowEnergyService::ServiceDiscovered) {
// qDebug() << gattCommunicationChannelService->characteristics();
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
gattNotify1Characteristic = gattCommunicationChannelService->characteristic(_gattNotify1CharacteristicId);
Q_ASSERT(gattWriteCharacteristic.isValid());
Q_ASSERT(gattNotify1Characteristic.isValid());
// establish hook into notifications
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this,
&apexbike::characteristicChanged);
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this,
&apexbike::characteristicWritten);
connect(gattCommunicationChannelService,
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
this, &apexbike::errorService);
connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this,
&apexbike::descriptorWritten);
// ******************************************* virtual bike init *************************************
if (!firstStateChanged && !virtualBike
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
&& !h
#endif
#endif
) {
QSettings settings;
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence =
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence) {
qDebug() << "ios_peloton_workaround activated!";
h = new lockscreen();
h->virtualbike_ios();
} else
#endif
#endif
if (virtual_device_enabled) {
qDebug() << QStringLiteral("creating virtual bike interface...");
virtualBike =
new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
// connect(virtualBike,&virtualbike::debug ,this,&apexbike::debug);
connect(virtualBike, &virtualbike::changeInclination, this, &apexbike::changeInclination);
}
}
firstStateChanged = 1;
// ********************************************************************************************************
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
gattCommunicationChannelService->writeDescriptor(
gattNotify1Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
}
}
void apexbike::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
qDebug() << QStringLiteral("descriptorWritten ") + descriptor.name() + QStringLiteral(" ") + newValue.toHex(' ');
initRequest = true;
emit connectedAndDiscovered();
}
void apexbike::characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
Q_UNUSED(characteristic);
qDebug() << QStringLiteral("characteristicWritten ") + newValue.toHex(' ');
}
void apexbike::serviceScanDone(void) {
qDebug() << QStringLiteral("serviceScanDone");
QBluetoothUuid _gattCommunicationChannelServiceId((quint16)0xfff0);
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &apexbike::stateChanged);
gattCommunicationChannelService->discoverDetails();
}
void apexbike::errorService(QLowEnergyService::ServiceError err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
qDebug() << QStringLiteral("apexbike::errorService") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString();
}
void apexbike::error(QLowEnergyController::Error err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
qDebug() << QStringLiteral("apexbike::error") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString();
}
void apexbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
qDebug() << QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')';
{
bluetoothDevice = device;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &apexbike::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &apexbike::serviceScanDone);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, &apexbike::error);
connect(m_control, &QLowEnergyController::stateChanged, this, &apexbike::controllerStateChanged);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, [this](QLowEnergyController::Error error) {
Q_UNUSED(error);
Q_UNUSED(this);
qDebug() << QStringLiteral("Cannot connect to remote device.");
emit disconnected();
});
connect(m_control, &QLowEnergyController::connected, this, [this]() {
Q_UNUSED(this);
qDebug() << QStringLiteral("Controller connected. Search services...");
m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
Q_UNUSED(this);
qDebug() << QStringLiteral("LowEnergy controller disconnected");
emit disconnected();
});
// Connect
m_control->connectToDevice();
return;
}
}
bool apexbike::connected() {
if (!m_control) {
return false;
}
return m_control->state() == QLowEnergyController::DiscoveredState;
}
void *apexbike::VirtualBike() { return virtualBike; }
void *apexbike::VirtualDevice() { return VirtualBike(); }
uint16_t apexbike::watts() {
return wattFromHR(true);
}
void apexbike::controllerStateChanged(QLowEnergyController::ControllerState state) {
qDebug() << QStringLiteral("controllerStateChanged") << state;
if (state == QLowEnergyController::UnconnectedState && m_control) {
lastResistanceBeforeDisconnection = Resistance.value();
qDebug() << QStringLiteral("trying to connect back again...");
initDone = false;
m_control->connectToDevice();
}
}

View File

@@ -1,102 +0,0 @@
#ifndef APEXBIKE_H
#define APEXBIKE_H
#include <QBluetoothDeviceDiscoveryAgent>
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QtCore/qbytearray.h>
#ifndef Q_OS_ANDROID
#include <QtCore/qcoreapplication.h>
#else
#include <QtGui/qguiapplication.h>
#endif
#include <QtCore/qlist.h>
#include <QtCore/qmutex.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimer.h>
#include <QDateTime>
#include <QObject>
#include <QString>
#include "bike.h"
#include "virtualbike.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
#endif
class apexbike : public bike {
Q_OBJECT
public:
apexbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain);
bool connected();
void *VirtualBike();
void *VirtualDevice();
private:
const resistance_t max_resistance = 32;
void btinit();
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
bool wait_for_response = false);
void startDiscover();
void forceResistance(resistance_t requestResistance);
void sendPoll();
uint16_t watts();
QTimer *refresh;
virtualbike *virtualBike = nullptr;
QLowEnergyService *gattCommunicationChannelService = nullptr;
QLowEnergyCharacteristic gattWriteCharacteristic;
QLowEnergyCharacteristic gattNotify1Characteristic;
uint8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
uint8_t counterPoll = 1;
uint8_t sec1Update = 0;
QByteArray lastPacket;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
uint8_t firstStateChanged = 0;
resistance_t lastResistanceBeforeDisconnection = -1;
bool initDone = false;
bool initRequest = false;
bool noWriteResistance = false;
bool noHeartService = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif
Q_SIGNALS:
void disconnected();
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
private slots:
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
void stateChanged(QLowEnergyService::ServiceState state);
void controllerStateChanged(QLowEnergyController::ControllerState state);
void serviceDiscovered(const QBluetoothUuid &gatt);
void serviceScanDone(void);
void update();
void error(QLowEnergyController::Error err);
void errorService(QLowEnergyService::ServiceError);
};
#endif // APEXBIKE_H

View File

@@ -29,9 +29,6 @@ bhfitnesselliptical::bhfitnesselliptical(bool noWriteResistance, bool noHeartSer
initDone = false;
connect(refresh, &QTimer::timeout, this, &bhfitnesselliptical::update);
refresh->start(200ms);
// this bike doesn't send resistance, so I have to use the default value
Resistance = default_resistance;
}
void bhfitnesselliptical::writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
@@ -56,17 +53,13 @@ void bhfitnesselliptical::writeCharacteristic(uint8_t *data, uint8_t data_len, c
loop.exec();
}
void bhfitnesselliptical::forceResistance(resistance_t requestResistance) {
void bhfitnesselliptical::forceResistance(int8_t requestResistance) {
uint8_t write[] = {FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS, 0x00, 0x00, 0x00, 0x00, 0x21, 0x22};
uint8_t write[] = {FTMS_SET_TARGET_RESISTANCE_LEVEL, 0x00};
write[3] = ((int16_t)(requestResistance - default_resistance) * 33) & 0xFF;
write[4] = ((int16_t)(requestResistance - default_resistance) * 33) >> 8;
write[1] = ((uint16_t)requestResistance) & 0xFF;
writeCharacteristic(write, sizeof(write), QStringLiteral("forceResistance ") + QString::number(requestResistance));
// this bike doesn't send resistance, so I have to use the value forced
Resistance = requestResistance;
}
void bhfitnesselliptical::update() {
@@ -76,7 +69,6 @@ void bhfitnesselliptical::update() {
}
if (initRequest) {
initRequest = false;
} else if (bluetoothDevice.isValid() &&
m_control->state() == QLowEnergyController::DiscoveredState //&&
@@ -93,15 +85,16 @@ void bhfitnesselliptical::update() {
}
if (requestResistance != -1) {
if (requestResistance > max_resistance) {
requestResistance = max_resistance;
if (requestResistance > 100) {
requestResistance = 100;
} // TODO, use the bluetooth value
else if (requestResistance == 0) {
requestResistance = 1;
}
if (requestResistance != currentResistance().value()) {
if (((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike)) {
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
forceResistance(requestResistance);
}
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
forceResistance(requestResistance);
}
requestResistance = -1;
}
@@ -131,9 +124,8 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
Q_UNUSED(characteristic);
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();
settings.value(QStringLiteral("heart_rate_belt_name"), QStringLiteral("Disabled")).toString();
bool disable_hr_frommachinery = settings.value(QStringLiteral("heart_ignore_builtin"), false).toBool();
emit debug(QStringLiteral(" << ") + newValue.toHex(' '));
@@ -176,17 +168,14 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
index += 2;
if (!Flags.moreData) {
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
if (!settings.value(QStringLiteral("speed_power_based"), false).toBool()) {
// this elliptical doesn't send speed so i have to calculate this based on cadence
/*
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),
0 /* not useful for elliptical*/);
Speed = metric::calculateSpeedFromPower(m_watt.value(), Inclination.value());
}
index += 2;
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
@@ -202,7 +191,7 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
}
if (Flags.instantCadence) {
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
if (settings.value(QStringLiteral("cadence_sensor_name"), QStringLiteral("Disabled"))
.toString()
.startsWith(QStringLiteral("Disabled"))) {
Cadence = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
@@ -210,7 +199,7 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
2.0;
// this elliptical doesn't send speed so i have to calculate this based on cadence
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
if (!settings.value(QStringLiteral("speed_power_based"), false).toBool()) {
Speed = Cadence.value() / 10.0;
}
}
@@ -249,7 +238,7 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
}
if (Flags.instantPower) {
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
if (settings.value(QStringLiteral("power_sensor_name"), QStringLiteral("Disabled"))
.toString()
.startsWith(QStringLiteral("Disabled")))
m_watt = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
@@ -278,8 +267,8 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
} 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(QStringLiteral("weight"), 75.0).toFloat() *
3.5) /
200.0) /
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
@@ -289,7 +278,7 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
if (settings.value("ant_heart", false).toBool())
Heart = (uint8_t)KeepAwakeHelper::heart();
else
#endif
@@ -339,10 +328,10 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
/*
bool cadence = settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround = settings.value(QZSettings::ios_peloton_workaround,
QZSettings::default_ios_peloton_workaround).toBool(); if (ios_peloton_workaround && cadence && h &&
firstStateChanged) { h->virtualTreadmill_setCadence(currentCrankRevolutions(), lastCrankEventTime());
bool cadence = settings.value("bike_cadence_sensor", false).toBool();
bool ios_peloton_workaround = settings.value("ios_peloton_workaround", true).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->virtualTreadmill_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualTreadmill_setHeartRate((uint8_t)metrics_override_heartrate());
}
*/
@@ -438,7 +427,7 @@ void bhfitnesselliptical::stateChanged(QLowEnergyService::ServiceState state) {
}
// ******************************************* virtual bike init *************************************
if (!firstStateChanged
if (!firstStateChanged && !virtualTreadmill
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
&& !h
@@ -446,53 +435,25 @@ void bhfitnesselliptical::stateChanged(QLowEnergyService::ServiceState state) {
#endif
) {
QSettings settings;
if (!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_force_bike) {
debug("creating virtual treadmill interface...");
virtualTreadmill = new virtualtreadmill(this, noHeartService);
connect(virtualTreadmill, &virtualtreadmill::debug, this, &bhfitnesselliptical::debug);
connect(virtualTreadmill, &virtualtreadmill::changeInclination, this,
&bhfitnesselliptical::changeInclinationRequested);
} else {
debug("creating virtual bike interface...");
virtualBike = new virtualbike(this);
connect(virtualBike, &virtualbike::changeInclination, this,
&bhfitnesselliptical::changeInclinationRequested);
connect(virtualBike, &virtualbike::changeInclination, this,
&bhfitnesselliptical::changeInclination);
connect(virtualBike, &virtualbike::ftmsCharacteristicChanged, this,
&bhfitnesselliptical::ftmsCharacteristicChanged);
}
}
bool virtual_device_enabled = settings.value(QStringLiteral("virtual_device_enabled"), true).toBool();
if (virtual_device_enabled) {
emit debug(QStringLiteral("creating virtual treadmill interface..."));
virtualTreadmill = new virtualtreadmill(this, noHeartService);
// connect(virtualTreadmill,&virtualTreadmill::debug ,this,&bhfitnesselliptical::debug);
connect(virtualTreadmill, &virtualtreadmill::changeInclination, this,
&bhfitnesselliptical::changeInclination);
}
}
firstStateChanged = 1;
// ********************************************************************************************************
}
void bhfitnesselliptical::changeInclinationRequested(double grade, double percentage) {
if (percentage < 0)
percentage = 0;
changeInclination(grade, percentage);
}
void bhfitnesselliptical::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic,
const QByteArray &newValue) {
QByteArray b = newValue;
if (gattWriteCharControlPointId.isValid()) {
qDebug() << "routing FTMS packet to the bike from virtualBike" << characteristic.uuid() << newValue.toHex(' ');
// handling reading current resistance
if (b.at(0) == 0x11) {
int16_t slope = (((uint8_t)b.at(3)) + (b.at(4) << 8));
Resistance = (slope / 33) + default_resistance;
}
qDebug() << "routing FTMS packet to the bike from virtualTreadmill" << characteristic.uuid()
<< newValue.toHex(' ');
gattFTMSService->writeCharacteristic(gattWriteCharControlPointId, b);
}

View File

@@ -27,7 +27,6 @@
#include <QString>
#include "elliptical.h"
#include "virtualbike.h"
#include "virtualtreadmill.h"
#ifdef Q_OS_IOS
@@ -49,11 +48,10 @@ class bhfitnesselliptical : public elliptical {
bool wait_for_response = false);
void startDiscover();
uint16_t watts();
void forceResistance(resistance_t requestResistance);
void forceResistance(int8_t requestResistance);
QTimer *refresh;
virtualtreadmill *virtualTreadmill = nullptr;
virtualbike *virtualBike = nullptr;
QList<QLowEnergyService *> gattCommunicationChannelService;
QLowEnergyCharacteristic gattWriteCharControlPointId;
@@ -65,8 +63,6 @@ class bhfitnesselliptical : public elliptical {
uint8_t firstStateChanged = 0;
uint8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
const uint8_t max_resistance = 72; // 24;
const uint8_t default_resistance = 6;
bool initDone = false;
bool initRequest = false;
@@ -100,7 +96,6 @@ class bhfitnesselliptical : public elliptical {
void update();
void error(QLowEnergyController::Error err);
void errorService(QLowEnergyService::ServiceError);
void changeInclinationRequested(double grade, double percentage);
void ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
};

View File

@@ -5,7 +5,7 @@
bike::bike() { elapsed.setType(metric::METRIC_ELAPSED); }
void bike::changeResistance(resistance_t resistance) {
void bike::changeResistance(int8_t resistance) {
lastRawRequestedResistanceValue = resistance;
if (autoResistanceEnable) {
double v = (resistance * m_difficult) + gears();
@@ -24,7 +24,7 @@ void bike::changeInclination(double grade, double percentage) {
}
// originally made for renphobike, but i guess it could be very generic
uint16_t bike::powerFromResistanceRequest(resistance_t requestResistance) {
uint16_t bike::powerFromResistanceRequest(int8_t requestResistance) {
// this bike has resistance level to N.m so the formula is Power (kW) = Torque (N.m) x Speed (RPM) / 9.5488
double cadence = RequestedCadence.value();
if (cadence <= 0)
@@ -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(QStringLiteral("virtualbike_forceresistance"), true).toBool();
// bool erg_mode = settings.value(QStringLiteral("zwift_erg"), false).toBool(); //Not used anywhere in code
double erg_filter_upper = settings.value(QStringLiteral("zwift_erg_filter"), 0.0).toDouble();
double erg_filter_lower = settings.value(QStringLiteral("zwift_erg_filter_down"), 0.0).toDouble();
double zwift_erg_resistance_up = settings.value(QStringLiteral("zwift_erg_resistance_up"), 999.0).toDouble();
double zwift_erg_resistance_down = settings.value(QStringLiteral("zwift_erg_resistance_down"), 0.0).toDouble();
double deltaDown = wattsMetric().value() - ((double)power);
double deltaUp = ((double)power) - wattsMetric().value();
@@ -59,13 +52,13 @@ void bike::changePower(int32_t power) {
QString::number(erg_filter_upper) + " " + QString::number(erg_filter_lower);
if (!ergModeSupported && force_resistance /*&& erg_mode*/ &&
(deltaUp > erg_filter_upper || deltaDown > erg_filter_lower)) {
resistance_t r = (resistance_t)resistanceFromPowerRequest(power);
int8_t r = (int8_t)resistanceFromPowerRequest(power);
if ((double)r > zwift_erg_resistance_up) {
qDebug() << "zwift_erg_resistance_up filter enabled!";
r = (resistance_t)zwift_erg_resistance_up;
r = (int8_t)zwift_erg_resistance_up;
} else if ((double)r < zwift_erg_resistance_down) {
qDebug() << "zwift_erg_resistance_down filter enabled!";
r = (resistance_t)zwift_erg_resistance_down;
r = (int8_t)zwift_erg_resistance_down;
}
changeResistance(r); // resistance start from 1
}
@@ -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);
}
@@ -93,10 +84,10 @@ uint8_t bike::fanSpeed() { return FanSpeed; }
bool bike::connected() { return false; }
uint16_t bike::watts() { return 0; }
metric bike::pelotonResistance() { return m_pelotonResistance; }
resistance_t bike::pelotonToBikeResistance(int pelotonResistance) { return pelotonResistance; }
resistance_t bike::resistanceFromPowerRequest(uint16_t power) { return power / 10; } // in order to have something
int bike::pelotonToBikeResistance(int pelotonResistance) { return pelotonResistance; }
uint8_t bike::resistanceFromPowerRequest(uint16_t power) { return power / 10; } // in order to have something
void bike::cadenceSensor(uint8_t cadence) { Cadence.setValue(cadence); }
void bike::powerSensor(uint16_t power) { m_watt.setValue(power, false); }
void bike::powerSensor(uint16_t power) { m_watt.setValue(power); }
bluetoothdevice::BLUETOOTH_TYPE bike::deviceType() { return bluetoothdevice::BIKE; }
@@ -171,7 +162,7 @@ uint8_t bike::metrics_override_heartrate() {
QSettings settings;
QString setting =
settings.value(QZSettings::peloton_heartrate_metric, QZSettings::default_peloton_heartrate_metric).toString();
settings.value(QStringLiteral("peloton_heartrate_metric"), QStringLiteral("Heart Rate")).toString();
if (!setting.compare(QStringLiteral("Heart Rate"))) {
return qRound(currentHeart().value());
} else if (!setting.compare(QStringLiteral("Speed"))) {
@@ -249,34 +240,4 @@ uint8_t bike::metrics_override_heartrate() {
return qRound(currentHeart().value());
}
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;
}
bool bike::inclinationAvailableByHardware() {return false;}

View File

@@ -20,9 +20,9 @@ class bike : public bluetoothdevice {
virtual uint16_t lastCrankEventTime();
virtual bool connected();
virtual uint16_t watts();
virtual resistance_t pelotonToBikeResistance(int pelotonResistance);
virtual resistance_t resistanceFromPowerRequest(uint16_t power);
virtual uint16_t powerFromResistanceRequest(resistance_t requestResistance);
virtual int pelotonToBikeResistance(int pelotonResistance);
virtual uint8_t resistanceFromPowerRequest(uint16_t power);
virtual uint16_t powerFromResistanceRequest(int8_t requestResistance);
virtual bool ergManagedBySS2K() { return false; }
bluetoothdevice::BLUETOOTH_TYPE deviceType();
metric pelotonResistance();
@@ -32,20 +32,11 @@ class bike : public bluetoothdevice {
uint8_t metrics_override_heartrate();
void setGears(int8_t d);
int8_t gears();
void setSpeedLimit(double speed) {m_speedLimit = speed;}
double speedLimit() {return m_speedLimit;}
/**
* @brief currentSteeringAngle Gets a metric object to get or set the current steering angle
* for the Elite Sterzo or emulating device. Expected range -45 to +45 degrees.
* @return A metric object.
*/
metric currentSteeringAngle() { return m_steeringAngle; }
virtual bool inclinationAvailableByHardware();
public Q_SLOTS:
virtual void changeResistance(resistance_t res);
virtual void changeResistance(int8_t res);
virtual void changeCadence(int16_t cad);
virtual void changePower(int32_t power);
virtual void changeRequestedPelotonResistance(int8_t resistance);
@@ -53,12 +44,12 @@ class bike : public bluetoothdevice {
virtual void powerSensor(uint16_t power);
virtual void changeInclination(double grade, double percentage);
virtual void changeSteeringAngle(double angle) { m_steeringAngle = angle; }
virtual void resistanceFromFTMSAccessory(resistance_t res) { Q_UNUSED(res); }
virtual void resistanceFromFTMSAccessory(int8_t res) { Q_UNUSED(res); }
Q_SIGNALS:
void bikeStarted();
void resistanceChanged(resistance_t resistance);
void resistanceRead(resistance_t resistance);
void resistanceChanged(int8_t resistance);
void resistanceRead(int8_t resistance);
void steeringAngleChanged(double angle);
protected:
@@ -67,7 +58,7 @@ class bike : public bluetoothdevice {
metric RequestedCadence;
metric RequestedPower;
resistance_t requestResistance = -1;
int8_t requestResistance = -1;
double requestInclination = -100;
int16_t requestPower = -1;
@@ -75,17 +66,13 @@ class bike : public bluetoothdevice {
// request there is no need to translate in resistance levels
int8_t m_gears = 0;
resistance_t lastRawRequestedResistanceValue = -1;
int8_t lastRawRequestedResistanceValue = -1;
uint16_t LastCrankEventTime = 0;
double CrankRevs = 0;
metric m_pelotonResistance;
metric m_steeringAngle;
double m_speedLimit = 0;
uint16_t wattFromHR(bool useSpeedAndCadence);
};
#endif // BIKE_H

File diff suppressed because it is too large Load Diff

View File

@@ -17,19 +17,12 @@
#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"
@@ -44,7 +37,6 @@
#include "eslinkertreadmill.h"
#include "fakebike.h"
#include "fakeelliptical.h"
#include "faketreadmill.h"
#include "fitmetria_fanfit.h"
#include "fitplusbike.h"
@@ -60,29 +52,20 @@
#include "keepbike.h"
#include "kingsmithr1protreadmill.h"
#include "kingsmithr2treadmill.h"
#include "lifefitnesstreadmill.h"
#include "m3ibike.h"
#include "mcfbike.h"
#include "mepanelbike.h"
#include "nautilusbike.h"
#include "nautiluselliptical.h"
#include "nautilustreadmill.h"
#include "nordictrackelliptical.h"
#include "nordictrackifitadbbike.h"
#include "nordictrackifitadbtreadmill.h"
#include "npecablebike.h"
#include "octaneelliptical.h"
#include "octanetreadmill.h"
#include "pafersbike.h"
#include "paferstreadmill.h"
#include "pelotonbike.h"
#include "proformbike.h"
#include "proformelliptical.h"
#include "proformellipticaltrainer.h"
#include "proformrower.h"
#include "proformtreadmill.h"
#include "proformwifibike.h"
#include "proformwifitreadmill.h"
#include "schwinnic4bike.h"
#include "signalhandler.h"
#include "skandikawiribike.h"
@@ -115,20 +98,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 +118,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;
@@ -170,16 +143,9 @@ class bluetooth : public QObject, public SignalHandler {
echelonconnectsport *echelonConnectSport = nullptr;
yesoulbike *yesoulBike = nullptr;
flywheelbike *flywheelBike = nullptr;
nordictrackelliptical *nordictrackElliptical = nullptr;
nordictrackifitadbtreadmill *nordictrackifitadbTreadmill = nullptr;
nordictrackifitadbbike *nordictrackifitadbBike = nullptr;
octaneelliptical *octaneElliptical = nullptr;
octanetreadmill *octaneTreadmill = nullptr;
pelotonbike *pelotonBike = nullptr;
proformrower *proformRower = nullptr;
proformbike *proformBike = nullptr;
proformwifibike *proformWifiBike = nullptr;
proformwifitreadmill *proformWifiTreadmill = nullptr;
proformelliptical *proformElliptical = nullptr;
proformellipticaltrainer *proformEllipticalTrainer = nullptr;
proformtreadmill *proformTreadmill = nullptr;
@@ -197,7 +163,6 @@ class bluetooth : public QObject, public SignalHandler {
snodebike *snodeBike = nullptr;
eslinkertreadmill *eslinkerTreadmill = nullptr;
m3ibike *m3iBike = nullptr;
mepanelbike *mepanelBike = nullptr;
skandikawiribike *skandikaWiriBike = nullptr;
cscbike *cscBike = nullptr;
mcfbike *mcfBike = nullptr;
@@ -212,7 +177,6 @@ class bluetooth : public QObject, public SignalHandler {
ftmsrower *ftmsRower = nullptr;
smartrowrower *smartrowRower = nullptr;
echelonstride *echelonStride = nullptr;
lifefitnesstreadmill *lifefitnessTreadmill = nullptr;
keepbike *keepBike = nullptr;
kingsmithr1protreadmill *kingsmithR1ProTreadmill = nullptr;
kingsmithr2treadmill *kingsmithR2Treadmill = nullptr;
@@ -230,15 +194,12 @@ 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;
fakebike *fakeBike = nullptr;
fakeelliptical *fakeElliptical = nullptr;
faketreadmill *fakeTreadmill = nullptr;
QList<fitmetria_fanfit *> fitmetriaFanfit;
QList<wahookickrheadwind *> wahookickrHeadWind;
QString filterDevice = QLatin1String("");
bool testResistance = false;
@@ -251,16 +212,6 @@ class bluetooth : public QObject, public SignalHandler {
double bikeResistanceGain = 1.0;
bool forceHeartBeltOffForTimeout = false;
/**
* @brief Start the Bluetooth discovery agent.
*/
void startDiscovery();
/**
* @brief Stop the Bluetooth discovery agent.
*/
void stopDiscovery();
bool handleSignal(int signal) override;
void stateFileUpdate();
void stateFileRead();
@@ -270,20 +221,12 @@ class bluetooth : public QObject, public SignalHandler {
bool powerSensorAvaiable();
bool eliteRizerAvaiable();
bool eliteSterzoSmartAvaiable();
bool fitmetriaFanfitAvaiable();
bool fitmetria_fanfit_isconnected(QString name);
#ifdef Q_OS_WIN
QTimer discoveryTimeout;
#endif
/**
* @brief Store the name and other info in the settings.
* @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 +237,9 @@ class bluetooth : public QObject, public SignalHandler {
void restart();
void debug(const QString &string);
void heartRate(uint8_t heart);
void deviceDiscovered(const QBluetoothDeviceInfo &device);
private slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
void deviceUpdated(const QBluetoothDeviceInfo &device, QBluetoothDeviceInfo::Fields updateFields);
#endif

View File

@@ -1,6 +1,5 @@
#include "bluetoothdevice.h"
#include <QFile>
#include <QSettings>
#include <QTime>
@@ -8,11 +7,7 @@ bluetoothdevice::bluetoothdevice() {}
bluetoothdevice::BLUETOOTH_TYPE bluetoothdevice::deviceType() { return bluetoothdevice::UNKNOWN; }
void bluetoothdevice::start() { requestStart = 1; }
void bluetoothdevice::stop(bool pause) {
requestStop = 1;
if (pause)
requestPause = 1;
}
void bluetoothdevice::stop() { requestStop = 1; }
metric bluetoothdevice::currentHeart() { return Heart; }
metric bluetoothdevice::currentSpeed() { return Speed; }
metric bluetoothdevice::currentInclination() { return Inclination; }
@@ -34,7 +29,7 @@ metric bluetoothdevice::currentResistance() { return Resistance; }
metric bluetoothdevice::currentCadence() { return Cadence; }
double bluetoothdevice::currentCrankRevolutions() { return 0; }
uint16_t bluetoothdevice::lastCrankEventTime() { return 0; }
void bluetoothdevice::changeResistance(resistance_t resistance) {}
void bluetoothdevice::changeResistance(int8_t resistance) {}
void bluetoothdevice::changePower(int32_t power) {}
void bluetoothdevice::changeInclination(double grade, double percentage) {}
@@ -42,7 +37,7 @@ void bluetoothdevice::offsetElapsedTime(int offset) { elapsed += offset; }
QTime bluetoothdevice::currentPace() {
QSettings settings;
bool miles = settings.value(QZSettings::miles_unit, QZSettings::default_miles_unit).toBool();
bool miles = settings.value(QStringLiteral("miles_unit"), false).toBool();
double unit_conversion = 1.0;
if (miles) {
unit_conversion = 0.621371;
@@ -59,7 +54,7 @@ QTime bluetoothdevice::currentPace() {
QTime bluetoothdevice::averagePace() {
QSettings settings;
bool miles = settings.value(QZSettings::miles_unit, QZSettings::default_miles_unit).toBool();
bool miles = settings.value(QStringLiteral("miles_unit"), false).toBool();
double unit_conversion = 1.0;
if (miles) {
unit_conversion = 0.621371;
@@ -76,7 +71,7 @@ QTime bluetoothdevice::averagePace() {
QTime bluetoothdevice::maxPace() {
QSettings settings;
bool miles = settings.value(QZSettings::miles_unit, QZSettings::default_miles_unit).toBool();
bool miles = settings.value(QStringLiteral("miles_unit"), false).toBool();
double unit_conversion = 1.0;
if (miles) {
unit_conversion = 0.621371;
@@ -124,18 +119,9 @@ 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) }
void bluetoothdevice::instantaneousStrideLengthSensor(double length) { Q_UNUSED(length); }
void bluetoothdevice::groundContactSensor(double groundContact) { Q_UNUSED(groundContact); }
void bluetoothdevice::verticalOscillationSensor(double verticalOscillation) { Q_UNUSED(verticalOscillation); }
double bluetoothdevice::calculateMETS() { return ((0.048 * m_watt.value()) + 1.19); }
@@ -145,19 +131,17 @@ void bluetoothdevice::update_metrics(bool watt_calc, const double watts) {
QDateTime current = QDateTime::currentDateTime();
double deltaTime = (((double)_lastTimeUpdate.msecsTo(current)) / ((double)1000.0));
QSettings settings;
bool power_as_bike =
settings.value(QZSettings::power_sensor_as_bike, QZSettings::default_power_sensor_as_bike).toBool();
bool power_as_treadmill =
settings.value(QZSettings::power_sensor_as_treadmill, QZSettings::default_power_sensor_as_treadmill).toBool();
bool power_as_bike = settings.value(QStringLiteral("power_sensor_as_bike"), false).toBool();
bool power_as_treadmill = settings.value(QStringLiteral("power_sensor_as_treadmill"), false).toBool();
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
if (settings.value(QStringLiteral("power_sensor_name"), QStringLiteral("Disabled"))
.toString()
.startsWith(QStringLiteral("Disabled")) == false &&
!power_as_bike && !power_as_treadmill)
watt_calc = false;
if (!_firstUpdate && !paused) {
if (currentSpeed().value() > 0.0 || settings.value(QZSettings::continuous_moving, true).toBool()) {
if (currentSpeed().value() > 0.0 || settings.value(QStringLiteral("continuous_moving"), true).toBool()) {
elapsed += deltaTime;
}
@@ -169,21 +153,18 @@ void bluetoothdevice::update_metrics(bool watt_calc, const double watts) {
if (watt_calc) {
m_watt = watts;
}
WattKg = m_watt.value() / settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
WattKg = m_watt.value() / settings.value(QStringLiteral("weight"), 75.0).toFloat();
} else if (m_watt.value() > 0) {
if (watt_calc) {
m_watt = 0;
}
m_watt = 0;
WattKg = 0;
}
} else if (paused && settings.value(QZSettings::instant_power_on_pause, QZSettings::default_instant_power_on_pause)
.toBool()) {
} else if (paused && settings.value(QStringLiteral("instant_power_on_pause"), false).toBool()) {
// useful for FTP test
if (watt_calc) {
m_watt = watts;
}
WattKg = m_watt.value() / settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
WattKg = m_watt.value() / settings.value(QStringLiteral("weight"), 75.0).toFloat();
} else if (m_watt.value() > 0) {
m_watt = 0;
@@ -276,13 +257,13 @@ QStringList bluetoothdevice::metrics() {
return r;
}
resistance_t bluetoothdevice::maxResistance() { return 100; }
uint8_t bluetoothdevice::maxResistance() { return 100; }
uint8_t bluetoothdevice::metrics_override_heartrate() {
QSettings settings;
QString setting =
settings.value(QZSettings::peloton_heartrate_metric, QZSettings::default_peloton_heartrate_metric).toString();
settings.value(QStringLiteral("peloton_heartrate_metric"), QStringLiteral("Heart Rate")).toString();
if (!setting.compare(QStringLiteral("Heart Rate"))) {
return currentHeart().value();
} else if (!setting.compare(QStringLiteral("Speed"))) {
@@ -361,19 +342,16 @@ uint8_t bluetoothdevice::metrics_override_heartrate() {
return currentHeart().value();
}
void bluetoothdevice::changeGeoPosition(QGeoCoordinate p, double azimuth, double avgAzimuthNext300Meters) {
void bluetoothdevice::changeGeoPosition(QGeoCoordinate p, double azimuth) {
coordinateTS = QDateTime::currentMSecsSinceEpoch();
coordinateOdometer = odometer();
coordinate = p;
this->setAverageAzimuthNext300m(avgAzimuthNext300Meters);
this->azimuth = azimuth;
}
QGeoCoordinate bluetoothdevice::currentCordinate() {
if (coordinateTS) {
double distance = odometer() - coordinateOdometer;
QGeoCoordinate c = coordinate.atDistanceAndAzimuth(distance * 1000.0, this->azimuth);
c.setAltitude(coordinate.altitude());
// qDebug() << "currentCordinate" << c << distance << currentSpeed().value();
double distance = currentSpeed().value() * ((QDateTime::currentMSecsSinceEpoch() - coordinateTS) / 3600.0);
QGeoCoordinate c = coordinate.atDistanceAndAzimuth(distance, this->azimuth);
qDebug() << "currentCordinate" << c << distance << currentSpeed().value();
return c;
}
return coordinate;
@@ -381,12 +359,3 @@ QGeoCoordinate bluetoothdevice::currentCordinate() {
void bluetoothdevice::workoutEventStateChanged(bluetoothdevice::WORKOUT_EVENT_STATE state) { lastState = state; }
void bluetoothdevice::setInclination(double inclination) { Inclination = inclination; }
void bluetoothdevice::setGPXFile(QString filename) {
gpxFileName = filename;
QFile input(filename);
if (input.open(QIODevice::ReadOnly)) {
QByteArray asSaved = input.readAll();
gpxBase64 = "data:@file/xml;base64," + asSaved.toBase64();
input.close();
}
}

View File

@@ -1,10 +1,7 @@
#ifndef BLUETOOTHDEVICE_H
#define BLUETOOTHDEVICE_H
#include "definitions.h"
#include "metric.h"
#include "qzsettings.h"
#include <QBluetoothDeviceDiscoveryAgent>
#include <QBluetoothDeviceInfo>
#include <QDateTime>
@@ -28,397 +25,77 @@
#define SAME_BLUETOOTH_DEVICE(d1, d2) (d1.address() == d2.address())
#endif
/**
* @brief The MetersByInclination class represents a section of track at a specific inclination.
*/
class MetersByInclination {
public:
/**
* @brief meters The length of the section. Units: meters
*/
double meters;
/**
* @brief inclination The inclination of the section.
* Units: Percentage vertical to horizontal
*/
double inclination;
};
class bluetoothdevice : public QObject {
Q_OBJECT
public:
bluetoothdevice();
/**
* @brief currentHeart Gets a metric object for getting and setting the current heart rate. Units: beats per minute
*/
virtual metric currentHeart();
/**
* @brief currentSpeed Gets a metric object for getting and setting the speed. Units: km/h
*/
virtual metric currentSpeed();
/**
* @brief currentPace Gets the current pace. Units: time per km
*/
virtual QTime currentPace();
/**
* @brief currentInclination The current inclination.
* Units: Percentage vertical to horizontal
* Expected range: Depends on device.
*/
virtual metric currentInclination();
/**
* @brief setInclination Set the protected Inclination metric, which could be different from that
* returned by an overridden currentInclination().
* @param inclination The inclination. Units: Percentage vertical to horizontal. Expected range: Depends on device.
*/
void setInclination(double inclination);
/**
* @brief averagePace Gets the average time per kilometer travelled. Units: time per km
*/
virtual QTime averagePace();
/**
* @brief maxPace Gets the maximum pace (minimum time per kilometer) for the session. Units: time per km
*/
virtual QTime maxPace();
/**
* @brief odometer Gets the total distance travelled. Units: km
* @return
*/
virtual double odometer();
/**
* @brief calories Gets a metric object to get and set the amount of energy expended.
* Default implementation returns the protected KCal property. Units: kcal
* Other implementations could have different units.
* @return
*/
virtual metric calories();
/**
* @brief jouls Gets a metric object to get and set the number of joules expended. Units: joules
*/
metric jouls();
/**
* @brief fanSpeed Gets the current fan speed. Units: depends on device
*/
virtual uint8_t fanSpeed();
/**
* @brief elapsedTime The elapsed time for the session(?).
*/
virtual QTime elapsedTime();
/**
* @brief offsetElapsedTime Shifts the elapsed time (stored in the protected member: elapsed)
* for the session by the specified offset.
* @param offset The time offset to shift by. Default unit: seconds but this could be overridden.
*/
virtual void offsetElapsedTime(int offset);
/**
* @brief movingTime Gets the time spent moving.
*/
virtual QTime movingTime();
/**
* @brief lapElapsedTime Gets the time elapsed on the current lap.
*/
virtual QTime lapElapsedTime();
/**
* @brief connected Gets a value to indicate if the device is connected.
*/
virtual bool connected();
/**
* @brief currentResistance Gets a metric object to get or set the currently requested resistance.
* Expected range: 0 to maxResistance()
*/
virtual metric currentResistance();
/**
* @brief currentCadence Gets a metric object to get and set the current cadence. Units: revolutions per minute
*/
virtual metric currentCadence();
/**
* @brief currentCrankRevolutions Gets the current total number of crank revolutions.
*/
virtual double currentCrankRevolutions();
/**
* @brief currentCordinate Gets the current geographic coordinates.
*/
virtual QGeoCoordinate currentCordinate();
/**
* @brief nextInclination300Meters The next 300m of track sections: length and inclination
* @return A list of MetersByInclination objects
*/
virtual QList<MetersByInclination> nextInclination300Meters() { return NextInclination300Meters; }
/**
* @brief currentAzimuth Gets the current azimuth. Units: degrees (? = North)
*/
virtual double currentAzimuth() { return azimuth; }
/**
* @brief averageAzimuthNext300m Gets the average azimuth for the next 300m
* Units: degrees (? = North)
*/
virtual double averageAzimuthNext300m() { return azimuthAvgNext300m; }
/**
* @brief setAverageAzimuthNext300m Sets the average azimuth for the next 300m.
* @param azimuth The azimuth. Units: degrees (? = North)
*/
virtual void setAverageAzimuthNext300m(double azimuth) { azimuthAvgNext300m = azimuth; }
/**
* @brief lastCrankEventTime The time of the last crank event. Units: 1/1024s
*/
virtual uint16_t lastCrankEventTime();
/**
* @brief VirtualDevice The virtual bridge to Zwift for example, or to any 3rd party app.
*/
virtual void *VirtualDevice();
/**
* @brief watts Calculates the amount of power used. Units: watts
* @param weight The weight of the rider. Units: kg
*/
uint16_t watts(double weight);
/**
* @brief wattsMetric Gets a metric object to get or set the amount of power used. Units: watts
*/
metric wattsMetric();
/**
* @brief changeFanSpeed Tries to change the fan speed.
* @param speed The requested fan speed. Units: depends on device
*/
virtual bool changeFanSpeed(uint8_t speed);
/**
* @brief elevationGain Gets a metric object to get and set the elevation gain. Units: ?
*/
virtual metric elevationGain();
/**
* @brief clearStats Clear the statistics.
*/
virtual void clearStats();
/**
* @brief bluetoothDevice The bluetooth device information.
*/
QBluetoothDeviceInfo bluetoothDevice;
/**
* @brief disconnectBluetooth Disconnect from the device from bluetooth.
*/
void disconnectBluetooth();
/**
* @brief setPaused Sets the paused mode.
* @param p True to pause, false to resume.
*/
virtual void setPaused(bool p);
/**
* @brief isPaused Indicates if the device is currently paused.
*/
bool isPaused() { return paused; }
/**
* @brief setLap Begins a new lap for the statistics calculated by the metrics objects.
*/
virtual void setLap();
/**
* @brief setAutoResistance Toggles auto-resistance.
*/
void setAutoResistance(bool value) { autoResistanceEnable = value; }
/**
* @brief autoResistance Indicates the state of auto-resistance.
* @return
*/
bool autoResistance() { return autoResistanceEnable; }
/**
* @brief setDifficult Sets the difficulty gain 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.
* @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
*/
double weightLoss() { return WeightLoss.value(); }
/**
* @brief wattKg Gets a metric object to get and set the watt kg of something. Units: watt kg
* @return
*/
metric wattKg() { return WattKg; }
/**
* @brief currentMETS Gets a metric object to get and set the current METS (Metabolic Equivalent of Tasks)
* Units: METs (1 MET is approximately 3.5mL of Oxygen consumed per kg of body weight per minute)
*/
metric currentMETS() { return METS; }
/**
* @brief currentHeartZone Gets a metric object to get or set the current heart zone. Units: depends on
* implementation.
*/
metric currentHeartZone() { return HeartZone; }
/**
* @brief currentPowerZone Gets a metric object to get or set the current power zome. Units: depends on
* implementation.
* @return
*/
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.
*/
void setGPXFile(QString filename);
/**
* @brief currentGPXBase64 Returns the Base64 encode for the current GPX used.
*/
QString currentGPXBase64() { return gpxBase64; }
// in the future these 2 should be calculated inside the update_metrics()
/**
* @brief setHeartZone Set the current heart zone.
* This is equivalent to currentHeartZone().setvalue(hz)
* @param hz The heart zone. Unit: depends on implementation.
*/
void setHeartZone(double hz) { HeartZone = hz; }
/**
* @brief setPowerZone Set the current power zone.
* This is equivalent to currentPowerZone().setvalue(pz)
* @param pz The power zone. Unit: depends on implementation.
*/
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 };
/**
* @brief deviceType The type of device represented by this object.
*/
virtual BLUETOOTH_TYPE deviceType();
/**
* @brief metrics Gets a list of available metrics.
* @return
*/
static QStringList metrics();
/**
* @brief metrics_override_heartrate Provides a way to override the metrics heart rate with another metric.
* Units: beats per minute
*/
virtual uint8_t metrics_override_heartrate();
/**
* @brief Overridden in subclasses to specify the maximum resistance level supported by the device.
*/
virtual resistance_t maxResistance();
virtual uint8_t maxResistance();
public Q_SLOTS:
virtual void start();
virtual void stop(bool pause);
virtual void stop();
virtual void heartRate(uint8_t heart);
virtual void cadenceSensor(uint8_t cadence);
virtual void powerSensor(uint16_t power);
virtual void speedSensor(double speed);
virtual void changeResistance(resistance_t res);
virtual void changeResistance(int8_t res);
virtual void changePower(int32_t power);
virtual void changeInclination(double grade, double percentage);
virtual void changeGeoPosition(QGeoCoordinate p, double azimuth, double avgAzimuthNext300Meters);
virtual void changeGeoPosition(QGeoCoordinate p, double azimuth);
virtual void workoutEventStateChanged(bluetoothdevice::WORKOUT_EVENT_STATE state);
virtual void instantaneousStrideLengthSensor(double length);
virtual void groundContactSensor(double groundContact);
virtual void verticalOscillationSensor(double verticalOscillation);
virtual void changeNextInclination300Meters(QList<MetersByInclination> i) { NextInclination300Meters = i; }
Q_SIGNALS:
void connectedAndDiscovered();
@@ -427,216 +104,47 @@ class bluetoothdevice : public QObject {
void powerChanged(uint16_t power);
void inclinationChanged(double grade, double percentage);
void fanSpeedChanged(uint8_t speed);
void instantaneousStrideLengthChanged(double length);
void groundContactChanged(double groundContact);
void verticalOscillationChanged(double verticalOscillation);
protected:
QLowEnergyController *m_control = nullptr;
/**
* @brief elapsed A metric object to get and set the elapsed time for the session. Units: seconds
*/
metric elapsed;
/**
* @brief moving The time spent moving in the current session. Units: seconds
*/
metric moving; // moving time
/**
* @brief KCal The number of kilocalories expended in the session. Units: kcal
*/
metric KCal;
/**
* @brief Speed The simulated speed of the device. Units: km/h
* e.g. the product of bike flywheel speed and simulated wheel size, or
* the belt speed of a treadmill.
*/
metric Speed;
/**
* @brief Distance The simulated distance travelled. Units: km
* Depends on the device.
* e.g. the number of bike flywheel revolutions multiplied by the simulated wheel circumference, or
* the length of belt traversed on a treadmill.
*/
metric KCal;
metric Distance;
/**
* @brief FanSpeed The currently requested fan speed. Units: revolutions per second
*/
uint8_t FanSpeed = 0;
/**
* @brief Heart rate. Unit: beats per minute
*/
metric Heart;
int8_t requestStart = -1;
int8_t requestStop = -1;
int8_t requestPause = -1;
int8_t requestIncreaseFan = -1;
int8_t requestDecreaseFan = -1;
double requestFanSpeed = -1;
/**
* @brief m_difficult The current difficulty gain. 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
*/
metric elevationAcc;
/**
* @brief m_watt Metric to get and set the power expended in the session. Unit: watts
*/
metric m_watt;
/**
* @brief WattKg Metric to get and set the watt kg for the session (what's this?). Unit: watt kg
*/
metric WattKg;
/**
* @brief WeightLoss Metric to get and set the lost weight in the session (?). Unit: kg
*/
metric WeightLoss;
/**
* @brief The speed at which the crank is turning. Units: device-specific actions per minute
* e.g. crank revolutions on a bike, steps on a treadmill,
* strokes on a rower, stride rate on an elliptical trainer
*/
metric Cadence;
/**
* @brief The currently requested resistance level. Expected range: 0 to maxResistance()
*/
metric Resistance;
/**
* @brief METS A metric object to get and set the METS (Metabolic Equivalent of Tasks) for the session.
* Units: METs (1 MET is approximately 3.5mL of Oxygen consumed per kg of body weight per minute)
*/
metric METS;
/**
* @brief coordinate The geolocation for the device.
*/
QGeoCoordinate coordinate;
/**
* @brief azimuth The azimuth. Units: degrees (? = North)
*/
double azimuth;
/**
* @brief azimuthAvgNext300m The average azimuth for the next 300m. Units: degrees (? = North)
*/
double azimuthAvgNext300m;
/**
* @brief coordinateTS ???. Unit: ???
*/
quint64 coordinateTS = 0;
/**
* @brief coordinateOdometer ???. Unit: ???
*/
double coordinateOdometer = 0;
/**
* @brief The currently loaded gpxBase64 GPS exchange data.
*/
QString gpxBase64 = "";
/**
* @brief gpxFileName The file path of the currently loaded GPS exchange data.
*/
QString gpxFileName = "";
/**
* @brief NextInclination300Meters A list of the length and inclination of track sections for the next 300m
*/
QList<MetersByInclination> NextInclination300Meters;
/**
* @brief Inclination A metric to get and set the currently requested inclinaton. Units: degrees (0 = horizontal)
*/
metric Inclination;
/**
* @brief HeartZone A metric to get and set the current heart zone. Unit: depends on implementation
*/
metric HeartZone;
/**
* @brief PowerZone A metric to get and set the current power zone. Unit: depends on implementation
*/
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;
/**
* @brief paused Indicates if the device is currently paused.
*/
bool paused = false;
/**
* @brief autoResistanceEnable Indicates if auto-resistance is currently enabled.
*/
bool autoResistanceEnable = true;
/**
* @brief _lastTimeUpdate The time the (client was last updated / last update was received from the device) ???
*/
QDateTime _lastTimeUpdate;
/**
* @brief _firstUpdate Indicates if this is the first update.
*/
bool _firstUpdate = true;
/**
* @brief update_metrics Updates the metrics given the specified inputs.
* @param watt_calc ??
* @param watts ?. Unit: watts
*/
void update_metrics(bool watt_calc, const double watts);
/**
* @brief calculateMETS Calculate the METS (Metabolic Equivalent of Tasks)
* Units: METs (1 MET is approximately 3.5mL of Oxygen consumed per kg of body weight per minute)
*/
double calculateMETS();
};

View File

@@ -93,8 +93,7 @@ void bowflext216treadmill::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(QStringLiteral("virtual_device_enabled"), true).toBool();
if (virtual_device_enabled) {
emit debug(QStringLiteral("creating virtual treadmill interface..."));
virtualTreadMill = new virtualtreadmill(this, noHeartService);
@@ -104,7 +103,7 @@ void bowflext216treadmill::update() {
}
// ********************************************************************************************************
update_metrics(true, watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()));
update_metrics(true, watts(settings.value(QStringLiteral("weight"), 75.0).toFloat()));
// updating the treadmill console every second
// it seems that stops the communication
@@ -125,7 +124,7 @@ void bowflext216treadmill::update() {
requestSpeed = -1;
}
if (requestInclination != -100) {
if (requestInclination < 0)
if(requestInclination < 0)
requestInclination = 0;
if (requestInclination != currentInclination().value() && requestInclination >= 0 &&
requestInclination <= 15) {
@@ -168,7 +167,7 @@ void bowflext216treadmill::characteristicChanged(const QLowEnergyCharacteristic
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
settings.value(QStringLiteral("heart_rate_belt_name"), QStringLiteral("Disabled")).toString();
Q_UNUSED(characteristic);
QByteArray value = newValue;
@@ -189,16 +188,13 @@ void bowflext216treadmill::characteristicChanged(const QLowEnergyCharacteristic
if ((newValue.length() != 20))
return;
if (bowflex_t6 == true && newValue.at(1) != 0x00)
return;
double speed = GetSpeedFromPacket(value);
double incline = GetInclinationFromPacket(value);
// double kcal = GetKcalFromPacket(value);
// double distance = GetDistanceFromPacket(value);
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
if (settings.value("ant_heart", false).toBool())
Heart = (uint8_t)KeepAwakeHelper::heart();
else
#endif
@@ -229,11 +225,10 @@ void bowflext216treadmill::characteristicChanged(const QLowEnergyCharacteristic
}
if (!firstCharacteristicChanged) {
if (watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()))
if (watts(settings.value(QStringLiteral("weight"), 75.0).toFloat()))
KCal +=
((((0.048 * ((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) +
1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
((((0.048 * ((double)watts(settings.value(QStringLiteral("weight"), 75.0).toFloat())) + 1.19) *
settings.value(QStringLiteral("weight"), 75.0).toFloat() * 3.5) /
200.0) /
(60000.0 / ((double)lastTimeCharacteristicChanged.msecsTo(
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
@@ -243,8 +238,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) {
@@ -256,15 +249,9 @@ void bowflext216treadmill::characteristicChanged(const QLowEnergyCharacteristic
}
double bowflext216treadmill::GetSpeedFromPacket(const QByteArray &packet) {
if (bowflex_t6 == false) {
uint16_t convertedData = (packet.at(7) << 8) | packet.at(6);
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);
double data = (double)convertedData / 100.0f;
return data * 1.60934;
}
uint16_t convertedData = (packet.at(7) << 8) | packet.at(6);
double data = (double)convertedData / 100.0f;
return data * 1.60934;
}
double bowflext216treadmill::GetKcalFromPacket(const QByteArray &packet) {
@@ -279,14 +266,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) {
@@ -377,16 +360,6 @@ void bowflext216treadmill::serviceScanDone(void) {
QBluetoothUuid _gattCommunicationChannelServiceId(QStringLiteral("edff9e80-cad7-11e5-ab63-0002a5d5c51b"));
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
if (gattCommunicationChannelService == nullptr) {
qDebug() << "trying with the BOWFLEX T6 treadmill";
bowflex_t6 = true;
QBluetoothUuid _gattCommunicationChannelServiceId(QStringLiteral("15B7BF49-1693-481E-B877-69D33CE6BAFA"));
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
if (gattCommunicationChannelService == nullptr) {
qDebug() << "WRONG SERVICE";
return;
}
}
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,
&bowflext216treadmill::stateChanged);
gattCommunicationChannelService->discoverDetails();

View File

@@ -79,8 +79,6 @@ class bowflext216treadmill : public treadmill {
bool initDone = false;
bool initRequest = false;
bool bowflex_t6 = false;
Q_SIGNALS:
void disconnected();
void debug(QString string);

View File

@@ -95,8 +95,7 @@ void bowflextreadmill::update() {
QSettings settings;
// ******************************************* virtual treadmill init *************************************
if (!firstInit && !virtualTreadMill) {
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
bool virtual_device_enabled = settings.value(QStringLiteral("virtual_device_enabled"), true).toBool();
if (virtual_device_enabled) {
emit debug(QStringLiteral("creating virtual treadmill interface..."));
virtualTreadMill = new virtualtreadmill(this, noHeartService);
@@ -106,7 +105,7 @@ void bowflextreadmill::update() {
}
// ********************************************************************************************************
update_metrics(true, watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()));
update_metrics(true, watts(settings.value(QStringLiteral("weight"), 75.0).toFloat()));
// updating the treadmill console every second
// it seems that stops the communication
@@ -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) {
@@ -167,7 +166,7 @@ void bowflextreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
settings.value(QStringLiteral("heart_rate_belt_name"), QStringLiteral("Disabled")).toString();
Q_UNUSED(characteristic);
QByteArray value = newValue;
@@ -184,7 +183,7 @@ void bowflextreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
// double distance = GetDistanceFromPacket(value);
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
if (settings.value("ant_heart", false).toBool())
Heart = (uint8_t)KeepAwakeHelper::heart();
else
#endif
@@ -215,11 +214,10 @@ void bowflextreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
}
if (!firstCharacteristicChanged) {
if (watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()))
if (watts(settings.value(QStringLiteral("weight"), 75.0).toFloat()))
KCal +=
((((0.048 * ((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) +
1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
((((0.048 * ((double)watts(settings.value(QStringLiteral("weight"), 75.0).toFloat())) + 1.19) *
settings.value(QStringLiteral("weight"), 75.0).toFloat() * 3.5) /
200.0) /
(60000.0 / ((double)lastTimeCharacteristicChanged.msecsTo(
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
@@ -229,8 +227,6 @@ void bowflextreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
(1000.0 / (lastTimeCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
}
cadenceFromAppleWatch();
emit debug(QStringLiteral("Current Distance Calculated: ") + QString::number(Distance.value()));
if (m_control->error() != QLowEnergyController::NoError) {

View File

@@ -4,7 +4,7 @@
CharacteristicNotifier2A5B::CharacteristicNotifier2A5B(bluetoothdevice *Bike, QObject *parent)
: CharacteristicNotifier(0x2a5b, parent), Bike(Bike) {
QSettings settings;
bike_wheel_revs = settings.value(QZSettings::bike_wheel_revs, QZSettings::default_bike_wheel_revs).toBool();
bike_wheel_revs = settings.value(QStringLiteral("bike_wheel_revs"), false).toBool();
}
int CharacteristicNotifier2A5B::notify(QByteArray &value) {

View File

@@ -8,8 +8,8 @@ CharacteristicNotifier2ACD::CharacteristicNotifier2ACD(bluetoothdevice *Bike, QO
int CharacteristicNotifier2ACD::notify(QByteArray &value) {
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL) {
value.append(0x08); // Inclination available
value.append((char)0x01); // heart rate available
value.append(0x08); // Inclination avaiable
value.append((char)0x01); // heart rate avaiable
uint16_t normalizeSpeed = (uint16_t)qRound(Bike->currentSpeed().value() * 100);
char a = (normalizeSpeed >> 8) & 0XFF;

View File

@@ -34,7 +34,7 @@ int CharacteristicNotifier2AD2::notify(QByteArray &value) {
return CN_OK;
} else if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL) {
QSettings settings;
bool double_cadence = settings.value(QZSettings::powr_sensor_running_cadence_double, QZSettings::default_powr_sensor_running_cadence_double).toBool();
bool double_cadence = settings.value(QStringLiteral("powr_sensor_running_cadence_double"), false).toBool();
double cadence_multiplier = 2.0;
if (double_cadence)
cadence_multiplier = 1.0;

View File

@@ -1,89 +0,0 @@
#include "characteristicwriteprocessor.h"
#include <QSettings>
CharacteristicWriteProcessor::CharacteristicWriteProcessor(double bikeResistanceGain, uint8_t bikeResistanceOffset,
bluetoothdevice *bike, QObject *parent)
: QObject(parent), bikeResistanceOffset(bikeResistanceOffset), bikeResistanceGain(bikeResistanceGain), Bike(bike) {}
void CharacteristicWriteProcessor::changePower(uint16_t power) { Bike->changePower(power); }
void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr, uint8_t cw) {
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
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();
bool zwift_negative_inclination_x2 =
settings.value(QZSettings::zwift_negative_inclination_x2, QZSettings::default_zwift_negative_inclination_x2)
.toBool();
double offset =
settings.value(QZSettings::zwift_inclination_offset, QZSettings::default_zwift_inclination_offset).toDouble();
double gain =
settings.value(QZSettings::zwift_inclination_gain, QZSettings::default_zwift_inclination_gain).toDouble();
double CRRGain = settings.value(QZSettings::CRRGain, QZSettings::default_CRRGain).toDouble();
double CWGain = settings.value(QZSettings::CWGain, QZSettings::default_CWGain).toDouble();
qDebug() << QStringLiteral("new requested resistance zwift erg grade ") + QString::number(iresistance) +
QStringLiteral(" enabled ") + force_resistance;
double resistance = ((double)iresistance * 1.5) / 100.0;
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
Pavement .004 .01 .008 .008
Sand .004 .01 .008 .008
Brick .0055 .01 .008 .008
Wood .0065 .01 .008 .008
Cobbles .0065 .01 .008 .008
Ice/Snow .0075 .014 .018 .018
Dirt .025 .014 .016 .018
Grass .042
*/
const double fCRR = crr / 10000.0;
const double CRR_offset = ((crr - 40) * 0.05) * CRRGain;
const double fCW = cw / 100.0;
const double CW_offset = ((crr - 40) * 0.05) * CWGain;
qDebug() << "changeSlope CRR = " << fCRR << CRR_offset << "CW = " << fCW;
if (dt == bluetoothdevice::BIKE) {
// if the bike doesn't have the inclination by hardware, i'm simulating inclination with the value received
// form Zwift
if (!((bike *)Bike)->inclinationAvailableByHardware())
Bike->setInclination(grade + CRR_offset + CW_offset);
emit changeInclination(grade, percentage);
if (force_resistance && !erg_mode) {
// same on the training program
Bike->changeResistance((resistance_t)(round(resistance * bikeResistanceGain)) + bikeResistanceOffset + 1 +
CRR_offset + CW_offset); // resistance start from 1
}
} else if (dt == bluetoothdevice::TREADMILL) {
emit changeInclination(grade, percentage);
} else if (dt == bluetoothdevice::ELLIPTICAL) {
bool inclinationAvailableByHardware = ((elliptical *)Bike)->inclinationAvailableByHardware();
qDebug() << "inclinationAvailableByHardware" << inclinationAvailableByHardware << "erg_mode" << erg_mode;
emit changeInclination(grade, percentage);
if (!inclinationAvailableByHardware) {
if (force_resistance && !erg_mode) {
// same on the training program
((elliptical *)Bike)
->changeResistance((resistance_t)(round(resistance * bikeResistanceGain)) + bikeResistanceOffset +
1 + CRR_offset + CW_offset); // resistance start from 1
}
}
}
emit slopeChanged();
}

View File

@@ -1,13 +1,7 @@
#ifndef CHARACTERISTICWRITEPROCESSOR_H
#define CHARACTERISTICWRITEPROCESSOR_H
#include "bike.h"
#include "bluetoothdevice.h"
#include "elliptical.h"
#include "treadmill.h"
#include <QObject>
#include <QSettings>
#include <QtMath>
#define CP_INVALID -1
#define CP_OK 0
@@ -15,18 +9,9 @@
class CharacteristicWriteProcessor : public QObject {
Q_OBJECT
public:
uint8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
bluetoothdevice *Bike;
explicit CharacteristicWriteProcessor(double bikeResistanceGain, uint8_t bikeResistanceOffset,
bluetoothdevice *bike, QObject *parent = nullptr);
explicit CharacteristicWriteProcessor(QObject *parent = nullptr) : QObject(parent) {}
virtual int writeProcess(quint16 uuid, const QByteArray &data, QByteArray &out) = 0;
virtual void changePower(uint16_t power);
virtual void changeSlope(int16_t iresistance, uint8_t crr, uint8_t cw);
signals:
void changeInclination(double grade, double percentage);
void slopeChanged();
};
#endif // CHARACTERISTICWRITEPROCESSOR_H

View File

@@ -9,23 +9,22 @@ CharacteristicWriteProcessor2AD9::CharacteristicWriteProcessor2AD9(double bikeRe
uint8_t bikeResistanceOffset, bluetoothdevice *bike,
CharacteristicNotifier2AD9 *notifier,
QObject *parent)
: CharacteristicWriteProcessor(bikeResistanceGain, bikeResistanceOffset, bike, parent), notifier(notifier) {}
: CharacteristicWriteProcessor(parent), bikeResistanceOffset(bikeResistanceOffset),
bikeResistanceGain(bikeResistanceGain), Bike(bike), notifier(notifier) {}
int CharacteristicWriteProcessor2AD9::writeProcess(quint16 uuid, const QByteArray &data, QByteArray &reply) {
if (data.size()) {
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
if (dt == bluetoothdevice::BIKE) {
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();
bool force_resistance = settings.value(QStringLiteral("virtualbike_forceresistance"), true).toBool();
bool erg_mode = settings.value(QStringLiteral("zwift_erg"), false).toBool();
char cmd = data.at(0);
emit ftmsCharacteristicChanged(QLowEnergyCharacteristic(), data);
if (cmd == FTMS_SET_TARGET_RESISTANCE_LEVEL) {
// Set Target Resistance
resistance_t uresistance = data.at(1);
uint8_t uresistance = data.at(1);
uresistance = uresistance / 10;
if (force_resistance && !erg_mode) {
Bike->changeResistance(uresistance);
@@ -44,9 +43,7 @@ int CharacteristicWriteProcessor2AD9::writeProcess(quint16 uuid, const QByteArra
reply.append((quint8)FTMS_SUCCESS);
int16_t iresistance = (((uint8_t)data.at(3)) + (data.at(4) << 8));
uint8_t crr = data.at(5);
uint8_t cw = data.at(6);
changeSlope(iresistance, crr, cw);
changeSlope(iresistance);
} else if (cmd == FTMS_SET_TARGET_POWER) // erg mode
{
@@ -102,12 +99,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
{
@@ -121,18 +114,66 @@ int CharacteristicWriteProcessor2AD9::writeProcess(quint16 uuid, const QByteArra
{
qDebug() << QStringLiteral("indoor bike simulation parameters");
int16_t iresistance = (((uint8_t)data.at(3)) + (data.at(4) << 8));
uint8_t crr = data.at(5);
uint8_t cw = data.at(6);
changeSlope(iresistance, crr, cw);
changeSlope(iresistance);
}
reply.append((quint8)FTMS_RESPONSE_CODE);
reply.append((quint8)data.at(0));
reply.append((quint8)FTMS_SUCCESS);
}
if (notifier) {
if(notifier) {
notifier->answer = reply;
}
return CP_OK;
} else
return CP_INVALID;
}
void CharacteristicWriteProcessor2AD9::changePower(uint16_t power) { Bike->changePower(power); }
void CharacteristicWriteProcessor2AD9::changeSlope(int16_t iresistance) {
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
if (dt == bluetoothdevice::BIKE) {
QSettings settings;
bool force_resistance = settings.value(QStringLiteral("virtualbike_forceresistance"), true).toBool();
bool erg_mode = settings.value(QStringLiteral("zwift_erg"), false).toBool();
bool zwift_negative_inclination_x2 =
settings.value(QStringLiteral("zwift_negative_inclination_x2"), false).toBool();
double offset = settings.value(QStringLiteral("zwift_inclination_offset"), 0.0).toDouble();
double gain = settings.value(QStringLiteral("zwift_inclination_gain"), 1.0).toDouble();
qDebug() << QStringLiteral("new requested resistance zwift erg grade ") + QString::number(iresistance) +
QStringLiteral(" enabled ") + force_resistance;
double resistance = ((double)iresistance * 1.5) / 100.0;
qDebug() << QStringLiteral("calculated erg grade ") + QString::number(resistance);
double grade = ((iresistance / 100.0) * gain) + offset;
// if the bike doesn't have the inclination by hardware, i'm simulating inclination with the value received form Zwift
if(!((bike*)Bike)->inclinationAvailableByHardware())
Bike->setInclination(grade);
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
Bike->changeResistance((int8_t)(round(resistance * bikeResistanceGain)) + bikeResistanceOffset +
1); // resistance start from 1
}
} else if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL) {
QSettings settings;
double offset = settings.value(QStringLiteral("zwift_inclination_offset"), 0.0).toDouble();
double gain = settings.value(QStringLiteral("zwift_inclination_gain"), 1.0).toDouble();
qDebug() << QStringLiteral("new requested resistance zwift erg grade ") + QString::number(iresistance);
double resistance = ((double)iresistance * 1.5) / 100.0;
qDebug() << QStringLiteral("calculated erg grade ") + QString::number(resistance);
emit changeInclination(((iresistance / 100.0) * gain) + offset,
((qTan(qDegreesToRadians(iresistance / 100.0)) * 100.0) * gain) + offset);
}
emit slopeChanged();
}

View File

@@ -1,19 +1,26 @@
#ifndef CHARACTERISTICWRITEPROCESSOR2AD9_H
#define CHARACTERISTICWRITEPROCESSOR2AD9_H
#include "characteristicnotifier2ad9.h"
#include "bluetoothdevice.h"
#include "characteristicwriteprocessor.h"
#include "characteristicnotifier2ad9.h"
class CharacteristicWriteProcessor2AD9 : public CharacteristicWriteProcessor {
Q_OBJECT
uint8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
bluetoothdevice *Bike;
CharacteristicNotifier2AD9 *notifier = nullptr;
public:
explicit CharacteristicWriteProcessor2AD9(double bikeResistanceGain, uint8_t bikeResistanceOffset,
bluetoothdevice *bike, CharacteristicNotifier2AD9 *notifier,
QObject *parent = nullptr);
bluetoothdevice *bike, CharacteristicNotifier2AD9 *notifier, QObject *parent = nullptr);
virtual int writeProcess(quint16 uuid, const QByteArray &data, QByteArray &out);
void changeSlope(int16_t slope);
void changePower(uint16_t power);
signals:
void changeInclination(double grade, double percentage);
void slopeChanged();
void ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
};

View File

@@ -1,49 +0,0 @@
#include "characteristicwriteprocessore005.h"
#include "elliptical.h"
#include "ftmsbike.h"
#include "treadmill.h"
#include "wahookickrsnapbike.h"
#include <QtMath>
CharacteristicWriteProcessorE005::CharacteristicWriteProcessorE005(double bikeResistanceGain,
uint8_t bikeResistanceOffset, bluetoothdevice *bike,
// CharacteristicNotifier2AD9 *notifier,
QObject *parent)
: CharacteristicWriteProcessor(bikeResistanceGain, bikeResistanceOffset, bike, parent) {}
int CharacteristicWriteProcessorE005::writeProcess(quint16 uuid, const QByteArray &data, QByteArray &reply) {
if (data.size()) {
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
if (dt == bluetoothdevice::BIKE) {
char cmd = data.at(0);
emit ftmsCharacteristicChanged(QLowEnergyCharacteristic(), data);
if (cmd == wahookickrsnapbike::_setSimMode && data.count() >= 7) {
weight = ((double)((uint16_t)data.at(1)) + (((uint16_t)data.at(2)) >> 8)) / 100.0;
rrc = ((double)((uint16_t)data.at(3)) + (((uint16_t)data.at(4)) >> 8)) / 1000.0;
wrc = ((double)((uint16_t)data.at(5)) + (((uint16_t)data.at(6)) >> 8)) / 1000.0;
qDebug() << "weigth" << weight << "rrc" << rrc << "wrc" << wrc;
} else if (cmd == wahookickrsnapbike::_setSimGrade && data.count() >= 3) {
uint16_t grade;
double fgrade;
grade = (uint16_t)((uint8_t)data.at(1)) + (((uint16_t)((uint8_t)data.at(2))) << 8);
fgrade = (((((double)grade) / 65535.0) * 2) - 1.0) * 100.0;
qDebug() << "grade" << grade << "fgrade" << fgrade;
changeSlope(fgrade * 100.0, rrc, wrc);
} else if (cmd == wahookickrsnapbike::_setErgMode && data.count() >= 3) {
uint16_t watts;
watts = (uint16_t)((uint8_t)data.at(1)) + (((uint16_t)((uint8_t)data.at(2))) << 8);
qDebug() << "erg mode" << watts;
changePower(watts);
}
} else if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL) {
}
reply.append((quint8)FTMS_RESPONSE_CODE);
reply.append((quint8)data.at(0));
reply.append((quint8)FTMS_SUCCESS);
/*if (notifier) {
notifier->answer = reply;
}*/
return CP_OK;
} else
return CP_INVALID;
}

View File

@@ -1,24 +0,0 @@
#ifndef CHARACTERISTICWRITEPROCESSORE005_H
#define CHARACTERISTICWRITEPROCESSORE005_H
#include "bluetoothdevice.h"
#include "characteristicnotifier2ad9.h"
#include "characteristicwriteprocessor.h"
class CharacteristicWriteProcessorE005 : public CharacteristicWriteProcessor {
Q_OBJECT
public:
explicit CharacteristicWriteProcessorE005(double bikeResistanceGain, uint8_t bikeResistanceOffset,
bluetoothdevice *bike, // CharacteristicNotifier2AD9 *notifier,
QObject *parent = nullptr);
virtual int writeProcess(quint16 uuid, const QByteArray &data, QByteArray &out);
private:
double weight, rrc, wrc;
signals:
void ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
};
#endif // CHARACTERISTICWRITEPROCESSORE005_H

View File

@@ -129,7 +129,7 @@ void chronobike::characteristicChanged(const QLowEnergyCharacteristic &character
Q_UNUSED(characteristic);
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
settings.value(QStringLiteral("heart_rate_belt_name"), QStringLiteral("Disabled")).toString();
emit debug(QStringLiteral(" << ") + newValue.toHex(' '));
@@ -138,26 +138,23 @@ void chronobike::characteristicChanged(const QLowEnergyCharacteristic &character
if (newValue.length() != 19)
return;
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
if (settings.value(QStringLiteral("power_sensor_name"), QStringLiteral("Disabled"))
.toString()
.startsWith(QStringLiteral("Disabled")))
m_watt = (uint16_t)((uint8_t)newValue.at(17)) + ((uint16_t)((uint8_t)newValue.at(18)) << 8);
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
if (settings.value(QStringLiteral("cadence_sensor_name"), QStringLiteral("Disabled"))
.toString()
.startsWith(QStringLiteral("Disabled"))) {
Cadence = ((uint8_t)newValue.at(8)) / 2;
}
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
if (!settings.value(QStringLiteral("speed_power_based"), false).toBool()) {
Speed = ((double)((uint16_t)((uint8_t)newValue.at(6)) + ((uint16_t)((uint8_t)newValue.at(7)) << 8))) / 100.0;
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
Speed = metric::calculateSpeedFromPower(m_watt.value(), Inclination.value());
}
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(QStringLiteral("weight"), 75.0).toFloat() * 3.5) /
200.0) /
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in kg
@@ -179,8 +176,8 @@ void chronobike::characteristicChanged(const QLowEnergyCharacteristic &character
(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()) +
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
settings.value(QStringLiteral("peloton_gain"), 1.0).toDouble()) +
settings.value(QStringLiteral("peloton_offset"), 0.0).toDouble();
Resistance = m_pelotonResistance;
emit resistanceRead(Resistance.value());
@@ -192,7 +189,7 @@ void chronobike::characteristicChanged(const QLowEnergyCharacteristic &character
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
if (settings.value("ant_heart", false).toBool())
Heart = (uint8_t)KeepAwakeHelper::heart();
else
#endif
@@ -213,9 +210,8 @@ void chronobike::characteristicChanged(const QLowEnergyCharacteristic &character
#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("bike_cadence_sensor", false).toBool();
bool ios_peloton_workaround = settings.value("ios_peloton_workaround", false).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
@@ -272,14 +268,11 @@ void chronobike::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(QStringLiteral("virtual_device_enabled"), true).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("bike_cadence_sensor", false).toBool();
bool ios_peloton_workaround = settings.value("ios_peloton_workaround", false).toBool();
if (ios_peloton_workaround && cadence) {
qDebug() << "ios_peloton_workaround activated!";
h = new lockscreen();

View File

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

View File

@@ -1,95 +0,0 @@
#ifndef COMPUTRAINERBIKE_H
#define COMPUTRAINERBIKE_H
#include <QAbstractOAuth2>
#include <QObject>
#include <QSettings>
#include <QTimer>
#include <QtCore/qbytearray.h>
#ifndef Q_OS_ANDROID
#include <QtCore/qcoreapplication.h>
#else
#include <QtGui/qguiapplication.h>
#endif
#include <QtCore/qlist.h>
#include <QtCore/qmutex.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimer.h>
#include <QDateTime>
#include <QString>
#include "Computrainer.h"
#include "bike.h"
#include "virtualbike.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
#endif
class computrainerbike : public bike {
Q_OBJECT
public:
computrainerbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
double bikeResistanceGain);
resistance_t pelotonToBikeResistance(int pelotonResistance);
resistance_t resistanceFromPowerRequest(uint16_t power);
resistance_t maxResistance() { return max_resistance; }
bool inclinationAvailableByHardware();
bool connected();
void *VirtualBike();
void *VirtualDevice();
private:
resistance_t max_resistance = 100;
resistance_t min_resistance = -20;
uint16_t wattsFromResistance(resistance_t resistance);
double GetDistanceFromPacket(QByteArray packet);
QTime GetElapsedFromPacket(QByteArray packet);
void btinit();
void startDiscover();
void sendPoll();
uint16_t watts();
void forceResistance(double requestResistance);
void innerWriteResistance();
QTimer *refresh;
virtualbike *virtualBike = nullptr;
uint8_t counterPoll = 0;
uint8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
uint8_t sec1Update = 0;
QString lastPacket;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
uint8_t firstStateChanged = 0;
metric target_watts;
bool initDone = false;
bool initRequest = false;
bool noWriteResistance = false;
bool noHeartService = false;
Computrainer *myComputrainer = nullptr;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif
signals:
void disconnected();
void debug(QString string);
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
private slots:
void update();
};
#endif // COMPUTRAINERBIKE_H

View File

@@ -52,7 +52,7 @@ void concept2skierg::writeCharacteristic(uint8_t *data, uint8_t data_len, const
loop.exec();
}
void concept2skierg::forceResistance(resistance_t requestResistance) {
void concept2skierg::forceResistance(int8_t requestResistance) {
uint8_t write[] = {FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
@@ -131,7 +131,7 @@ void concept2skierg::characteristicChanged(const QLowEnergyCharacteristic &chara
Q_UNUSED(characteristic);
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
settings.value(QStringLiteral("heart_rate_belt_name"), QStringLiteral("Disabled")).toString();
qDebug() << QStringLiteral(" << ") << characteristic.uuid() << " " << newValue.toHex(' ');
@@ -180,7 +180,7 @@ void concept2skierg::characteristicChanged(const QLowEnergyCharacteristic &chara
uint8_t heart_rate = newValue.at(7);
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
if (settings.value("ant_heart", false).toBool())
Heart = (uint8_t)KeepAwakeHelper::heart();
else
#endif
@@ -248,8 +248,8 @@ void concept2skierg::characteristicChanged(const QLowEnergyCharacteristic &chara
#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("bike_cadence_sensor", false).toBool();
bool ios_peloton_workaround = settings.value("ios_peloton_workaround", true).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
@@ -365,11 +365,11 @@ void concept2skierg::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(QStringLiteral("virtual_device_enabled"), true).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("bike_cadence_sensor", false).toBool();
bool ios_peloton_workaround = settings.value("ios_peloton_workaround", true).toBool();
if (ios_peloton_workaround && cadence) {
qDebug() << "ios_peloton_workaround activated!";

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